From a974aa4e6573c8becf93f78610406747fec14c1c Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 19 Mar 2024 17:08:46 -0400 Subject: cleaned up user templates to not get changed on reload. made setting a template add it to the template tools list and as a tools button. fixed linking to parts of a template. fixed disappearing templates caused by stacking view set a field with an empty key. updated field assignment syntax in trees, dash field views, and key value box to all use :,:=,=,=:= syntax. added text elide button. added @(title) syntax for hyperlinking. made using a template both inherit from the template to get default values and use the template to render. fixed submenu placement of context menu. updated RTF markdown doc. --- src/client/views/global/globalScripts.ts | 33 ++++++++++---------------------- 1 file changed, 10 insertions(+), 23 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 0579b07c7..3fdc9a488 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -1,29 +1,26 @@ import { Colors } from 'browndash-components'; import { action, runInAction } from 'mobx'; +import { aggregateBounds } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { aggregateBounds } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; -import { undoable, UndoManager } from '../../util/UndoManager'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm'; +import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; // import { InkTranscription } from '../InkTranscription'; +import { DocData } from '../../../fields/DocSymbols'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; -import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { WebBox } from '../nodes/WebBox'; import { VideoBox } from '../nodes/VideoBox'; -import { DocData } from '../../../fields/DocSymbols'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { PrefetchProxy } from '../../../fields/Proxy'; -import { MakeTemplate } from '../../util/DropConverter'; +import { WebBox } from '../nodes/WebBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -76,19 +73,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b // toggle: Set overlay status of selected document ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { - if (checkResult) { - return Doc.UserDoc().defaultTextLayout; - } - const view = SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView instanceof FormattedTextBox ? SelectionManager.Views[0] : undefined; - - if (view) { - const tempDoc = view.Document; - if (!view.layoutDoc.isTemplateDoc) { - MakeTemplate(tempDoc); - } - Doc.UserDoc().defaultTextLayout = new PrefetchProxy(tempDoc); - tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', tempDoc); - } else Doc.UserDoc().defaultTextLayout = undefined; + return DocumentView.setDefaultTemplate(checkResult); }); // toggle: Set overlay status of selected document ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { @@ -197,7 +182,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh map.get(attr)?.setDoc?.(); }); -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; +type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { @@ -221,6 +206,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: 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), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), -- cgit v1.2.3-70-g09d2 From b420caf2c7ecd386cae2cc550904522474b541aa Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Mar 2024 22:34:10 -0400 Subject: added empty image tool and click on empty image to select from filesystem. fixed following links in lightbox and showing links to stackedTimelines. fixed embedding docs into text. fixed not resizing text boxes that also show up in pivot view. prevent context menu from going off top of screen. fixed freeform clustering colors and click to type. fixed links to stackedTimeline marks, and titles for marks. made title editing from doc deco and header use same syntax as keyValue. fixed marquee selection on webBoxes. turn off transitions in freeformdocview after timeout. enabled iconifying templates to propagate to "offspring". fixes images in templates. don't show headr on schema views. --- src/client/documents/Documents.ts | 52 ++++++++-------- src/client/util/DocumentManager.ts | 4 +- src/client/views/ContextMenu.tsx | 6 +- src/client/views/DocumentDecorations.tsx | 71 ++++++++-------------- src/client/views/FieldsDropdown.tsx | 1 - src/client/views/LightboxView.tsx | 2 + src/client/views/MarqueeAnnotator.tsx | 19 ++++-- src/client/views/ObservableReactComponent.tsx | 3 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/StyleProvider.tsx | 6 +- .../collections/CollectionStackedTimeline.tsx | 13 ++-- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 ++-- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- .../collectionSchema/SchemaTableCell.tsx | 2 +- src/client/views/global/globalScripts.ts | 4 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++-- src/client/views/nodes/DocumentContentsView.tsx | 7 ++- src/client/views/nodes/DocumentView.tsx | 35 +++++++---- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/ImageBox.tsx | 55 +++++++++++++---- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/LinkBox.tsx | 47 ++++++++++---- src/client/views/nodes/VideoBox.tsx | 5 +- src/client/views/nodes/WebBox.tsx | 65 ++++++++++++++------ .../views/nodes/formattedText/FormattedTextBox.tsx | 17 ++++-- src/client/views/nodes/trails/PresBox.tsx | 2 +- src/fields/Doc.ts | 29 +++++---- src/fields/PresField.ts | 6 -- src/fields/RichTextField.ts | 7 --- 31 files changed, 311 insertions(+), 195 deletions(-) delete mode 100644 src/fields/PresField.ts (limited to 'src/client/views/global') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 17cb6fef8..b63c5e429 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1017,8 +1017,8 @@ export namespace Docs { } export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { - const imgField = url instanceof ImageField ? url : new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); + const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined; + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc); } export function PresDocument(options: DocumentOptions = {}) { @@ -1950,6 +1950,31 @@ export namespace DocUtils { return dd; } + export function assignImageInfo(result: Upload.FileInformation, proto: Doc) { + if (Upload.isImageInformation(result)) { + const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); + const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); + proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); + proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + if (NumCast(proto['data-nativeOrientation']) >= 5) { + proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + } + proto.data_exif = JSON.stringify(result.exifData?.data); + proto.data_contentSize = result.contentSize; + // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates + const latitude = result.exifData?.data?.GPSLatitude; + const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; + const longitude = result.exifData?.data?.GPSLongitude; + const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; + if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { + proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); + proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); + } + } + } + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); @@ -1961,28 +1986,7 @@ export namespace DocUtils { if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; - if (Upload.isImageInformation(result)) { - const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); - proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); - proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (NumCast(proto['data-nativeOrientation']) >= 5) { - proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - } - proto.data_exif = JSON.stringify(result.exifData?.data); - proto.data_contentSize = result.contentSize; - // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates - const latitude = result.exifData?.data?.GPSLatitude; - const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; - const longitude = result.exifData?.data?.GPSLongitude; - const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; - if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { - proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); - proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); - } - } + !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); if (Upload.isVideoInformation(result)) { proto.data_duration = result.duration; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a38a330da..40d28c690 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -59,7 +59,7 @@ export class DocumentManager { private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => { if (doc) { - const dv = this.getDocumentView(doc); + const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc); this._viewRenderedCbs.push({ doc, func }); if (dv) { this.callAddViewFuncs(dv); @@ -262,7 +262,7 @@ export class DocumentManager { return res(this.getDocumentView(docContextPath[0])!); } options.didMove = true; - docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); + (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); })); if (options.openLocation === OpenWhere.lightbox) { diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 8f4e43978..ca877b93e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> { this._width = DivWidth(r); this._height = DivHeight(r); } + this._searchRef.current?.focus(); })} style={{ display: this._display ? '' : 'none', left: this.pageX, - ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }), + ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }), background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, }}> @@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> { const item = this.flatItems[this._selectedIndex]; if (item) { item.event({ x: this.pageX, y: this.pageY }); - } else if (this._searchString.startsWith(this._defaultPrefix)) { + } else { + //if (this._searchString.startsWith(this._defaultPrefix)) { this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length)); } this.closeMenu(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2fb9f0fc1..4d9b93896 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -34,6 +34,7 @@ import { Colors } from './global/globalEnums'; import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { KeyValueBox } from './nodes/KeyValueBox'; interface DocumentDecorationsProps { PanelWidth: number; @@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent { - if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) { + if (this._accumulatedTitle.startsWith('$')) { this._titleControlString = this._accumulatedTitle; - } else if (this._titleControlString.startsWith('#')) { + } else if (this._titleControlString.startsWith('$')) { if (this._accumulatedTitle.startsWith('-->#')) { SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`))); } @@ -131,26 +132,7 @@ export class DocumentDecorations extends ObservableReactComponent')) { - const title = titleField.toString().replace(/\.?/, ''); - const curKey = Doc.LayoutFieldKey(d.Document); - if (curKey !== title) { - if (title) { - if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') { - d.Document.layout_fieldKey = `layout_${title}`; - d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title); - d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0; - } - } else { - d.Document.layout_fieldKey = undefined; - } - } - } else { - Doc.SetInPlace(d.Document, titleFieldKey, titleField, true); - } + KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle); }), 'edit title' ); @@ -181,7 +163,8 @@ export class DocumentDecorations extends ObservableReactComponent this.onBackgroundMove(true, e), emptyFunction, action(e => { - !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString); + const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined; + !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString); this._editingTitle = true; this._keyinput.current && setTimeout(this._keyinput.current.focus); }) @@ -622,11 +605,8 @@ export class DocumentDecorations extends ObservableReactComponent { - this._editingTitle = false; - !hideTitle && this.titleBlur(); - })} - onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))} - onKeyDown={hideTitle ? emptyFunction : this.titleEntered} - onPointerDown={e => e.stopPropagation()} - /> + <> + {r - x < 150 ? null : {this._titleControlString + ':'}} + { + this._editingTitle = false; + !hideTitle && this.titleBlur(); + })} + onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))} + onKeyDown={hideTitle ? emptyFunction : this.titleEntered} + onPointerDown={e => e.stopPropagation()} + /> + ) : (
{hideTitle ? null : ( diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx index 5638d34c6..6a5c2cb4c 100644 --- a/src/client/views/FieldsDropdown.tsx +++ b/src/client/views/FieldsDropdown.tsx @@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent filteredOptions.push(pair[0])); const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet })); - console.log(options); return ( + {!this.values.length ? null : ( + - )} */} + )}
); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 66df1eaf2..3700b08d6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2064,8 +2064,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._isHovering = true))} - onPointerLeave={action(() => (this._isHovering = false))} + onPointerEnter={action(() => { + this._isHovering = true; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true); + })} + onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))} ref={r => { this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; -- 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/global') 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 ac58c8da5dc8ee5346bccd6da1f6883a7ce0d3d7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 2 Apr 2024 14:49:15 -0400 Subject: fixed highlight list type button --- src/client/views/global/globalScripts.ts | 2 +- .../views/nodes/formattedText/RichTextMenu.tsx | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 497ab98d8..2a5732708 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -199,7 +199,7 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: (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), + [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false), toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]); // prettier-ignore const attrs:attrfuncs[] = [ diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9c282a1d2..4bd4ca72b 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -51,7 +51,7 @@ export class RichTextMenu extends AntimodeMenu { @observable private _activeFontSize: string = '13px'; @observable private _activeFontFamily: string = ''; - @observable private activeListType: string = ''; + @observable private _activeListType: string = ''; @observable private _activeAlignment: string = 'left'; @observable private brushMarks: Set = new Set(); @@ -103,6 +103,9 @@ export class RichTextMenu extends AntimodeMenu { @computed get fontSize() { return this._activeFontSize; } + @computed get listStyle() { + return this._activeListType; + } @computed get textAlign() { return this._activeAlignment; } @@ -143,7 +146,7 @@ export class RichTextMenu extends AntimodeMenu { const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc(); const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey); - this.activeListType = this.getActiveListStyle(); + this._activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0]; @@ -199,16 +202,15 @@ export class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveListStyle() { - if (this.view && this.TextView?._props.rootSelected?.()) { - const path = (this.view.state.selection.$from as any).path; - for (let i = 0; i < path.length; i += 3) { - if (path[i].type === this.view.state.schema.nodes.ordered_list) { - return path[i].attrs.mapStyle; + const state = this.view?.state; + if (state) { + const pos = state.selection.$anchor; + for (let i = 0; i < pos.depth; i++) { + const node = pos.node(i); + if (node.type === schema.nodes.ordered_list) { + return node.attrs.mapStyle; } } - if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) { - return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; - } } return ''; } -- cgit v1.2.3-70-g09d2