You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-06 23:56:13 +02:00
Desktop: Accessibility: Add setting to increase scrollbar and other small control sizes (#11627)
This commit is contained in:
@ -289,7 +289,6 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
|||||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.js
|
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||||
@ -480,6 +479,7 @@ packages/app-desktop/gui/hooks/useDocument.js
|
|||||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||||
packages/app-desktop/gui/hooks/useElementHeight.js
|
packages/app-desktop/gui/hooks/useElementHeight.js
|
||||||
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
||||||
|
packages/app-desktop/gui/hooks/useMarkupToHtml.js
|
||||||
packages/app-desktop/gui/hooks/usePrevious.js
|
packages/app-desktop/gui/hooks/usePrevious.js
|
||||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -264,7 +264,6 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
|||||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.js
|
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||||
@ -455,6 +454,7 @@ packages/app-desktop/gui/hooks/useDocument.js
|
|||||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||||
packages/app-desktop/gui/hooks/useElementHeight.js
|
packages/app-desktop/gui/hooks/useElementHeight.js
|
||||||
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
||||||
|
packages/app-desktop/gui/hooks/useMarkupToHtml.js
|
||||||
packages/app-desktop/gui/hooks/usePrevious.js
|
packages/app-desktop/gui/hooks/usePrevious.js
|
||||||
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
packages/app-desktop/gui/hooks/usePropsDebugger.js
|
||||||
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
packages/app-desktop/gui/lib/SearchInput/SearchInput.js
|
||||||
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { useState, useEffect, useRef, forwardRef, useCallback, useImperativeHandle, ForwardedRef, useContext } from 'react';
|
import { useState, useEffect, useRef, forwardRef, useCallback, useImperativeHandle, ForwardedRef, useContext } from 'react';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { EditorCommand, MarkupToHtmlOptions, NoteBodyEditorProps, NoteBodyEditorRef } from '../../../utils/types';
|
import { EditorCommand, NoteBodyEditorProps, NoteBodyEditorRef } from '../../../utils/types';
|
||||||
import { commandAttachFileToBody, getResourcesFromPasteEvent } from '../../../utils/resourceHandling';
|
import { commandAttachFileToBody, getResourcesFromPasteEvent } from '../../../utils/resourceHandling';
|
||||||
import { ScrollOptions, ScrollOptionTypes } from '../../../utils/types';
|
import { ScrollOptions, ScrollOptionTypes } from '../../../utils/types';
|
||||||
import { CommandValue } from '../../../utils/types';
|
import { CommandValue } from '../../../utils/types';
|
||||||
@ -34,6 +34,7 @@ import useWebviewIpcMessage from '../utils/useWebviewIpcMessage';
|
|||||||
import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
||||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
|
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
|
||||||
|
import { MarkupToHtmlOptions } from '../../../../hooks/useMarkupToHtml';
|
||||||
|
|
||||||
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
|
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
|
||||||
return { ...override };
|
return { ...override };
|
||||||
|
@ -1035,6 +1035,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
|||||||
|
|
||||||
const allAssetsOptions: NoteStyleOptions = {
|
const allAssetsOptions: NoteStyleOptions = {
|
||||||
contentMaxWidthTarget: '.mce-content-body',
|
contentMaxWidthTarget: '.mce-content-body',
|
||||||
|
scrollbarSize: props.scrollbarSize,
|
||||||
themeId: props.contentMarkupLanguage === MarkupLanguage.Html ? 1 : null,
|
themeId: props.contentMarkupLanguage === MarkupLanguage.Html ? 1 : null,
|
||||||
whiteBackgroundNoteRendering: props.whiteBackgroundNoteRendering,
|
whiteBackgroundNoteRendering: props.whiteBackgroundNoteRendering,
|
||||||
};
|
};
|
||||||
@ -1051,7 +1052,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
|||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||||
}, [editor, props.themeId, props.markupToHtml, props.allAssets, props.content, props.resourceInfos, props.contentKey, props.contentMarkupLanguage, props.whiteBackgroundNoteRendering]);
|
}, [editor, props.themeId, props.scrollbarSize, props.markupToHtml, props.allAssets, props.content, props.resourceInfos, props.contentKey, props.contentMarkupLanguage, props.whiteBackgroundNoteRendering]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return () => {};
|
if (!editor) return () => {};
|
||||||
|
@ -9,7 +9,7 @@ import useNoteSearchBar from './utils/useNoteSearchBar';
|
|||||||
import useMessageHandler from './utils/useMessageHandler';
|
import useMessageHandler from './utils/useMessageHandler';
|
||||||
import useWindowCommandHandler from './utils/useWindowCommandHandler';
|
import useWindowCommandHandler from './utils/useWindowCommandHandler';
|
||||||
import useDropHandler from './utils/useDropHandler';
|
import useDropHandler from './utils/useDropHandler';
|
||||||
import useMarkupToHtml from './utils/useMarkupToHtml';
|
import useMarkupToHtml from '../hooks/useMarkupToHtml';
|
||||||
import useFormNote, { OnLoadEvent, OnSetFormNote } from './utils/useFormNote';
|
import useFormNote, { OnLoadEvent, OnSetFormNote } from './utils/useFormNote';
|
||||||
import useEffectiveNoteId from './utils/useEffectiveNoteId';
|
import useEffectiveNoteId from './utils/useEffectiveNoteId';
|
||||||
import useFolder from './utils/useFolder';
|
import useFolder from './utils/useFolder';
|
||||||
@ -45,7 +45,6 @@ import PlainEditor from './NoteBody/PlainEditor/PlainEditor';
|
|||||||
import CodeMirror6 from './NoteBody/CodeMirror/v6/CodeMirror';
|
import CodeMirror6 from './NoteBody/CodeMirror/v6/CodeMirror';
|
||||||
import CodeMirror5 from './NoteBody/CodeMirror/v5/CodeMirror';
|
import CodeMirror5 from './NoteBody/CodeMirror/v5/CodeMirror';
|
||||||
import { openItemById } from './utils/contextMenu';
|
import { openItemById } from './utils/contextMenu';
|
||||||
import getPluginSettingValue from '@joplin/lib/services/plugins/utils/getPluginSettingValue';
|
|
||||||
import { MarkupLanguage } from '@joplin/renderer';
|
import { MarkupLanguage } from '@joplin/renderer';
|
||||||
import useScrollWhenReadyOptions from './utils/useScrollWhenReadyOptions';
|
import useScrollWhenReadyOptions from './utils/useScrollWhenReadyOptions';
|
||||||
import useScheduleSaveCallbacks from './utils/useScheduleSaveCallbacks';
|
import useScheduleSaveCallbacks from './utils/useScheduleSaveCallbacks';
|
||||||
@ -180,7 +179,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
|||||||
whiteBackgroundNoteRendering,
|
whiteBackgroundNoteRendering,
|
||||||
customCss: props.customCss,
|
customCss: props.customCss,
|
||||||
plugins: props.plugins,
|
plugins: props.plugins,
|
||||||
settingValue: getPluginSettingValue,
|
scrollbarSize: props.scrollbarSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
@ -200,9 +199,10 @@ function NoteEditorContent(props: NoteEditorProps) {
|
|||||||
return markupToHtml.allAssets(markupLanguage, theme, {
|
return markupToHtml.allAssets(markupLanguage, theme, {
|
||||||
contentMaxWidth: props.contentMaxWidth,
|
contentMaxWidth: props.contentMaxWidth,
|
||||||
contentMaxWidthTarget: options.contentMaxWidthTarget,
|
contentMaxWidthTarget: options.contentMaxWidthTarget,
|
||||||
|
scrollbarSize: props.scrollbarSize,
|
||||||
whiteBackgroundNoteRendering: options.whiteBackgroundNoteRendering,
|
whiteBackgroundNoteRendering: options.whiteBackgroundNoteRendering,
|
||||||
});
|
});
|
||||||
}, [props.themeId, props.customCss, props.contentMaxWidth]);
|
}, [props.themeId, props.scrollbarSize, props.customCss, props.contentMaxWidth]);
|
||||||
|
|
||||||
const handleProvisionalFlag = useCallback(() => {
|
const handleProvisionalFlag = useCallback(() => {
|
||||||
if (props.isProvisional) {
|
if (props.isProvisional) {
|
||||||
@ -494,6 +494,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
|||||||
plugins: props.plugins,
|
plugins: props.plugins,
|
||||||
fontSize: Setting.value('style.editor.fontSize'),
|
fontSize: Setting.value('style.editor.fontSize'),
|
||||||
contentMaxWidth: props.contentMaxWidth,
|
contentMaxWidth: props.contentMaxWidth,
|
||||||
|
scrollbarSize: props.scrollbarSize,
|
||||||
isSafeMode: props.isSafeMode,
|
isSafeMode: props.isSafeMode,
|
||||||
useCustomPdfViewer: props.useCustomPdfViewer,
|
useCustomPdfViewer: props.useCustomPdfViewer,
|
||||||
// We need it to identify the context for which media is rendered.
|
// We need it to identify the context for which media is rendered.
|
||||||
@ -747,6 +748,7 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
|
|||||||
'setTags',
|
'setTags',
|
||||||
], whenClauseContext)[0] as ToolbarButtonInfo,
|
], whenClauseContext)[0] as ToolbarButtonInfo,
|
||||||
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
|
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
|
||||||
|
scrollbarSize: state.settings['style.scrollbarSize'],
|
||||||
isSafeMode: state.settings.isSafeMode,
|
isSafeMode: state.settings.isSafeMode,
|
||||||
useCustomPdfViewer: false,
|
useCustomPdfViewer: false,
|
||||||
syncUserId: state.settings['sync.userId'],
|
syncUserId: state.settings['sync.userId'],
|
||||||
|
@ -5,3 +5,5 @@
|
|||||||
@use "./styles/note-title-wrapper.scss";
|
@use "./styles/note-title-wrapper.scss";
|
||||||
@use "./styles/note-editor-wrapper.scss";
|
@use "./styles/note-editor-wrapper.scss";
|
||||||
@use "./styles/note-editor-viewer-row.scss";
|
@use "./styles/note-editor-viewer-row.scss";
|
||||||
|
@use "./styles/revision-viewer-root.scss";
|
||||||
|
@use "./styles/revision-viewer-title.scss";
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
.revision-viewer-root {
|
||||||
|
background-color: var(--joplin-background-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
.revision-viewer-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-color: var(--joplin-divider-color);
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
> .revisions {
|
||||||
|
margin-left: 10px;
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ import { DropHandler } from './useDropHandler';
|
|||||||
import { SearchMarkers } from './useSearchMarkers';
|
import { SearchMarkers } from './useSearchMarkers';
|
||||||
import { ParseOptions } from '@joplin/lib/HtmlToMd';
|
import { ParseOptions } from '@joplin/lib/HtmlToMd';
|
||||||
import { ScrollStrategy } from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
import { ScrollStrategy } from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
||||||
|
import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml';
|
||||||
|
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||||
|
|
||||||
export interface AllAssetsOptions {
|
export interface AllAssetsOptions {
|
||||||
contentMaxWidthTarget?: string;
|
contentMaxWidthTarget?: string;
|
||||||
@ -51,6 +53,7 @@ export interface NoteEditorProps {
|
|||||||
toolbarButtonInfos: ToolbarItem[];
|
toolbarButtonInfos: ToolbarItem[];
|
||||||
setTagsToolbarButtonInfo: ToolbarButtonInfo;
|
setTagsToolbarButtonInfo: ToolbarButtonInfo;
|
||||||
contentMaxWidth: number;
|
contentMaxWidth: number;
|
||||||
|
scrollbarSize: ScrollbarSize;
|
||||||
isSafeMode: boolean;
|
isSafeMode: boolean;
|
||||||
useCustomPdfViewer: boolean;
|
useCustomPdfViewer: boolean;
|
||||||
shareCacheSetting: string;
|
shareCacheSetting: string;
|
||||||
@ -72,22 +75,7 @@ export interface NoteBodyEditorRef {
|
|||||||
execCommand(command: CommandValue): Promise<void>;
|
execCommand(command: CommandValue): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkupToHtmlOptions {
|
export { MarkupToHtmlOptions };
|
||||||
replaceResourceInternalToExternalLinks?: boolean;
|
|
||||||
resourceInfos?: ResourceInfos;
|
|
||||||
contentMaxWidth?: number;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
|
||||||
plugins?: Record<string, any>;
|
|
||||||
bodyOnly?: boolean;
|
|
||||||
mapsToLine?: boolean;
|
|
||||||
useCustomPdfViewer?: boolean;
|
|
||||||
noteId?: string;
|
|
||||||
vendorDir?: string;
|
|
||||||
platformName?: string;
|
|
||||||
allowedFilePrefixes?: string[];
|
|
||||||
whiteBackgroundNoteRendering?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MarkupToHtmlHandler = (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
|
export type MarkupToHtmlHandler = (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
|
||||||
export type HtmlToMarkdownHandler = (markupLanguage: number, html: string, originalCss: string, parseOptions?: ParseOptions)=> Promise<string>;
|
export type HtmlToMarkdownHandler = (markupLanguage: number, html: string, originalCss: string, parseOptions?: ParseOptions)=> Promise<string>;
|
||||||
|
|
||||||
@ -105,6 +93,8 @@ export interface NoteBodyEditorProps {
|
|||||||
// avoid cases where black text is rendered over a dark background.
|
// avoid cases where black text is rendered over a dark background.
|
||||||
whiteBackgroundNoteRendering: boolean;
|
whiteBackgroundNoteRendering: boolean;
|
||||||
|
|
||||||
|
scrollbarSize: ScrollbarSize;
|
||||||
|
|
||||||
content: string;
|
content: string;
|
||||||
contentKey: string;
|
contentKey: string;
|
||||||
contentMarkupLanguage: number;
|
contentMarkupLanguage: number;
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { themeStyle } from '@joplin/lib/theme';
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import NoteTextViewer from './NoteTextViewer';
|
import NoteTextViewer, { NoteViewerControl } from './NoteTextViewer';
|
||||||
import HelpButton from './HelpButton';
|
import HelpButton from './HelpButton';
|
||||||
import BaseModel from '@joplin/lib/BaseModel';
|
import BaseModel from '@joplin/lib/BaseModel';
|
||||||
import Revision from '@joplin/lib/models/Revision';
|
import Revision from '@joplin/lib/models/Revision';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
|
||||||
import RevisionService from '@joplin/lib/services/RevisionService';
|
import RevisionService from '@joplin/lib/services/RevisionService';
|
||||||
import { MarkupToHtml } from '@joplin/renderer';
|
import { MarkupLanguage } from '@joplin/renderer';
|
||||||
import time from '@joplin/lib/time';
|
import time from '@joplin/lib/time';
|
||||||
import bridge from '../services/bridge';
|
import bridge from '../services/bridge';
|
||||||
import markupLanguageUtils from '@joplin/lib/utils/markupLanguageUtils';
|
|
||||||
import { NoteEntity, RevisionEntity } from '@joplin/lib/services/database/types';
|
import { NoteEntity, RevisionEntity } from '@joplin/lib/services/database/types';
|
||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
const urlUtils = require('@joplin/lib/urlUtils');
|
const urlUtils = require('@joplin/lib/urlUtils');
|
||||||
@ -18,147 +16,109 @@ const ReactTooltip = require('react-tooltip');
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
import shared from '@joplin/lib/components/shared/note-screen-shared';
|
import shared from '@joplin/lib/components/shared/note-screen-shared';
|
||||||
import shim, { MessageBoxType } from '@joplin/lib/shim';
|
import shim, { MessageBoxType } from '@joplin/lib/shim';
|
||||||
|
import { RefObject, useCallback, useRef, useState } from 'react';
|
||||||
|
import useQueuedAsyncEffect from '@joplin/lib/hooks/useQueuedAsyncEffect';
|
||||||
|
import useMarkupToHtml from './hooks/useMarkupToHtml';
|
||||||
|
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
|
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
noteId: string;
|
noteId: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
onBack: ()=> void;
|
||||||
onBack: Function;
|
|
||||||
customCss: string;
|
customCss: string;
|
||||||
|
scrollbarSize: ScrollbarSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
const useNoteContent = (
|
||||||
note: NoteEntity;
|
viewerRef: RefObject<NoteViewerControl>,
|
||||||
revisions: RevisionEntity[];
|
currentRevId: string,
|
||||||
currentRevId: string;
|
revisions: RevisionEntity[],
|
||||||
restoring: boolean;
|
themeId: number,
|
||||||
}
|
customCss: string,
|
||||||
|
scrollbarSize: ScrollbarSize,
|
||||||
|
) => {
|
||||||
|
const [note, setNote] = useState<NoteEntity>(null);
|
||||||
|
|
||||||
class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
const markupToHtml = useMarkupToHtml({
|
||||||
|
themeId,
|
||||||
|
customCss,
|
||||||
|
plugins: {},
|
||||||
|
whiteBackgroundNoteRendering: false,
|
||||||
|
scrollbarSize,
|
||||||
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
useAsyncEffect(async (event) => {
|
||||||
private viewerRef_: any;
|
if (!revisions.length || !currentRevId) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
setNote(null);
|
||||||
private helpButton_onClick: Function;
|
} else {
|
||||||
|
const revIndex = BaseModel.modelIndexById(revisions, currentRevId);
|
||||||
|
const note = await RevisionService.instance().revisionNote(revisions, revIndex);
|
||||||
|
if (!note || event.cancelled) return;
|
||||||
|
setNote(note);
|
||||||
|
}
|
||||||
|
}, [revisions, currentRevId, themeId, customCss, viewerRef]);
|
||||||
|
|
||||||
public constructor(props: Props) {
|
useQueuedAsyncEffect(async () => {
|
||||||
super(props);
|
const noteBody = note?.body ?? _('This note has no history');
|
||||||
|
const markupLanguage = note.markup_language ?? MarkupLanguage.Markdown;
|
||||||
|
const result = await markupToHtml(markupLanguage, noteBody, {
|
||||||
|
resources: await shared.attachedResources(noteBody),
|
||||||
|
whiteBackgroundNoteRendering: markupLanguage === MarkupLanguage.Html,
|
||||||
|
});
|
||||||
|
|
||||||
this.state = {
|
viewerRef.current.setHtml(result.html, {
|
||||||
revisions: [],
|
pluginAssets: result.pluginAssets,
|
||||||
currentRevId: '',
|
});
|
||||||
note: null,
|
}, [note, viewerRef]);
|
||||||
restoring: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.viewerRef_ = React.createRef();
|
return note;
|
||||||
|
};
|
||||||
|
|
||||||
this.viewer_domReady = this.viewer_domReady.bind(this);
|
const NoteRevisionViewerComponent: React.FC<Props> = ({ themeId, noteId, onBack, customCss, scrollbarSize }) => {
|
||||||
this.revisionList_onChange = this.revisionList_onChange.bind(this);
|
const helpButton_onClick = useCallback(() => {}, []);
|
||||||
this.importButton_onClick = this.importButton_onClick.bind(this);
|
const viewerRef = useRef<NoteViewerControl|null>(null);
|
||||||
this.backButton_click = this.backButton_click.bind(this);
|
|
||||||
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public style() {
|
const [revisions, setRevisions] = useState<RevisionEntity[]>([]);
|
||||||
const theme = themeStyle(this.props.themeId);
|
const [currentRevId, setCurrentRevId] = useState('');
|
||||||
|
const [restoring, setRestoring] = useState(false);
|
||||||
|
|
||||||
const style = {
|
const note = useNoteContent(viewerRef, currentRevId, revisions, themeId, customCss, scrollbarSize);
|
||||||
root: {
|
|
||||||
backgroundColor: theme.backgroundColor,
|
|
||||||
display: 'flex',
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
titleInput: { ...theme.inputStyle, flex: 1 },
|
|
||||||
revisionList: { ...theme.dropdownList, marginLeft: 10, flex: 0.5 },
|
|
||||||
};
|
|
||||||
|
|
||||||
return style;
|
const viewer_domReady = useCallback(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
private async viewer_domReady() {
|
|
||||||
// this.viewerRef_.current.openDevTools();
|
// this.viewerRef_.current.openDevTools();
|
||||||
|
|
||||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||||
|
|
||||||
this.setState(
|
setRevisions(revisions);
|
||||||
{
|
setCurrentRevId(revisions.length ? revisions[revisions.length - 1].id : '');
|
||||||
revisions: revisions,
|
}, [noteId]);
|
||||||
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
void this.reloadNote();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async importButton_onClick() {
|
const importButton_onClick = useCallback(async () => {
|
||||||
if (!this.state.note) return;
|
if (!note) return;
|
||||||
this.setState({ restoring: true });
|
setRestoring(true);
|
||||||
await RevisionService.instance().importRevisionNote(this.state.note);
|
await RevisionService.instance().importRevisionNote(note);
|
||||||
this.setState({ restoring: false });
|
setRestoring(false);
|
||||||
await shim.showMessageBox(RevisionService.instance().restoreSuccessMessage(this.state.note), { type: MessageBoxType.Info });
|
await shim.showMessageBox(RevisionService.instance().restoreSuccessMessage(note), { type: MessageBoxType.Info });
|
||||||
}
|
}, [note]);
|
||||||
|
|
||||||
private backButton_click() {
|
const backButton_click = useCallback(() => {
|
||||||
if (this.props.onBack) this.props.onBack();
|
if (onBack) onBack();
|
||||||
}
|
}, [onBack]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
private revisionList_onChange(event: any) {
|
const revisionList_onChange: React.ChangeEventHandler<HTMLSelectElement> = useCallback((event) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
if (this.props.onBack) this.props.onBack();
|
if (onBack) onBack();
|
||||||
} else {
|
} else {
|
||||||
this.setState(
|
setCurrentRevId(value);
|
||||||
{
|
|
||||||
currentRevId: value,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
void this.reloadNote();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}, [onBack]);
|
||||||
|
|
||||||
public async reloadNote() {
|
|
||||||
let noteBody = '';
|
|
||||||
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
|
||||||
if (!this.state.revisions.length || !this.state.currentRevId) {
|
|
||||||
noteBody = _('This note has no history');
|
|
||||||
this.setState({ note: null });
|
|
||||||
} else {
|
|
||||||
const revIndex = BaseModel.modelIndexById(this.state.revisions, this.state.currentRevId);
|
|
||||||
const note = await RevisionService.instance().revisionNote(this.state.revisions, revIndex);
|
|
||||||
if (!note) return;
|
|
||||||
noteBody = note.body;
|
|
||||||
markupLanguage = note.markup_language;
|
|
||||||
this.setState({ note: note });
|
|
||||||
}
|
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
|
||||||
|
|
||||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, {
|
|
||||||
resourceBaseUrl: `joplin-content://note-viewer/${Setting.value('resourceDir')}/`,
|
|
||||||
customCss: this.props.customCss ? this.props.customCss : '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await markupToHtml.render(markupLanguage, noteBody, theme, {
|
|
||||||
codeTheme: theme.codeThemeCss,
|
|
||||||
resources: await shared.attachedResources(noteBody),
|
|
||||||
postMessageSyntax: 'ipcProxySendToHost',
|
|
||||||
});
|
|
||||||
|
|
||||||
this.viewerRef_.current.setHtml(result.html, {
|
|
||||||
// cssFiles: result.cssFiles,
|
|
||||||
pluginAssets: result.pluginAssets,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
private async webview_ipcMessage(event: any) {
|
const webview_ipcMessage = useCallback(async (event: any) => {
|
||||||
// For the revision view, we only support a minimal subset of the IPC messages.
|
// For the revision view, we only support a minimal subset of the IPC messages.
|
||||||
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
||||||
// We try to get most links work though, except for internal (joplin://) links.
|
// We try to get most links work though, except for internal (joplin://) links.
|
||||||
@ -182,60 +142,57 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
console.warn(error);
|
console.warn(error);
|
||||||
bridge().showErrorMessageBox(error.message);
|
bridge().showErrorMessageBox(error.message);
|
||||||
}
|
}
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
public render() {
|
const theme = themeStyle(themeId);
|
||||||
const theme = themeStyle(this.props.themeId);
|
|
||||||
const style = this.style();
|
|
||||||
|
|
||||||
const revisionListItems = [];
|
const revisionListItems = [];
|
||||||
const revs = this.state.revisions.slice().reverse();
|
const revs = revisions.slice().reverse();
|
||||||
for (let i = 0; i < revs.length; i++) {
|
for (let i = 0; i < revs.length; i++) {
|
||||||
const rev = revs[i];
|
const rev = revs[i];
|
||||||
const stats = Revision.revisionPatchStatsText(rev);
|
const stats = Revision.revisionPatchStatsText(rev);
|
||||||
|
|
||||||
revisionListItems.push(
|
revisionListItems.push(
|
||||||
<option key={rev.id} value={rev.id}>
|
<option key={rev.id} value={rev.id}>
|
||||||
{`${time.formatMsToLocal(rev.item_updated_time)} (${stats})`}
|
{`${time.formatMsToLocal(rev.item_updated_time)} (${stats})`}
|
||||||
</option>,
|
</option>,
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const restoreButtonTitle = _('Restore');
|
|
||||||
const helpMessage = _('Click "%s" to restore the note. It will be copied in the notebook named "%s". The current version of the note will not be replaced or modified.', restoreButtonTitle, RevisionService.instance().restoreFolderTitle());
|
|
||||||
|
|
||||||
const titleInput = (
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom: 10 }}>
|
|
||||||
<button onClick={this.backButton_click} style={{ ...theme.buttonStyle, marginRight: 10, height: theme.inputStyle.height }}>
|
|
||||||
<i style={theme.buttonIconStyle} className={'fa fa-chevron-left'}></i>{_('Back')}
|
|
||||||
</button>
|
|
||||||
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''} />
|
|
||||||
<select disabled={!this.state.revisions.length} value={this.state.currentRevId} style={style.revisionList} onChange={this.revisionList_onChange}>
|
|
||||||
{revisionListItems}
|
|
||||||
</select>
|
|
||||||
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={{ ...theme.buttonStyle, marginLeft: 10, height: theme.inputStyle.height }}>
|
|
||||||
{restoreButtonTitle}
|
|
||||||
</button>
|
|
||||||
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewer = <NoteTextViewer themeId={this.props.themeId} viewerStyle={{ display: 'flex', flex: 1, borderLeft: 'none' }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} onIpcMessage={this.webview_ipcMessage} />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
|
||||||
<div style={style.root as any}>
|
|
||||||
{titleInput}
|
|
||||||
{viewer}
|
|
||||||
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const restoreButtonTitle = _('Restore');
|
||||||
|
const helpMessage = _('Click "%s" to restore the note. It will be copied in the notebook named "%s". The current version of the note will not be replaced or modified.', restoreButtonTitle, RevisionService.instance().restoreFolderTitle());
|
||||||
|
|
||||||
|
const titleInput = (
|
||||||
|
<div className='revision-viewer-title'>
|
||||||
|
<button onClick={backButton_click} style={{ ...theme.buttonStyle, marginRight: 10, height: theme.inputStyle.height }}>
|
||||||
|
<i style={theme.buttonIconStyle} className={'fa fa-chevron-left'}></i>{_('Back')}
|
||||||
|
</button>
|
||||||
|
<input readOnly type="text" className='title' style={theme.inputStyle} value={note?.title ?? ''} />
|
||||||
|
<select disabled={!revisions.length} value={currentRevId} className='revisions' style={theme.dropdownList} onChange={revisionList_onChange}>
|
||||||
|
{revisionListItems}
|
||||||
|
</select>
|
||||||
|
<button disabled={!revisions.length || restoring} onClick={importButton_onClick} className='restore'style={{ ...theme.buttonStyle, marginLeft: 10, height: theme.inputStyle.height }}>
|
||||||
|
{restoreButtonTitle}
|
||||||
|
</button>
|
||||||
|
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={helpButton_onClick} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewer = <NoteTextViewer themeId={themeId} viewerStyle={{ display: 'flex', flex: 1, borderLeft: 'none' }} ref={viewerRef} onDomReady={viewer_domReady} onIpcMessage={webview_ipcMessage} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='revision-viewer-root'>
|
||||||
|
{titleInput}
|
||||||
|
{viewer}
|
||||||
|
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
|
scrollbarSize: state.settings['style.scrollbarSize'],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,9 +17,11 @@ import { themeStyle } from '@joplin/lib/theme';
|
|||||||
import useDocument from '../hooks/useDocument';
|
import useDocument from '../hooks/useDocument';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
|
scrollbarSize: ScrollbarSize;
|
||||||
editorFontSetting: string;
|
editorFontSetting: string;
|
||||||
customChromeCssPaths: string[];
|
customChromeCssPaths: string[];
|
||||||
}
|
}
|
||||||
@ -106,6 +108,11 @@ const StyleSheetContainer: React.FC<Props> = props => {
|
|||||||
/* Theme CSS */
|
/* Theme CSS */
|
||||||
${themeCss}
|
${themeCss}
|
||||||
|
|
||||||
|
/* Base scrollbar size */
|
||||||
|
:root {
|
||||||
|
--scrollbar-size: ${Number(props.scrollbarSize)}px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Editor font CSS */
|
/* Editor font CSS */
|
||||||
${editorCss}
|
${editorCss}
|
||||||
`);
|
`);
|
||||||
@ -118,6 +125,7 @@ export default connect((state: AppState) => {
|
|||||||
return {
|
return {
|
||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
editorFontSetting: state.settings['style.editor.fontFamily'] as string,
|
editorFontSetting: state.settings['style.editor.fontFamily'] as string,
|
||||||
|
scrollbarSize: state.settings['style.scrollbarSize'],
|
||||||
customChromeCssPaths: state.customChromeCssPaths,
|
customChromeCssPaths: state.customChromeCssPaths,
|
||||||
};
|
};
|
||||||
})(StyleSheetContainer);
|
})(StyleSheetContainer);
|
||||||
|
@ -6,20 +6,27 @@ import shim from '@joplin/lib/shim';
|
|||||||
|
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import { MarkupToHtmlOptions, ResourceInfos } from './types';
|
import { ResourceInfos } from '../NoteEditor/utils/types';
|
||||||
import { resourceFullPath } from '@joplin/lib/models/utils/resourceUtils';
|
import { resourceFullPath } from '@joplin/lib/models/utils/resourceUtils';
|
||||||
|
import { RenderOptions } from '@joplin/renderer/types';
|
||||||
|
import getPluginSettingValue from '@joplin/lib/services/plugins/utils/getPluginSettingValue';
|
||||||
|
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||||
|
|
||||||
|
export interface MarkupToHtmlOptions extends RenderOptions {
|
||||||
|
resourceInfos?: ResourceInfos;
|
||||||
|
replaceResourceInternalToExternalLinks?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface HookDependencies {
|
interface HookDependencies {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
customCss: string;
|
customCss: string;
|
||||||
plugins: PluginStates;
|
plugins: PluginStates;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
|
||||||
settingValue: (pluginId: string, key: string)=> any;
|
|
||||||
whiteBackgroundNoteRendering: boolean;
|
whiteBackgroundNoteRendering: boolean;
|
||||||
|
scrollbarSize: ScrollbarSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useMarkupToHtml(deps: HookDependencies) {
|
export default function useMarkupToHtml(deps: HookDependencies) {
|
||||||
const { themeId, customCss, plugins, whiteBackgroundNoteRendering } = deps;
|
const { themeId, customCss, plugins, whiteBackgroundNoteRendering, scrollbarSize } = deps;
|
||||||
|
|
||||||
const resourceBaseUrl = useMemo(() => {
|
const resourceBaseUrl = useMemo(() => {
|
||||||
return `joplin-content://note-viewer/${Setting.value('resourceDir')}/`;
|
return `joplin-content://note-viewer/${Setting.value('resourceDir')}/`;
|
||||||
@ -32,8 +39,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
|||||||
});
|
});
|
||||||
}, [plugins, customCss, resourceBaseUrl]);
|
}, [plugins, customCss, resourceBaseUrl]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
return useCallback(async (markupLanguage: number, md: string, options: MarkupToHtmlOptions|null = null) => {
|
||||||
return useCallback(async (markupLanguage: number, md: string, options: MarkupToHtmlOptions = null): Promise<any> => {
|
|
||||||
options = {
|
options = {
|
||||||
replaceResourceInternalToExternalLinks: false,
|
replaceResourceInternalToExternalLinks: false,
|
||||||
resourceInfos: {},
|
resourceInfos: {},
|
||||||
@ -61,8 +67,9 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
|||||||
splitted: true,
|
splitted: true,
|
||||||
externalAssetsOnly: true,
|
externalAssetsOnly: true,
|
||||||
codeHighlightCacheKey: 'useMarkupToHtml',
|
codeHighlightCacheKey: 'useMarkupToHtml',
|
||||||
settingValue: deps.settingValue,
|
settingValue: getPluginSettingValue,
|
||||||
whiteBackgroundNoteRendering,
|
whiteBackgroundNoteRendering,
|
||||||
|
scrollbarSize: scrollbarSize,
|
||||||
itemIdToUrl: (id: string, urlParameters = '') => {
|
itemIdToUrl: (id: string, urlParameters = '') => {
|
||||||
if (!(id in resources) || !resources[id]) {
|
if (!(id in resources) || !resources[id]) {
|
||||||
return null;
|
return null;
|
||||||
@ -74,5 +81,5 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [themeId, markupToHtml, whiteBackgroundNoteRendering, resourceBaseUrl, deps.settingValue]);
|
}, [themeId, markupToHtml, whiteBackgroundNoteRendering, scrollbarSize, resourceBaseUrl]);
|
||||||
}
|
}
|
@ -415,6 +415,12 @@
|
|||||||
|
|
||||||
addPluginAssets(event.options.pluginAssets);
|
addPluginAssets(event.options.pluginAssets);
|
||||||
|
|
||||||
|
if (event.options.increaseControlSize) {
|
||||||
|
document.documentElement.classList.add('-larger-controls');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('-larger-controls');
|
||||||
|
}
|
||||||
|
|
||||||
if (event.options.downloadResources === 'manual') {
|
if (event.options.downloadResources === 'manual') {
|
||||||
webviewLib.setupResourceManualDownload();
|
webviewLib.setupResourceManualDownload();
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: var(--scrollbar-size, 7px);
|
||||||
height: 7px;
|
height: var(--scrollbar-size, 7px);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
@ -45,7 +45,7 @@ a {
|
|||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: rgba(100, 100, 100, 0.3);
|
background: rgba(100, 100, 100, 0.3);
|
||||||
border-radius: 5px;
|
border-radius: calc(var(--scrollbar-size, 7px) * 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track:hover {
|
::-webkit-scrollbar-track:hover {
|
||||||
|
@ -19,6 +19,12 @@ export enum CameraDirection {
|
|||||||
Front,
|
Front,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ScrollbarSize {
|
||||||
|
Small = 7,
|
||||||
|
Medium = 12,
|
||||||
|
Large = 24,
|
||||||
|
}
|
||||||
|
|
||||||
const builtInMetadata = (Setting: typeof SettingType) => {
|
const builtInMetadata = (Setting: typeof SettingType) => {
|
||||||
const platform = shim.platformName();
|
const platform = shim.platformName();
|
||||||
const mobilePlatform = shim.mobilePlatform();
|
const mobilePlatform = shim.mobilePlatform();
|
||||||
@ -1120,6 +1126,26 @@ const builtInMetadata = (Setting: typeof SettingType) => {
|
|||||||
|
|
||||||
'style.editor.contentMaxWidth': { value: 0, type: SettingItemType.Int, public: true, storage: SettingStorage.File, isGlobal: true, appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor maximum width'), description: () => _('Set it to 0 to make it take the complete available space. Recommended width is 600.') },
|
'style.editor.contentMaxWidth': { value: 0, type: SettingItemType.Int, public: true, storage: SettingStorage.File, isGlobal: true, appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor maximum width'), description: () => _('Set it to 0 to make it take the complete available space. Recommended width is 600.') },
|
||||||
|
|
||||||
|
'style.scrollbarSize': {
|
||||||
|
value: ScrollbarSize.Small,
|
||||||
|
type: SettingItemType.String,
|
||||||
|
public: true,
|
||||||
|
section: 'appearance',
|
||||||
|
appTypes: [AppType.Desktop],
|
||||||
|
isEnum: true,
|
||||||
|
|
||||||
|
options: () => ({
|
||||||
|
[ScrollbarSize.Small]: _('Small'),
|
||||||
|
[ScrollbarSize.Medium]: _('Medium'),
|
||||||
|
[ScrollbarSize.Large]: _('Large'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
label: () => _('Scrollbar size'),
|
||||||
|
description: () => _('Configures the size of scrollbars used in the app.'),
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
isGlobal: true,
|
||||||
|
},
|
||||||
|
|
||||||
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, isGlobal: true, public: false, appTypes: [AppType.Desktop] },
|
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, isGlobal: true, public: false, appTypes: [AppType.Desktop] },
|
||||||
|
|
||||||
'ui.lastSelectedPluginPanel': {
|
'ui.lastSelectedPluginPanel': {
|
||||||
|
@ -6,7 +6,7 @@ import Folder from '../../models/Folder';
|
|||||||
import Note from '../../models/Note';
|
import Note from '../../models/Note';
|
||||||
import Setting from '../../models/Setting';
|
import Setting from '../../models/Setting';
|
||||||
import { MarkupToHtml } from '@joplin/renderer';
|
import { MarkupToHtml } from '@joplin/renderer';
|
||||||
import { NoteEntity, ResourceEntity } from '../database/types';
|
import { NoteEntity, ResourceEntity, ResourceLocalStateEntity } from '../database/types';
|
||||||
import { contentScriptsToRendererRules } from '../plugins/utils/loadContentScripts';
|
import { contentScriptsToRendererRules } from '../plugins/utils/loadContentScripts';
|
||||||
import { basename, friendlySafeFilename, rtrimSlashes, dirname } from '../../path-utils';
|
import { basename, friendlySafeFilename, rtrimSlashes, dirname } from '../../path-utils';
|
||||||
import htmlpack from '@joplin/htmlpack';
|
import htmlpack from '@joplin/htmlpack';
|
||||||
@ -17,6 +17,8 @@ import getPluginSettingValue from '../plugins/utils/getPluginSettingValue';
|
|||||||
import { LinkRenderingType } from '@joplin/renderer/MdToHtml';
|
import { LinkRenderingType } from '@joplin/renderer/MdToHtml';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import { parseRenderedNoteMetadata } from './utils';
|
import { parseRenderedNoteMetadata } from './utils';
|
||||||
|
import ResourceLocalState from '../../models/ResourceLocalState';
|
||||||
|
import { ResourceInfos } from '@joplin/renderer/types';
|
||||||
|
|
||||||
const logger = Logger.create('InteropService_Exporter_Html');
|
const logger = Logger.create('InteropService_Exporter_Html');
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
|
|||||||
private createdDirs_: string[] = [];
|
private createdDirs_: string[] = [];
|
||||||
private resourceDir_: string;
|
private resourceDir_: string;
|
||||||
private markupToHtml_: MarkupToHtml;
|
private markupToHtml_: MarkupToHtml;
|
||||||
private resources_: ResourceEntity[] = [];
|
private resources_: ResourceInfos = {};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
private style_: any;
|
private style_: any;
|
||||||
private packIntoSingleFile_ = false;
|
private packIntoSingleFile_ = false;
|
||||||
@ -174,10 +176,14 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
public async processResource(resource: any, filePath: string) {
|
public async processResource(resource: ResourceEntity, filePath: string) {
|
||||||
const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`;
|
const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`;
|
||||||
await shim.fsDriver().copy(filePath, destResourcePath);
|
await shim.fsDriver().copy(filePath, destResourcePath);
|
||||||
this.resources_.push(resource);
|
const localState: ResourceLocalStateEntity = await ResourceLocalState.load(resource.id);
|
||||||
|
this.resources_[resource.id] = {
|
||||||
|
localState,
|
||||||
|
item: resource,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async close() {
|
public async close() {
|
||||||
|
@ -3,7 +3,7 @@ import HtmlToHtml from './HtmlToHtml';
|
|||||||
import htmlUtils from './htmlUtils';
|
import htmlUtils from './htmlUtils';
|
||||||
import { Options as NoteStyleOptions } from './noteStyle';
|
import { Options as NoteStyleOptions } from './noteStyle';
|
||||||
import { AllHtmlEntities } from 'html-entities';
|
import { AllHtmlEntities } from 'html-entities';
|
||||||
import { FsDriver, MarkupRenderer, MarkupToHtmlConverter, OptionsResourceModel, RenderResult } from './types';
|
import { FsDriver, MarkupRenderer, MarkupToHtmlConverter, OptionsResourceModel, RenderOptions, RenderResult } from './types';
|
||||||
import defaultResourceModel from './defaultResourceModel';
|
import defaultResourceModel from './defaultResourceModel';
|
||||||
const MarkdownIt = require('markdown-it');
|
const MarkdownIt = require('markdown-it');
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ export default class MarkupToHtml implements MarkupToHtmlConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
public async render(markupLanguage: MarkupLanguage, markup: string, theme: any, options: any): Promise<RenderResult> {
|
public async render(markupLanguage: MarkupLanguage, markup: string, theme: any, options: RenderOptions): Promise<RenderResult> {
|
||||||
if (this.options_.isSafeMode) {
|
if (this.options_.isSafeMode) {
|
||||||
const htmlentities = new AllHtmlEntities();
|
const htmlentities = new AllHtmlEntities();
|
||||||
return {
|
return {
|
||||||
|
@ -4,7 +4,7 @@ import { fileExtension } from '@joplin/utils/path';
|
|||||||
import setupLinkify from './MdToHtml/setupLinkify';
|
import setupLinkify from './MdToHtml/setupLinkify';
|
||||||
import validateLinks from './MdToHtml/validateLinks';
|
import validateLinks from './MdToHtml/validateLinks';
|
||||||
import { Options as NoteStyleOptions } from './noteStyle';
|
import { Options as NoteStyleOptions } from './noteStyle';
|
||||||
import { FsDriver, ItemIdToUrlHandler, MarkupRenderer, OptionsResourceModel, RenderOptions, RenderResult, RenderResultPluginAsset } from './types';
|
import { FsDriver, ItemIdToUrlHandler, MarkupRenderer, OptionsResourceModel, RenderOptions, RenderResult, RenderResultPluginAsset, ResourceInfos } from './types';
|
||||||
import hljs from './highlight';
|
import hljs from './highlight';
|
||||||
import * as MarkdownIt from 'markdown-it';
|
import * as MarkdownIt from 'markdown-it';
|
||||||
|
|
||||||
@ -161,8 +161,7 @@ export interface RuleOptions {
|
|||||||
postMessageSyntax: string;
|
postMessageSyntax: string;
|
||||||
ResourceModel: OptionsResourceModel;
|
ResourceModel: OptionsResourceModel;
|
||||||
resourceBaseUrl: string;
|
resourceBaseUrl: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
resources: ResourceInfos; // resourceId: Resource
|
||||||
resources: any; // resourceId: Resource
|
|
||||||
|
|
||||||
// Used by checkboxes to specify how it should be rendered
|
// Used by checkboxes to specify how it should be rendered
|
||||||
checkboxRenderingType?: number;
|
checkboxRenderingType?: number;
|
||||||
@ -643,6 +642,7 @@ export default class MdToHtml implements MarkupRenderer {
|
|||||||
const renderedBody = markdownIt.render(body, context);
|
const renderedBody = markdownIt.render(body, context);
|
||||||
|
|
||||||
let cssStrings = noteStyle(options.theme, {
|
let cssStrings = noteStyle(options.theme, {
|
||||||
|
scrollbarSize: options.scrollbarSize,
|
||||||
contentMaxWidth: options.contentMaxWidth,
|
contentMaxWidth: options.contentMaxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ describe('linkReplacement', () => {
|
|||||||
ResourceModel: defaultResourceModel,
|
ResourceModel: defaultResourceModel,
|
||||||
resources: {
|
resources: {
|
||||||
[resourceId]: {
|
[resourceId]: {
|
||||||
item: {},
|
item: { id: 'test' },
|
||||||
localState: {
|
localState: {
|
||||||
fetch_status: 2, // FETCH_STATUS_DONE
|
fetch_status: 2, // FETCH_STATUS_DONE
|
||||||
},
|
},
|
||||||
@ -46,7 +46,7 @@ describe('linkReplacement', () => {
|
|||||||
ResourceModel: defaultResourceModel,
|
ResourceModel: defaultResourceModel,
|
||||||
resources: {
|
resources: {
|
||||||
[resourceId]: {
|
[resourceId]: {
|
||||||
item: {},
|
item: { id: 'test' },
|
||||||
localState: {
|
localState: {
|
||||||
fetch_status: 0, // FETCH_STATUS_IDLE
|
fetch_status: 0, // FETCH_STATUS_IDLE
|
||||||
},
|
},
|
||||||
@ -66,7 +66,7 @@ describe('linkReplacement', () => {
|
|||||||
ResourceModel: defaultResourceModel,
|
ResourceModel: defaultResourceModel,
|
||||||
resources: {
|
resources: {
|
||||||
[resourceId]: {
|
[resourceId]: {
|
||||||
item: {},
|
item: { id: 'test' },
|
||||||
localState: {
|
localState: {
|
||||||
fetch_status: 2, // FETCH_STATUS_DONE
|
fetch_status: 2, // FETCH_STATUS_DONE
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { LinkRenderingType } from '../MdToHtml';
|
import { LinkRenderingType } from '../MdToHtml';
|
||||||
import { ItemIdToUrlHandler, OptionsResourceModel } from '../types';
|
import { ItemIdToUrlHandler, OptionsResourceModel, ResourceInfos } from '../types';
|
||||||
import * as utils from '../utils';
|
import * as utils from '../utils';
|
||||||
import createEventHandlingAttrs from './createEventHandlingAttrs';
|
import createEventHandlingAttrs from './createEventHandlingAttrs';
|
||||||
const Entities = require('html-entities').AllHtmlEntities;
|
const Entities = require('html-entities').AllHtmlEntities;
|
||||||
@ -9,8 +9,7 @@ const { getClassNameForMimeType } = require('font-awesome-filetypes');
|
|||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
title?: string;
|
title?: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
resources?: ResourceInfos;
|
||||||
resources?: any;
|
|
||||||
ResourceModel?: OptionsResourceModel;
|
ResourceModel?: OptionsResourceModel;
|
||||||
linkRenderingType?: LinkRenderingType;
|
linkRenderingType?: LinkRenderingType;
|
||||||
plainResourceRendering?: boolean;
|
plainResourceRendering?: boolean;
|
||||||
|
@ -11,6 +11,7 @@ function formatCssSize(v: any): string {
|
|||||||
export interface Options {
|
export interface Options {
|
||||||
contentMaxWidth?: number;
|
contentMaxWidth?: number;
|
||||||
contentMaxWidthTarget?: string;
|
contentMaxWidthTarget?: string;
|
||||||
|
scrollbarSize?: number;
|
||||||
themeId?: number;
|
themeId?: number;
|
||||||
whiteBackgroundNoteRendering?: boolean;
|
whiteBackgroundNoteRendering?: boolean;
|
||||||
}
|
}
|
||||||
@ -117,9 +118,17 @@ export default function(theme: any, options: Options = null) {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: ${theme.codeBackgroundColor};
|
background-color: ${theme.codeBackgroundColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--scrollbar-size: ${Number(options.scrollbarSize ?? 7)}px;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: var(--scrollbar-size);
|
||||||
height: 7px;
|
height: var(--scrollbar-size);
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: calc(var(--scrollbar-size) / 2);
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -4,12 +4,27 @@ import { Options as NoteStyleOptions } from './noteStyle';
|
|||||||
export type ItemIdToUrlHandler = (resourceId: string, urlParameters?: string)=> string;
|
export type ItemIdToUrlHandler = (resourceId: string, urlParameters?: string)=> string;
|
||||||
|
|
||||||
interface ResourceEntity {
|
interface ResourceEntity {
|
||||||
id: string;
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
mime?: string;
|
mime?: string;
|
||||||
file_extension?: string;
|
file_extension?: string;
|
||||||
|
updated_time?: number;
|
||||||
|
|
||||||
|
encryption_applied?: number;
|
||||||
|
encryption_blob_encrypted?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResourceLocalState {
|
||||||
|
fetch_status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceInfo {
|
||||||
|
localState: ResourceLocalState;
|
||||||
|
item: ResourceEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResourceInfos = Record<string, ResourceInfo>;
|
||||||
|
|
||||||
export interface FsDriver {
|
export interface FsDriver {
|
||||||
writeFile: (path: string, content: string, encoding: string)=> Promise<void>;
|
writeFile: (path: string, content: string, encoding: string)=> Promise<void>;
|
||||||
exists: (path: string)=> Promise<boolean>;
|
exists: (path: string)=> Promise<boolean>;
|
||||||
@ -19,6 +34,7 @@ export interface FsDriver {
|
|||||||
|
|
||||||
export interface RenderOptions {
|
export interface RenderOptions {
|
||||||
contentMaxWidth?: number;
|
contentMaxWidth?: number;
|
||||||
|
scrollbarSize?: number;
|
||||||
bodyOnly?: boolean;
|
bodyOnly?: boolean;
|
||||||
splitted?: boolean;
|
splitted?: boolean;
|
||||||
enableLongPress?: boolean;
|
enableLongPress?: boolean;
|
||||||
@ -48,13 +64,15 @@ export interface RenderOptions {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
settingValue?: (pluginId: string, key: string)=> any;
|
settingValue?: (pluginId: string, key: string)=> any;
|
||||||
|
|
||||||
resources?: Record<string, ResourceEntity>;
|
resources?: ResourceInfos;
|
||||||
|
|
||||||
onResourceLoaded?: ()=> void;
|
onResourceLoaded?: ()=> void;
|
||||||
editPopupFiletypes?: string[];
|
editPopupFiletypes?: string[];
|
||||||
createEditPopupSyntax?: string;
|
createEditPopupSyntax?: string;
|
||||||
destroyEditPopupSyntax?: string;
|
destroyEditPopupSyntax?: string;
|
||||||
|
|
||||||
|
platformName?: string;
|
||||||
|
|
||||||
// HtmlToHtml only
|
// HtmlToHtml only
|
||||||
whiteBackgroundNoteRendering?: boolean;
|
whiteBackgroundNoteRendering?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { attributesHtml } from './htmlUtils';
|
import { attributesHtml } from './htmlUtils';
|
||||||
import { ItemIdToUrlHandler, OptionsResourceModel } from './types';
|
import { ItemIdToUrlHandler, OptionsResourceModel, ResourceInfo, ResourceInfos } from './types';
|
||||||
|
|
||||||
const Entities = require('html-entities').AllHtmlEntities;
|
const Entities = require('html-entities').AllHtmlEntities;
|
||||||
const htmlentities = new Entities().encode;
|
const htmlentities = new Entities().encode;
|
||||||
@ -98,8 +98,7 @@ export const resourceStatusName = function(index: number) {
|
|||||||
throw new Error(`Unknown index: ${index}`);
|
throw new Error(`Unknown index: ${index}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
export const resourceStatus = function(ResourceModel: OptionsResourceModel, resourceInfo: ResourceInfo) {
|
||||||
export const resourceStatus = function(ResourceModel: OptionsResourceModel, resourceInfo: any) {
|
|
||||||
if (!ResourceModel) return 'ready';
|
if (!ResourceModel) return 'ready';
|
||||||
|
|
||||||
let status = 'ready';
|
let status = 'ready';
|
||||||
@ -130,8 +129,7 @@ type ImageMarkupData = {
|
|||||||
title: string;
|
title: string;
|
||||||
}|{ src: string; before: string; after: string };
|
}|{ src: string; before: string; after: string };
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
export const imageReplacement = function(ResourceModel: OptionsResourceModel, markup: ImageMarkupData, resources: ResourceInfos, resourceBaseUrl: string, itemIdToUrl: ItemIdToUrlHandler = null) {
|
||||||
export const imageReplacement = function(ResourceModel: OptionsResourceModel, markup: ImageMarkupData, resources: any, resourceBaseUrl: string, itemIdToUrl: ItemIdToUrlHandler = null) {
|
|
||||||
if (!ResourceModel || !resources) return null;
|
if (!ResourceModel || !resources) return null;
|
||||||
|
|
||||||
const src = markup.src;
|
const src = markup.src;
|
||||||
|
Reference in New Issue
Block a user