aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-01-30 00:40:43 -0500
committerbobzel <zzzman@gmail.com>2024-01-30 00:40:43 -0500
commit8ac814bbb81b690a6a10f5a07aa5ce0e8cafe283 (patch)
treeffb2b2b275e14aeeb8436effbd8aaae7cdf1e7fb
parent1a32884f5084d9c39190e44bd9331e94590322e5 (diff)
changed dropConverter to keep title of dropped Doc. added paintFunc node/ checkbox view to formatted text. changed paintFunc to be computed based on layouytfieldkey being text in a freeformview. changed some inputRules to apply to code blocks. changed : contextmenu to allow regular note to be created. changed experimental tools to be user tmeplate tools. fixed focus on search bar when opening context menu
-rw-r--r--package-lock.json10
-rw-r--r--package.json2
-rw-r--r--src/Utils.ts8
-rw-r--r--src/client/documents/Documents.ts39
-rw-r--r--src/client/util/CurrentUserUtils.ts70
-rw-r--r--src/client/util/DropConverter.ts16
-rw-r--r--src/client/views/ContextMenu.tsx18
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/InkControlPtHandles.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx6
-rw-r--r--src/client/views/nodes/DocumentView.tsx10
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss10
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx8
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx39
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx7
-rw-r--r--src/client/views/nodes/formattedText/PaintButtonView.tsx113
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts164
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts12
-rw-r--r--src/fields/Doc.ts10
19 files changed, 397 insertions, 149 deletions
diff --git a/package-lock.json b/package-lock.json
index 3564c4f1f..aa4108243 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -154,7 +154,7 @@
"prosemirror-commands": "^1.5.2",
"prosemirror-find-replace": "^0.9.0",
"prosemirror-history": "^1.3.2",
- "prosemirror-inputrules": "^1.3.0",
+ "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
"prosemirror-keymap": "^1.2.2",
"prosemirror-model": "^1.19.3",
"prosemirror-schema-list": "^1.3.0",
@@ -28700,8 +28700,9 @@
},
"node_modules/prosemirror-inputrules": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.3.0.tgz",
- "integrity": "sha512-z1GRP2vhh5CihYMQYsJSa1cOwXb3SYxALXOIfAkX8nZserARtl9LiL+CEl+T+OFIsXc3mJIHKhbsmRzC0HDAXA==",
+ "resolved": "git+ssh://git@github.com/bobzel/prosemirror-inputrules.git#3b3b3e0b1a1dcf80490d81675206369b6be96276",
+ "integrity": "sha512-l81tcwS7ugPJEvCy78RtEvR2/2mVaTkFHJD9ACRxRDXhrfPWV0FkFYBAO7GolN4XlzIbIsal2MVvq2baLQ2guw==",
+ "license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
@@ -32239,7 +32240,8 @@
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
},
"node_modules/textarea-caret": {
"version": "3.1.0",
diff --git a/package.json b/package.json
index 760099bd5..f8144a0b3 100644
--- a/package.json
+++ b/package.json
@@ -237,7 +237,7 @@
"prosemirror-commands": "^1.5.2",
"prosemirror-find-replace": "^0.9.0",
"prosemirror-history": "^1.3.2",
- "prosemirror-inputrules": "^1.3.0",
+ "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
"prosemirror-keymap": "^1.2.2",
"prosemirror-model": "^1.19.3",
"prosemirror-schema-list": "^1.3.0",
diff --git a/src/Utils.ts b/src/Utils.ts
index 502cf7db7..e8bd35ac4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -514,8 +514,8 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
}
-export function stringHash(s?:string) {
- return !s? undefined: Math.abs(s.split('').reduce((a: any, b: any) => ((a) => a & a)((a << 5) - a + b.charCodeAt(0)),0));
+export function stringHash(s?: string) {
+ return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0));
}
export function percent2frac(percent: string) {
@@ -852,9 +852,11 @@ export function setupMoveUpEvents(
(target as any)._downX = (target as any)._lastX = e.clientX;
(target as any)._downY = (target as any)._lastY = e.clientY;
(target as any)._noClick = false;
+ var moving = false;
const _moveEvent = (e: PointerEvent): void => {
- if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+ if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+ moving = true;
if ((target as any)._doubleTime) {
clearTimeout((target as any)._doubleTime);
(target as any)._doubleTime = undefined;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ac418ecfe..cc983ffa7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1667,10 +1667,37 @@ export namespace DocUtils {
}
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
+ const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ .filter(btnDoc => !btnDoc.hidden)
+ .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+ .map((dragDoc, i) => ({
+ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+ event: undoable((args: { x: number; y: number }) => {
+ const newDoc = DocUtils.copyDragFactory(dragDoc);
+ if (newDoc) {
+ newDoc.author = Doc.CurrentUserEmail;
+ newDoc.x = x;
+ newDoc.y = y;
+ EquationBox.SelectOnLoad = newDoc[Id];
+ if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
+ if (pivotField) {
+ newDoc[pivotField] = pivotValue;
+ }
+ docAdder?.(newDoc);
+ }
+ }, StrCast(dragDoc.title)),
+ icon: Doc.toIcon(dragDoc),
+ })) as ContextMenuProps[];
+ ContextMenu.Instance.addItem({
+ description: 'Create document',
+ subitems: documentList,
+ icon: 'file',
+ });
!simpleMenu &&
ContextMenu.Instance.addItem({
- description: 'Quick Notes',
- subitems: DocListCast((Doc.UserDoc()['template_notes'] as Doc).data).map((note, i) => ({
+ description: 'Styled Notes',
+ subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
description: ':' + StrCast(note.title),
event: undoable((args: { x: number; y: number }) => {
const textDoc = Docs.Create.TextDocument('', {
@@ -1691,14 +1718,14 @@ export namespace DocUtils {
})) as ContextMenuProps[],
icon: 'sticky-note',
});
- const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
.filter(btnDoc => !btnDoc.hidden)
.map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
.map((dragDoc, i) => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
event: undoable((args: { x: number; y: number }) => {
- const newDoc = DocUtils.copyDragFactory(dragDoc);
+ const newDoc = DocUtils.delegateDragFactory(dragDoc);
if (newDoc) {
newDoc.author = Doc.CurrentUserEmail;
newDoc.x = x;
@@ -1714,8 +1741,8 @@ export namespace DocUtils {
icon: Doc.toIcon(dragDoc),
})) as ContextMenuProps[];
ContextMenu.Instance.addItem({
- description: 'Create document',
- subitems: documentList,
+ description: 'User Templates',
+ subitems: userDocList,
icon: 'file',
});
} // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8f46f844c..31f0308b7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -61,49 +61,16 @@ export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
// initializes experimental advanced template views - slideView, headerView
- static setupExperimentalTemplateButtons(doc: Doc, tempDocs:Opt<Doc>, userBtns:Doc[]) {
- const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
- {
- btnOpts: { title: "slide", icon: "address-card" },
- templateOpts: { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, isSystem: true },
- template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
- [
- Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
- Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
- ], opts)
- },
- {
- btnOpts: { title: "mobile", icon: "mobile" },
- templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, },
- template: (opts:DocumentOptions) => this.mobileButton(opts,
- [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }),
- this.mobileTextContainer({},
- [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])
- ]
- )
- },
- ];
- const requiredTypes = [...requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
- const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
- const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' };
- const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
- if (templateBtn) {
- DocUtils.AssignOpts(templateBtn,btnOpts);
- DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
- }
- return templateBtn;
- };
- return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
- }), ...userBtns];
-
+ static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
+ const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
const reqdOpts:DocumentOptions = {
- title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
+ title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
_dragOnlyWithinContainer: true, _layout_hideContextMenu: true, isSystem: true, _forceActive: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
- const reqdFuncs = { hidden: "IsNoviceMode()" };
- return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
+ const reqdFuncs = { /* hidden: "IsNoviceMode()" */ };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
}
/// Initializes templates for editing click funcs of a document
@@ -148,7 +115,7 @@ export class CurrentUserUtils {
static setupNoteTemplates(doc: Doc, field="template_notes") {
const tempNotes = DocCast(doc[field]);
const reqdTempOpts:DocumentOptions[] = [
- { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
+ { noteType: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
{ noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
{ noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
const reqdNoteList = reqdTempOpts.map(opts => {
@@ -257,6 +224,16 @@ export class CurrentUserUtils {
MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
return header;
}
+ const slideView = (opts:DocumentOptions) => {
+ const slide = Docs.Create.MultirowDocument(
+ [
+ Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
+ Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
+ ], opts);
+
+ MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
+ return slide;
+ }
const emptyThings:{key:string, // the field name where the empty thing will be stored
opts:DocumentOptions, // the document options that are required for the empty thing
funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing
@@ -279,6 +256,7 @@ export class CurrentUserUtils {
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
{key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+ {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
{key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree,
@@ -307,6 +285,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
{ toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
{ toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
// { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay},
@@ -330,7 +309,7 @@ export class CurrentUserUtils {
});
const reqdOpts:DocumentOptions = {
- title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+ title: "Document Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
childDragAction: 'embed'
};
@@ -464,16 +443,17 @@ export class CurrentUserUtils {
/// Initializes the panel of draggable tools that is opened from the left sidebar.
static setupToolsBtnPanel(doc: Doc, field:string) {
const myTools = DocCast(doc[field]);
- const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
- const tempBtns = DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined;
- const userTemplateBtns = DocListCast(tempBtns?.data).filter(btn => !btn.isSystem);
- const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc, tempBtns, userTemplateBtns);
+ const allTools = DocListCast(myTools?.data);
+ const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
+ const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
+ const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
+ //doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.",
_layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true,
};
- return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]);
}
/// initializes the left sidebar dashboard pane
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 8c3b56452..ba981145d 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -3,7 +3,7 @@ import { DocData } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
-import { ScriptField } from '../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../fields/ScriptField';
import { Cast, StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs } from '../documents/Documents';
@@ -39,15 +39,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
any = makeTemplate(d, false) || any;
}
});
- if (first) {
- if (!docs.length) {
- // bcz: feels hacky : if the root level document has items, it's not a field template
- any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any;
- }
- }
- if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
+ if (first && !docs.length) {
+ // bcz: feels hacky : if the root level document has items, it's not a field template
+ any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || any;
+ } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
if (!StrCast(layoutDoc.title).startsWith('-')) {
- any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]);
+ any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
}
}
rename && (doc.title = rename);
@@ -82,6 +79,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
icon: 'bolt',
isSystem: false,
});
+ dbox.title = ComputedField.MakeFunction('this.dragFactory.title');
dbox.dragFactory = layoutDoc;
dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8dcdd80e5..8c3c9df2e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -183,11 +183,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@computed get menuItems() {
if (!this._searchString) {
- return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />);
+ return this._items.map((item, ind) => <ContextMenuItem key={item.description + ind} {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} closeMenu={this.closeMenu} />);
}
return this.filteredItems.map((value, index) =>
Array.isArray(value) ? (
<div
+ key={index + value.join(' -> ')}
className="contextMenu-group"
style={{
background: StrCast(SettingsManager.userVariantColor),
@@ -204,7 +205,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15;
}
+ _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do...
+
render() {
+ this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus());
return (
<div
className="contextMenu-cont"
@@ -226,7 +230,17 @@ export class ContextMenu extends ObservableReactComponent<{}> {
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
- <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+ <input
+ ref={this._searchRef}
+ style={{ color: 'black' }}
+ className="contextMenu-item contextMenu-description search"
+ type="text"
+ placeholder="Filter Menu..."
+ value={this._searchString}
+ onKeyDown={this.onKeyDown}
+ onChange={this.onChange}
+ autoFocus
+ />
</span>
)}
{this.menuItems}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d134d9e7b..733383002 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -160,7 +160,7 @@ export class KeyManager {
if (LightboxView.LightboxDoc) {
LightboxView.Instance.SetLightboxDoc(undefined);
SelectionManager.DeselectAll();
- } else DocumentDecorations.Instance.onCloseClick(true);
+ } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true);
return { stopPropagation: true, preventDefault: true };
}
break;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 7dd57e04d..31b13d2c8 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -189,6 +189,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
@observable _overStart: boolean = false;
@observable _overEnd: boolean = false;
+ _throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter
@action
dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
SnappingManager.SetIsDragging(true);
@@ -196,6 +197,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
this,
e,
action(e => {
+ if (this._throttle++ % 2 !== 0) return false;
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
const p1 = pt1();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82ada4fb5..54e8b08b6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
import { DocData, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
@@ -53,6 +53,7 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
+import { RichTextField } from '../../../../fields/RichTextField';
export interface collectionFreeformViewProps {
NativeWidth?: () => number;
@@ -75,7 +76,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
- const paintFunc = StrCast(this.Document[DocData].paintFunc).trim();
+ const field = this.layoutDoc[this.fieldKey];
+ const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
return !paintFunc
? ''
: `const dashDiv = document.querySelector('#${this._paintedId}');
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e1ba2a7e..51f4b1a68 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -456,11 +456,11 @@ 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(
`toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
.replace('layout_', '')
- .replace(/^layout$/, 'detail')}")`,
+ .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
{ documentView: 'any' }
)),
'set toggle detail'
@@ -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 = () => this._docViewInternal?.setToggleDetail();
+ public setToggleDetail = (defaultLayout = '') => this._docViewInternal?.setToggleDetail(defaultLayout);
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);
@@ -1460,8 +1460,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
});
-ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') {
+ if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
});
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index db2ffa756..2db285910 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,5 +1,15 @@
@import '../../global/globalCssVariables.module.scss';
+// bcz: something's messed up with the IconButton css. this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text')
+.iconButton-container {
+ width: unset !important;
+ min-width: 30px !important;
+ height: unset !important;
+ min-height: 30px;
+ .color {
+ height: 3px !important;
+ }
+}
.menuButton {
height: 100%;
display: flex;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8290e102c..3577cc8d9 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -381,7 +381,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
case ButtonType.ToggleButton: return this.toggleButton;
- case ButtonType.ClickButton:
+ case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
case ButtonType.TextButton: return <Button {...btnProps} color={color}
background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
@@ -392,6 +392,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
};
render() {
- return <div onContextMenu={this.specificContextMenu}>{this.renderButton()}</div>;
+ return (
+ <div style={{ margin: 'auto', width: '100%' }} onContextMenu={this.specificContextMenu}>
+ {this.renderButton()}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index b7d2a24c2..a72ed1813 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import * as React from 'react';
+import { IReactionDisposer, computed, reaction } from 'mobx';
+import { NumCast } from '../../../../fields/Types';
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
@@ -10,8 +12,10 @@ import * as React from 'react';
export class DashDocCommentView {
dom: HTMLDivElement; // container for label and value
root: any;
+ node: any;
constructor(node: any, view: any, getPos: any) {
+ this.node = node;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
@@ -32,10 +36,14 @@ export class DashDocCommentView {
};
this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docId={node.attrs.docId} />);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
(this as any).dom = this.dom;
}
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
+ };
+
destroy() {
this.root.unmount();
}
@@ -51,9 +59,15 @@ interface IDashDocCommentViewInternal {
docId: string;
view: any;
getPos: any;
+ setHeight: (height: number) => void;
}
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
+ _reactionDisposer: IReactionDisposer | undefined;
+
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -61,15 +75,32 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
}
+ componentDidMount(): void {
+ this._reactionDisposer?.();
+ this._dashDoc.then(
+ doc =>
+ doc instanceof Doc &&
+ (this._reactionDisposer = reaction(
+ () => NumCast((doc as Doc)._height),
+ hgt => this.props.setHeight(hgt),
+ {
+ fireImmediately: true,
+ }
+ ))
+ );
+ }
+ componentWillUnmount(): void {
+ this._reactionDisposer?.();
+ }
onPointerLeaveCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
}
onPointerEnterCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
}
@@ -82,7 +113,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
} catch (e) {}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0b857794b..973f90501 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -68,6 +68,7 @@ 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 {
@@ -1410,6 +1411,9 @@ 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);
},
@@ -1538,15 +1542,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._downX = e.clientX;
this._downY = e.clientY;
this._downTime = Date.now();
- this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
+ (this.ProseRef?.children?.[0] as any).focus();
}
}
+ this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
diff --git a/src/client/views/nodes/formattedText/PaintButtonView.tsx b/src/client/views/nodes/formattedText/PaintButtonView.tsx
new file mode 100644
index 000000000..74423c772
--- /dev/null
+++ b/src/client/views/nodes/formattedText/PaintButtonView.tsx
@@ -0,0 +1,113 @@
+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 be32a2c4a..8f7bc5282 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -4,8 +4,7 @@ import { Doc, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { NumCast } from '../../../../fields/Types';
+import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -15,6 +14,7 @@ import { RichTextMenu } from './RichTextMenu';
import { schema } from './schema_rts';
import { CollectionView } from '../../collections/CollectionView';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { ContextMenu } from '../../ContextMenu';
export class RichTextRules {
public Document: Doc;
@@ -69,23 +69,39 @@ export class RichTextRules {
// ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
-
- new InputRule(new RegExp(/^\^@paint/), (state, match, start, end) => {
- const { dataDoc, layoutDoc, fieldKey } = this.TextBox;
- layoutDoc.type_collection = CollectionViewType.Freeform;
- dataDoc.layout_painted = CollectionView.LayoutString('painted');
- const layoutFieldKey = layoutDoc.layout_fieldKey;
- layoutDoc.layout_fieldKey = 'layout_painted';
- this.TextBox.DocumentView?.().setToggleDetail();
- layoutDoc.layout_fieldKey = layoutFieldKey;
- dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${fieldKey}']?.Text)`);
- const comment = '/* this is now a paint func */';
- const tr = state.tr
- .deleteRange(start, end)
- .insertText(comment)
- .insert(start + comment.length, schema.nodes.code_block.create());
- return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + 2)));
- }),
+ 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 }
+ ),
// %<font-size> set the font size
new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
@@ -94,6 +110,32 @@ export class RichTextRules {
}),
//Create annotation to a field on the text document
+ new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ const creator = (doc: Doc) => {
+ const textDoc = this.Document[DocData];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ this.TextBox.EditorView?.dispatch(
+ node
+ ? this.TextBox.EditorView.state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 2, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ : this.TextBox.EditorView.state.tr
+ );
+ };
+ DocUtils.addDocumentCreatorMenuItems(creator, creator, 200, 200);
+ const cm = ContextMenu.Instance;
+ cm.displayMenu(200, 200, undefined, true);
+
+ return null;
+ }),
+ //Create annotation to a field on the text document
new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -117,7 +159,7 @@ export class RichTextRules {
textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
textDoc[inlineFieldKey] = ''; // set a default value for the annotation
const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] });
+ const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
const sm = state.storedMarks || undefined;
const replaced = node
@@ -265,49 +307,53 @@ export class RichTextRules {
// [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:docTitle]] => show field of doc
- new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
- const fieldKey = match[1];
- const docTitle = match[3]?.replace(':', '');
- const value = match[2]?.substring(1);
- const linkToDoc = (target: Doc) => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
+ new InputRule(
+ new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docTitle = match[3]?.replace(':', '');
+ const value = match[2]?.substring(1);
+ const linkToDoc = (target: Doc) => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
- DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+ DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- };
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
- }
- const titledDoc = DocServer.FindDocByTitle(docTitle);
- return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
- };
- if (!fieldKey) {
- if (docTitle) {
- const target = getTitledDoc(docTitle);
- if (target) {
- setTimeout(() => linkToDoc(target));
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+ }
+ };
+ const getTitledDoc = (docTitle: string) => {
+ if (!DocServer.FindDocByTitle(docTitle)) {
+ Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+ }
+ const titledDoc = DocServer.FindDocByTitle(docTitle);
+ return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
+ };
+ if (!fieldKey) {
+ if (docTitle) {
+ const target = getTitledDoc(docTitle);
+ if (target) {
+ setTimeout(() => linkToDoc(target));
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ }
}
+ return state.tr;
}
- return state.tr;
- }
- if (value !== '' && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
- }
- const target = getTitledDoc(docTitle);
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
- return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
- }),
+ if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const target = getTitledDoc(docTitle);
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+ return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+ },
+ { inCode: true }
+ ),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// wiki:title
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 31f001b11..4706a97fa 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -179,6 +179,7 @@ export const nodes: { [index: string]: NodeSpec } = {
dashComment: {
attrs: {
docId: { default: '' },
+ reflow: { default: true },
},
inline: true,
group: 'inline',
@@ -275,6 +276,17 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
+ paintButton: {
+ inline: true,
+ attrs: {},
+ group: 'inline',
+ draggable: false,
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ['div', { ...node.attrs, ...attrs }];
+ },
+ },
+
video: {
inline: true,
attrs: {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 67f09f37b..30e3aa5f0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -60,7 +60,7 @@ export namespace Field {
case 'number':
case 'boolean':rawjava = String(field);
break;
- default: rawjava = field?.[ToJavascriptString]?.() ?? 'null';
+ default: rawjava = field?.[ToJavascriptString]?.() ?? '';
} // prettier-ignore
var script = rawjava;
// this is a bit hacky, but we treat '^@' references to a published document
@@ -866,7 +866,7 @@ export namespace Doc {
export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
if (infield instanceof Promise) return;
if (!(infield instanceof Doc)) {
- infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
return;
}
const doc = infield as Doc;
@@ -1032,13 +1032,13 @@ export namespace Doc {
// This function converts a generic field layout display into a field layout that displays a specific
// metadata field indicated by the title of the template field (not the default field that it was rendering)
//
- export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
+ export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>, keepFieldKey = false): boolean {
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+ const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
- templateField.title = metadataFieldKey;
+ !keepFieldKey && (templateField.title = metadataFieldKey);
const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
const templateCaptionValue = templateField.caption;