1
0
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:
Henry Heino
2025-01-13 08:33:42 -08:00
committed by GitHub
parent e177bffb1c
commit 35a0b22df2
23 changed files with 276 additions and 219 deletions

View File

@ -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
View File

@ -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

View File

@ -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 };

View File

@ -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 () => {};

View File

@ -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'],

View File

@ -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";

View File

@ -0,0 +1,7 @@
.revision-viewer-root {
background-color: var(--joplin-background-color);
display: flex;
flex-direction: column;
flex: 1;
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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'],
}; };
}; };

View File

@ -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);

View File

@ -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]);
} }

View File

@ -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();
} }

View File

@ -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 {

View File

@ -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': {

View File

@ -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() {

View File

@ -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 {

View File

@ -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,
}); });

View File

@ -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
}, },

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;