From d99f64efe9e69f2159f1ad8f851b24533a996ba5 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Mon, 26 Feb 2024 12:57:48 -0500
Subject: fixed some string types to be enumerations for dropAction. fixed bug
in golden layout dragging where a stack's tabs could disappear.
---
src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 001ad5ab6..46867665d 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
-import { FInfo } from '../../../documents/Documents';
+import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -29,6 +29,7 @@ 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';
export interface SchemaTableCellProps {
Document: Doc;
@@ -86,7 +87,7 @@ export class SchemaTableCell extends ObservableReactComponent
Date: Sat, 9 Mar 2024 19:05:30 -0500
Subject: fixed up default text that uses a template to process an initial
carriage return properly. fixed text with inherited templates to be able to
show fields with a default dashField value from template that can be
overidden on instance.
---
src/client/documents/Documents.ts | 11 +++---
src/client/views/MarqueeAnnotator.tsx | 4 +--
src/client/views/PreviewCursor.tsx | 2 +-
src/client/views/collections/TreeView.tsx | 2 +-
.../collectionFreeForm/CollectionFreeFormView.tsx | 36 +++++++++++--------
.../collections/collectionFreeForm/MarqueeView.tsx | 2 +-
.../collectionSchema/SchemaTableCell.tsx | 2 +-
src/client/views/nodes/MapBox/MapBox.tsx | 4 +--
.../views/nodes/MapboxMapBox/MapboxContainer.tsx | 4 +--
.../views/nodes/formattedText/FormattedTextBox.tsx | 40 ++++++++++------------
10 files changed, 55 insertions(+), 52 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e6969d1f3..7a3b965fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -3,7 +3,7 @@ import { action, reaction, runInAction } from 'mobx';
import { basename } from 'path';
import { DateField } from '../../fields/DateField';
import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
-import { Initializing } from '../../fields/DocSymbols';
+import { DocData, Initializing } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { HtmlField } from '../../fields/HtmlField';
import { InkField, PointData } from '../../fields/InkField';
@@ -1981,10 +1981,8 @@ export namespace DocUtils {
}
}
- export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, backgroundColor?: string) {
+ export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
const tbox = Docs.Create.TextDocument('', {
- _xMargin: noMargins ? 0 : undefined,
- _yMargin: noMargins ? 0 : undefined,
annotationOn,
backgroundColor,
_width: width || 200,
@@ -1997,11 +1995,14 @@ export namespace DocUtils {
_layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
title,
});
+
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;
+ 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)
}
return tbox;
}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a4303c3aa..f59042b04 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -88,7 +88,7 @@ export class MarqueeAnnotator extends ObservableReactComponent this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 456b753b4..a94c18295 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -85,7 +85,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
} else {
FormattedTextBox.PasteOnLoad = e;
if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
- UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
+ UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined)), 'paste');
}
}
//pasting in images
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0c0e49411..c6bbcb0a5 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -884,7 +884,7 @@ export class TreeView extends ObservableReactComponent {
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.treeView.outlineMode ? undefined : 1;
- case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+ case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.treeView.outlineMode) return undefined;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4eb946939..9500b918a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -30,7 +30,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
import { freeformScrollMode } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
@@ -1146,23 +1146,29 @@ export class CollectionFreeFormView extends CollectionSubView this._props.isContentActive();
- @undoBatch
+ /**
+ * Create a new text note of the same style as the one being typed into.
+ * If the text doc is be part of a larger templated doc, the new Doc will be a copy of the templated Doc
+ *
+ * @param fieldProps render props for the text doc being typed into
+ * @param below whether to place the new text Doc below or to the right of the one being typed into.
+ * @returns whether the new text doc was created and added successfully
+ */
+ createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
+ const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+ const newDoc = Doc.MakeCopy(textDoc, true);
+ newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+ newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+ newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+ FormattedTextBox.SetSelectOnLoad(newDoc);
+ FormattedTextBox.DontSelectInitialText = true;
+ return this.addDocument?.(newDoc);
+ }, 'copied text note');
+
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== 'Tab';
- const layout_fieldKey = StrCast(fieldProps.fieldKey);
- const newDoc = Doc.MakeCopy(fieldProps.Document, true);
- const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
- newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined;
- if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
- else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
- if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
- newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
- }
- newDoc[DocData].text = undefined;
- FormattedTextBox.SetSelectOnLoad(newDoc);
- return this.addDocument?.(newDoc);
+ return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
}
};
@computed get childPointerEvents() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e22b83307..b913e05ad 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -178,7 +178,7 @@ export class MarqueeView extends ObservableReactComponent() implem
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -592,7 +592,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+ text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 8a5bd7ce6..3eb051dbf 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -232,7 +232,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -466,7 +466,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
+ text: (StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location') as any,
config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
config_map_zoom: NumCast(this.dataDoc.map_zoom),
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 54e3e7b44..1ff7274f8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,7 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
-import Select from 'react-select';
// import * as applyDevTools from 'prosemirror-dev-tools';
@observer
export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -100,7 +99,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent;
private _keymap: any = undefined;
@@ -306,7 +304,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
@@ -1457,39 +1455,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark];
- const tr = this._editorView.state.tr
- .setStoredMarks(storedMarks)
- .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size)
- .setStoredMarks(storedMarks);
+ const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
+ const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1);
+ const tr = tr2.setStoredMarks(storedMarks);
+
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
}
}
- selectOnLoad && this._editorView!.focus();
+ if (selectOnLoad) {
+ FormattedTextBox.DontSelectInitialText = false;
+ this._editorView!.focus();
+ }
if (this._props.isContentActive()) this.prepareForTyping();
- if (this._editorView) {
- const tr = this._editorView.state.tr;
- const { from, to } = tr.selection;
- // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated.
- if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250);
-
- if (FormattedTextBox.PasteOnLoad) {
- const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
- FormattedTextBox.PasteOnLoad = undefined;
- pdfAnchorId && this.addPdfReference(pdfAnchorId);
- }
+ if (this._editorView && FormattedTextBox.PasteOnLoad) {
+ const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor');
+ FormattedTextBox.PasteOnLoad = undefined;
+ pdfAnchorId && this.addPdfReference(pdfAnchorId);
}
- FormattedTextBox.DontSelectInitialText = false;
}
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
--
cgit v1.2.3-70-g09d2
From 606088e419f0e146715244d00840349b587c80ba Mon Sep 17 00:00:00 2001
From: bobzel
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/collections/collectionSchema/SchemaTableCell.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 2e0cb3e0a470994eecbb7f6b2ec87296baf517b9 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Sun, 24 Mar 2024 19:04:42 -0400
Subject: fixed linkdocpreviews to sequence through multiple links. fixed text
boxes to update text when dashfieldView text changes (but the fieldview
doesn't), fixed dashFieldViews to be editable cleanly, and to allow
sub-dashFieldViews to be editbale. allowed toggle on/off of dashFieldView
fieldKey. got rid of sidebars in scemaCells. fied editing dashFieldViews
in captions and as childrend of dashFieldViews by passing rootSelected
---
.../collectionSchema/SchemaTableCell.tsx | 9 ++++-
src/client/views/nodes/DocumentView.tsx | 1 +
src/client/views/nodes/LinkDocPreview.tsx | 1 +
.../views/nodes/formattedText/DashFieldView.scss | 32 ++++++++++-----
.../views/nodes/formattedText/DashFieldView.tsx | 45 ++++++++++++++--------
.../views/nodes/formattedText/FormattedTextBox.tsx | 26 +++++++++----
6 files changed, 80 insertions(+), 34 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 711ef507c..ce73ff8a4 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -51,6 +51,8 @@ export interface SchemaTableCellProps {
options?: string[];
menuTarget: HTMLDivElement | null;
transform: () => Transform;
+ autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click
+ rootSelected?: () => boolean;
}
@observer
@@ -89,6 +91,7 @@ export class SchemaTableCell extends ObservableReactComponent
this._props.autoFocus && r?.setIsFocused(true)}
oneLine={this._props.oneLine}
allowCRs={this._props.allowCRs}
contents={undefined}
@@ -314,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent this.selected;
render() {
const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
fieldProps.isContentActive = this.selectedFunc;
return (
- {this.selected ? : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+ {this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
);
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e9ce98583..1044d6609 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -892,6 +892,7 @@ export class DocumentViewInternal extends DocComponent this._expanded && this._props.editable;
- finishEdit = action(() => (this._expanded = false));
+
+ finishEdit = action(() => {
+ if (this._expanded) {
+ this._expanded = false;
+ // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus
+ // the timeout allows switching focus from one dashFieldView to another in the same text box
+ setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.());
+ }
+ });
selectedCell = (): [Doc, number] => [this._dashDoc!, 0];
+ columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey
// set the display of the field's value (checkbox for booleans, span of text for strings)
@computed get fieldValueContent() {
return !this._dashDoc ? null : (
- (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
+
(this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
);
@@ -187,12 +198,12 @@ export class DashFieldViewInternal extends ObservableReactComponent
{
+ onPointerDownLabelSpan = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
@@ -215,7 +226,7 @@ export class DashFieldViewInternal extends ObservableReactComponent
+
{this.values.map(val => (
{val.label}
))}
@@ -272,14 +283,18 @@ export class DashFieldViewMenu extends AntimodeMenu {
};
render() {
return this.getElement(
- {`Show Pivot Viewer for '${this._fieldKey}'`} }>
-
-
-
-
-
-
-
+ <>
+ {`Show Pivot Viewer for '${this._fieldKey}'`}}>
+
+
+
+
+ Toggle view of field key}>
+
+
+
+
+ >
);
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2b48494f2..793595694 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, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, 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';
@@ -68,8 +68,13 @@ import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
// import * as applyDevTools from 'prosemirror-dev-tools';
+
+interface FormattedTextBoxProps extends FieldViewProps {
+ onBlur?: () => void; // callback when text loses focus
+ autoFocus?: boolean; // whether text should get input focus when created
+}
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -200,7 +205,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
if (node.type === this._editorView?.state.schema.nodes.dashField) {
const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
- return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field);
+ const fieldKey = StrCast(node.attrs.fieldKey);
+ return fieldKey + ':' + Field.toJavascriptString(refDoc[fieldKey] as Field);
}
return '';
};
@@ -359,15 +365,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
}
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -1724,6 +1732,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
};
onKeyDown = (e: React.KeyboardEvent) => {
--
cgit v1.2.3-70-g09d2
From ab76273acd2126d92455ff12c4ba80d8bb9473c5 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Sun, 24 Mar 2024 19:22:41 -0400
Subject: fixed crash on richtextfield to string when there's no data. fixed
schema cells not to edit unless selected.
---
src/client/views/collections/collectionSchema/SchemaTableCell.tsx | 2 +-
src/fields/RichTextField.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index ce73ff8a4..67dc508d5 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -141,7 +141,7 @@ export class SchemaTableCell extends ObservableReactComponent
Date: Tue, 26 Mar 2024 22:34:10 -0400
Subject: added empty image tool and click on empty image to select from
filesystem. fixed following links in lightbox and showing links to
stackedTimelines. fixed embedding docs into text. fixed not resizing text
boxes that also show up in pivot view. prevent context menu from going off
top of screen. fixed freeform clustering colors and click to type. fixed
links to stackedTimeline marks, and titles for marks. made title editing
from doc deco and header use same syntax as keyValue. fixed marquee
selection on webBoxes. turn off transitions in freeformdocview after
timeout. enabled iconifying templates to propagate to "offspring". fixes
images in templates. don't show headr on schema views.
---
src/client/documents/Documents.ts | 52 ++++++++--------
src/client/util/DocumentManager.ts | 4 +-
src/client/views/ContextMenu.tsx | 6 +-
src/client/views/DocumentDecorations.tsx | 71 ++++++++--------------
src/client/views/FieldsDropdown.tsx | 1 -
src/client/views/LightboxView.tsx | 2 +
src/client/views/MarqueeAnnotator.tsx | 19 ++++--
src/client/views/ObservableReactComponent.tsx | 3 +-
src/client/views/PropertiesView.tsx | 2 +-
src/client/views/StyleProvider.tsx | 6 +-
.../collections/CollectionStackedTimeline.tsx | 13 ++--
.../CollectionFreeFormLayoutEngines.tsx | 2 +-
.../collectionFreeForm/CollectionFreeFormView.tsx | 11 ++--
.../collections/collectionFreeForm/MarqueeView.tsx | 4 +-
.../collectionSchema/SchemaTableCell.tsx | 2 +-
src/client/views/global/globalScripts.ts | 4 +-
.../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++--
src/client/views/nodes/DocumentContentsView.tsx | 7 ++-
src/client/views/nodes/DocumentView.tsx | 35 +++++++----
src/client/views/nodes/FieldView.tsx | 1 +
src/client/views/nodes/ImageBox.tsx | 55 +++++++++++++----
src/client/views/nodes/KeyValueBox.tsx | 2 +-
src/client/views/nodes/LabelBox.tsx | 2 +-
src/client/views/nodes/LinkBox.tsx | 47 ++++++++++----
src/client/views/nodes/VideoBox.tsx | 5 +-
src/client/views/nodes/WebBox.tsx | 65 ++++++++++++++------
.../views/nodes/formattedText/FormattedTextBox.tsx | 17 ++++--
src/client/views/nodes/trails/PresBox.tsx | 2 +-
src/fields/Doc.ts | 29 +++++----
src/fields/PresField.ts | 6 --
src/fields/RichTextField.ts | 7 ---
31 files changed, 311 insertions(+), 195 deletions(-)
delete mode 100644 src/fields/PresField.ts
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 17cb6fef8..b63c5e429 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1017,8 +1017,8 @@ export namespace Docs {
}
export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
- const imgField = url instanceof ImageField ? url : new ImageField(url);
- return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+ const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
+ return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
}
export function PresDocument(options: DocumentOptions = {}) {
@@ -1950,6 +1950,31 @@ export namespace DocUtils {
return dd;
}
+ export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+ if (Upload.isImageInformation(result)) {
+ const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+ const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+ proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+ proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ if (NumCast(proto['data-nativeOrientation']) >= 5) {
+ proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+ proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+ }
+ proto.data_exif = JSON.stringify(result.exifData?.data);
+ proto.data_contentSize = result.contentSize;
+ // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+ const latitude = result.exifData?.data?.GPSLatitude;
+ const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+ const longitude = result.exifData?.data?.GPSLongitude;
+ const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+ if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+ proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+ proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+ }
+ }
+ }
+
async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
if (result instanceof Error) {
alert(`Upload failed: ${result.message}`);
@@ -1961,28 +1986,7 @@ export namespace DocUtils {
if (doc) {
const proto = Doc.GetProto(doc);
proto.text = result.rawText;
- if (Upload.isImageInformation(result)) {
- const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
- const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
- proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- if (NumCast(proto['data-nativeOrientation']) >= 5) {
- proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
- proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
- }
- proto.data_exif = JSON.stringify(result.exifData?.data);
- proto.data_contentSize = result.contentSize;
- // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
- const latitude = result.exifData?.data?.GPSLatitude;
- const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
- const longitude = result.exifData?.data?.GPSLongitude;
- const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
- if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
- proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
- proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
- }
- }
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
if (Upload.isVideoInformation(result)) {
proto.data_duration = result.duration;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a38a330da..40d28c690 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -59,7 +59,7 @@ export class DocumentManager {
private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => {
if (doc) {
- const dv = this.getDocumentView(doc);
+ const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc);
this._viewRenderedCbs.push({ doc, func });
if (dv) {
this.callAddViewFuncs(dv);
@@ -262,7 +262,7 @@ export class DocumentManager {
return res(this.getDocumentView(docContextPath[0])!);
}
options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
if (options.openLocation === OpenWhere.lightbox) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8f4e43978..ca877b93e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -218,11 +218,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
this._width = DivWidth(r);
this._height = DivHeight(r);
}
+ this._searchRef.current?.focus();
})}
style={{
display: this._display ? '' : 'none',
left: this.pageX,
- ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
+ ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
background: SettingsManager.userBackgroundColor,
color: SettingsManager.userColor,
}}>
@@ -265,7 +266,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
const item = this.flatItems[this._selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
- } else if (this._searchString.startsWith(this._defaultPrefix)) {
+ } else {
+ //if (this._searchString.startsWith(this._defaultPrefix)) {
this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
}
this.closeMenu();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fb9f0fc1..4d9b93896 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -34,6 +34,7 @@ import { Colors } from './global/globalEnums';
import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from './nodes/KeyValueBox';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -57,7 +58,7 @@ export class DocumentDecorations extends ObservableReactComponent {
- if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
+ if (this._accumulatedTitle.startsWith('$')) {
this._titleControlString = this._accumulatedTitle;
- } else if (this._titleControlString.startsWith('#')) {
+ } else if (this._titleControlString.startsWith('$')) {
if (this._accumulatedTitle.startsWith('-->#')) {
SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
}
@@ -131,26 +132,7 @@ export class DocumentDecorations extends ObservableReactComponent')) {
- const title = titleField.toString().replace(/\.?/, '');
- const curKey = Doc.LayoutFieldKey(d.Document);
- if (curKey !== title) {
- if (title) {
- if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
- d.Document.layout_fieldKey = `layout_${title}`;
- d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
- d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
- }
- } else {
- d.Document.layout_fieldKey = undefined;
- }
- }
- } else {
- Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
- }
+ KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
}),
'edit title'
);
@@ -181,7 +163,8 @@ export class DocumentDecorations extends ObservableReactComponent this.onBackgroundMove(true, e),
emptyFunction,
action(e => {
- !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
})
@@ -622,11 +605,8 @@ export class DocumentDecorations extends ObservableReactComponent {
- this._editingTitle = false;
- !hideTitle && this.titleBlur();
- })}
- onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
- onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
- onPointerDown={e => e.stopPropagation()}
- />
+ <>
+ {r - x < 150 ? null : {this._titleControlString + ':'} }
+ {
+ this._editingTitle = false;
+ !hideTitle && this.titleBlur();
+ })}
+ onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
+ onPointerDown={e => e.stopPropagation()}
+ />
+ >
) : (
{hideTitle ? null : (
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 5638d34c6..6a5c2cb4c 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent
filteredOptions.push(pair[0]));
const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
- console.log(options);
return (
{
}}>
(this._docView = r !== null ? r : undefined))}
Document={this._doc}
PanelWidth={this.lightboxWidth}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index bd6be2519..c29474fcd 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps {
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
getPageFromScroll?: (top: number) => number;
- finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void;
+ finishMarquee: (x?: number, y?: number) => void;
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined;
highlightDragSrcColor?: string;
@@ -168,7 +168,6 @@ export class MarqueeAnnotator extends ObservableReactComponent {
+ const movLoc = this.getTransformedScreenPt(pt);
+ this._width = movLoc.x - this._start.x;
+ this._height = movLoc.y - this._start.y;
+ };
+
@action
onSelectMove = (e: PointerEvent) => {
const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
@@ -248,9 +254,12 @@ export class MarqueeAnnotator extends ObservableReactComponent {
e.stopPropagation();
+ this.onEnd(e.clientX, e.clientY);
+ };
+ @action
+ onEnd = (x: number, y: number) => {
const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
if (!this.isEmpty && marqueeStyle) {
@@ -266,9 +275,9 @@ export class MarqueeAnnotator extends ObservableReactComponent extends React.Component
makeObservable(this);
}
componentDidUpdate(prevProps: Readonly): void {
- Object.keys(prevProps).forEach(action(pkey =>
- (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+ Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
}
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index cbd3ff358..195b1a04c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1399,7 +1399,7 @@ export class PropertiesView extends ObservableReactComponent
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1adb0d9e5..7a15e67e4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -25,6 +25,7 @@ import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PropertiesView } from './PropertiesView';
import './StyleProvider.scss';
+import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
export enum StyleProp {
TreeViewIcon = 'treeView_Icon',
@@ -137,14 +138,15 @@ export function DefaultStyleProvider(doc: Opt, props: Opt boolean | undefined;
ScreenToLocalTransform: () => Transform;
+ containerViewPath?: () => DocumentView[];
_timeline: HTMLDivElement | null;
focus: FocusFuncType;
currentTimecode: () => number;
@@ -796,12 +798,15 @@ class StackedTimelineAnchor extends ObservableReactComponent {
+ this._props.mark[DocData].title = ComputedField.MakeFunction(
+ `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
+ );
+ };
// context menu
contextMenuItems = () => {
const resetTitle = {
- script: ScriptField.MakeFunction(
- `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
- )!,
+ method: this.resetTitle,
icon: 'folder-plus',
label: 'Reset Title',
};
@@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent (anchor.view = r))}
Document={mark}
TemplateDataDocument={undefined}
- containerViewPath={returnEmptyDoclist}
+ containerViewPath={this._props.containerViewPath}
pointerEvents={this.noEvents ? returnNone : undefined}
styleProvider={this._props.styleProvider}
renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index b8c0967c1..6484658e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -388,7 +388,7 @@ function normalizeResults(
minWidth: number,
extras: ViewDefBounds[]
): ViewDefResult[] {
- const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds));
+ const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
const aggBounds = aggregateBounds(
extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1fd453e96..5495735ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -565,7 +565,7 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => {
let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (doc && this.childDocList?.includes(doc))
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.BackgroundColor:
const cluster = NumCast(doc?.layout_cluster);
if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
@@ -590,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView {
- if (this._hitCluster !== -1) {
+ if (addToSel && this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
@@ -1288,15 +1288,16 @@ export class CollectionFreeFormView extends CollectionSubView this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+ cm.setDefaultItem('?', (str: string) =>
+ this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight)
+ );
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this._props.ungroup) {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 67dc508d5..bda8afa41 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -213,7 +213,7 @@ export class SchemaImageCell extends ObservableReactComponent Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
.filter(url => url)
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3fdc9a488..d12639fc7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -87,8 +87,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
});
} else {
Doc.SharingDoc().headingColor = undefined;
- Doc.GetProto(Doc.SharingDoc()).headingColor = color;
- Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
+ Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+ Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
}
});
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 2800ea200..0d0a7c623 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { OmitKeys, numberRange } from '../../../Utils';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
+import { TransitionTimer } from '../../../fields/DocSymbols';
/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -91,6 +92,16 @@ export class CollectionFreeFormDocumentView extends DocComponent (this.Document[TransitionTimer] = this.Transition = undefined)),
+ num
+ );
+ }
+ }
+
componentDidUpdate(prevProps: Readonly>) {
super.componentDidUpdate(prevProps);
this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
@@ -98,14 +109,14 @@ export class CollectionFreeFormDocumentView extends DocComponent this._props.transition; // prettier-ignore
+ DataTransition = () => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
styleProvider = (doc: Doc | undefined, props: Opt, property: string) => {
if (doc === this.layoutDoc) {
- switch (property) {
+ switch (property.split(':')[0]) {
case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
case StyleProp.BackgroundColor: return this.BackgroundColor;
case StyleProp.Color: return this.Color;
@@ -224,7 +235,8 @@ export class CollectionFreeFormDocumentView extends DocComponent {
if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
- const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ const backColor = this.BackgroundColor;
+ const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
render() {
@@ -237,7 +249,7 @@ export class CollectionFreeFormDocumentView extends DocComponent
@@ -251,6 +263,8 @@ export class CollectionFreeFormDocumentView extends DocComponent
)}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5a326ecb0..e729e2fa2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -7,7 +7,7 @@ import { OmitKeys, Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { InkingStroke } from '../InkingStroke';
import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -128,7 +128,9 @@ export class DocumentContentsView extends ObservableReactComponentawaiting layout
';
if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
+ const layoutDoc = tempLayout ?? this.layoutDoc;
+ const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string');
if (layout === undefined) return this._props.Document.data ? " " : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return 'Loading layout
';
@@ -154,6 +156,7 @@ export class DocumentContentsView extends ObservableReactComponent string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
- contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+ contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
@@ -560,7 +559,13 @@ export class DocumentViewInternal extends DocComponent
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
- this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
+ this._props
+ .contextMenuItems?.()
+ .forEach(
+ item =>
+ item.label &&
+ cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
+ );
if (!this.Document.isFolder) {
const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
@@ -597,7 +602,7 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' });
!Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
- cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
@@ -810,8 +815,7 @@ export class DocumentViewInternal extends DocComponent Field.toString(targetDoc[field.trim()] as Field))
+ .map(field => Field.toJavascriptString(this.Document[field] as Field))
.join(' \\ ') || '-unset-'
}
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
- GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
+ GetValue={() =>
+ showTitle
+ .split(';')
+ .map(field => Field.toKeyValueString(this.Document, field))
+ .join('\\')
+ }
SetValue={undoBatch((input: string) => {
- if (input?.startsWith('#')) {
+ if (input?.startsWith('$')) {
if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = input?.substring(1);
+ this.layoutDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
} else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+ Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'title';
}
- } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+ } else if (showTitle && !showTitle.includes(';') && !showTitle.includes('Date') && showTitle !== 'author') {
KeyValueBox.SetField(targetDoc, showTitle, input);
}
return true;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5b47dd91d..771856788 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -92,6 +92,7 @@ export interface FieldViewSharedProps {
waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
pointerEvents?: () => Opt;
+ suppressSetHeight?: boolean;
}
/**
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 251235b93..86e8ed60a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -9,7 +9,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -29,6 +29,12 @@ import { OpenWhere } from './DocumentView';
import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
+import { Colors } from 'browndash-components';
+import { listSpec } from '../../../fields/Schema';
+import { List } from '../../../fields/List';
+import { url } from 'inspector';
+import { OverlayView } from '../OverlayView';
+import { Networking } from '../../Network';
@observer
export class ImageBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface {
@@ -134,7 +140,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
- return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
}, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -259,7 +265,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
const ext = extname(url.href);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
ref={this._overlayIconRef}
onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
style={{
- display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +317,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
@computed get paths() {
const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
- const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
- const altpaths = alts
- .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
- .filter(url => url)
- .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List; // retrieve alternate documents that may be rendered as alternate images
+ const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+ const altpaths =
+ alts
+ ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
+ .filter(url => url)
+ .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+ return paths.length ? paths : [defaultUrl.href];
}
@observable _error = '';
@@ -326,7 +334,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
@computed get content() {
TraceMobx();
- const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -370,6 +378,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
+ if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
@@ -468,4 +477,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
);
}
+
+ public chooseImage = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.accept = 'image/*';
+ input.onchange = async _e => {
+ const file = input.files?.[0];
+ if (file) {
+ const disposer = OverlayView.ShowSpinner();
+ const [{ result }] = await Networking.UploadFilesToServer({ file });
+ if (result instanceof Error) {
+ alert('Error uploading files - possibly due to unsupported file types');
+ } else {
+ this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+ }
+ disposer();
+ } else {
+ console.log('No file selected');
+ }
+ };
+ input.click();
+ };
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 5394c7dbb..78e4435ce 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -113,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent {
if (key) target[key] = script.originalScript;
return false;
}
- field === undefined && (field = res.result);
+ field === undefined && (field = res.result instanceof Array ? new List(res.result) : res.result);
}
}
if (!key) return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index be20b5934..74e78c671 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent() {
}
@computed get Title() {
- return Field.toString(this.dataDoc[this.fieldKey] as Field);
+ return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
}
protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 6e4d0e92a..0809e2ad6 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import Xarrow from 'react-xarrows';
+import { FieldResult } from '../../../fields/Doc';
import { DocCss, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
@@ -22,8 +23,8 @@ export class LinkBox extends ViewBoxBaseComponent() {
public static LayoutString(fieldKey: string = 'link') {
return FieldView.LayoutString(LinkBox, fieldKey);
}
- disposer: IReactionDisposer | undefined;
- @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+ _disposer: IReactionDisposer | undefined;
+ @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
@observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
constructor(props: FieldViewProps) {
@@ -39,11 +40,11 @@ export class LinkBox extends ViewBoxBaseComponent() {
return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
componentWillUnmount() {
- this.disposer?.();
+ this._disposer?.();
}
componentDidMount() {
this._props.setContentViewBox?.(this);
- this.disposer = reaction(
+ this._disposer = reaction(
() => ({ drag: SnappingManager.IsDragging }),
({ drag }) => {
!LightboxView.Contains(this.DocumentView?.()) &&
@@ -64,12 +65,13 @@ export class LinkBox extends ViewBoxBaseComponent() {
}
})
);
- },
- { fireImmediately: true }
+ }
);
}
render() {
+ TraceMobx();
+
if (this._hide) return null;
const a = this.anchor1;
const b = this.anchor2;
@@ -92,18 +94,34 @@ export class LinkBox extends ViewBoxBaseComponent() {
const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+ var foundParent = false;
+ const getAnchor = (field: FieldResult): Element[] => {
+ const doc = DocCast(field);
+ const ele = document.getElementById(doc[Id]);
+ if (ele?.getBoundingClientRect().width) return [ele];
+ const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+ const annoOn = DocCast(doc.annotationOn);
+ if (eles.length || !annoOn) return eles;
+ const pareles = getAnchor(annoOn);
+ foundParent = pareles.length ? true : false;
+ return pareles;
+ };
// if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext ),
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
- const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+ const targetAhyperlinks = getAnchor(this.dataDoc.link_anchor_1);
+ const targetBhyperlinks = getAnchor(this.dataDoc.link_anchor_2);
- const aid = targetAhyperlink?.id || a.Document[Id];
- const bid = targetBhyperlink?.id || b.Document[Id];
- if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ const container = this.DocumentView?.().containerViewPath?.().lastElement().ContentDiv;
+ const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
+ const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
+ if (!aid || !bid) {
setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
return null;
}
+ if (foundParent) {
+ setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 1)));
+ }
if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
@@ -192,6 +210,11 @@ export class LinkBox extends ViewBoxBaseComponent() {
>
);
}
+
+ setTimeout(
+ action(() => (this._forceAnimate = this._forceAnimate + 1)),
+ 2
+ );
return (
() impl
Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
- link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
+ // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
@@ -373,7 +373,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl
getView = (doc: Doc, options: FocusViewOptions) => {
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
- this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+ // do we want to always open up the timeline when followin a link? kind of clunky visually
+ //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
options.didMove = true;
}
return this._stackedTimeline.getView(doc, options);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c9340edc0..f4d5eef05 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { htmlToText } from 'html-to-text';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
@@ -340,8 +340,18 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
_textAnnotationCreator: (() => ObservableMap) | undefined;
savedAnnotationsCreator: () => ObservableMap = () => this._textAnnotationCreator?.() || this._savedAnnotations;
+ @action
+ iframeMove = (e: PointerEvent) => {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ this._marqueeref.current?.onMove(theclick);
+ };
@action
iframeUp = (e: PointerEvent) => {
+ this._iframe?.contentDocument?.removeEventListener('pointermove', this.iframeMove);
+ this.marqueeing = undefined;
this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
this._textAnnotationCreator = undefined;
this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
@@ -358,6 +368,29 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
GPTPopup.Instance.addDoc = this.sidebarAddDocument;
}
+ } else {
+ const theclick = this.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(e.clientX, e.clientY - NumCast(this.layoutDoc.layout_scrollTop));
+ if (!this._marqueeref.current?.isEmpty) this._marqueeref.current?.onEnd(theclick[0], theclick[1]);
+ else {
+ if (!(e.target as any)?.tagName?.includes('INPUT')) this.finishMarquee(theclick[0], theclick[1]);
+ this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ this.marqueeing = undefined;
+ }
+
+ ContextMenu.Instance.closeMenu();
+ ContextMenu.Instance.setIgnoreEvents(false);
+ if (e?.button === 2 || e?.altKey) {
+ e?.preventDefault();
+ e?.stopPropagation();
+ setTimeout(() => {
+ // if menu comes up right away, the down event can still be active causing a menu item to be selected
+ this.specificContextMenu(undefined as any);
+ this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
+ });
+ }
}
};
@action
@@ -400,6 +433,12 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
};
@action
iframeDown = (e: PointerEvent) => {
+ this._textAnnotationCreator = undefined;
+ const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+ if (sel?.empty)
+ sel.empty(); // Chrome
+ else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+
this._props.select(false);
const theclick = this.props
.ScreenToLocalTransform()
@@ -409,6 +448,8 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
this.marqueeing = theclick;
+ this._marqueeref.current?.onInitiateSelection(this.marqueeing);
+ this._iframe?.contentDocument?.addEventListener('pointermove', this.iframeMove);
e.preventDefault();
}
};
@@ -739,28 +780,10 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
this.marqueeing = undefined;
}
};
- @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
+ @action finishMarquee = (x?: number, y?: number) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.marqueeing = undefined;
- this._textAnnotationCreator = undefined;
- const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
- if (sel?.empty)
- sel.empty(); // Chrome
- else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
- if (x !== undefined && y !== undefined) {
- ContextMenu.Instance.closeMenu();
- ContextMenu.Instance.setIgnoreEvents(false);
- if (e?.button === 2 || e?.altKey) {
- e?.preventDefault();
- e?.stopPropagation();
- setTimeout(() => {
- // if menu comes up right away, the down event can still be active causing a menu item to be selected
- this.specificContextMenu(undefined as any);
- this.DocumentView?.().onContextMenu(undefined, x, y);
- });
- }
- }
};
@observable lighttext = false;
@@ -992,6 +1015,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
}
childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
@computed get webpage() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
@@ -1071,6 +1095,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem
: 'none';
annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
render() {
+ TraceMobx();
const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 793595694..eb7293054 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -450,16 +450,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
const newAutoLinks = new Set();
- const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this.Document).filter(
+ link =>
+ ((!Doc.isTemplateForField(this.Document) &&
+ (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+ (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+ (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+ link.link_relationship === LinkManager.AutoKeywords
+ ); // prettier-ignore
if (this._editorView?.state.doc.textContent) {
- const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
+
const t = this._editorView.state.selection.to;
var tr = this._editorView.state.tr as any;
const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
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)));
+ tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
}
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
@@ -624,7 +631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent p + toHgt(child), margins);
const scrollHeight = this.ProseRef && proseHeight;
- if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
+ if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e34144fae..ae6da8fb0 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -703,7 +703,7 @@ export class PresBox extends ViewBoxBaseComponent() {
if (pinProps.pinData.temporal) {
pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
- pinDoc.config_clipEnd = NumCast(targetDoc.clipEnd, duration);
+ pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
}
}
if (pinProps?.pinViewport) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 246828709..1bd49cf3f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -45,18 +45,23 @@ export namespace 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 && showComputedValue
- ? field._lastComputedResult
- : field instanceof ComputedField
- ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
- : field instanceof ScriptField
- ? `$=${field.script.originalScript}`
- : Field.toScriptString(field));
+ const valFunc = (field: Field): string => {
+ const res =
+ field instanceof ComputedField && showComputedValue
+ ? field._lastComputedResult
+ : field instanceof ComputedField
+ ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}`
+ : field instanceof ScriptField
+ ? `$=${field.script.originalScript}`
+ : Field.toScriptString(field);
+ const resStr = (res + '').replace(/^`(.*)`$/, '$1');
+ return typeof field === 'string' && (+resStr).toString() !== resStr && !Array.from('+-*/.').some(k => Array.from(resStr).includes(k))
+ ? resStr
+ : (res + '') // adjust the key value string to be easier to enter: represent any initial list as an array with []
+ .trim()
+ .replace(/^new List\((.*)\)$/, '$1');
+ };
+ return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
}
export function toScriptString(field: Field) {
switch (typeof field) {
diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts
deleted file mode 100644
index f236a04fd..000000000
--- a/src/fields/PresField.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-//insert code here
-import { ObjectField } from "./ObjectField";
-
-export abstract class PresField extends ObjectField {
-
-}
\ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index f5801de73..d0a149506 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,11 +36,4 @@ export class RichTextField extends ObjectField {
[ToString]() {
return this.Text;
}
-
- public static DashField(fieldKey: string) {
- return new RichTextField(
- `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"${fieldKey}","docId":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`,
- ''
- );
- }
}
--
cgit v1.2.3-70-g09d2
From 52bd492747cf169df7bcdecdbdbb353d45b19734 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Thu, 28 Mar 2024 00:05:05 -0400
Subject: fixed tabbing between items in nested dashFieldViews
---
src/client/views/EditableView.tsx | 1 +
.../views/collections/collectionSchema/CollectionSchemaView.scss | 9 +++++++++
.../views/collections/collectionSchema/SchemaTableCell.tsx | 8 ++++----
src/fields/Doc.ts | 3 +++
src/fields/RichTextField.ts | 7 +++++++
5 files changed, 24 insertions(+), 4 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 44c8e4e1f..85e893e19 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -121,6 +121,7 @@ export class EditableView extends ObservableReactComponent {
@action
onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here
switch (e.key) {
case 'Tab':
e.stopPropagation();
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index ac0bd2378..9768877ff 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -226,6 +226,15 @@
align-items: center;
}
+.schemaRTFCell {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ min-width: 10px;
+ min-height: 10px;
+}
+
.schema-row {
cursor: grab;
justify-content: flex-end;
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bda8afa41..ab05a62d3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -17,6 +17,7 @@ import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { dropActionType } from '../../../util/DragManager';
import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
import { EditableView } from '../../EditableView';
@@ -29,7 +30,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import { SnappingManager } from '../../../util/SnappingManager';
export interface SchemaTableCellProps {
Document: Doc;
@@ -128,7 +128,7 @@ export class SchemaTableCell extends ObservableReactComponent
this._props.autoFocus && r?.setIsFocused(true)}
+ ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)}
oneLine={this._props.oneLine}
allowCRs={this._props.allowCRs}
contents={undefined}
@@ -141,7 +141,7 @@ export class SchemaTableCell extends ObservableReactComponent
+
{this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 1bd49cf3f..bdf600475 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1713,3 +1713,6 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
ScriptingGlobals.add(function toJavascriptString(str: string) {
return Field.toJavascriptString(str as Field);
});
+ScriptingGlobals.add(function RtfField() {
+ return RichTextField.RTFfield();
+});
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index d0a149506..3f13f7e6d 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -36,4 +36,11 @@ export class RichTextField extends ObjectField {
[ToString]() {
return this.Text;
}
+
+ public static RTFfield() {
+ return new RichTextField(
+ `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`,
+ ''
+ );
+ }
}
--
cgit v1.2.3-70-g09d2
From a9baa52c557faaa82fd3f696f0d603dd10b982a8 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Sat, 30 Mar 2024 02:18:07 -0400
Subject: updated mermaids template. updated paintFunc to also render the
underlying code text so that the paintFunc will update if dashFields in the
code change. fixed schema views to render templates properly in preview.
fixed text to only modify the data doc. changed setPixels in imageBox to not
consider scaling of outer containers.
---
src/client/util/CurrentUserUtils.ts | 4 ++--
src/client/views/StyleProvider.tsx | 2 +-
.../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++++--
.../views/collections/collectionSchema/SchemaTableCell.tsx | 6 +++---
src/client/views/nodes/ImageBox.tsx | 3 +--
src/client/views/nodes/formattedText/FormattedTextBox.tsx | 12 ++++++++----
6 files changed, 19 insertions(+), 14 deletions(-)
(limited to 'src/client/views/collections/collectionSchema/SchemaTableCell.tsx')
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3855bdeb6..dbf9641b6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -321,9 +321,9 @@ export class CurrentUserUtils {
{type:"text",text:" "},
{type:"text",text:"Minerals in my tap water"},
{type:"text",text:"\n \"Calcium\" : "},
- {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:false,hideValue:false,editable:true}},
+ {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:true,hideValue:false,editable:true}},
{type:"text",text:"\n \"Potassium\" : "},
- {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,hideValue:false,editable:true}},
+ {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:true,hideValue:false,editable:true}},
{type:"text",text:"\n \"Magnesium\" : 10.01"}
]}
]},
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8073d0842..c8b0c0560 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -139,7 +139,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt
- {this.paintFunc ? null : this._lightboxDoc ? (
+ {this.paintFunc ? (
+ // need this so that any live dashfieldviews will update the underlying text that the code eval reads
+ ) : this._lightboxDoc ? (
Field.toKeyValueString(this._props.Document, this._props.fieldKey, SnappingManager.MetaKey)}
+ GetValue={() => Field.toKeyValueString(fieldProps.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);
+ const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined);
this._props.finishEdit?.();
return ret;
}, 'edit schema cell')}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 86e8ed60a..469869e21 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,8 +165,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl
@undoBatch
setNativeSize = action(() => {
- const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
- const nscale = NumCast(this._props.PanelWidth()) / scaling;
+ const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3b583771a..b815342e3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -326,10 +326,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
if (node.type === this._editorView?.state.schema.nodes.dashField) {
- const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
const fieldKey = StrCast(node.attrs.fieldKey);
return (
- (node.attrs.hideKey ? '' : '"' + fieldKey + '"' + ':') + //
+ (node.attrs.hideKey ? '' : fieldKey + ':') + //
(node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
);
}
@@ -346,7 +346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent {
if (this._editorView && (this._editorView as any).docView) {
const state = this._editorView.state;
- const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+ const dataDoc = this.dataDoc;
const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
@@ -1608,7 +1608,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent