From 1e37adacfe5b44a71ed7d7dfd785c6c2d0808eae Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 31 Mar 2024 08:43:38 -0400 Subject: fixed divider column for notetaking view. fixed scrolling of text docs. fixed keeping user docs in system button bars. --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.scss') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 03ff0436b..00d890860 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -156,6 +156,7 @@ audiotag:hover { .formattedTextBox-inner, .formattedTextBox-inner-minimal { height: 100%; + overflow: auto; white-space: pre-wrap; .ProseMirror:hover { background: rgba(200, 200, 200, 0.2); -- cgit v1.2.3-70-g09d2 From e4854bdb361f1d9e0ae85ee46327e2919117fc48 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 1 Apr 2024 11:14:24 -0400 Subject: revert back to old style of forced text scrolling - doesn't work for pres trails. --- src/Utils.ts | 5 +++-- src/client/views/nodes/formattedText/FormattedTextBox.scss | 1 - src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.scss') diff --git a/src/Utils.ts b/src/Utils.ts index 38325a463..21c91278f 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -653,13 +653,14 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen const animateScroll = () => { const currentDate = new Date().getTime(); const currentTime = currentDate - startDate; - elements.map((element, i) => (element.scrollTop = easeFunc(transition, currentTime, starts[i], to - starts[i], duration))); + const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value); + elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration))); if (!_stop) { if (currentTime < duration) { requestAnimationFrame(animateScroll); } else { - elements.forEach(element => (element.scrollTop = to)); + elements.forEach(element => setScrollTop(element, to)); } } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 00d890860..03ff0436b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -156,7 +156,6 @@ audiotag:hover { .formattedTextBox-inner, .formattedTextBox-inner-minimal { height: 100%; - overflow: auto; white-space: pre-wrap; .ProseMirror:hover { background: rgba(200, 200, 200, 0.2); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 80e56efe0..a78d8363e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -91,7 +91,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(); private _sidebarTagRef = React.createRef(); private _ref: React.RefObject = React.createRef(); - private _scrollRef: React.RefObject = React.createRef(); + private _scrollRef: HTMLDivElement | null = null; private _editorView: Opt; public _applyingChange: string = ''; private _inDrop = false; @@ -1415,14 +1415,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const docPos = editorView.coordsAtPos(editorView.state.selection.to); const viewRect = self._ref.current!.getBoundingClientRect(); - const scrollRef = self.ProseRef; + const scrollRef = self._scrollRef; const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined; const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined; if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) { const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE); const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale; if (this._focusSpeed !== undefined) { - scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper)); + setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper))); } else { scrollRef.scrollTo({ top: scrollPos }); } @@ -1823,10 +1823,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) { + if (!LinkInfo.Instance?.LinkInfo && this._scrollRef) { if (!this._props.dontSelectOnLoad) { this._ignoreScroll = true; - this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop; + this.layoutDoc._layout_scrollTop = this._scrollRef.scrollTop; this._ignoreScroll = false; e.stopPropagation(); e.preventDefault(); @@ -2031,7 +2031,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
(this._scrollRef = r)} style={{ width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined, -- cgit v1.2.3-70-g09d2 From 75cbf3ffc1893d02b48ef65ec834e9ae597398bf Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 1 Apr 2024 20:23:24 -0400 Subject: fixed text toggle buttons to highlight based on selection. enabled background colors for text lists. cleaned up text insertion point setting on pointer up. fixed autoHeight for text boxes with 'auto' nodes like ordered lists. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/global/globalScripts.ts | 50 +++--- .../views/nodes/formattedText/DashFieldView.scss | 1 + .../nodes/formattedText/FormattedTextBox.scss | 14 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 47 ++--- .../formattedText/ProsemirrorExampleTransfer.ts | 2 +- .../views/nodes/formattedText/RichTextMenu.tsx | 199 ++++----------------- .../views/nodes/formattedText/RichTextRules.ts | 4 +- src/client/views/nodes/formattedText/nodes_rts.ts | 30 ++-- 9 files changed, 113 insertions(+), 236 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.scss') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b73111062..38ebc86e7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -725,7 +725,7 @@ pie title Minerals in my tap water btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 }, { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}}, - { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, + { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index dab642499..497ab98d8 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -151,26 +151,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, - setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), + setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value), }], ['highlight', { - checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, - setDoc: () => value && RichTextMenu.Instance.setHighlight(value), + checkResult: () => RichTextMenu.Instance?.fontHighlight, + setDoc: () => value && RichTextMenu.Instance?.setHighlight(value), }], ['fontColor', { checkResult: () => RichTextMenu.Instance?.fontColor, - setDoc: () => value && RichTextMenu.Instance.setColor(value), + setDoc: () => value && RichTextMenu.Instance?.setColor(value), }], ['alignment', { - checkResult: () => RichTextMenu.Instance.textAlign, - setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), + checkResult: () => RichTextMenu.Instance?.textAlign, + setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), }], ['fontSize', { checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), setDoc: () => { if (typeof value === 'number') value = value.toString(); if (value && Number(value).toString() === value) value += 'px'; - RichTextMenu.Instance.setFontSize(value); + RichTextMenu.Instance?.setFontSize(value); }, }], ]); @@ -182,45 +182,45 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh }); type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; -type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; +type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }]; ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { const textView = RichTextMenu.Instance?.TextView; const editorView = textView?.EditorView; // prettier-ignore const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) => - [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance.textVcenter: - (RichTextMenu.Instance.textAlign === where)): + [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: + (RichTextMenu.Instance?.textAlign === where)): where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): (Doc.UserDoc().textAlign ===where) ? true:false), - toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch): - RichTextMenu.Instance.align(editorView, editorView.dispatch, where)): + toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch): + RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered: (Doc.UserDoc().textAlign = where))}]); // prettier-ignore // prettier-ignore const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => - [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), - toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); + [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.getActiveListStyle() === list:false), + toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]); // prettier-ignore const attrs:attrfuncs[] = [ ['dictation', { checkResult: () => textView?._recordingDictation ? true:false, toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }], ['elide', { checkResult: () => false, toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], - ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), + ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], - ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] + ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false), + toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], + ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false), + toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false), + toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] const map = new Map(attrs.concat(alignments).concat(listings)); if (checkResult) { return map.get(charStyle)?.checkResult(); } - undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)(); + undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)(); }); export function checkInksToGroup() { @@ -448,10 +448,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { */ ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { SelectionManager.Docs.map(doc => (doc._text_fontFamily = key)); - const editorView = RichTextMenu.Instance.TextView?.EditorView; + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily); } - if (editorView) RichTextMenu.Instance.setFontFamily(key); + if (editorView) RichTextMenu.Instance?.setFontFamily(key); else Doc.UserDoc().fontFamily = key; }); diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 74eeb014c..d79df4272 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -26,6 +26,7 @@ display: inline-block; font-weight: normal; background: rgba(0, 0, 0, 0.1); + cursor: default; } .dashFieldView-fieldSpan { min-width: 8px; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 03ff0436b..3dcc45c96 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -273,6 +273,7 @@ footnote::before { height: 20px; &::before { content: '→'; + cursor: default; } &:hover { background: orange; @@ -348,6 +349,7 @@ footnote::before { touch-action: none; span { font-family: inherit; + background-color: inherit; } blockquote { @@ -397,6 +399,7 @@ footnote::before { font-family: inherit; } margin-left: 0; + background-color: inherit; } .decimal2-ol { counter-reset: deci2; @@ -406,6 +409,7 @@ footnote::before { } font-size: smaller; padding-left: 2.1em; + background-color: inherit; } .decimal3-ol { counter-reset: deci3; @@ -415,6 +419,7 @@ footnote::before { } font-size: smaller; padding-left: 2.85em; + background-color: inherit; } .decimal4-ol { counter-reset: deci4; @@ -424,6 +429,7 @@ footnote::before { } font-size: smaller; padding-left: 3.85em; + background-color: inherit; } .decimal5-ol { counter-reset: deci5; @@ -432,6 +438,7 @@ footnote::before { font-family: inherit; } font-size: smaller; + background-color: inherit; } .decimal6-ol { counter-reset: deci6; @@ -440,6 +447,7 @@ footnote::before { font-family: inherit; } font-size: smaller; + background-color: inherit; } .decimal7-ol { counter-reset: deci7; @@ -448,6 +456,7 @@ footnote::before { font-family: inherit; } font-size: smaller; + background-color: inherit; } .multi1-ol { @@ -458,6 +467,7 @@ footnote::before { } margin-left: 0; padding-left: 1.2em; + background-color: inherit; } .multi2-ol { counter-reset: multi2; @@ -467,6 +477,7 @@ footnote::before { } font-size: smaller; padding-left: 2em; + background-color: inherit; } .multi3-ol { counter-reset: multi3; @@ -476,6 +487,7 @@ footnote::before { } font-size: smaller; padding-left: 2.85em; + background-color: inherit; } .multi4-ol { counter-reset: multi4; @@ -485,6 +497,7 @@ footnote::before { } font-size: smaller; padding-left: 3.85em; + background-color: inherit; } //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " } @@ -788,6 +801,7 @@ footnote::before { height: 20px; &::before { content: '→'; + cursor: default; } &:hover { background: orange; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2e8444379..c2f3a6e4b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -291,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._editorView?.state && RichTextMenu.Instance.setHighlight(color); + this._editorView?.state && RichTextMenu.Instance?.setHighlight(color); return undefined; }, 'highlght text'); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); @@ -1577,32 +1577,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const editor = this._editorView!; - const state = editor?.state; - if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return; - if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); - else if (this._props.isContentActive() && !e.button) { - const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); - let xpos = pcords?.pos || 0; - while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) { - xpos = xpos - 1; - } - let node: any; - try { - node = state.doc.nodeAt(xpos); - } catch (e) {} - if (node?.type !== schema.nodes.dashFieldView) { - editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos)))); - let target = e.target as any; // hrefs are stored on the dataset of the node that wraps the hyerlink - while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); - } else if (node) { - try { - editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos)))); - } catch (e) { - editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1)))); - } - } + const state = this.EditorView?.state; + if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) { + if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu(); + let target = e.target as any; // hrefs are stored on the dataset of the node that wraps the hyerlink + for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement); + while (target && !target.dataset?.targethrefs) target = target.parentElement; + FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); } }; @action @@ -1788,7 +1769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Number(val.replace('px', '').replace('auto', '0')); - const toHgt = (node: Element) => { + const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; + const toNum = (val: string) => Number(val.replace('px', '')); + const toHgt = (node: Element): number => { const { height, marginTop, marginBottom } = getComputedStyle(node); - return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom)); + const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height); + return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom)); }; - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins); + const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children); const scrollHeight = this.ProseRef && proseHeight; if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 92cc65fc5..ec8879487 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -104,7 +104,7 @@ export function buildKeymap>(schema: S, props: any, mapKey !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { const tx25 = updateBullets(tx2, schema); const ol_node = tx25.doc.nodeAt(range!.start)!; - const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, { ...ol_node.attrs, ...(marks?.[0]?.type === schema.marks.pFontSize ? { fontSize: marks[0].attrs.fontSize } : {}) }); + const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9b6eb8b8d..9c282a1d2 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -27,7 +27,10 @@ const { toggleMark } = require('prosemirror-commands'); @observer export class RichTextMenu extends AntimodeMenu { - @observable static Instance: RichTextMenu; + static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined }); + static get Instance() { + return RichTextMenu._instance?.menu; + } public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); @@ -67,7 +70,7 @@ export class RichTextMenu extends AntimodeMenu { constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); - RichTextMenu.Instance = this; + RichTextMenu._instance.menu = this; this.updateMenu(undefined, undefined, props, this.layoutDoc); this._canFade = false; this.Pinned = true; @@ -131,11 +134,7 @@ export class RichTextMenu extends AntimodeMenu { if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; } - // update active marks - const activeMarks = this.getActiveMarksOnSelection(); - this.setActiveMarkButtons(activeMarks); - - // update active font family and size + this.setActiveMarkButtons(this.getActiveMarksOnSelection()); const active = this.getActiveFontStylesOnSelection(); const activeFamilies = active.activeFamilies; const activeSizes = active.activeSizes; @@ -164,15 +163,13 @@ export class RichTextMenu extends AntimodeMenu { const fromRange = numberRange(state.selection.from).reverse(); const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from; const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined); - if (node?.type === schema.nodes.ordered_list) { - let attrs = node.attrs; - if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize }; - if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color }; - const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema); - dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); - } - { + if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) { + const hasMark = node.marks.some(m => m.type === mark.type); + const otherMarks = node.marks.filter(m => m.type !== mark.type); + const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey])); + const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]); + dispatch(updateBullets(markup, state.schema)); + } else { const state = this.view?.state; const tr = this.view?.state.tr; if (tr && state) { @@ -225,11 +222,12 @@ export class RichTextMenu extends AntimodeMenu { if (this.view && this.TextView?._props.rootSelected?.()) { const state = this.view.state; const pos = this.view.state.selection.$from; - const marks: Mark[] = [...(state.storedMarks ?? [])]; + var marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.storedMarks !== null) { } else if (state.selection.empty) { - const ref_node = this.reference_node(pos); - marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); + for (let i = 0; i <= pos.depth; i++) { + marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks]; + } } else { state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); @@ -256,41 +254,26 @@ export class RichTextMenu extends AntimodeMenu { //finds all active marks on selection in given group getActiveMarksOnSelection() { - let activeMarks: MarkType[] = []; - if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks; + if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[]; - const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; - if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); - //current selection - const { empty, ranges, $to } = this.view.state.selection as TextSelection; const state = this.view.state; - if (!empty) { - activeMarks = markGroup.filter(mark => { - const has = false; - for (let i = 0; !has && i < ranges.length; i++) { - return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark); - } - return false; - }); - } else { - const pos = this.view.state.selection.$from; - const ref_node: ProsNode | null = this.reference_node(pos); - if (ref_node !== null && ref_node !== this.view.state.doc) { - if (ref_node.isText) { - } else { - return []; - } - activeMarks = markGroup.filter(mark_type => { - // if (mark_type === state.schema.marks.pFontSize) { - // return mark.isINSet - // ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - // } - const mark = state.schema.mark(mark_type); - return mark.isInSet(ref_node.marks); - }); + var marks: Mark[] = [...(state.storedMarks ?? [])]; + const pos = this.view.state.selection.$from; + if (state.storedMarks !== null) { + } else if (state.selection.empty) { + for (let i = 0; i <= pos.depth; i++) { + marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks]; } + } else { + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); + }); } - return activeMarks; + const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; + return markGroup.filter(mark_type => { + const mark = state.schema.mark(mark_type); + return mark.isInSet(marks); + }); } @action @@ -418,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu { // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text changeListType = (mapStyle: string) => { - const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); + const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle(); const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle }); if (!this.view || nodeType?.attrs.mapStyle === '') return; @@ -566,7 +549,7 @@ export class RichTextMenu extends AntimodeMenu { // todo: add brushes to brushMap to save with a style name onBrushNameKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { - RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); + RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); this._brushNameRef.current!.style.background = 'lightGray'; } }; @@ -574,7 +557,7 @@ export class RichTextMenu extends AntimodeMenu { @action clearBrush() { - RichTextMenu.Instance.brushMarks = new Set(); + RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set()); } @action @@ -710,118 +693,8 @@ export class RichTextMenu extends AntimodeMenu { } }; - linkExtend($start: ResolvedPos, href: string) { - const mark = this.view!.state.schema.marks.linkAnchor; - - let startIndex = $start.index(); - let endIndex = $start.indexAfter(); - - while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--; - while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++; - - let startPos = $start.start(); - let endPos = startPos; - for (let i = 0; i < endIndex; i++) { - const size = $start.parent.child(i).nodeSize; - if (i < startIndex) startPos += size; - endPos += size; - } - return { from: startPos, to: endPos }; - } - - reference_node(pos: ResolvedPos): ProsNode | null { - if (!this.view) return null; - - let ref_node: ProsNode = this.view.state.doc; - if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { - ref_node = pos.nodeBefore; - } - if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { - if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) { - ref_node = pos.nodeAfter; - } - } - if (!ref_node && pos.pos > 0) { - let skip = false; - for (let i: number = pos.pos - 1; i > 0; i--) { - this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { - if (node.isLeaf && !skip) { - ref_node = node; - skip = true; - } - }); - } - } - if (!ref_node.isLeaf && ref_node.childCount > 0) { - ref_node = ref_node.child(0); - } - return ref_node; - } - render() { return null; - // TraceMobx(); - // const row1 =
{[ - // //!this.collapsed ? this.getDragger() : (null), - // // !this.Pinned ? (null) :
{[ - // // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), - // // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), - // // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), - // // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), - // // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), - // // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), - // //
- // // ]}
, - // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), - // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), - // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), - // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), - // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), - // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), - // this.createColorButton(), - // this.createHighlighterButton(), - // this.createLinkButton(), - // this.createBrushButton(), - //
, - // this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft), - // this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter), - // this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight), - // this.createButton("indent", "Inset More", undefined, this.insetParagraph), - // this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph), - // this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph), - // this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph), - // ]}
; - - // const row2 =
- // {this.collapsed ? this.getDragger() : (null)} - //
- //
- // {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => { - // this.activeFontSize = val; - // SelectionManager.Views.map(dv => dv.Document._text_fontSize = val); - // })), - // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => { - // this.activeFontFamily = val; - // SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val); - // })), - //
, - // this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})), - // this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), - // this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote), - // this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule) - // ]} - //
- // {/*
- // {
- // - //
} - // - //
*/} - //
; } } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e8cf9e992..42665830f 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -246,7 +246,7 @@ export class RichTextRules { // activate a style by name using prefix '%' new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => { const color = match[0].substring(1, match[0].length); - const marks = RichTextMenu.Instance._brushMap.get(color); + const marks = RichTextMenu.Instance?._brushMap.get(color); if ( DocListCast((Doc.UserDoc().template_notes as Doc).data) @@ -367,7 +367,7 @@ export class RichTextRules { if (count) { const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); - RichTextMenu.Instance.elideSelection(this.TextBox.EditorView?.state, true); + RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true); } count++; }); diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index cab3a6ef5..ceafc3ba1 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -24,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec The top level document node. doc: { content: 'block+', + marks: '_', }, paragraph: ParagraphNodeSpec, @@ -331,12 +332,10 @@ export const nodes: { [index: string]: NodeSpec } = { ...orderedList, content: 'list_item+', group: 'block', + marks: '_', attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" - fontColor: { default: 'inherit' }, - fontSize: { default: undefined }, - fontFamily: { default: undefined }, + mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet", visibility: { default: true }, indent: { default: undefined }, }, @@ -376,9 +375,10 @@ export const nodes: { [index: string]: NodeSpec } = { ], toDOM(node: Node) { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; - const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ''; - const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ''; - const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ''; + const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight); + const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize); + const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family); + const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color); const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ''; if (node.attrs.mapStyle === 'bullet') { return [ @@ -386,7 +386,7 @@ export const nodes: { [index: string]: NodeSpec } = { { 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle, - style: `${fsize} ${ffam} ${fcol} ${marg}`, + style: `${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`, }, 0, ]; @@ -398,7 +398,7 @@ export const nodes: { [index: string]: NodeSpec } = { class: `${map}-ol`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle, - style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`, + style: `list-style: none; ${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`, }, 0, ] @@ -422,16 +422,22 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, ], - toDOM(node: any) { + toDOM(node: Node) { + const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight); + const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize); + const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family); + const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color); const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; return [ 'li', - { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle }, + { class: `${map}`, style: `${fhigh} ${fsize} ${ffam} ${fcol} `, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle }, node.attrs.visibility ? 0 : [ 'span', - { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` }, + { + style: `${fhigh} ${fsize} ${ffam} ${fcol} position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre`, + }, `${node.firstChild?.textContent}...`, ], ]; -- cgit v1.2.3-70-g09d2 From 9c92d1ec78eed34700d49ff3daa3c5cca622ef35 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 3 Apr 2024 19:24:48 -0400 Subject: fixed chrome error with adding space between and
    s after deleting a list_item --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 1 + src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.scss') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 3dcc45c96..038e6ab90 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -350,6 +350,7 @@ footnote::before { span { font-family: inherit; background-color: inherit; + display: inline-block; } blockquote { diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index ec8879487..11d0b61ab 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -269,7 +269,9 @@ export function buildKeymap>(schema: S, props: any, mapKey if ( !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) backspace(view.state, view.dispatch, view, false); + if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) { + // backspace(view.state, view.dispatch, view, false); + } }) ) { if ( -- cgit v1.2.3-70-g09d2 From 2c8c60f255e069f8ee9085c4b5789f0923c125ea Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 3 Apr 2024 23:54:06 -0400 Subject: more fixes to text editor bullets. one small artifact lingers with clicking on empty list item, then backspacing over empty list items, then having to double-backspace when you get to a non-empty item. --- .../nodes/formattedText/FormattedTextBox.scss | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 65 ++++++++++++---------- 2 files changed, 37 insertions(+), 30 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.scss') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 038e6ab90..38dd2e847 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -350,7 +350,7 @@ footnote::before { span { font-family: inherit; background-color: inherit; - display: inline-block; + display: contents; // fixes problem where extra space is added around
      lists when inside a prosemirror span } blockquote { diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 11d0b61ab..1c0738bd3 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -257,7 +257,7 @@ export function buildKeymap>(schema: S, props: any, mapKey }); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); - const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { + const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -269,8 +269,9 @@ export function buildKeymap>(schema: S, props: any, mapKey if ( !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) { - // backspace(view.state, view.dispatch, view, false); + if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) { + // gets rid of an extra paragraph when joining two list items together. + joinBackward(view.state, (tx: Transaction) => view.dispatch(tx)); } }) ) { @@ -298,7 +299,7 @@ export function buildKeymap>(schema: S, props: any, mapKey const depth = trange ? liftTarget(trange) : null; if ( depth !== null && - state.selection.$from.node(state.selection.$from.depth - 1)?.type === state.schema.nodes.blockquote && // + state.selection.$from.node(-1)?.type === state.schema.nodes.blockquote && // !state.selection.$from.node().content.size && trange ) { @@ -308,7 +309,13 @@ export function buildKeymap>(schema: S, props: any, mapKey const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!newlineInCode(state, dispatch as any)) { - if (once && view.state.selection.$from.depth > 1 && !view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) { + const olNode = view.state.selection.$anchor.node(-2); + const liNode = view.state.selection.$anchor.node(-1); + // prettier-ignore + if (liNode?.type === schema.nodes.list_item && !liNode.textContent && + olNode?.type === schema.nodes.ordered_list && olNode.lastChild === liNode && once && view.state.selection.$from.depth === 3) + { + // handles case of hitting enter at then end of a top-level empty list item - the result is to create a paragraph for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++); } else if ( !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { @@ -316,32 +323,32 @@ export function buildKeymap>(schema: S, props: any, mapKey marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); dispatch(tx3); + // removes an extra paragraph created when selecting text across two list items or splitting an empty list item + !once && view.dispatch(view.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2)); }) ) { - const fromattrs = state.selection.$from.node().attrs; - if ( - !splitBlockKeepMarks(state, (tx3: Transaction) => { - const tonode = tx3.selection.$to.node(); - if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { - const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); - dispatch(tx4); - if ( - view.state.selection.$from.parentOffset && // - !view.state.selection.$from.node().content.size - ) - liftListItem(schema.nodes.list_item)(view.state, view.dispatch); - else if ( - once && - view.state.selection.$from.parentOffset && - view.state.selection.$from.depth > 1 && // - view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === schema.nodes.list_item - ) - enter(view.state, view.dispatch, view, false); - else if (once && depth && !view.state.selection.$from.parentOffset) backspace(view.state, view.dispatch, view, false); - } else dispatch(tx3.insertText('\r\n')); - }) - ) { - return false; + if (once && view.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') { + // handles case of hitting enter on an empty list item which needs to create a second empty paragraph, then split it by calling enter() again + view.dispatch(view.state.tr.insert(view.state.selection.from, schema.nodes.paragraph.create({}))); + enter(view.state, view.dispatch, view, false); + } else { + const fromattrs = state.selection.$from.node().attrs; + if ( + !splitBlockKeepMarks(state, (tx3: Transaction) => { + const tonode = tx3.selection.$to.node(); + if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { + const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); + dispatch(tx4); + } + + if (view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { + // if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items. + enter(view.state, dispatch, view, false); + } + }) + ) { + return false; + } } } } -- cgit v1.2.3-70-g09d2