1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Allow setting a max width for the editor content

This commit is contained in:
Laurent Cozic 2021-08-14 12:19:53 +01:00
parent a9961ae3ec
commit 8063c94ff7
9 changed files with 87 additions and 23 deletions

View File

@ -33,10 +33,11 @@ const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import { reg } from '@joplin/lib/registry';
import ErrorBoundary from '../../../ErrorBoundary';
import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
const menuUtils = new MenuUtils(CommandService.instance());
function markupRenderOptions(override: any = null) {
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
return { ...override };
}
@ -384,6 +385,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
if (Setting.value('style.editor.monospaceFontFamily')) monospaceFonts.push(`"${Setting.value('style.editor.monospaceFontFamily')}"`);
monospaceFonts.push('monospace');
const maxWidthCss = props.contentMaxWidth ? `
margin-right: auto !important;
margin-left: auto !important;
max-width: ${props.contentMaxWidth}px !important;
` : '';
const element = document.createElement('style');
element.setAttribute('id', 'codemirrorStyle');
document.head.appendChild(element);
@ -418,6 +425,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
/* Add a fixed right padding to account for the appearance (and disappearance) */
/* of the sidebar */
padding-right: 10px !important;
${maxWidthCss}
}
/* This enforces monospace for certain elements (code, tables, etc.) */
@ -533,7 +541,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
return () => {
document.head.removeChild(element);
};
}, [props.themeId]);
}, [props.themeId, props.contentMaxWidth]);
const webview_domReady = useCallback(() => {
setWebviewReady(true);
@ -572,7 +580,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
bodyToRender = `<i>${_('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout'))}</i>`;
}
const result = await props.markupToHtml(props.contentMarkupLanguage, bodyToRender, markupRenderOptions({ resourceInfos: props.resourceInfos }));
const result = await props.markupToHtml(props.contentMarkupLanguage, bodyToRender, markupRenderOptions({
resourceInfos: props.resourceInfos,
contentMaxWidth: props.contentMaxWidth,
}));
if (cancelled) return;
@ -795,6 +806,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
viewerStyle={styles.viewer}
onIpcMessage={webview_ipcMessage}
onDomReady={webview_domReady}
contentMaxWidth={props.contentMaxWidth}
/>
</div>
);

View File

@ -14,18 +14,18 @@ import { _, closestSupportedLocale } from '@joplin/lib/locale';
import useContextMenu from './utils/useContextMenu';
import { copyHtmlToClipboard } from '../../utils/clipboardUtils';
import shim from '@joplin/lib/shim';
const { MarkupToHtml } = require('@joplin/renderer');
import { MarkupToHtml } from '@joplin/renderer';
import { reg } from '@joplin/lib/registry';
import BaseItem from '@joplin/lib/models/BaseItem';
import setupToolbarButtons from './utils/setupToolbarButtons';
import { plainTextToHtml } from '@joplin/lib/htmlUtils';
import openEditDialog from './utils/openEditDialog';
const { themeStyle } = require('@joplin/lib/theme');
import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
import { themeStyle } from '@joplin/lib/theme';
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
function markupRenderOptions(override: any = null) {
function markupRenderOptions(override: MarkupToHtmlOptions = null): MarkupToHtmlOptions {
return {
plugins: {
checkbox: {
@ -148,8 +148,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
if (!resourceMd) return;
const result = await props.markupToHtml(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, resourceMd, markupRenderOptions({ bodyOnly: true }));
editor.insertContent(result.html);
// editor.fire('joplinChange');
// dispatchDidUpdate(editor);
}, [props.markupToHtml, editor]);
const insertResourcesIntoContentRef = useRef(null);

View File

@ -159,8 +159,8 @@ function NoteEditor(props: NoteEditorProps) {
customCss: props.customCss,
});
return markupToHtml.allAssets(markupLanguage, theme);
}, [props.themeId, props.customCss]);
return markupToHtml.allAssets(markupLanguage, theme, { contentMaxWidth: props.contentMaxWidth });
}, [props.themeId, props.customCss, props.contentMaxWidth]);
const handleProvisionalFlag = useCallback(() => {
if (props.isProvisional) {
@ -400,6 +400,7 @@ function NoteEditor(props: NoteEditorProps) {
noteToolbarButtonInfos: props.toolbarButtonInfos,
plugins: props.plugins,
fontSize: Setting.value('style.editor.fontSize'),
contentMaxWidth: props.contentMaxWidth,
};
let editor = null;
@ -601,6 +602,7 @@ const mapStateToProps = (state: AppState) => {
setTagsToolbarButtonInfo: toolbarButtonUtils.commandsToToolbarButtons([
'setTags',
], whenClauseContext)[0],
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
};
};

View File

@ -4,6 +4,7 @@ import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUt
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { MarkupLanguage } from '@joplin/renderer';
import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/MarkupToHtml';
import { MarkupToHtmlOptions } from './useMarkupToHtml';
export interface ToolbarButtonInfos {
[key: string]: ToolbarButtonInfo;
@ -37,6 +38,7 @@ export interface NoteEditorProps {
toolbarButtonInfos: ToolbarButtonInfo[];
setTagsToolbarButtonInfo: ToolbarButtonInfo;
richTextBannerDismissed: boolean;
contentMaxWidth: number;
}
export interface NoteBodyEditorProps {
@ -51,7 +53,7 @@ export interface NoteBodyEditorProps {
onWillChange(event: any): void;
onMessage(event: any): void;
onScroll(event: any): void;
markupToHtml: (markupLanguage: MarkupLanguage, markup: string, options: any)=> Promise<RenderResult>;
markupToHtml: (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
htmlToMarkdown: Function;
allAssets: (markupLanguage: MarkupLanguage)=> Promise<RenderResultPluginAsset[]>;
disabled: boolean;
@ -67,6 +69,7 @@ export interface NoteBodyEditorProps {
noteToolbarButtonInfos: ToolbarButtonInfo[];
plugins: PluginStates;
fontSize: number;
contentMaxWidth: number;
}
export interface FormNote {

View File

@ -13,9 +13,12 @@ interface HookDependencies {
plugins: PluginStates;
}
interface MarkupToHtmlOptions {
export interface MarkupToHtmlOptions {
replaceResourceInternalToExternalLinks?: boolean;
resourceInfos?: ResourceInfos;
contentMaxWidth?: number;
plugins?: Record<string, any>;
bodyOnly?: boolean;
}
export default function useMarkupToHtml(deps: HookDependencies) {

View File

@ -972,6 +972,8 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
},
'style.editor.contentMaxWidth': { value: 600, type: SettingItemType.Int, public: true, storage: SettingStorage.File, appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor maximum width'), description: () => _('Set it to 0 to make it take the complete available space.') },
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
// TODO: Is there a better way to do this? The goal here is to simply have

View File

@ -1,6 +1,7 @@
import MdToHtml from './MdToHtml';
import HtmlToHtml from './HtmlToHtml';
import htmlUtils from './htmlUtils';
import { Options as NoteStyleOptions } from './noteStyle';
const MarkdownIt = require('markdown-it');
export enum MarkupLanguage {
@ -48,7 +49,7 @@ export default class MarkupToHtml {
private options_: Options;
private rawMarkdownIt_: any;
public constructor(options: Options) {
public constructor(options: Options = null) {
this.options_ = {
ResourceModel: {
isResourceUrl: () => false,
@ -119,7 +120,7 @@ export default class MarkupToHtml {
return this.renderer(markupLanguage).render(markup, theme, options);
}
public async allAssets(markupLanguage: MarkupLanguage, theme: any) {
return this.renderer(markupLanguage).allAssets(theme);
public async allAssets(markupLanguage: MarkupLanguage, theme: any, noteStyleOptions: NoteStyleOptions = null) {
return this.renderer(markupLanguage).allAssets(theme, noteStyleOptions);
}
}

View File

@ -5,10 +5,28 @@ import setupLinkify from './MdToHtml/setupLinkify';
import validateLinks from './MdToHtml/validateLinks';
import { ItemIdToUrlHandler } from './utils';
import { RenderResult, RenderResultPluginAsset } from './MarkupToHtml';
import { Options as NoteStyleOptions } from './noteStyle';
const MarkdownIt = require('markdown-it');
const md5 = require('md5');
export interface RenderOptions {
contentMaxWidth?: number;
bodyOnly?: boolean;
splitted?: boolean;
externalAssetsOnly?: boolean;
postMessageSyntax?: string;
highlightedKeywords?: string[];
codeTheme?: string;
theme?: any;
plugins?: Record<string, any>;
audioPlayerEnabled?: boolean;
videoPlayerEnabled?: boolean;
pdfViewerEnabled?: boolean;
codeHighlightCacheKey?: string;
plainResourceRendering?: boolean;
}
interface RendererRule {
install(context: any, ruleOptions: any): any;
assets?(theme: any): any;
@ -331,7 +349,7 @@ export default class MdToHtml {
}
// This is similar to allProcessedAssets() but used only by the Rich Text editor
public async allAssets(theme: any) {
public async allAssets(theme: any, noteStyleOptions: NoteStyleOptions = null) {
const assets: any = {};
for (const key in rules) {
if (!this.pluginEnabled(key)) continue;
@ -343,7 +361,7 @@ export default class MdToHtml {
}
const processedAssets = this.processPluginAssets(assets);
processedAssets.cssStrings.splice(0, 0, noteStyle(theme).join('\n'));
processedAssets.cssStrings.splice(0, 0, noteStyle(theme, noteStyleOptions).join('\n'));
if (this.customCss_) processedAssets.cssStrings.push(this.customCss_);
const output = await this.outputAssetsToExternalAssets_(processedAssets);
return output.pluginAssets;
@ -376,8 +394,9 @@ export default class MdToHtml {
}
// "theme" is the theme as returned by themeStyle()
public async render(body: string, theme: any = null, options: any = null): Promise<RenderResult> {
options = Object.assign({}, {
public async render(body: string, theme: any = null, options: RenderOptions = null): Promise<RenderResult> {
options = {
// In bodyOnly mode, the rendered Markdown is returned without the wrapper DIV
bodyOnly: false,
// In splitted mode, the CSS and HTML will be returned in separate properties.
@ -395,7 +414,10 @@ export default class MdToHtml {
audioPlayerEnabled: this.pluginEnabled('audioPlayer'),
videoPlayerEnabled: this.pluginEnabled('videoPlayer'),
pdfViewerEnabled: this.pluginEnabled('pdfViewer'),
}, options);
contentMaxWidth: 0,
...options,
};
// The "codeHighlightCacheKey" option indicates what set of cached object should be
// associated with this particular Markdown body. It is only used to allow us to
@ -525,7 +547,9 @@ export default class MdToHtml {
const renderedBody = markdownIt.render(body, context);
let cssStrings = noteStyle(options.theme);
let cssStrings = noteStyle(options.theme, {
contentMaxWidth: options.contentMaxWidth,
});
let output = { ...this.allProcessedAssets(allRules, options.theme, options.codeTheme) };
cssStrings = cssStrings.concat(output.cssStrings);

View File

@ -7,11 +7,28 @@ function formatCssSize(v: any): string {
return `${v}px`;
}
export default function(theme: any) {
export interface Options {
contentMaxWidth?: number;
}
export default function(theme: any, options: Options = null) {
options = {
contentMaxWidth: 0,
...options,
};
theme = theme ? theme : {};
const fontFamily = '\'Avenir\', \'Arial\', sans-serif';
const maxWidthCss = options.contentMaxWidth ? `
#rendered-md {
max-width: ${options.contentMaxWidth}px;
margin-left: auto;
margin-right: auto;
}
` : '';
const css =
`
/* https://necolas.github.io/normalize.css/ */
@ -61,6 +78,8 @@ export default function(theme: any) {
background: rgba(100, 100, 100, 0.7);
}
${maxWidthCss}
/* Remove top padding and margin from first child so that top of rendered text is aligned to top of text editor text */
#rendered-md > h1:first-child,