diff options
author | bobzel <zzzman@gmail.com> | 2024-01-31 20:39:47 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-01-31 20:39:47 -0500 |
commit | 0d8c05db9cbb5a23a94554c65015c347ff8c38b9 (patch) | |
tree | fa1cd6ba23262e81ee7b1b03a89783d0026cbba0 /src | |
parent | 8ac814bbb81b690a6a10f5a07aa5ce0e8cafe283 (diff) |
cleaned up accessing/setting proto_embeddings with api on Doc. fixed some css related to IconButtons. added a paintView toggle button to dec decorations for text with code blocks. enabled text with code to modify Docs and get this and documentView as params.
Diffstat (limited to 'src')
19 files changed, 122 insertions, 200 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index f730d17fe..53d472c66 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -355,8 +355,8 @@ export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomC }); } }; - if (Doc.IsDataProto(doc) && DocListCast(doc.proto_embeddings).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { - doc = DocListCast(doc.proto_embeddings).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; + if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { + doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; } if (doc.hidden) { doc.hidden = false; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index ba981145d..54066d267 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -73,6 +73,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { _nativeHeight: 100, _width: 100, _height: 100, + _layout_hideContextMenu: true, backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), btnType: ButtonType.ClickButton, diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index dfaacf318..f5e162d16 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -61,7 +61,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an const compiledFunction = (() => { try { return new Function(...paramNames, `return ${script}`); - } catch { + } catch (e) { + console.log(e); return undefined; } })(); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 73fa6709c..58be41f53 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -189,7 +189,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() { toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true); - Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true); + Doc.RemoveEmbedding(doc, doc); doc.embedContainer = undefined; if (recent && !dontAddToRemoved) { doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2193acf62..dec109d7b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -283,7 +283,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora } else { var openDoc = selectedDocs[0].Document; if (openDoc.layout_fieldKey === 'layout_icon') { - openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); + openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); Doc.deiconifyView(openDoc); } LightboxView.Instance.SetLightboxDoc( diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 54f141a36..361451c4d 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -28,14 +28,14 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope if (!this._props.DocView) return []; const target = this._props.DocView._props.Document; const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document; - const embeddings = DocListCast(target.proto_embeddings); + const embeddings = Doc.GetEmbeddings(target); const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>()); - const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings)); + const containerSets = Array.from(containerProtos.keys()).map(container => Doc.GetEmbeddings(container)); const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()); - const doclayoutSets = Array.from(containers.keys()).map(dp => DocListCast(dp.proto_embeddings)); + const doclayoutSets = Array.from(containers.keys()).map(dp => Doc.GetEmbeddings(dp)); const doclayouts = Array.from( doclayoutSets .reduce((p, set) => { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 208ed56c9..a7a065a95 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -173,7 +173,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps if (this.dataDoc && this.selectedDoc) { const ids = new Set<string>(reqdKeys); const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); - docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && ids.add(key))); + docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))); // prettier-ignore Array.from(ids).filter(filter).sort().map(key => { @@ -263,8 +263,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps @computed get contextCount() { if (this.selectedDocumentView) { const target = this.selectedDocumentView.Document; - const embeddings = DocListCast(target.proto_embeddings); - return embeddings.length - 1; + return Doc.GetEmbeddings(target).length - 1; } else { return 0; } diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 4d3096f71..00bf503f5 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -1,5 +1,6 @@ .styleProvider-filter, .styleProvider-audio, +.styleProvider-paint, .styleProvider-lock { font-size: 10; width: 15; @@ -28,6 +29,9 @@ .styleProvider-audio { right: 30; } +.styleProvider-paint { + top: 15; +} .styleProvider-lock:hover, .styleProvider-audio:hover, .styleProvider-filter:hover { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5a0167338..d3d13988f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -9,7 +9,7 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { DocViews } from '../../fields/DocSymbols'; -import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; @@ -53,6 +53,16 @@ export enum StyleProp { function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); } +function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>) { + const scriptProps = { + this: doc, + _readOnly_: false, + documentView: props?.DocumentView?.(), + value: undefined, + }; + e.stopPropagation(); + UndoManager.RunInBatch(() => doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView'); +} export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${ @@ -267,6 +277,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & <FontAwesomeIcon icon='lock' size="lg" /> </div> ); + const paint = () => !doc?.onPaint ? null : ( + <div className="styleProvider-paint" onClick={e => togglePaintView(e, doc, props)}> + <FontAwesomeIcon icon='pen' size="lg" /> + </div> + ); const filter = () => { const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)); const showFilterIcon = @@ -329,6 +344,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & }; return ( <> + {paint()} {lock()} {filter()} {audio()} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 87973fd81..31ca86f0f 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() { // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined; if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); - Doc.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc); + Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc); } if (CollectionDockingView.Instance) { const dview = CollectionDockingView.Instance.Document; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 0a1946f09..09701ddb5 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -128,6 +128,10 @@ position: relative; z-index: 1; + .treeView-rightButtons > .iconButton-container { + min-height: unset; + } + .treeView-background { width: 100%; height: 100%; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 54e8b08b6..9368560e9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -54,6 +54,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import { RichTextField } from '../../../../fields/RichTextField'; +import { CompileScript } from '../../../util/Scripting'; export interface collectionFreeformViewProps { NativeWidth?: () => number; @@ -80,8 +81,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim(); return !paintFunc ? '' - : `const dashDiv = document.querySelector('#${this._paintedId}'); - (async () => { ${paintFunc} })()`; + : paintFunc.includes('dashDiv') + ? `const dashDiv = document.querySelector('#${this._paintedId}'); + (async () => { ${paintFunc} })()` + : paintFunc; } constructor(props: any) { super(props); @@ -1496,7 +1499,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._disposers.paintFunc = reaction( () => ({ code: this.paintFunc, first: this._firstRender, width: this.Document._width, height: this.Document._height }), - ({ code, first }) => code && !first && eval(code), + ({ code, first }) => { + if (!code.includes('dashDiv')) { + const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true }); + if (script.compiled) script.run({ this: this.Document, docView: this.DocumentView?.() }); + } else code && !first && eval(code); + }, { fireImmediately: true } ); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 29d121974..29491569a 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -128,7 +128,7 @@ .row-menu { display: flex; - justify-content: flex-end; + justify-content: center; } } @@ -224,7 +224,10 @@ display: flex; flex-direction: row; min-width: 50px; - justify-content: flex-end; + justify-content: center; + .iconButton-container { + min-width: unset !important; + } } .row-cells { diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index f2fe0dde7..39fea2d2e 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -121,7 +121,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { pointerEvents: !this._props.isContentActive() ? 'none' : undefined, }}> <IconButton - tooltip="whether document interations are enabled" + tooltip="whether document interactions are enabled" icon={this.Document._lockedPosition ? <CgLockUnlock size="12px" /> : <CgLock size="12px" />} size={Size.XSMALL} onPointerDown={e => diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 51f4b1a68..b5355fb99 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -456,8 +456,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc'); setToggleDetail = undoable( - (defaultLayout: string) => - (this.Document.onClick = ScriptField.MakeScript( + (defaultLayout: string, scriptFieldKey: 'onClick') => + (this.Document[scriptFieldKey] = ScriptField.MakeScript( `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) .replace('layout_', '') .replace(/^layout$/, 'detail')}", "${defaultLayout}")`, @@ -1212,7 +1212,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { }; public noOnClick = () => this._docViewInternal?.noOnClick(); public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); - public setToggleDetail = (defaultLayout = '') => this._docViewInternal?.setToggleDetail(defaultLayout); + public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey); public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents(); public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 973f90501..b82ab4219 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -67,8 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; -import { CollectionView } from '../../collections/CollectionView'; -import { PaintButtonView } from './PaintButtonView'; // import * as applyDevTools from 'prosemirror-dev-tools'; @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { @@ -488,14 +486,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' + /** + * Searches the text for occurences of any strings that match the names of 'published' documents. These document + * names will begin with an '@' prefix. However, valid matches within the text can have any of the following formats: + * name, @<name>, or ^@<name> + * The last of these is interpreted as an include directive when converting the text into evaluated code in the paint + * function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published + * document into the code being evaluated. + */ hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) { const autoLinkTerm = StrCast(target.title).replace(/^@/, ''); var alink: Doc | undefined; this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => { - const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); - if (!sel.$anchor.pos || [autoLinkTerm, StrCast(target.title)].includes(editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim())) { + if ( + !sel.$anchor.pos || + autoLinkTerm === + editorView.state.doc + .textBetween(sel.$anchor.pos - 1, sel.$to.pos) + .trim() + .replace(/[\^@]+/, '') + ) { + const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); tr = tr.addMark(sel.from, sel.to, splitter); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { @@ -668,12 +681,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let index = 0, foundAt; const ep = this.getNodeEndpoints(pm.state.doc, node); - const regexp = new RegExp(find.replace('*', ''), 'i'); + const regexp = new RegExp(find, 'i'); if (regexp) { - while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) { - const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); - ret.push(sel); - index = index + foundAt + find.length; + var blockOffset = 0; + for (var i = 0; i < node.childCount; i++) { + var textContent = ''; + while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) { + textContent += node.child(i).textContent; + i++; + } + while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) { + const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1)); + ret.push(sel); + index = index + foundAt + find.length; + } + blockOffset += textContent.length; + if (i < node.childCount) blockOffset += node.child(i).nodeSize; } } } else { @@ -934,17 +957,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars', }); - optionItems.push({ - description: 'Make Paint Function', - event: () => { - this.dataDoc.layout_painted = CollectionView.LayoutString('painted'); - this.layoutDoc.layout_fieldKey = 'layout_painted'; - this.layoutDoc.type_collection = CollectionViewType.Freeform; - this.DocumentView?.().setToggleDetail(); - this.dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${this.fieldKey}']?.Text)`); - }, - icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', - }); !Doc.noviceMode && optionItems.push({ description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`, @@ -1411,9 +1423,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, - paintButton(node: any, view: any, getPos: any) { - return new PaintButtonView(node, view, getPos, self); - }, equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, diff --git a/src/client/views/nodes/formattedText/PaintButtonView.tsx b/src/client/views/nodes/formattedText/PaintButtonView.tsx deleted file mode 100644 index 74423c772..000000000 --- a/src/client/views/nodes/formattedText/PaintButtonView.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import * as ReactDOM from 'react-dom/client'; -import { Doc } from '../../../../fields/Doc'; -import { ObservableReactComponent } from '../../ObservableReactComponent'; -import './DashFieldView.scss'; -import { FormattedTextBox } from './FormattedTextBox'; -import { CollectionViewType } from '../../../documents/DocumentTypes'; -import { CollectionView } from '../../collections/CollectionView'; -import { StrCast } from '../../../../fields/Types'; - -export class PaintButtonView { - dom: HTMLDivElement; // container for label and value - root: any; - node: any; - tbox: FormattedTextBox; - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this.node = node; - this.tbox = tbox; - this.dom = document.createElement('div'); - this.dom.style.width = node.attrs.width; - this.dom.style.height = node.attrs.height; - this.dom.style.position = 'relative'; - this.dom.style.display = 'inline-block'; - this.dom.onkeypress = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeydown = function (e: any) { - e.stopPropagation(); - }; - this.dom.onkeyup = function (e: any) { - e.stopPropagation(); - }; - this.dom.onmousedown = function (e: any) { - e.stopPropagation(); - }; - - this.root = ReactDOM.createRoot(this.dom); - this.root.render(<PaintButtonViewInternal node={node} getPos={getPos} width={node.attrs.width} height={node.attrs.height} tbox={tbox} />); - } - destroy() { - setTimeout(() => { - try { - this.root.unmount(); - } catch {} - }); - } - deselectNode() { - this.dom.classList.remove('ProseMirror-selectednode'); - } - selectNode() { - this.dom.classList.add('ProseMirror-selectednode'); - } -} - -interface IPaintButtonViewInternal { - tbox: FormattedTextBox; - width: number; - height: number; - node: any; - getPos: any; -} - -@observer -export class PaintButtonViewInternal extends ObservableReactComponent<IPaintButtonViewInternal> { - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - - constructor(props: IPaintButtonViewInternal) { - super(props); - makeObservable(this); - this._textBoxDoc = this._props.tbox.Document; - } - - return100 = () => 100; - @computed get _checked() { - return this._props.tbox.Document.onClick ? true : false; - } - - onCheckClick = () => { - const textView = this._props.tbox.DocumentView?.(); - if (textView) { - const paintedField = 'layout_' + this._props.tbox.fieldKey + 'Painted'; - const layoutFieldKey = StrCast(textView.layoutDoc.layout_fieldKey, 'layout'); - if (textView.layoutDoc.onClick) { - textView.layoutDoc[paintedField] = undefined; - textView.layoutDoc.onClick = undefined; - } else { - textView.layoutDoc.type_collection = CollectionViewType.Freeform; - textView.dataDoc[paintedField] = CollectionView.LayoutString(this._props.tbox.fieldKey); - textView.layoutDoc.layout_fieldKey = paintedField; - textView.setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', '')); - textView.layoutDoc.layout_fieldKey = layoutFieldKey; - } - } - }; - - render() { - return ( - <div - className="dashFieldView" - style={{ - width: this._props.width, - height: this._props.height, - pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none', - }}> - <input type="checkbox" value="paint" checked={this._checked} onChange={e => this.onCheckClick()} /> - </div> - ); - } -} diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 8f7bc5282..ce2c33fb4 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -68,40 +68,21 @@ export class RichTextRules { ), // ``` create code block - textblockTypeInputRule(/^```$/, schema.nodes.code_block), - new InputRule( - new RegExp(/(^|\n)\^@paint/), // for code blocks '^' means the beginning of the block, not the line, so need to test for \n - (state, match, start, end) => { - const { dataDoc, layoutDoc, fieldKey } = this.TextBox; - layoutDoc.type_collection = CollectionViewType.Freeform; - const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; - dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey); - const layoutFieldKey = StrCast(layoutDoc.layout_fieldKey); - layoutDoc.layout_fieldKey = paintedField; - this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', '')); - layoutDoc.layout_fieldKey = layoutFieldKey; - const comment = '/* enable as paint function '; - const endComment = ' */\n'; - const inCode = state.tr.selection.$anchor.node().type === schema.nodes.code_block; - if (inCode) { - const tr = state.tr - .deleteRange(start, end) - .insertText(comment) - .insert(start + comment.length, schema.nodes.paintButton.create()) - .insertText(endComment); - return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 1))); - } else { - const tr = state.tr - .deleteRange(start, end) - .insertText(comment) - .insert(start + comment.length, schema.nodes.paintButton.create()) - .insertText(endComment) - .insert(start + comment.length + endComment.length + 1, schema.nodes.code_block.create()); - return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 3))); - } - }, - { inCode: true } - ), + new InputRule(/^```$/, (state, match, start, end) => { + let $start = state.doc.resolve(start); + if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null; + + // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script + this.TextBox.layoutDoc.type_collection = CollectionViewType.Freeform; // make it a freeform when rendered as a collection since those are the only views that know about the paint function + const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; // make a layout field key for storing the CollectionView jsx string pointing to the textbox's text + this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey); + const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey + this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key + this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view + this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text + + return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block); + }), // %<font-size> set the font size new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 30e3aa5f0..f3fc51671 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -572,6 +572,16 @@ export namespace Doc { return false; } + export function RemoveEmbedding(doc: Doc, embedding: Doc) { + Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', embedding); + } + export function AddEmbedding(doc: Doc, embedding: Doc) { + Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding, undefined, undefined, undefined, undefined, undefined, true); + } + export function GetEmbeddings(doc: Doc) { + return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true)); + } + export function MakeEmbedding(doc: Doc, id?: string) { const embedding = (!GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); const layout = Doc.LayoutField(embedding); @@ -579,20 +589,18 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1; + embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1; !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`)); embedding.author = Doc.CurrentUserEmail; - Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding); - return embedding; } export function BestEmbedding(doc: Doc) { const dataDoc = doc[DocData]; - const availableEmbeddings = DocListCast(dataDoc.proto_embeddings); + const availableEmbeddings = Doc.GetEmbeddings(dataDoc); const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail); - bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc); + bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc, undefined, undefined, undefined, undefined, undefined, true); return bestEmbedding ?? Doc.MakeEmbedding(doc); } @@ -951,7 +959,7 @@ export namespace Doc { Doc.GetProto(copy).embedContainer = undefined; Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]); } else { - Doc.AddDocToList(copy[DocData], 'proto_embeddings', copy); + Doc.AddEmbedding(copy, copy); } copy.embedContainer = undefined; if (retitle) { @@ -972,9 +980,10 @@ export namespace Doc { Object.keys(doc) .filter(key => key.startsWith('acl')) .forEach(key => (delegate[key] = doc[key])); - if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate); + if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate); title && (delegate.title = title); delegate[Initializing] = false; + Doc.AddEmbedding(doc, delegate); return delegate; } return undefined; @@ -995,7 +1004,7 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = delegateProto; delegate.author = Doc.CurrentUserEmail; - Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate); + Doc.AddEmbedding(delegateProto, delegate); delegate[Initializing] = false; delegateProto[Initializing] = false; return delegate; |