From 26cd21b5148d07e0be39cd6997723b34a70ebab6 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Tue, 5 Mar 2024 12:16:53 -0500
Subject: added mermaid and poltly to tools list
---
src/client/views/nodes/ComparisonBox.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 116dc48a6..ef8c045cc 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -6,7 +6,7 @@ import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents }
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
@@ -135,7 +135,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
this,
e,
e => {
- const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], 'move');
+ const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
this.clearDoc(which);
return addDocument(doc);
--
cgit v1.2.3-70-g09d2
From c563aec906c5728a5563fefac6ab573a31375641 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Tue, 12 Mar 2024 09:09:59 -0400
Subject: made text templates be both layout templates and prototypes of new
text documents. fixed onPaint funcs to be undoable. fixed comparisonBox to
render a text box if it's fieldKey has a richtext field - this makes
flashcard templates much easier. fixed right-click on hyperlinks to bring up
menu. fixed layout_centered to be settable on templates. added enable
flashcard property for text.
---
src/client/documents/Documents.ts | 32 ++++++++++++----------
src/client/util/CurrentUserUtils.ts | 4 +--
src/client/views/PropertiesButtons.tsx | 21 ++++++++++++--
src/client/views/StyleProvider.tsx | 2 +-
src/client/views/nodes/ComparisonBox.tsx | 13 ++++++---
src/client/views/nodes/DocumentView.tsx | 27 ++++++++++++++----
src/client/views/nodes/KeyValueBox.tsx | 2 +-
.../views/nodes/formattedText/FormattedTextBox.tsx | 4 +--
.../views/nodes/formattedText/RichTextMenu.tsx | 4 +--
.../views/nodes/formattedText/RichTextRules.ts | 2 +-
10 files changed, 75 insertions(+), 36 deletions(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7a3b965fe..a13edec77 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1982,27 +1982,29 @@ export namespace DocUtils {
}
export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
+ const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
const tbox = Docs.Create.TextDocument('', {
annotationOn,
backgroundColor,
- _width: width || 200,
- _height: 35,
- x: x,
- y: y,
- _layout_centered: BoolCast(Doc.UserDoc().layout_centered),
- _layout_fitWidth: true,
- _layout_autoHeight: true,
- _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
+ x,
+ y,
title,
+ ...(defaultTextTemplate
+ ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+ : {
+ _width: width || 200,
+ _height: 35,
+ _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
+ _layout_fitWidth: true,
+ _layout_autoHeight: true,
+ _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
+ }),
});
- const template = Doc.UserDoc().defaultTextLayout;
- if (template instanceof Doc) {
- // if a default text template is specified
- tbox._width = NumCast(template._width);
- tbox.layout_fieldKey = 'layout_' + StrCast(template.title);
- Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template; // set the text doc's layout to render with the text template
- tbox[DocData].proto = template; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
+ if (defaultTextTemplate) {
+ tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
+ Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
+ tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
}
return tbox;
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index fc315fdbe..84a33500d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -276,7 +276,7 @@ export class CurrentUserUtils {
slide[DocData].text = rtfield;
slide[DocData].layout_textPainted = ``;
slide[DocData]._type_collection = CollectionViewType.Freeform;
- slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"});
+ slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
const mermaidsApi = () => {
@@ -330,7 +330,7 @@ pie title Minerals in my tap water
slide[DocData].text = rtfield;
slide[DocData].layout_textPainted = ``;
slide[DocData]._type_collection = CollectionViewType.Freeform;
- slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"});
+ slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
const apis = [plotlyApi(), mermaidsApi()]
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 3cb835e39..02f288a68 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -13,7 +13,8 @@ import { RxWidth } from 'react-icons/rx';
import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
import { TfiBarChart } from 'react-icons/tfi';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+import { DocData } from '../../fields/DocSymbols';
+import { ScriptField } from '../../fields/ScriptField';
import { BoolCast, ScriptCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
@@ -27,6 +28,7 @@ import { InkingStroke } from './InkingStroke';
import './PropertiesButtons.scss';
import { Colors } from './global/globalEnums';
import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@observer
export class PropertiesButtons extends React.Component<{}, {}> {
@@ -174,6 +176,20 @@ export class PropertiesButtons extends React.Component<{}, {}> {
);
}
+ @computed get flashcardButton() {
+ return this.propertyToggleBtn(
+ on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'),
+ 'layout_textPainted',
+ on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
+ on => ,
+ (dv, doc) => {
+ const on = doc.onPaint ? true : false;
+ doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
+ doc[DocData].layout_textPainted = on ? undefined : ``;
+ }
+ );
+ }
+
@computed get fitContentButton() {
return this.propertyToggleBtn(
on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
@@ -481,7 +497,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
render() {
const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
- const isText = layoutField instanceof RichTextField;
+ const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox;
const isInk = this.selectedDoc?.layout_isSvg;
const isImage = layoutField instanceof ImageField;
const isMap = this.selectedDoc?.type === DocumentType.MAP;
@@ -507,6 +523,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{/* {toggle(this.freezeThumb)} */}
{toggle(this.forceActiveButton)}
{toggle(this.verticalAlignButton, { display: !isText ? 'none' : '' })}
+ {toggle(this.flashcardButton, { display: !isText ? 'none' : '' })}
{toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
{/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */}
{toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0794efe4c..ab811858a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -61,7 +61,7 @@ function togglePaintView(e: React.MouseEvent, doc: Opt, props: Opt doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView');
+ ScriptCast(doc?.onPaint)?.script.run(scriptProps);
}
export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ef8c045cc..2b57178f4 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -14,6 +14,8 @@ import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { RichTextField } from '../../../fields/RichTextField';
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -172,12 +174,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
const displayDoc = (which: string) => {
const whichDoc = DocCast(this.dataDoc[which]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- return targetDoc ? (
+ // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
+ const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+ return targetDoc || layoutTemplateString ? (
<>
()
hideLinkButton={true}
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
- {clearButton(which)}
+ {layoutTemplateString ? null : clearButton(which)}
> // placeholder image if doc is missing
) : (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 29266bd8e..40592c2cd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -457,11 +457,11 @@ export class DocumentViewInternal extends DocComponent this._props.removeDocument?.(this.Document), 'delete doc');
setToggleDetail = undoable(
- (defaultLayout: string, scriptFieldKey: 'onClick') =>
+ (scriptFieldKey: 'onClick') =>
(this.Document[scriptFieldKey] = ScriptField.MakeScript(
`toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
.replace('layout_', '')
- .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
+ .replace(/^layout$/, 'detail')}")`,
{ documentView: 'any' }
)),
'set toggle detail'
@@ -1201,7 +1201,7 @@ export class DocumentView extends DocComponent() {
};
public noOnClick = () => this._docViewInternal?.noOnClick();
public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
- public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey);
+ public setToggleDetail = (scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(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);
@@ -1279,7 +1279,22 @@ export class DocumentView extends DocComponent() {
custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
}, 'set custom view');
+ /**
+ * This switches between the current view of a Doc and a specified alternate layout view.
+ * The current view of the Doc is stored in the layout_default field so that it can be restored.
+ * If the current view of the Doc is already the specified alternate layout view, this will switch
+ * back to the original layout (stored in layout_default)
+ * @param detailLayoutKeySuffix the name of the alternate layout field key (NOTE: 'layout_' will be prepended to this string to get the actual field nam)
+ */
+ public toggleDetail = (detailLayoutKeySuffix: string) => {
+ const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', '');
+ if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout;
+ const defaultLayout = StrCast(this.Document.layout_default);
+ if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
+ else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
+ };
public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+ const batch = UndoManager.StartBatch('switchView:' + view);
runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
setTimeout(
action(() => {
@@ -1292,6 +1307,7 @@ export class DocumentView extends DocComponent() {
setTimeout(
action(() => {
this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
+ batch.end();
finished?.();
}),
Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
@@ -1451,9 +1467,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
});
-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);
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
+ dv.toggleDetail(detailLayoutKeySuffix);
});
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 39a45693e..89a5ac0b8 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -76,7 +76,7 @@ export class KeyValueBox extends ObservableReactComponent {
value = eq ? value.substring(1) : value;
const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
value = dubEq ? value.substring(2) : value;
- const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: true };
+ const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true };
if (dubEq) options.typecheck = false;
const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() });
return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1ff7274f8..1bd230891 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1574,7 +1574,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -2074,7 +2074,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
{
return this._activeAlignment;
}
@computed get textVcenter() {
- return BoolCast(this.layoutDoc?.layout_centered);
+ return BoolCast(this.layoutDoc?._layout_centered);
}
_disposer: IReactionDisposer | undefined;
componentDidMount() {
@@ -450,7 +450,7 @@ export class RichTextMenu extends AntimodeMenu {
}
vcenterToggle = (view: EditorView, dispatch: any) => {
- this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered);
+ this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
};
align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView?._props.rootSelected?.()) {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9bd41f42c..d5c91fc09 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -78,7 +78,7 @@ export class RichTextRules {
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.DocumentView?.().setToggleDetail('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);
--
cgit v1.2.3-70-g09d2
From 92879a2f211f5e5c3c5fe5b28bd25a6f22d274ee Mon Sep 17 00:00:00 2001
From: bobzel
Date: Thu, 14 Mar 2024 16:34:21 -0400
Subject: Added a GPT capability to comparison boxes ..
---
src/client/apis/gpt/GPT.ts | 6 +++---
src/client/views/nodes/ComparisonBox.tsx | 27 +++++++++++++++++++++++++--
2 files changed, 28 insertions(+), 5 deletions(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index cea862330..fb51278ae 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -14,9 +14,9 @@ type GPTCallOpts = {
};
const callTypeMap: { [type: string]: GPTCallOpts } = {
- summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
- edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
- completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' },
+ summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
+ edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
+ completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
};
/**
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 2b57178f4..e759030f5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
@@ -16,6 +16,8 @@ import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { RichTextField } from '../../../fields/RichTextField';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
+import { DocData } from '../../../fields/DocSymbols';
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -174,8 +176,29 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
const displayDoc = (which: string) => {
const whichDoc = DocCast(this.dataDoc[which]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
+ const subjectText = RTFCast(this.Document[this.fieldKey])?.Text;
// if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
- const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+ // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot
+ const layoutTemplateString = !targetDoc
+ ? which.endsWith('1') && subjectText !== undefined
+ ? FormattedTextBox.LayoutString(this.fieldKey)
+ : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string')
+ ? FormattedTextBox.LayoutString(which)
+ : undefined
+ : undefined;
+
+ // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic.
+ // If the second slot doesn't have anything in it, but the fieldKey slot has text
+ // and the fieldKey + "_alternate" has a text that incldues "--TEXT--", then
+ // treat the fieldKey + "_altenrate" text as a GPT query parameterized by the fieldKey text
+ // Call GPT to fill in an "answer" value in the second slot.
+ if (which.endsWith('2') && !layoutTemplateString && !targetDoc) {
+ const queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text;
+ if (queryText?.includes('--TEXT--') && subjectText) {
+ this.Document[DocData][this.fieldKey + '_2'] = '';
+ gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_2'] = value.trim()));
+ }
+ }
return targetDoc || layoutTemplateString ? (
<>
Date: Sun, 17 Mar 2024 11:50:15 -0400
Subject: use metakey to edit computedfield result instead of expression in
schema cell, set default new field values on data doc. fixed stacking view
from autoresizing when switching to a different collection view. changed
syntax for setting fields in text docs to use ':=' for computed fields.
Added call to Chat in computed functions when (( )) is used. Added caching
of computed function result when a function called by ComputedField uses the
_setCacheResult_ method (currently only gptCallChat).
---
src/client/documents/Documents.ts | 2 +-
src/client/util/SnappingManager.ts | 5 +-
src/client/views/GlobalKeyHandler.ts | 2 +
.../views/collections/CollectionStackingView.tsx | 1 +
.../collectionFreeForm/CollectionFreeFormView.tsx | 2 +-
.../collectionSchema/CollectionSchemaView.tsx | 11 ++-
.../collectionSchema/SchemaTableCell.tsx | 23 +++--
src/client/views/nodes/ComparisonBox.tsx | 35 ++++---
src/client/views/nodes/DocumentView.tsx | 2 +-
src/client/views/nodes/FieldView.tsx | 6 +-
src/client/views/nodes/KeyValueBox.tsx | 65 ++++++++-----
src/client/views/nodes/KeyValuePair.tsx | 2 +-
.../views/nodes/formattedText/DashFieldView.tsx | 2 +-
.../views/nodes/formattedText/RichTextRules.ts | 36 +++++---
src/client/views/nodes/formattedText/nodes_rts.ts | 1 -
src/fields/Doc.ts | 21 ++++-
src/fields/ScriptField.ts | 102 +++++++++++++--------
src/server/public/assets/documentation.png | Bin 0 -> 4526 bytes
18 files changed, 203 insertions(+), 115 deletions(-)
create mode 100644 src/server/public/assets/documentation.png
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a13edec77..1d7c73306 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -175,7 +175,7 @@ class DateInfo extends FInfo {
}
class RtfInfo extends FInfo {
constructor(d: string, filterable?: boolean) {
- super(d, true);
+ super(d);
this.filterable = filterable;
}
fieldType? = FInfoFieldType.rtf;
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 40c3f76fb..359140732 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -9,6 +9,7 @@ export class SnappingManager {
@observable _shiftKey = false;
@observable _ctrlKey = false;
+ @observable _metaKey = false;
@observable _isLinkFollowing = false;
@observable _isDragging: boolean = false;
@observable _isResizing: Doc | undefined = undefined;
@@ -32,6 +33,7 @@ export class SnappingManager {
public static get VertSnapLines() { return this.Instance._vertSnapLines; } // prettier-ignore
public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore
+ public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore
public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore
public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore
@@ -39,7 +41,8 @@ export class SnappingManager {
public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore
public static SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore
- public static SetIsLinkFollowing= (follow: boolean) => runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
+ public static SetMetaKey = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore
+ public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
public static SetIsDragging = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore
public static SetIsResizing = (doc: Opt) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore
public static SetCanEmbed = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e800798ca..667d8dbb0 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -55,10 +55,12 @@ export class KeyManager {
public handleModifiers = action((e: KeyboardEvent) => {
if (e.shiftKey) SnappingManager.SetShiftKey(true);
if (e.ctrlKey) SnappingManager.SetCtrlKey(true);
+ if (e.metaKey) SnappingManager.SetMetaKey(true);
});
public unhandleModifiers = action((e: KeyboardEvent) => {
if (!e.shiftKey) SnappingManager.SetShiftKey(false);
if (!e.ctrlKey) SnappingManager.SetCtrlKey(false);
+ if (!e.metaKey) SnappingManager.SetMetaKey(false);
});
public handle = action((e: KeyboardEvent) => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ea1caf58f..2b23935eb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -225,6 +225,7 @@ export class CollectionStackingView extends CollectionSubView this._disposers[key]());
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7bfbbf3f9..b2fb5848e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -249,7 +249,7 @@ export class CollectionFreeFormView extends CollectionSubView this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
- zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
+ zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
PanZoomCenterXf = () =>
this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 12f0ad5e9..4a0ca8fe5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,5 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, ObservableMap, observe, trace } from 'mobx';
+import { Popup, PopupTrigger, Type } from 'browndash-components';
+import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
@@ -12,12 +13,13 @@ import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Docum
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
+import { SettingsManager } from '../../../util/SettingsManager';
import { undoable, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -25,8 +27,7 @@ import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
import { SchemaColumnHeader } from './SchemaColumnHeader';
import { SchemaRowBox } from './SchemaRowBox';
-import { Popup, PopupTrigger, Type } from 'browndash-components';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { DocData } from '../../../../fields/DocSymbols';
const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
export enum ColumnType {
@@ -284,7 +285,7 @@ export class CollectionSchemaView extends CollectionSubView() {
};
@action
- addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
+ addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
@undoBatch
removeColumn = (index: number) => {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index ed1b519b4..711ef507c 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,8 +1,11 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Popup, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
import Select from 'react-select';
import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
import { DateField } from '../../../../fields/DateField';
@@ -12,6 +15,8 @@ import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../..
import { ImageField } from '../../../../fields/URLField';
import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
+import { dropActionType } from '../../../util/DragManager';
+import { SettingsManager } from '../../../util/SettingsManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
@@ -24,12 +29,7 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import 'react-datepicker/dist/react-datepicker.css';
-import { Popup, Size, Type } from 'browndash-components';
-import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { SettingsManager } from '../../../util/SettingsManager';
-import { dropActionType } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
export interface SchemaTableCellProps {
Document: Doc;
@@ -129,10 +129,12 @@ export class SchemaTableCell extends ObservableReactComponent Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
+ GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey, SnappingManager.MetaKey)}
SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
+ return true;
}
const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
this._props.finishEdit?.();
@@ -345,8 +347,7 @@ export class SchemaBoolCell extends ObservableReactComponent | undefined) => {
if ((value?.nativeEvent as any).shiftKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
- }
- KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+ } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
})}
/>
{
if (shiftDown && enterKey) {
this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
+ this._props.finishEdit?.();
+ return true;
}
- const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+ const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
this._props.finishEdit?.();
return set;
})}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index e759030f5..715b23fb6 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
+import { RichTextField } from '../../../fields/RichTextField';
import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -13,11 +14,9 @@ import { StyleProp } from '../StyleProvider';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
+import { KeyValueBox } from './KeyValueBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { RichTextField } from '../../../fields/RichTextField';
-import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
-import { DocData } from '../../../fields/DocSymbols';
+import { PinProps, PresBox } from './trails';
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -173,10 +172,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
);
};
+
+ /**
+ * Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case
+ * where if there are no Docs in the slots, but the main fieldKey contains text, then
+ * @param which
+ * @returns
+ */
const displayDoc = (which: string) => {
const whichDoc = DocCast(this.dataDoc[which]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const subjectText = RTFCast(this.Document[this.fieldKey])?.Text;
+ const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
// if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
// of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot
const layoutTemplateString = !targetDoc
@@ -188,15 +194,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
: undefined;
// A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic.
- // If the second slot doesn't have anything in it, but the fieldKey slot has text
- // and the fieldKey + "_alternate" has a text that incldues "--TEXT--", then
- // treat the fieldKey + "_altenrate" text as a GPT query parameterized by the fieldKey text
- // Call GPT to fill in an "answer" value in the second slot.
+ // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
+ // and the fieldKey + "_alternate" has text, then treat the _alternate's text as a GPT query (indicated by (( && )) ) that is parameterized (optionally)
+ // by the field references in the text (eg., this.text_alternate is
+ // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
+ // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
+ // A GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
if (which.endsWith('2') && !layoutTemplateString && !targetDoc) {
- const queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text;
- if (queryText?.includes('--TEXT--') && subjectText) {
- this.Document[DocData][this.fieldKey + '_2'] = '';
- gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_2'] = value.trim()));
+ var queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])
+ ?.Text.replace('(this)', subjectText) // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+ .trim();
+ if (subjectText && queryText.match(/\(\(.*\)\)/)) {
+ KeyValueBox.SetField(this.Document, which, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
}
}
return targetDoc || layoutTemplateString ? (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bbeacef88..9848f18e0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1215,7 +1215,7 @@ export class DocumentView extends DocComponent() {
if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', '');
} else {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true);
this.Document.deiconifyLayout = undefined;
this._props.bringToFront?.(this.Document);
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8a49b4757..4ecaaa283 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -11,6 +11,7 @@ import { ViewBoxInterface } from '../DocComponent';
import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
import { DocumentView, OpenWhere } from './DocumentView';
import { PinProps } from './trails';
+import { computed } from 'mobx';
export interface FocusViewOptions {
willPan?: boolean; // determines whether to pan to target document
@@ -121,9 +122,12 @@ export class FieldView extends React.Component {
public static LayoutString(fieldType: { name: string }, fieldStr: string) {
return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., ""
}
+ @computed get fieldval() {
+ return this.props.Document[this.props.fieldKey];
+ }
render() {
- const field = this.props.Document[this.props.fieldKey];
+ const field = this.fieldval;
// prettier-ignore
if (field instanceof Doc) return
{field.title?.toString()}
;
if (field === undefined) return
{''}
;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 89a5ac0b8..2257e6455 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -6,7 +6,7 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { DocCast } from '../../../fields/Types';
+import { DocCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
@@ -71,34 +71,51 @@ export class KeyValueBox extends ObservableReactComponent {
}
}
};
- public static CompileKVPScript(value: string): KVPScript | undefined {
- const eq = value.startsWith('=');
- value = eq ? value.substring(1) : value;
- const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
- value = dubEq ? value.substring(2) : value;
- const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true };
- if (dubEq) options.typecheck = false;
- const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() });
- return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
+ /**
+ * this compiles a string as a script after parsing off initial characters that determine script parameters
+ * if the script starts with '=', then it will be stored on the delegate of the Doc, otherise on the data doc
+ * if the script then starts with a ':=', then it will be treated as ComputedField,
+ * '$=', then it will just be a Script
+ * @param value
+ * @returns
+ */
+ public static CompileKVPScript(rawvalue: string): KVPScript | undefined {
+ const onDelegate = rawvalue.startsWith('=');
+ rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
+ const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
+ rawvalue = type ? rawvalue.substring(2) : rawvalue;
+ rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, "$1")');
+ const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`';
+
+ var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
+ if (!script.compiled) {
+ script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
+ }
+ return !script.compiled ? undefined : { script, type, onDelegate };
}
- public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
+ public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
const { script, type, onDelegate } = kvpScript;
//const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
- let field: Field;
- if (type === 'computed') {
- field = new ComputedField(script);
- } else if (type === 'script') {
- field = new ScriptField(script);
- } else {
- const res = script.run({ this: Doc.Layout(doc), self: doc }, console.log);
- if (!res.success) {
- target[key] = script.originalScript;
- return true;
+ let field: Field | undefined;
+ switch (type) {
+ case 'computed': field = new ComputedField(script); break; // prettier-ignore
+ case 'script': field = new ScriptField(script); break; // prettier-ignore
+ default: {
+ const _setCacheResult_ = (value: FieldResult) => {
+ field = value as Field;
+ setResult?.(value);
+ };
+ const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log);
+ if (!res.success) {
+ if (key) target[key] = script.originalScript;
+ return false;
+ }
+ field === undefined && (field = res.result);
}
- field = res.result;
}
+ if (!key) return field;
if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) {
target[key] = field;
return true;
@@ -107,10 +124,10 @@ export class KeyValueBox extends ObservableReactComponent {
}
@undoBatch
- public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean) {
+ public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
const script = this.CompileKVPScript(value);
if (!script) return false;
- return this.ApplyKVPScript(doc, key, script, forceOnDelegate);
+ return this.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult);
}
onPointerDown = (e: React.PointerEvent): void => {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f9e8ce4f3..d59489a78 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -125,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent {
pinToPres: returnZero,
}}
GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
- SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
+ SetValue={(value: string) => (KeyValueBox.SetField(this._props.doc, this._props.keyName, value) ? true : false)}
/>
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 5c4d850ad..62cb460c2 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -137,7 +137,7 @@ export class DashFieldViewInternal extends ObservableReactComponent this._props.tbox._props.PanelWidth() - 20 : returnZero}
selectedCell={() => [this._dashDoc!, 0]}
fieldKey={this._fieldKey}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index d5c91fc09..c798ae4b3 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,6 +1,6 @@
import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { Doc, StrListCast } from '../../../../fields/Doc';
+import { Doc, FieldResult, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
@@ -8,13 +8,14 @@ import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { CollectionView } from '../../collections/CollectionView';
+import { ContextMenu } from '../../ContextMenu';
+import { KeyValueBox } from '../KeyValueBox';
import { FormattedTextBox } from './FormattedTextBox';
import { wrappingInputRule } from './prosemirrorPatches';
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;
@@ -282,18 +283,28 @@ export class RichTextRules {
: tr;
}),
+ new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
+ var count = 0; // ignore first return value which will be the notation that chat is pending a result
+ KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
+ count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
+ count++;
+ });
+ return null;
+ }),
+
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// [[ : ]]
// [[:docTitle]] => hyperlink
// [[fieldKey]] => show field
- // [[fieldKey=value]] => show field and also set its value
+ // [[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-z,A-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+ new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)((=:|:)?=)([a-z,A-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 assign = match[2] === '=' ? '' : match[2];
+ const value = match[4];
+ const docTitle = match[5]?.replace(':', '');
const linkToDoc = (target: Doc) => {
const rstate = this.TextBox.EditorView?.state;
const selection = rstate?.selection.$from.pos;
@@ -325,13 +336,14 @@ export class RichTextRules {
}
return state.tr;
}
- if (value?.includes(',')) {
+ // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
+ if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v)));
- } else if (value !== '' && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ } else if (value) {
+ KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign ? undefined:
+ (gptval: FieldResult) => this.Document[DocData][fieldKey] = gptval as string ); // prettier-ignore
}
const target = getTitledDoc(docTitle);
const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 4706a97fa..c9115be90 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,4 +1,3 @@
-import * as React from 'react';
import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 921d7aa5d..30f5f716c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -34,14 +34,29 @@ import * as JSZip from 'jszip';
import { FieldViewProps } from '../client/views/nodes/FieldView';
export const LinkedTo = '-linkedTo';
export namespace Field {
- export function toKeyValueString(doc: Doc, key: string): string {
- const onDelegate = Object.keys(doc).includes(key.replace(/^_/, ''));
+ /**
+ * Converts a field to its equivalent input string in the key value box such that if the string
+ * is entered into a keyValueBox it will create an equivalent field (except if showComputedValue is set).
+ * @param doc doc containing key
+ * @param key field key to display
+ * @param showComputedValue whether copmuted function should display its value instead of its function
+ * @returns string representation of the field
+ */
+ export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
+ const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
return !Field.IsField(field)
? key.startsWith('_')
? '='
: ''
- : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
+ : (onDelegate ? '=' : '') +
+ (field instanceof ComputedField && showComputedValue
+ ? field._lastComputedResult
+ : field instanceof ComputedField
+ ? `:=${field.script.originalScript}`
+ : field instanceof ScriptField
+ ? `$=${field.script.originalScript}`
+ : Field.toScriptString(field));
}
export function toScriptString(field: Field) {
switch (typeof field) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index c7fe72ca6..9021c8896 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,15 +1,17 @@
+import { action, makeObservable, observable } from 'mobx';
import { computedFn } from 'mobx-utils';
import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
import { DocServer } from '../client/DocServer';
-import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting';
+import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { autoObject, Deserializable } from '../client/util/SerializationHelper';
import { numberRange } from '../Utils';
-import { Doc, Field, Opt } from './Doc';
-import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
+import { Doc, Field, FieldResult, Opt } from './Doc';
+import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
import { List } from './List';
import { ObjectField } from './ObjectField';
import { Cast, StrCast } from './Types';
+import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
function optional(propSchema: PropSchema) {
return custom(
@@ -85,6 +87,13 @@ export class ScriptField extends ObjectField {
readonly script: CompiledScript;
@serializable(object(scriptSchema))
readonly setterscript: CompiledScript | undefined;
+ @serializable
+ @observable
+ _cachedResult: FieldResult = undefined;
+ setCacheResult = action((value: FieldResult) => {
+ this._cachedResult = value;
+ this[FieldChanged]?.();
+ });
@serializable(autoObject())
captures?: List;
@@ -122,21 +131,25 @@ export class ScriptField extends ObjectField {
[ToString]() {
return this.script.originalScript;
}
- public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
+ public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {
return CompileScript(script, {
params: {
this: Doc?.name || 'Doc', // this is the doc that executes the script
self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script
+ documentView: 'any',
_last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run.
+ _setCacheResult_: 'any', // set the cached value of the function
_readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
...params,
},
+ transformer,
typecheck: false,
editable: true,
addReturn: addReturn,
capturedVariables,
});
}
+
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
@@ -146,29 +159,62 @@ export class ScriptField extends ObjectField {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
+ public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) {
+ if (typeof queryText === 'string' && setVal) {
+ while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) {
+ const fieldRef = queryText.split('(this.')[1].replace(/\).*/, '');
+ queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field));
+ }
+ setVal(`Chat Pending: ${queryText}`);
+ gptAPICall(queryText, GPTCallType.COMPLETION).then(result => {
+ if (queryText.includes('#')) {
+ const matches = result.match(/-?[0-9][0-9,]+[.]?[0-9]*/);
+ if (matches?.length) setVal(Number(matches[0].replace(/,/g, '')));
+ } else setVal(result.trim());
+ });
+ }
+ }
}
@scriptingGlobal
@Deserializable('computed', deserializeScript)
export class ComputedField extends ScriptField {
- _lastComputedResult: any;
- //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
- value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
-
- [ToValue](doc: Doc) {
- return ComputedField.toValue(doc, this);
+ static undefined = '__undefined';
+ static useComputed = true;
+ static DisableComputedFields() { this.useComputed = false; } // prettier-ignore
+ static EnableComputedFields() { this.useComputed = true; } // prettier-ignore
+ static WithoutComputed(fn: () => T) {
+ this.DisableComputedFields();
+ try {
+ return fn();
+ } finally {
+ this.EnableComputedFields();
+ }
}
- [Copy](): ObjectField {
- return new ComputedField(this.script, this.setterscript, this.rawscript);
+
+ constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) {
+ super(script, setterscript, rawscript);
+ makeObservable(this);
}
+ _lastComputedResult: FieldResult;
+ value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
+ _valueOutsideReaction = (doc: Doc) => {
+ this._lastComputedResult =
+ this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
+ return this._lastComputedResult;
+ };
+
+ [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) }; } // prettier-ignore
+ [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
+
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined;
return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined;
}
+
public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt) {
if (!doc[`${fieldKey}_indexed`]) {
const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as number[]);
@@ -206,33 +252,6 @@ export class ComputedField extends ScriptField {
return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined);
}
}
-export namespace ComputedField {
- let useComputed = true;
- export function DisableComputedFields() {
- useComputed = false;
- }
-
- export function EnableComputedFields() {
- useComputed = true;
- }
-
- export const undefined = '__undefined';
-
- export function WithoutComputed(fn: () => T) {
- DisableComputedFields();
- try {
- return fn();
- } finally {
- EnableComputedFields();
- }
- }
-
- export function toValue(doc: any, value: any) {
- if (useComputed) {
- return { value: value._valueOutsideReaction(doc) };
- }
- }
-}
ScriptingGlobals.add(
function setIndexVal(list: any[], index: number, value: any) {
@@ -258,3 +277,6 @@ ScriptingGlobals.add(
'returns the value at a given index of a list',
'(list: any[], index: number)'
);
+ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) {
+ ScriptField.CallGpt(queryText, setVal, target);
+}, 'calls chat gpt for the query string and then calls setVal with the result');
diff --git a/src/server/public/assets/documentation.png b/src/server/public/assets/documentation.png
new file mode 100644
index 000000000..95c76b198
Binary files /dev/null and b/src/server/public/assets/documentation.png differ
--
cgit v1.2.3-70-g09d2
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/util/CurrentUserUtils.ts | 19 +++--
src/client/util/DropConverter.ts | 45 +++++-----
src/client/util/LinkManager.ts | 13 +--
src/client/util/RTFMarkup.tsx | 30 +++----
src/client/views/ContextMenuItem.tsx | 2 +-
src/client/views/TemplateMenu.scss | 1 +
src/client/views/TemplateMenu.tsx | 7 +-
.../collections/CollectionMasonryViewFieldRow.tsx | 10 +--
.../CollectionStackingViewFieldColumn.tsx | 6 +-
src/client/views/collections/TreeView.tsx | 14 +++-
src/client/views/global/globalScripts.ts | 33 +++-----
src/client/views/nodes/ComparisonBox.tsx | 71 +++++++++-------
src/client/views/nodes/DocumentView.tsx | 32 ++++++-
src/client/views/nodes/FieldView.tsx | 1 +
src/client/views/nodes/FontIconBox/FontIconBox.tsx | 22 ++---
src/client/views/nodes/KeyValueBox.tsx | 2 +-
src/client/views/nodes/KeyValuePair.tsx | 2 +-
.../views/nodes/formattedText/DashFieldView.tsx | 57 +++++++++----
.../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++----
.../views/nodes/formattedText/RichTextMenu.tsx | 13 +++
.../views/nodes/formattedText/RichTextRules.ts | 97 ++++++++++++----------
src/client/views/nodes/formattedText/marks_rts.ts | 5 +-
src/client/views/nodes/formattedText/nodes_rts.ts | 2 +
src/fields/Doc.ts | 42 +++++-----
src/fields/util.ts | 4 +
25 files changed, 328 insertions(+), 236 deletions(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 84a33500d..bbee18707 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -73,7 +73,7 @@ export class CurrentUserUtils {
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
const reqdFuncs = { /* hidden: "IsNoviceMode()" */ };
- return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
+ return DocUtils.AssignScripts(userDocTemplates ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
}
/// Initializes templates for editing click funcs of a document
@@ -133,11 +133,19 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
+ static setupUserTemplates(doc: Doc, field="template_user") {
+ const tempUsers = DocCast(doc[field]);
+ const reqdUserList = DocListCast(tempUsers?.data);
+
+ const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+ return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
+ }
/// Initializes collection of templates for notes and click functions
static setupDocTemplates(doc: Doc, field="myTemplates") {
const templates = [
CurrentUserUtils.setupNoteTemplates(doc),
+ CurrentUserUtils.setupUserTemplates(doc),
CurrentUserUtils.setupClickEditorTemplates(doc)
];
CurrentUserUtils.setupChildClickEditors(doc)
@@ -375,9 +383,9 @@ pie title Minerals in my tap water
{ toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
{ toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
{ toolTip: "Tap or drag to create a mermaid node", title: "Mermaids", icon: "rocket", dragFactory: doc.emptyMermaids as Doc, clickFactory: DocCast(doc.emptyMermaids)},
- { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)},
+ { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)},
{ toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
+ { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
{ toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
{ toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)},
{ toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
@@ -385,10 +393,10 @@ pie title Minerals in my tap water
{ toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)},
{ toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
- { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
+ { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)},
{ 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: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", 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: '' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
@@ -738,6 +746,7 @@ pie title Minerals in my tap water
{ title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
]},
+ { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
{ title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
{ title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 3df3e36c6..ed5749d06 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -28,6 +28,7 @@ export function MakeTemplate(doc: Doc) {
}
/**
+ *
* Recursively converts 'doc' into a template that can be used to render other documents.
*
* For recurive Docs in the template, their target fieldKey is defined by their title,
@@ -75,32 +76,36 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
remProps.map(prop => (dbox[prop] = undefined));
}
} else if (!doc.onDragStart && !doc.isButtonBar) {
- const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
- if (layoutDoc.type !== DocumentType.FONTICON) {
- !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
- }
- layoutDoc.isTemplateDoc = true;
- dbox = Docs.Create.FontIconDocument({
- _nativeWidth: 100,
- _nativeHeight: 100,
- _width: 100,
- _height: 100,
- backgroundColor: StrCast(doc.backgroundColor),
- title: StrCast(layoutDoc.title),
- btnType: ButtonType.ClickButton,
- 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)');
+ dbox = makeUserTemplateButton(doc);
} else if (doc.isButtonBar) {
dbox.ignoreClick = true;
}
data.droppedDocuments[i] = dbox;
});
}
+export function makeUserTemplateButton(doc: Doc) {
+ const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+ if (layoutDoc.type !== DocumentType.FONTICON) {
+ !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
+ }
+ layoutDoc.isTemplateDoc = true;
+ const dbox = Docs.Create.FontIconDocument({
+ _nativeWidth: 100,
+ _nativeHeight: 100,
+ _width: 100,
+ _height: 100,
+ backgroundColor: StrCast(doc.backgroundColor),
+ title: StrCast(layoutDoc.title),
+ btnType: ButtonType.ClickButton,
+ 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('getCopy(this.dragFactory)');
+ return dbox;
+}
ScriptingGlobals.add(
function convertToButtons(dragData: any) {
convertDropDataToButtons(dragData as DragManager.DocumentDragData);
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 0c8d18a7a..cf16c4d6d 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -58,8 +58,8 @@ export class LinkManager {
link &&
action(lAnchProtoProtos => {
Doc.AddDocToList(Doc.UserDoc(), 'links', link);
- lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link);
- lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link);
+ lAnchs[0]?.[DocData][DirectLinks].add(link);
+ lAnchs[1]?.[DocData][DirectLinks].add(link);
})
)
)
@@ -170,10 +170,11 @@ export class LinkManager {
console.log('WAITING FOR DOC/PROTO IN LINKMANAGER');
return [];
}
- const dirLinks = Doc.GetProto(anchor)[DirectLinks];
- const annos = DocListCast(anchor[Doc.LayoutFieldKey(anchor) + '_annotations']);
- if (!annos) debugger;
- return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice());
+
+ const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as any));
+ const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
+ const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']);
+ return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks));
}, true);
// returns map of group type to anchor's links in that group type
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index f96d8a5df..315daad42 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -3,18 +3,12 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
import { SettingsManager } from './SettingsManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
@observer
export class RTFMarkup extends React.Component<{}> {
static Instance: RTFMarkup;
@observable private isOpen = false; // whether the SharingManager modal is open or not
- // private get linkVisible() {
- // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
- // }
-
@action
public open = () => (this.isOpen = true);
@@ -39,6 +33,10 @@ export class RTFMarkup extends React.Component<{}> {
{`wiki:phrase`}
{` display wikipedia page for entered text (terminate with carriage return)`}
+
+ {`(( any text ))`}
+ {` submit text to Chat GPT to have results appended afterward`}
+
{`#tag `}
{` add hashtag metadata to document. e.g, #idea`}
@@ -47,10 +45,6 @@ export class RTFMarkup extends React.Component<{}> {
{`#, ## ... ###### `}
{` set heading style based on number of '#'s between 1 and 6`}
{`>> `}
{` add a sidebar text document inline`}
@@ -61,7 +55,7 @@ export class RTFMarkup extends React.Component<{}> {
{`cmd-f `}
- {` collapse to an inline footnote)`}
+ {` collapse to an inline footnote`}
{`cmd-e `}
@@ -116,20 +110,20 @@ export class RTFMarkup extends React.Component<{}> {
{` start a block of text that begins with a hanging indent`}
- {`[:doctitle]] `}
+ {`@(doctitle) `}
{` hyperlink to document specified by it’s title`}
- {`[[fieldname]] `}
- {` display value of fieldname`}
+ {`[@(doctitle.)fieldname] `}
+ {` display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`}
- {`[[fieldname=value]] `}
- {` assign value to fieldname of document and display it`}
+ {`[@fieldname:value] `}
+ {` assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`}
- {`[[fieldname:doctitle]] `}
- {` show value of fieldname from doc specified by it’s title`}
+ {`[@fieldname:=expression] `}
+ {` assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`}
);
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),
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 715b23fb6..62f630c6c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -159,6 +159,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
+
+ /**
+ * Tests for whether a comparison box slot (ie, before or after) has renderable text content
+ * @param whichSlot field key for start or end slot
+ * @returns a JSX layout string if a text field is found, othwerise undefined
+ */
+ testForTextFields = (whichSlot: string) => {
+ const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string';
+ const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
+ const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
+ const layoutTemplateString =
+ slotHasText ? FormattedTextBox.LayoutString(whichSlot):
+ whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) :
+ altText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore
+
+ // A bit hacky to try out the concept of using GPT to fill in flashcards
+ // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
+ // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.).
+ // eg., this.text_alternate is
+ // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
+ // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
+ // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
+ if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
+ var queryText = altText.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+ if (queryText && queryText.match(/\(\(.*\)\)/)) {
+ KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
+ }
+ }
+ return layoutTemplateString;
+ };
+
_closeRef = React.createRef();
render() {
const clearButton = (which: string) => {
@@ -176,48 +207,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
/**
* Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case
* where if there are no Docs in the slots, but the main fieldKey contains text, then
- * @param which
+ * @param whichSlot
* @returns
*/
- const displayDoc = (which: string) => {
- const whichDoc = DocCast(this.dataDoc[which]);
+ const displayDoc = (whichSlot: string) => {
+ const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
- // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
- // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot
- const layoutTemplateString = !targetDoc
- ? which.endsWith('1') && subjectText !== undefined
- ? FormattedTextBox.LayoutString(this.fieldKey)
- : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string')
- ? FormattedTextBox.LayoutString(which)
- : undefined
- : undefined;
-
- // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic.
- // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
- // and the fieldKey + "_alternate" has text, then treat the _alternate's text as a GPT query (indicated by (( && )) ) that is parameterized (optionally)
- // by the field references in the text (eg., this.text_alternate is
- // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
- // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
- // A GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
- if (which.endsWith('2') && !layoutTemplateString && !targetDoc) {
- var queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])
- ?.Text.replace('(this)', subjectText) // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
- .trim();
- if (subjectText && queryText.match(/\(\(.*\)\)/)) {
- KeyValueBox.SetField(this.Document, which, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
- }
- }
+ const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot);
return targetDoc || layoutTemplateString ? (
<>
()
hideLinkButton={true}
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
- {layoutTemplateString ? null : clearButton(which)}
+ {layoutTemplateString ? null : clearButton(whichSlot)}
> // placeholder image if doc is missing
) : (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9848f18e0..e9ce98583 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,6 +10,7 @@ import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fi
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
+import { PrefetchProxy } from '../../../fields/Proxy';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -19,10 +20,11 @@ import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocOptions, DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
+import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -36,6 +38,7 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent, ViewBoxInterface } from '../DocComponent';
import { EditableView } from '../EditableView';
+import { FieldsDropdown } from '../FieldsDropdown';
import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
import { AudioAnnoState, StyleProp } from '../StyleProvider';
@@ -47,7 +50,6 @@ import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
-import { FieldsDropdown } from '../FieldsDropdown';
interface Window {
MediaRecorder: MediaRecorder;
}
@@ -1271,6 +1273,32 @@ export class DocumentView extends DocComponent() {
custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
}, 'set custom view');
+ public static setDefaultTemplate(checkResult?: boolean) {
+ if (checkResult) {
+ return Doc.UserDoc().defaultTextLayout;
+ }
+ const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
+ undoable(() => {
+ var tempDoc: Opt;
+ if (view) {
+ if (!view.layoutDoc.isTemplateDoc) {
+ tempDoc = view.Document;
+ MakeTemplate(tempDoc);
+ Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
+ Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButton(tempDoc));
+ tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc);
+ } else {
+ tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
+ if (!tempDoc) {
+ tempDoc = view.Document;
+ while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto);
+ }
+ }
+ }
+ Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
+ }, 'set default template')();
+ }
+
/**
* This switches between the current view of a Doc and a specified alternate layout view.
* The current view of the Doc is stored in the layout_default field so that it can be restored.
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 4ecaaa283..5b47dd91d 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -55,6 +55,7 @@ export interface FieldViewSharedProps {
ignoreAutoHeight?: boolean;
disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
+ ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
containerViewPath?: () => DocumentView[];
fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index f02ad7300..57ae92359 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -5,8 +5,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
-import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
@@ -61,23 +60,12 @@ export class FontIconBox extends ViewBoxBaseComponent() {
}
@observable noTooltip = false;
- showTemplate = (): void => {
- const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
- dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight);
- };
- dragAsTemplate = (): void => {
- this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
- };
- useAsPrototype = (): void => {
- this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
- };
+ showTemplate = (dragFactory: Doc) => this._props.addDocTab(dragFactory, OpenWhere.addRight);
specificContextMenu = (): void => {
- if (!Doc.noviceMode && Cast(this.layoutDoc.dragFactory, Doc, null)) {
- const cm = ContextMenu.Instance;
- cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
- cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
- cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' });
+ const dragFactory = DocCast(this.layoutDoc.dragFactory);
+ if (!Doc.noviceMode && dragFactory) {
+ ContextMenu.Instance.addItem({ description: 'Show Template', event: () => this.showTemplate(dragFactory), icon: 'tag' });
}
};
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2bcad806f..d85432631 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -115,7 +115,7 @@ export class KeyValueBox extends ObservableReactComponent {
field === undefined && (field = res.result);
}
}
- if (!key) return field;
+ if (!key) return false;
if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) {
target[key] = field;
return true;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index d59489a78..f9e8ce4f3 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -125,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent {
pinToPres: returnZero,
}}
GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
- SetValue={(value: string) => (KeyValueBox.SetField(this._props.doc, this._props.keyName, value) ? true : false)}
+ SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
/>
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 62cb460c2..6b66d829c 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
@@ -8,12 +8,12 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast } from '../../../../fields/Types';
+import { Cast, DocCast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
import { FilterPanel } from '../../FilterPanel';
@@ -21,6 +21,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
+import { DocData } from '../../../../fields/DocSymbols';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -62,6 +63,8 @@ export class DashFieldView {
height={node.attrs.height}
hideKey={node.attrs.hideKey}
editable={node.attrs.editable}
+ expanded={node.attrs.expanded}
+ dataDoc={node.attrs.dataDoc}
tbox={tbox}
/>
);
@@ -89,6 +92,8 @@ interface IDashFieldViewInternal {
width: number;
height: number;
editable: boolean;
+ expanded: boolean;
+ dataDoc: boolean;
node: any;
getPos: any;
unclickable: () => boolean;
@@ -101,18 +106,19 @@ export class DashFieldViewInternal extends ObservableReactComponent();
@observable _dashDoc: Doc | undefined = undefined;
- @observable _expanded = false;
+ @observable _expanded = this._props.expanded;
constructor(props: IDashFieldViewInternal) {
super(props);
makeObservable(this);
this._fieldKey = this._props.fieldKey;
- this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
+ this._textBoxDoc = this._props.tbox.Document;
+ const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc);
if (this._props.docId) {
- DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
} else {
- this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
+ setDoc(this._props.tbox.Document);
}
}
@@ -126,7 +132,9 @@ export class DashFieldViewInternal extends ObservableReactComponent 100;
+ isRowActive = () => this._expanded && this._props.editable;
+ finishEdit = action(() => (this._expanded = false));
+ selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
@@ -137,18 +145,18 @@ export class DashFieldViewInternal extends ObservableReactComponent this._props.tbox._props.PanelWidth() - 20 : returnZero}
- selectedCell={() => [this._dashDoc!, 0]}
+ maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth}
+ columnWidth={returnZero}
+ selectedCell={this.selectedCell}
fieldKey={this._fieldKey}
rowHeight={returnZero}
- isRowActive={() => this._expanded && this._props.editable}
+ isRowActive={this.isRowActive}
padding={0}
getFinfo={emptyFunction}
setColumnValues={returnFalse}
allowCRs={true}
oneLine={!this._expanded}
- finishEdit={action(() => (this._expanded = false))}
+ finishEdit={this.finishEdit}
transform={Transform.Identity}
menuTarget={null}
/>
@@ -173,11 +181,21 @@ export class DashFieldViewInternal extends ObservableReactComponent this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])),
+ 'hideKey'
+ );
+
+ @computed get _hideKey() {
+ return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey'];
+ }
+
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
onPointerDownLabelSpan = (e: any) => {
setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
+ DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
});
};
@@ -188,6 +206,7 @@ export class DashFieldViewInternal extends ObservableReactComponent ({ value: facet, label: facet }));
@@ -203,9 +222,9 @@ export class DashFieldViewInternal extends ObservableReactComponent
- {this._props.hideKey ? null : (
+ {this._props.hideKey || this._hideKey ? null : (
- {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+ {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
)}
{this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
@@ -224,6 +243,7 @@ export class DashFieldViewInternal extends ObservableReactComponent {
static Instance: DashFieldViewMenu;
static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+ static toggleFieldHide: () => void = emptyFunction;
constructor(props: any) {
super(props);
DashFieldViewMenu.Instance = this;
@@ -233,6 +253,10 @@ export class DashFieldViewMenu extends AntimodeMenu {
DashFieldViewMenu.createFieldView(e);
DashFieldViewMenu.Instance.fadeOut(true);
};
+ toggleFieldHide = (e: React.MouseEvent) => {
+ DashFieldViewMenu.toggleFieldHide();
+ DashFieldViewMenu.Instance.fadeOut(true);
+ };
@observable _fieldKey = '';
@@ -252,6 +276,9 @@ export class DashFieldViewMenu extends AntimodeMenu {
+
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fb709818c..2b48494f2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -451,7 +451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+ Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
@@ -958,8 +958,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
});
- optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+ const help = cm.findByDescription('Help...');
+ const helpItems = help && 'subitems' in help ? help.subitems : [];
+ helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: });
+ !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
};
@@ -1239,8 +1242,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tx.insertText(incomingValue.str)));
+ } else {
+ selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
}
}
}
@@ -1339,15 +1342,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
if (pdfAnchor instanceof Doc) {
const dashField = view.state.schema.nodes.paragraph.create({}, [
- view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
+ view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [
view.state.schema.marks.linkAnchor.create({
allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }],
- title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
+ title: StrCast(pdfAnchor.title),
noPreview: true,
- docref: false,
+ docref: true,
+ fontSize: '8px',
}),
- view.state.schema.marks.pFontSize.create({ fontSize: '8px' }),
- view.state.schema.marks.em.create({}),
]),
]);
@@ -1926,11 +1928,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
);
}
- cycleAlternateText = () => {
- if (this.layoutDoc._layout_enableAltContentUI) {
- const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
- this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
- }
+ cycleAlternateText = (skipHover?: boolean) => {
+ this.layoutDoc._layout_enableAltContentUI = true;
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
};
@computed get overlayAlternateIcon() {
const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
@@ -1965,7 +1966,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
});
}
+ elideSelection = () => {
+ const state = this.view?.state;
+ if (!state) return;
+ if (state.selection.empty) return false;
+ const mark = state.schema.marks.summarize.create();
+ const tr = state.tr;
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ const content = tr.selection.content();
+ const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+ this.view?.dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+ return true;
+ };
+
toggleNoAutoLinkAnchor = () => {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index c798ae4b3..b97141e92 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -267,7 +267,7 @@ export class RichTextRules {
// toggle alternate text UI %/
new InputRule(new RegExp(/%\//), (state, match, start, end) => {
- setTimeout(this.TextBox.cycleAlternateText);
+ setTimeout(() => this.TextBox.cycleAlternateText(true));
return state.tr.deleteRange(start, end);
}),
@@ -283,40 +283,26 @@ export class RichTextRules {
: tr;
}),
- new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
- var count = 0; // ignore first return value which will be the notation that chat is pending a result
- KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
- count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
- count++;
- });
- return null;
- }),
-
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // [[ : ]]
- // [[:docTitle]] => hyperlink
- // [[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-z,A-Z_@\?+\-*/\ 0-9\(\)]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const assign = match[2] === '=' ? '' : match[2];
- const value = match[4];
- const docTitle = match[5]?.replace(':', '');
+ // create a hyperlink to a titled document
+ // @()
+ new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@:\.\? \-0-9]+)\)/), (state, match, start, end) => {
+ const docTitle = match[2];
+ const prefixLength = '@('.length;
+ if (docTitle) {
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))));
+ const editor = this.TextBox.EditorView;
+ const selection = editor?.state?.selection.$from.pos;
+ if (editor) {
+ const estate = editor.state;
+ editor.dispatch(estate.tr.setSelection(new TextSelection(estate.doc.resolve(start), estate.doc.resolve(end - prefixLength))));
}
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 teditor = this.TextBox.EditorView;
+ if (teditor && selection) {
+ const tstate = teditor.state;
+ teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
}
};
const getTitledDoc = (docTitle: string) => {
@@ -326,32 +312,57 @@ export class RichTextRules {
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;
+ const target = getTitledDoc(docTitle);
+ if (target) {
+ setTimeout(() => linkToDoc(target));
+ return state.tr.insertText(' ').deleteRange(start, start + prefixLength);
}
+ }
+ return state.tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
+ // [@{this,doctitle,}.fieldKey]
+ new InputRule(
+ new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+ (state, match, start, end) => {
+ const docTitle = match[1].substring(1).replace(/\.$/, '');
+ const fieldKey = match[2];
+ const assign = match[4] === ':' ? (match[4] = '') : match[4];
+ const value = match[5];
+ const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
+ const getTitledDoc = (docTitle: string) => {
+ if (!DocServer.FindDocByTitle(docTitle)) {
+ Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+ }
+ return DocServer.FindDocByTitle(docTitle);
+ };
// if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
if (value?.includes(',') && !value.startsWith('((')) {
const values = value.split(',');
const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v)));
} else if (value) {
- KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign ? undefined:
- (gptval: FieldResult) => this.Document[DocData][fieldKey] = gptval as string ); // prettier-ignore
+ KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
+ (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
}
- const target = getTitledDoc(docTitle);
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+ const target = docTitle ? getTitledDoc(docTitle) : undefined;
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc });
return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
},
{ inCode: true }
),
+ new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => {
+ var count = 0; // ignore first return value which will be the notation that chat is pending a result
+ KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
+ count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string)));
+ count++;
+ });
+ return null;
+ }),
+
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// wiki:title
new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a141ef041..b68acc8f8 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -74,6 +74,7 @@ export const marks: { [index: string]: MarkSpec } = {
allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
title: { default: null },
noPreview: { default: false },
+ fontSize: { default: null },
docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
@@ -93,14 +94,16 @@ export const marks: { [index: string]: MarkSpec } = {
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
return node.attrs.docref && node.attrs.title
? [
- 'div',
+ 'a',
['span', 0],
[
'span',
{
...node.attrs,
class: 'prosemirror-attribution',
+ 'data-targethrefs': targethrefs,
href: node.attrs.allAnchors[0].href,
+ style: `font-size: ${node.attrs.fontSize}`,
},
node.attrs.title,
],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index c9115be90..905146ee2 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -265,6 +265,8 @@ export const nodes: { [index: string]: NodeSpec } = {
docId: { default: '' },
hideKey: { default: false },
editable: { default: true },
+ expanded: { default: null },
+ dataDoc: { default: false },
},
leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
group: 'inline',
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index daae32e9f..4b40d11b9 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -445,6 +445,12 @@ export namespace Doc {
export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): FieldResult {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult;
}
+ export function isTemplateDoc(doc: Doc) {
+ return GetT(doc, 'isTemplateDoc', 'boolean', true);
+ }
+ export function isTemplateForField(doc: Doc) {
+ return GetT(doc, 'isTemplateForField', 'string', true);
+ }
export function IsDataProto(doc: Doc) {
return GetT(doc, 'isDataDoc', 'boolean', true);
}
@@ -642,7 +648,7 @@ export namespace Doc {
cloneLinks: boolean,
cloneTemplates: boolean
): Promise {
- if (Doc.IsBaseProto(doc) || ((Doc.Get(doc, 'isTemplateDoc', true) || Doc.Get(doc, 'isTemplateForField', true)) && !cloneTemplates)) {
+ if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
return doc;
}
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
@@ -735,7 +741,7 @@ export namespace Doc {
const docAtKey = DocCast(clone[key]);
if (docAtKey && !Doc.IsSystem(docAtKey)) {
if (!Array.from(cloneMap.values()).includes(docAtKey)) {
- clone[key] = !cloneTemplates && (Doc.Get(docAtKey, 'isTemplateDoc', true) || Doc.Get(docAtKey, 'isTemplateForField', true)) ? docAtKey : cloneMap.get(docAtKey[Id]);
+ clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
} else {
repairClone(docAtKey, cloneMap, cloneTemplates, visited);
}
@@ -857,7 +863,7 @@ export namespace Doc {
// of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
// nothing to do if the layout isn't a template or we don't have a target that's different than the template
- if (!targetDoc || templateLayoutDoc === targetDoc || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) {
+ if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
return templateLayoutDoc;
}
@@ -874,7 +880,7 @@ export namespace Doc {
expandedTemplateLayout = undefined;
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
} else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
- if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) {
+ if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
@@ -910,8 +916,9 @@ export namespace Doc {
console.log('Warning: GetLayoutDataDocPair childDoc not defined');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc;
- return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
+ const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+ const templateRoot = DocCast(containerDoc?.rootDocument);
+ return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
}
export function FindReferences(infield: Doc | List, references: Set, system: boolean | undefined) {
@@ -1035,20 +1042,13 @@ export namespace Doc {
// (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
// This is appropriate if you're trying to create a document that behaves like all
// regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
- export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc {
- const delegateProto = new Doc();
- delegateProto[Initializing] = true;
- delegateProto.proto = doc;
- delegateProto.author = Doc.CurrentUserEmail;
- delegateProto.isDataDoc = true;
- title && (delegateProto.title = title);
- const delegate = new Doc(id, true);
- delegate[Initializing] = true;
- delegate.proto = delegateProto;
- delegate.author = Doc.CurrentUserEmail;
- delegate[Initializing] = false;
- delegateProto[Initializing] = false;
- return delegate;
+ export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) {
+ const ndoc = Doc.ApplyTemplate(doc);
+ if (ndoc) {
+ Doc.GetProto(ndoc).isDataDoc = true;
+ ndoc && (Doc.GetProto(ndoc).proto = doc);
+ }
+ return ndoc;
}
let _applyCount: number = 0;
@@ -1671,7 +1671,7 @@ ScriptingGlobals.add(function getEmbedding(doc: any) {
return Doc.MakeEmbedding(doc);
});
ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
- return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto);
+ return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto);
});
ScriptingGlobals.add(function copyField(field: any) {
return Field.Copy(field);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index b73520999..c2ec3f13a 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -286,6 +286,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
// target should be either a Doc or ListImpl. receiver should be a Proxy Or List.
//
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+ if (!in_prop) {
+ console.log('WARNING: trying to set an empty property. This should be fixed. ');
+ return false;
+ }
let prop = in_prop;
const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
--
cgit v1.2.3-70-g09d2
From e0b846bf66b5d830b452ce0faa73eeb5f8a6cdcd Mon Sep 17 00:00:00 2001
From: bobzel
Date: Mon, 25 Mar 2024 14:25:49 -0400
Subject: fixed exception in comparison box
---
src/client/views/nodes/ComparisonBox.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 62f630c6c..59d99b371 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -182,7 +182,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
// where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
// The GPT call will put the "answer" in the second slot of the comparison (eg., text_2)
if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
- var queryText = altText.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+ var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
if (queryText && queryText.match(/\(\(.*\)\)/)) {
KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
}
--
cgit v1.2.3-70-g09d2
From 1b592f74a7df8f6dd7b2881032725f26aedff403 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Thu, 28 Mar 2024 11:38:56 -0400
Subject: fixed keyvaluebox to show props document, never the doc in the
fieldKey slot. changed computedFIelds to do mobx caching. changed text
boxes to do updating from templates based on a fieldKey_autoUpdate flag
combined with modification timestamps. enabled comparison box to work with
text fields in addition to docs.
---
src/client/util/Scripting.ts | 9 +--
src/client/views/InkControlPtHandles.tsx | 85 ++++++++++++----------
src/client/views/PropertiesButtons.tsx | 4 +-
src/client/views/nodes/ComparisonBox.tsx | 6 +-
src/client/views/nodes/KeyValueBox.tsx | 2 +-
.../views/nodes/formattedText/FormattedTextBox.tsx | 31 +++++---
src/fields/Doc.ts | 2 +-
src/fields/ScriptField.ts | 22 ++++--
8 files changed, 90 insertions(+), 71 deletions(-)
(limited to 'src/client/views/nodes/ComparisonBox.tsx')
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index f5e162d16..31222aa50 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -88,15 +88,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
}
const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
- if (batch) {
- batch.end();
- }
-
+ batch?.end();
return { success: true, result };
} catch (error) {
- if (batch) {
- batch.end();
- }
+ batch?.end();
onError?.(script + ' ' + error);
return { success: false, error, result: errorVal };
}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 31b13d2c8..01d52135a 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc } from '../../fields/Doc';
import { ControlPoint, InkData, PointData } from '../../fields/InkField';
@@ -13,6 +13,7 @@ import { Colors } from './global/globalEnums';
import { InkingStroke } from './InkingStroke';
import { InkStrokeProperties } from './InkStrokeProperties';
import { SnappingManager } from '../util/SnappingManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
export interface InkControlProps {
inkDoc: Doc;
@@ -24,10 +25,15 @@ export interface InkControlProps {
}
@observer
-export class InkControlPtHandles extends React.Component {
+export class InkControlPtHandles extends ObservableReactComponent {
@observable private _overControl = -1;
get docView() {
- return this.props.inkView.DocumentView?.();
+ return this._props.inkView.DocumentView?.();
+ }
+
+ constructor(props: InkControlProps) {
+ super(props);
+ makeObservable(this);
}
componentDidMount() {
@@ -42,51 +48,51 @@ export class InkControlPtHandles extends React.Component {
*/
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
- const ptFromScreen = this.props.inkView.ptFromScreen;
+ const ptFromScreen = this._props.inkView.ptFromScreen;
if (ptFromScreen) {
const order = controlIndex % 4;
- const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
- const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
- const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'));
+ const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length;
+ const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this._props.inkCtrlPoints.length;
+ const brokenIndices = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'));
const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1;
- const origInk = this.props.inkCtrlPoints.slice();
+ const origInk = this._props.inkCtrlPoints.slice();
setupMoveUpEvents(
this,
e,
action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
return false;
}),
action(() => {
- if (this.props.inkView.controlUndo && this.docView) {
+ if (this._props.inkView.controlUndo && this.docView) {
InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
}),
action((e: PointerEvent, doubleTap: boolean | undefined) => {
- const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
+ const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
if (doubleTap || e.button === 2) {
if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) {
if (brokenIndices) brokenIndices.push(controlIndex);
- else this.props.inkDoc.brokenInkIndices = new List([controlIndex]);
+ else this._props.inkDoc.brokenInkIndices = new List([controlIndex]);
} else {
if (brokenIndices?.includes(equivIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
}
if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
+ if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
}
}
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
}
this.changeCurrPoint(controlIndex);
}),
@@ -126,14 +132,14 @@ export class InkControlPtHandles extends React.Component {
render() {
// Accessing the current ink's data and extracting all control points.
- const scrData = this.props.screenCtrlPoints;
+ const scrData = this._props.screenCtrlPoints;
const sreenCtrlPoints: ControlPoint[] = [];
for (let i = 0; i <= scrData.length - 4; i += 4) {
sreenCtrlPoints.push({ ...scrData[i], I: i });
sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 });
}
- const inkData = this.props.inkCtrlPoints;
+ const inkData = this._props.inkCtrlPoints;
const inkCtrlPts: ControlPoint[] = [];
for (let i = 0; i <= inkData.length - 4; i += 4) {
inkCtrlPts.push({ ...inkData[i], I: i });
@@ -141,23 +147,23 @@ export class InkControlPtHandles extends React.Component {
}
const closed = InkingStroke.IsClosed(inkData);
- const nearestScreenPt = this.props.nearestScreenPt();
+ const nearestScreenPt = this._props.nearestScreenPt();
const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle');
const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => {
- const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
+ const broken = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I);
const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements;
return (
this.onControlDown(e, control.I)}
@@ -170,7 +176,7 @@ export class InkControlPtHandles extends React.Component {
};
return (
);
@@ -185,10 +191,15 @@ export interface InkEndProps {
endPt: () => PointData;
}
@observer
-export class InkEndPtHandles extends React.Component {
+export class InkEndPtHandles extends ObservableReactComponent {
@observable _overStart: boolean = false;
@observable _overEnd: boolean = false;
+ constructor(props: InkEndProps) {
+ super(props);
+ makeObservable(this);
+ }
+
_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 }) => {
@@ -198,7 +209,7 @@ export class InkEndPtHandles extends React.Component {
e,
action(e => {
if (this._throttle++ % 2 !== 0) return false;
- if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
+ 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();
const p2 = pt2();
@@ -216,8 +227,8 @@ export class InkEndPtHandles extends React.Component {
}),
action(() => {
SnappingManager.SetIsDragging(false);
- this.props.inkView.controlUndo?.end();
- this.props.inkView.controlUndo = undefined;
+ this._props.inkView.controlUndo?.end();
+ this._props.inkView.controlUndo = undefined;
UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
}),
returnFalse
@@ -230,7 +241,7 @@ export class InkEndPtHandles extends React.Component {
key={key}
cx={pt.X}
cy={pt.Y}
- r={this.props.screenSpaceLineWidth * 2}
+ r={this._props.screenSpaceLineWidth * 2}
fill={this._overStart ? '#aaaaaa' : '#99999977'}
stroke={'#00007777'}
strokeWidth={0}
@@ -242,8 +253,8 @@ export class InkEndPtHandles extends React.Component {
);
return (
);
}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 02f288a68..517a80d63 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -87,7 +87,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
containerDoc._isLightbox = !containerDoc._isLightbox;
//containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
- //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
});
}
@@ -219,7 +219,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// containerDoc._isLightbox = !containerDoc._isLightbox;
// //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
// const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- // //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ // //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
// containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
// });
// }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 59d99b371..9ffdc350d 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -69,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
action((e, doubleTap) => {
if (doubleTap) {
this._isAnyChildContentActive = true;
- if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
- if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
+ if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
}
}),
false,
@@ -131,8 +131,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
return false;
};
- whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive));
-
closeDown = (e: React.PointerEvent, which: string) => {
setupMoveUpEvents(
this,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 78e4435ce..31a2367fc 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -55,7 +55,7 @@ export class KeyValueBox extends ObservableReactComponent {
@observable _splitPercentage = 50;
get fieldDocToLayout() {
- return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
+ return DocCast(this._props.Document);
}
@action
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d25101844..f06e5fad0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -21,7 +21,7 @@ import { List } from '../../../../fields/List';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
@@ -349,6 +349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent json?.replace(/"selection":.*/, '');
@@ -369,22 +370,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
this._props.renderDepth &&
@@ -1244,9 +1244,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc;
- return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) };
+ const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey];
+ const dataData = this.dataDoc[this.fieldKey];
+ const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
+ const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0;
+ const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
+ const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
+ return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
},
incomingValue => {
if (this._editorView && this._applyingChange !== this.fieldKey) {
@@ -1256,11 +1262,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
}
}
- }
+ },
+ { fireImmediately: true }
);
this._disposers.search = reaction(
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bdf600475..200896e25 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -48,7 +48,7 @@ export namespace Field {
const valFunc = (field: Field): string => {
const res =
field instanceof ComputedField && showComputedValue
- ? field._lastComputedResult
+ ? field.value(doc)
: field instanceof ComputedField
? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
: field instanceof ScriptField
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 9021c8896..8b51088b2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -198,14 +198,22 @@ export class ComputedField extends ScriptField {
}
_lastComputedResult: FieldResult;
- value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => {
- this._lastComputedResult =
- this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
- return this._lastComputedResult;
- };
+ value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ??
+ computedFn((doc: Doc) =>
+ this.script.compiled &&
+ this.script.run( {
+ this: doc,
+ //value: '',
+ _setCacheResult_: this.setCacheResult,
+ _last_: this._lastComputedResult,
+ _readOnly_: true,
+ },
+ console.log
+ ).result
+ )(doc)
+ ); // prettier-ignore
- [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) }; } // prettier-ignore
+ [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore
[Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
--
cgit v1.2.3-70-g09d2