aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts15
-rw-r--r--src/client/util/CurrentUserUtils.ts1
-rw-r--r--src/client/util/RecordingApi.ts269
-rw-r--r--src/client/views/DocumentDecorations.scss81
-rw-r--r--src/client/views/DocumentDecorations.tsx143
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx1641
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx102
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx660
-rw-r--r--src/client/views/global/globalEnums.tsx1
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx1
-rw-r--r--src/client/views/nodes/DataViz.tsx20
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx12
-rw-r--r--src/client/views/nodes/button/textButton/TextButton.tsx23
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx77
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx181
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx229
-rw-r--r--src/client/views/nodes/formattedText/EquationView.tsx99
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx1041
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx107
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts213
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx361
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts574
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx73
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts382
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts361
-rw-r--r--src/fields/Doc.ts865
-rw-r--r--src/fields/RichTextUtils.ts230
27 files changed, 4416 insertions, 3346 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index b87980397..6699aa133 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -677,7 +677,22 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
e.preventDefault();
}
+/**
+ * Helper method for converting pixel string eg. '32px' into number eg. 32
+ * @param value: string with 'px' ending
+ * @returns value: number
+ *
+ * Example:
+ * '32px' -> 32
+ */
+export function numberValue(value: string | undefined):number {
+ if (value == undefined) return 0;
+ return parseInt(value);
+}
+export function numbersAlmostEqual(num1: number, num2: number) {
+ return Math.abs( num1 - num2 ) < 0.2;
+}
export function setupMoveUpEvents(
target: object,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index dca77250c..b2a5fddcd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -905,7 +905,6 @@ export class CurrentUserUtils {
return doc.clickFuncs as Doc;
}
-
/// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
/// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
/// whether to revert to "default" values, or to leave them as the user/system last set them.
diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts
new file mode 100644
index 000000000..7bffb0379
--- /dev/null
+++ b/src/client/util/RecordingApi.ts
@@ -0,0 +1,269 @@
+import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
+import { IReactionDisposer, observable, reaction } from 'mobx';
+import { NumCast } from '../../fields/Types';
+import { Doc } from '../../fields/Doc';
+import { VideoBox } from '../views/nodes/VideoBox';
+import { scaleDiverging } from 'd3-scale';
+import { Transform } from './Transform';
+
+type Movement = {
+ time: number;
+ panX: number;
+ panY: number;
+ scale: number;
+};
+
+type Presentation = {
+ movements: Array<Movement> | null;
+ meta: Object;
+};
+
+export class RecordingApi {
+ private static NULL_PRESENTATION: Presentation = {
+ movements: null,
+ meta: {},
+ };
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private isRecording: boolean;
+ private absoluteStart: number;
+
+ // create static instance and getter for global use
+ @observable static _instance: RecordingApi;
+ public static get Instance(): RecordingApi {
+ return RecordingApi._instance;
+ }
+ public constructor() {
+ // init the global instance
+ RecordingApi._instance = this;
+
+ // init the instance variables
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION;
+ this.isRecording = false;
+ this.absoluteStart = -1;
+
+ // used for tracking movements in the view frame
+ this.disposeFunc = null;
+ this.recordingFFView = null;
+
+ // for now, set playFFView
+ this.playFFView = null;
+ this.timers = null;
+ }
+
+ // little helper :)
+ private get isInitPresenation(): boolean {
+ return this.currentPresentation.movements === null;
+ }
+
+ public start = (meta?: Object): Error | undefined => {
+ // check if already init a presentation
+ if (!this.isInitPresenation) {
+ console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.');
+ return new Error('[recordingApi.ts] start()');
+ }
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'recording';
+
+ // (1a) get start date for presenation
+ const startDate = new Date();
+ // (1b) set start timestamp to absolute timestamp
+ this.absoluteStart = startDate.getTime();
+
+ // (2) assign meta content if it exists
+ this.currentPresentation.meta = meta || {};
+ // (3) assign start date to currentPresenation
+ this.currentPresentation.movements = [];
+ // (4) set isRecording true to allow trackMovements
+ this.isRecording = true;
+ };
+
+ public clear = (): Error | Presentation => {
+ // TODO: maybe archive the data?
+ if (this.isRecording) {
+ console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first');
+ return new Error('[recordingApi.ts] clear()');
+ }
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'none';
+ // set the previus recording view to the play view
+ this.playFFView = this.recordingFFView;
+
+ const presCopy = { ...this.currentPresentation };
+
+ // clear presenation data
+ this.currentPresentation = RecordingApi.NULL_PRESENTATION;
+ // clear isRecording
+ this.isRecording = false;
+ // clear absoluteStart
+ this.absoluteStart = -1;
+ // clear the disposeFunc
+ this.removeRecordingFFView();
+
+ return presCopy;
+ };
+
+ public pause = (): Error | undefined => {
+ if (this.isInitPresenation) {
+ console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first');
+ return new Error('[recordingApi.ts] pause(): no presentation');
+ }
+ // don't allow track movments
+ this.isRecording = false;
+
+ // set adjust absoluteStart to add the time difference
+ const timestamp = new Date().getTime();
+ this.absoluteStart = timestamp - this.absoluteStart;
+ };
+
+ public resume = () => {
+ this.isRecording = true;
+ // set absoluteStart to the difference in time
+ this.absoluteStart = new Date().getTime() - this.absoluteStart;
+ };
+
+ private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => {
+ // ensure we are recording
+ if (!this.isRecording) {
+ return new Error('[recordingApi.ts] trackMovements()');
+ }
+ // check to see if the presetation is init
+ if (this.isInitPresenation) {
+ return new Error('[recordingApi.ts] trackMovements(): no presentation');
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart;
+ // make new movement object
+ const movement: Movement = { time, panX, panY, scale };
+
+ // add that movement to the current presentation data's movement array
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement);
+ };
+
+ // instance variable for the FFView
+ private disposeFunc: IReactionDisposer | null;
+ private recordingFFView: CollectionFreeFormView | null;
+
+ // set the FFView that will be used in a reaction to track the movements
+ public setRecordingFFView = (view: CollectionFreeFormView): void => {
+ // set the view to the current view
+ if (view === this.recordingFFView || view == null) return;
+
+ // this.recordingFFView = view;
+ // set the reaction to track the movements
+ this.disposeFunc = reaction(
+ () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
+ res => res.x !== -1 && res.y !== -1 && this.isRecording && this.trackMovements(res.x, res.y, res.scale)
+ );
+
+ // for now, set the most recent recordingFFView to the playFFView
+ this.recordingFFView = view;
+ };
+
+ // call on dispose function to stop tracking movements
+ public removeRecordingFFView = (): void => {
+ this.disposeFunc?.();
+ this.disposeFunc = null;
+ };
+
+ // TODO: extract this into different class with pause and resume recording
+ // TODO: store the FFview with the movements
+ private playFFView: CollectionFreeFormView | null;
+ private timers: NodeJS.Timeout[] | null;
+
+ public setPlayFFView = (view: CollectionFreeFormView): void => {
+ this.playFFView = view;
+ };
+
+ // pausing movements will dispose all timers that are planned to replay the movements
+ // play movemvents will recreate them when the user resumes the presentation
+ public pauseMovements = (): undefined | Error => {
+ if (this.playFFView === null) {
+ return new Error('[recordingApi.ts] pauseMovements() failed: no view');
+ }
+
+ if (!this._isPlaying) {
+ //return new Error('[recordingApi.ts] pauseMovements() failed: not playing')
+ return;
+ }
+ this._isPlaying = false;
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer));
+
+ // this.videoBox = null;
+ };
+
+ private videoBox: VideoBox | null = null;
+
+ // by calling pause on the VideoBox, the pauseMovements will be called
+ public pauseVideoAndMovements = (): boolean => {
+ this.videoBox?.Pause();
+
+ this.pauseMovements();
+ return this.videoBox == null;
+ };
+
+ public _isPlaying = false;
+
+ public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => {
+ if (presentation.movements === null || this.playFFView === null) {
+ return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view');
+ }
+ if (this._isPlaying) return;
+
+ this._isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // TODO: consider this bug at the end of the clip on seek
+ this.videoBox = videoBox || null;
+
+ // only get the movements that are remaining in the video time left
+ const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000);
+
+ // helper to replay a movement
+ const document = this.playFFView;
+ let preScale = -1;
+ const zoomAndPan = (movement: Movement) => {
+ const { panX, panY, scale } = movement;
+ scale !== -1 && preScale !== scale && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
+ document.Document._panX = panX;
+ document.Document._panY = panY;
+
+ preScale = scale;
+ };
+
+ // set the first frame to be at the start of the pres
+ zoomAndPan(filteredMovements[0]);
+
+ // make timers that will execute each movement at the correct replay time
+ this.timers = filteredMovements.map(movement => {
+ const timeDiff = movement.time - timeViewed * 1000;
+ return setTimeout(() => {
+ // replay the movement
+ zoomAndPan(movement);
+ // if last movement, presentation is done -> set the instance var
+ if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false;
+ }, timeDiff);
+ });
+ };
+
+ // Unfinished code for tracing multiple free form views
+ // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()
+
+ // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
+ // pres.set(ffView,
+ // reaction(() => ({ x: ffView.panX, y: ffView.panY }),
+ // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
+ // )
+ // }
+
+ // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
+ // const disposer = pres.get(ffView);
+ // disposer?.();
+ // pres.delete(ffView)
+ // }
+}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 135d6d001..b490278c3 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,6 +1,8 @@
@import 'global/globalCssVariables';
$linkGap: 3px;
+$headerHeight: 20px;
+$resizeHandler: 8px;
.documentDecorations-Dark,
.documentDecorations {
@@ -16,8 +18,8 @@ $linkGap: 3px;
top: 0;
left: 0;
display: grid;
- grid-template-rows: 20px 8px 1fr 8px;
- grid-template-columns: 8px 1fr 8px;
+ grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler;
+ grid-template-columns: $resizeHandler 1fr $resizeHandler;
pointer-events: none;
.documentDecorations-centerCont {
@@ -82,29 +84,46 @@ $linkGap: 3px;
grid-column: 3;
}
- .documentDecorations-rotation,
+ // Rotation handler
+ .documentDecorations-rotation {
+ border-radius: 100%;
+ height: 30;
+ width: 30;
+ right: -10;
+ top: 50%;
+ z-index: 1000000;
+ position: absolute;
+ pointer-events: all;
+ cursor: pointer;
+ background: white;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 30px;
+ }
+
+ // Border radius handler
.documentDecorations-borderRadius {
- grid-column: 3;
- grid-row: 4;
+ position: absolute;
border-radius: 100%;
- background: black;
- height: 8;
- right: -12;
- top: 12;
- position: relative;
+ left: 3px;
+ top: 23px;
+ background: $medium-gray;
+ height: 10;
+ width: 10;
pointer-events: all;
cursor: nwse-resize;
-
- .borderRadiusTooltip {
- width: 10px;
- height: 10px;
- position: absolute;
- }
}
- .documentDecorations-rotation {
- background: transparent;
- right: -15;
+ .documentDecorations-rotationPath {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ transform: translate(0px, -25%);
+ padding-bottom: 100%;
+ border-radius: 100%;
+ border: solid $medium-gray 10px;
}
.documentDecorations-topLeftResizer,
@@ -146,30 +165,6 @@ $linkGap: 3px;
grid-column: 3;
}
- .documentDecorations-rotation,
- .documentDecorations-borderRadius {
- grid-column: 3;
- grid-row: 4;
- border-radius: 100%;
- background: black;
- height: 8;
- right: -12;
- top: 12;
- position: relative;
- pointer-events: all;
- cursor: nwse-resize;
-
- .borderRadiusTooltip {
- width: 10px;
- height: 10px;
- position: absolute;
- }
- }
- .documentDecorations-rotation {
- background: transparent;
- right: -15;
- }
-
.documentDecorations-topLeftResizer,
.documentDecorations-bottomRightResizer {
cursor: nwse-resize;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9dc02e3f4..9544c588b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -10,7 +10,7 @@ import { InkField } from '../../fields/InkField';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
import { Cast, FieldValue, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, numberValue, numbersAlmostEqual } from '../../Utils';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
@@ -30,6 +30,7 @@ import { DocumentView } from './nodes/DocumentView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { ImageBox } from './nodes/ImageBox';
import React = require('react');
+import { Colors } from './global/globalEnums';
@observer
export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -49,20 +50,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@observable private _accumulatedTitle = '';
@observable private _titleControlString: string = '#title';
- @observable private _edtingTitle = false;
+ @observable private _editingTitle = false;
@observable private _hidden = false;
@observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
@observable public Interacting = false;
@observable public pushIcon: IconProp = 'arrow-alt-circle-up';
@observable public pullIcon: IconProp = 'arrow-alt-circle-down';
@observable public pullColor: string = 'white';
+ @observable private _isRotating: boolean = false;
+ @observable private _isRounding: boolean = false;
+ @observable private _isResizing: boolean = false;
constructor(props: any) {
super(props);
DocumentDecorations.Instance = this;
reaction(
() => SelectionManager.Views().slice(),
- action(docs => (this._edtingTitle = false))
+ action(docs => (this._editingTitle = false))
);
}
@@ -89,7 +93,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@action
titleBlur = () => {
- this._edtingTitle = false;
+ this._editingTitle = false;
if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith('#')) {
@@ -143,8 +147,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
e => this.onBackgroundMove(true, e),
e => {},
action(e => {
- !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
- this._edtingTitle = true;
+ !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
+ this._editingTitle = true;
this._keyinput.current && setTimeout(this._keyinput.current.focus);
})
);
@@ -262,30 +266,50 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
};
onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false);
-
+
+ /**
+ * Handles setting up events when user clicks on the border radius editor
+ * @param e PointerEvent
+ */
+ @action
onRadiusDown = (e: React.PointerEvent): void => {
+ this._isRounding = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
+ // Call util move event function
setupMoveUpEvents(
- this,
- e,
+ this, // target
+ e, // pointerEvent
(e, down) => {
- const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
+ const x = this.Bounds.x + 3;
+ const y = this.Bounds.y + 3;
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
+ if (e.clientX < x && e.clientY < y) dist = 0
SelectionManager.Views()
.map(dv => dv.props.Document)
- .map(doc => (doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)))
- .map(d => (d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`));
+ .map(doc => {
+ const docMax = Math.min(NumCast(doc.width)/2, NumCast(doc.height)/2);
+ const ratio = dist/maxDist;
+ const radius = Math.min(1, ratio) * docMax;
+ doc.borderRounding = `${radius}px`;
+ }
+ );
return false;
- },
- e => this._resizeUndo?.end(),
- e => {}
+ }, // moveEvent
+ action(e => {
+ this._isRounding = false;
+ this._resizeUndo?.end()
+ }), // upEvent
+ e => {} // clickEvent
);
};
@action
onRotateDown = (e: React.PointerEvent): void => {
+ this._isRotating = true;
const rotateUndo = UndoManager.StartBatch('rotatedown');
const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke);
- const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 };
+ const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 };
setupMoveUpEvents(
this,
e,
@@ -296,14 +320,33 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
if (selectedInk.length) {
angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint);
} else {
- SelectionManager.Views().forEach(dv => (dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - (angle * 180) / Math.PI));
+ SelectionManager.Views().forEach(dv => {
+ const oldRotation = NumCast(dv.rootDoc._jitterRotation);
+ // Rotation between -360 and 360
+ let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360;
+
+ const diff = Math.round(newRotation / 45) - newRotation / 45
+ if (diff < .05) {
+ console.log('show lines');
+ }
+ dv.rootDoc._jitterRotation = newRotation;
+ });
}
return false;
- },
- () => {
+ }, // moveEvent
+ action(() => {
+ SelectionManager.Views().forEach(dv => {
+ const oldRotation = NumCast(dv.rootDoc._jitterRotation);
+ const diff = Math.round(oldRotation / 45) - oldRotation / 45
+ if (diff < .05) {
+ let newRotation = Math.round(oldRotation / 45) * 45;
+ dv.rootDoc._jitterRotation = newRotation;
+ }
+ })
+ this._isRotating = false;
rotateUndo?.end();
UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']);
- },
+ }), // upEvent
emptyFunction
);
};
@@ -554,19 +597,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
return null;
}
// hide the decorations if the parent chooses to hide it or if the document itself hides it
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
- const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
- const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar;
+ const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating;
+ const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
+ const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding ||
+ this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
const hideOpenButton =
- seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton);
+ seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating;
const hideDeleteButton =
+ this._isRounding ||
+ this._isRotating ||
seldoc.props.hideDeleteButton ||
seldoc.rootDoc.hideDeleteButton ||
SelectionManager.Views().some(docView => {
const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin);
});
+
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
<div
@@ -589,7 +636,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? null : this._edtingTitle ? (
+ const titleArea = hideTitle ? null : this._editingTitle ? (
<input
ref={this._keyinput}
className={`documentDecorations-title${colorScheme}`}
@@ -615,11 +662,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
+ // Rotation constants: Only allow rotation on ink and images
const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox;
- const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
-
const rotation = NumCast(seldoc.rootDoc._jitterRotation);
+
+
+ const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
+ // Radius constants
+ const useRounding = seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof FormattedTextBox;
+ const borderRadius = numberValue(StrCast(seldoc.rootDoc.borderRounding));
+ const docMax = Math.min(NumCast(seldoc.rootDoc.width)/2, NumCast(seldoc.rootDoc.height)/2);
+ const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
+ const radiusHandle = (borderRadius / docMax) * maxDist;
+ const radiusHandleLocation = Math.min(radiusHandle, maxDist);
return (
<div className={`documentDecorations${colorScheme}`}>
<div
@@ -641,18 +697,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}}
/>
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : (
- <>
+ <div>
<div
className="documentDecorations-container"
key="container"
style={{
- transform: ` translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
- transformOrigin: `8px 26px`,
+ transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+ transformOrigin: `50% calc(50% + 10px)`,
width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px',
}}>
{hideDeleteButton ? <div /> : topBtn('close', this.hasIcons ? 'times' : 'window-maximize', undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), 'Close')}
- {titleArea}
+ {hideTitle ? null : titleArea}
{hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : (
<>
@@ -666,16 +722,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
<div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
<div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
- {useRotation && (
- <div key="rot" className={`documentDecorations-rotation`} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
- {'⟲'}
- </div>
- )}
- <div key="br" className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} />
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
</>
)}
+ {useRotation && (
+ <div key="rot" className={`documentDecorations-rotation`} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+ {'⟲'}
+ </div>
+ )}
+
+ {useRounding && (
+ <div key="rad" style={{
+ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`,
+ left:`${radiusHandleLocation + 3}`,
+ top:`${radiusHandleLocation + 23}`
+ }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()}
+ />
+ )}
+
{hideDocumentButtonBar ? null : (
<div
className="link-button-container"
@@ -687,7 +752,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
</div>
)}
</div>
- </>
+ </div>
)}
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 13cccb7dd..8444c9119 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,64 +1,62 @@
-import { Bezier } from "bezier-js";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { computedFn } from "mobx-utils";
-import { DateField } from "../../../../fields/DateField";
-import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField";
-import { List } from "../../../../fields/List";
-import { ObjectField } from "../../../../fields/ObjectField";
-import { RichTextField } from "../../../../fields/RichTextField";
-import { listSpec } from "../../../../fields/Schema";
-import { ScriptField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
-import { ImageField } from "../../../../fields/URLField";
-import { TraceMobx } from "../../../../fields/util";
-import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager, dropActionType } from "../../../util/DragManager";
-import { HistoryUtil } from "../../../util/History";
-import { InteractionUtils } from "../../../util/InteractionUtils";
-import { LinkManager } from "../../../util/LinkManager";
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { ColorScheme } from "../../../util/SettingsManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss";
-import { Timeline } from "../../animationtimeline/Timeline";
-import { ContextMenu } from "../../ContextMenu";
-import { GestureOverlay } from "../../GestureOverlay";
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "../../InkingStroke";
-import { LightboxView } from "../../LightboxView";
-import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
-import { FieldViewProps } from "../../nodes/FieldView";
-import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { PresBox } from "../../nodes/trails/PresBox";
-import { VideoBox } from "../../nodes/VideoBox";
-import { CreateImage } from "../../nodes/WebBoxRenderer";
-import { StyleProp } from "../../StyleProvider";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { CollectionSubView } from "../CollectionSubView";
-import { TreeViewType } from "../CollectionTreeView";
-import { CollectionViewType } from "../CollectionView";
-import { TabDocView } from "../TabDocView";
-import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
-import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
-import "./CollectionFreeFormView.scss";
-import { MarqueeView } from "./MarqueeView";
-import React = require("react");
-import e = require("connect-flash");
-import { ReplayMovements } from "../../../util/ReplayMovements";
+import { Bezier } from 'bezier-js';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
+import { DateField } from '../../../../fields/DateField';
+import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
+import { List } from '../../../../fields/List';
+import { ObjectField } from '../../../../fields/ObjectField';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { listSpec } from '../../../../fields/Schema';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { TraceMobx } from '../../../../fields/util';
+import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
+import { HistoryUtil } from '../../../util/History';
+import { InteractionUtils } from '../../../util/InteractionUtils';
+import { RecordingApi } from '../../../util/RecordingApi';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { ColorScheme } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
+import { Timeline } from '../../animationtimeline/Timeline';
+import { ContextMenu } from '../../ContextMenu';
+import { GestureOverlay } from '../../GestureOverlay';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { LightboxView } from '../../LightboxView';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
+import { FieldViewProps } from '../../nodes/FieldView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { PresBox } from '../../nodes/trails/PresBox';
+import { VideoBox } from '../../nodes/VideoBox';
+import { CreateImage } from '../../nodes/WebBoxRenderer';
+import { StyleProp } from '../../StyleProvider';
+import { CollectionDockingView } from '../CollectionDockingView';
+import { CollectionSubView } from '../CollectionSubView';
+import { TreeViewType } from '../CollectionTreeView';
+import { CollectionViewType } from '../CollectionView';
+import { TabDocView } from '../TabDocView';
+import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
+import './CollectionFreeFormView.scss';
+import { MarqueeView } from './MarqueeView';
+import React = require('react');
+import e = require('connect-flash');
+import { ReplayMovements } from '../../../util/ReplayMovements';
export type collectionFreeformViewProps = {
@@ -69,13 +67,15 @@ export type collectionFreeformViewProps = {
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
- dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
+ dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@observer
export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
- public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
+ public get displayName() {
+ return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')';
+ } // this makes mobx trace() statements more descriptive
private _lastNudge: any;
private _lastX: number = 0;
@@ -90,27 +90,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _disposers: { [name: string]: IReactionDisposer } = {};
private _renderCutoffData = observable.map<string, boolean>();
private _layoutPoolData = observable.map<string, PoolData>();
- private _layoutSizeData = observable.map<string, { width?: number, height?: number }>();
+ private _layoutSizeData = observable.map<string, { width?: number; height?: number }>();
private _cachedPool: Map<string, PoolData> = new Map();
private _lastTap = 0;
private _batch: UndoManager.Batch | undefined = undefined;
// private isWritingMode: boolean = true;
- // private writingModeDocs: Doc[] = [];
+ // private writingModeDocs: Doc[] = [];
- private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
- private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
- private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
+ private get isAnnotationOverlay() {
+ return this.props.isAnnotationOverlay;
+ }
+ private get scaleFieldKey() {
+ return this.props.scaleField || '_viewScale';
+ }
+ private get borderWidth() {
+ return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
+ }
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
- @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
+ @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _hLines: number[] | undefined;
@observable _vLines: number[] | undefined;
@observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
@observable _pullCoords: number[] = [0, 0];
- @observable _pullDirection: string = "";
+ @observable _pullDirection: string = '';
@observable _showAnimTimeline = false;
- @observable _clusterSets: (Doc[])[] = [];
+ @observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeRef = React.createRef<HTMLDivElement>();
@@ -118,43 +124,57 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _keyframeEditing = false;
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
- @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+ @computed get views() {
+ return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+ }
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 :
- Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
- this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x))
+ scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
- @computed get fitContentsToBox() { return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; }
- @computed get contentBounds() {
- const cb = Cast(this.rootDoc.contentBounds, listSpec("number"));
- return cb ? {x:cb[0], y:cb[1], r:cb[2], b: cb[3]} :
- this.props.contentBounds?.() ?? aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10));
+ @computed get fitContentsToBox() {
+ return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay;
+ }
+ @computed get contentBounds() {
+ const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
+ return cb
+ ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
+ : this.props.contentBounds?.() ??
+ aggregateBounds(
+ this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!),
+ NumCast(this.layoutDoc._xPadding, 10),
+ NumCast(this.layoutDoc._yPadding, 10)
+ );
+ }
+ @computed get nativeWidth() {
+ return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ }
+ @computed get nativeHeight() {
+ return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
}
- @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); }
- @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); }
@computed get cachedCenteringShiftX(): number {
const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
- return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+ return Transform.Identity()
+ .scale(1 / this.zoomScaling())
+ .translate(this.panX(), this.panY());
}
@computed get cachedGetContainerTransform(): Transform {
return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
}
@computed get cachedGetTransform(): Transform {
- return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
+ return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
- changeKeyFrame = (back=false) => {
- const currentFrame = Cast(this.Document._currentFrame, "number", null);
+ changeKeyFrame = (back = false) => {
+ const currentFrame = Cast(this.Document._currentFrame, 'number', null);
if (currentFrame === undefined) {
this.Document._currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -167,8 +187,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
}
- }
- @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set;
+ };
+ @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
getKeyFrameEditing = () => this._keyframeEditing;
onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
@@ -179,32 +199,35 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.layoutDoc._panX = vals.bounds.cx;
this.layoutDoc._panY = vals.bounds.cy;
this.layoutDoc._viewScale = vals.scale;
- }
- freeformData = (force?: boolean) => !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined;
- reverseNativeScaling = () => this.fitContentsToBox ? true : false;
+ };
+ freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
+ reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
// panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
- // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
+ // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
- contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ contentTransform = () =>
+ !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
+ ? ''
+ : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
getTransform = () => this.cachedGetTransform.copy();
getLocalTransform = () => this.cachedGetLocalTransform.copy();
getContainerTransform = () => this.cachedGetContainerTransform.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
isAnyChildContentActive = () => this.props.isAnyChildContentActive();
addLiveTextBox = (newBox: Doc) => {
- FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox);
- }
+ };
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
- }
+ };
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if (retVal = (this.props.addDocument?.(newBox) || false)) {
+ if ((retVal = this.props.addDocument?.(newBox) || false)) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
@@ -213,14 +236,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// bcz: deal with clusters
}
if (retVal) {
- const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+ const newBoxes = newBox instanceof Doc ? [newBox] : newBox;
for (const newBox of newBoxes) {
if (newBox.activeFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field]);
CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]);
CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]);
delete newBox.activeFrame;
- CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== "opacity" && (newBox[field] = vals[i]));
+ CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i]));
}
}
if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
@@ -228,13 +251,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
return retVal;
- }
+ };
isCurrent(doc: Doc) {
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
const curTime = NumCast(this.Document._currentTimecode, -1);
- return dispTime === -1 || ((curTime - dispTime) >= -1e-4 && curTime <= endTime);
+ return dispTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
}
@action
@@ -246,8 +269,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
- const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1);
+ const zsorted = this.childLayoutPairs
+ .map(pair => pair.layout)
+ .slice()
+ .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)];
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
@@ -267,8 +293,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
d._lastModified = new DateField();
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
- layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
- (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged(): d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+ layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
+ (d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -292,13 +318,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@undoBatch
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
- if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) { // dragged document is a child of this collection
- if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) { // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
- const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
+ // dragged document is a child of this collection
+ if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) {
+ // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, "annotated by:annotation of", ""); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
}
- e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
+ e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
return true;
}
return false;
@@ -310,25 +338,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
return false;
- }
+ };
onExternalDrop = (e: React.DragEvent) => {
return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
- }
+ };
pickCluster(probe: number[]) {
- return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
- const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
- if (grouping !== -1) {
- const layoutDoc = Doc.Layout(cd);
- const cx = NumCast(cd.x) - this._clusterDistance;
- const cy = NumCast(cd.y) - this._clusterDistance;
- const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
- return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
- }
- return cluster;
- }, -1);
+ return this.childLayoutPairs
+ .map(pair => pair.layout)
+ .reduce((cluster, cd) => {
+ const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
+ if (grouping !== -1) {
+ const layoutDoc = Doc.Layout(cd);
+ const cx = NumCast(cd.x) - this._clusterDistance;
+ const cy = NumCast(cd.y) - this._clusterDistance;
+ const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
+ return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
+ }
+ return cluster;
+ }, -1);
}
tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) {
@@ -338,10 +368,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? "alias" : undefined);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
de.moveDocument = this.props.moveDocument;
de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
- DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
+ DragManager.StartDocumentDrag(
+ clusterDocs.map(v => v.ContentDiv!),
+ de,
+ ptsParent.clientX,
+ ptsParent.clientY,
+ { hideSource: !de.dropAction }
+ );
return true;
}
}
@@ -364,12 +400,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
const preferredInd = NumCast(docFirst.cluster);
- docs.map(doc => doc.cluster = -1);
- docs.map(doc => this._clusterSets.map((set, i) => set.map(member => {
- if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- docFirst.cluster = i;
- }
- })));
+ docs.map(doc => (doc.cluster = -1));
+ docs.map(doc =>
+ this._clusterSets.map((set, i) =>
+ set.map(member => {
+ if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.cluster = i;
+ }
+ })
+ )
+ );
if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
docFirst.cluster = preferredInd;
}
@@ -385,7 +425,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
} else if (this._clusterSets.length) {
for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
+ docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc));
}
childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
}
@@ -399,11 +439,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
const preferredInd = NumCast(doc.cluster);
doc.cluster = -1;
- this._clusterSets.forEach((set, i) => set.forEach(member => {
- if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- doc.cluster = i;
- }
- }));
+ this._clusterSets.forEach((set, i) =>
+ set.forEach(member => {
+ if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ doc.cluster = i;
+ }
+ })
+ );
if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
doc.cluster = preferredInd;
}
@@ -423,7 +465,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
- let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
+ let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
if (property !== StyleProp.BackgroundColor) return styleProp;
const cluster = NumCast(doc?.cluster);
if (this.Document._useClusters) {
@@ -431,15 +473,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
setTimeout(() => doc && this.updateCluster(doc));
} else {
// choose a cluster color from a palette
- const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"];
+ const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
styleProp = colors[cluster % colors.length];
const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
- set?.map(s => styleProp = StrCast(s.backgroundColor));
+ set?.map(s => (styleProp = StrCast(s.backgroundColor)));
}
} //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
return styleProp;
- }
+ };
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
@@ -449,30 +491,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
return false;
- }
+ };
@action
onPenUp = (e: PointerEvent): void => {
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
- document.removeEventListener("pointerup", this.onPenUp);
- const currentCol = DocListCast(this.rootDoc.currentInkDoc)
+ document.removeEventListener('pointerup', this.onPenUp);
+ const currentCol = DocListCast(this.rootDoc.currentInkDoc);
const rootDocList = DocListCast(this.rootDoc.data);
currentCol.push(rootDocList[rootDocList.length - 1]);
console.log(currentCol);
this._batch?.end();
}
- }
+ };
@action
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
- if (!e.nativeEvent.cancelBubble &&
+ if (
+ !e.nativeEvent.cancelBubble &&
!this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
- !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
+ ) {
switch (CurrentUserUtils.ActiveTool) {
case InkTool.Highlighter:
break;
@@ -482,23 +526,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case InkTool.Pen:
break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
- document.addEventListener("pointermove", this.onEraserMove);
- document.addEventListener("pointerup", this.onEraserUp);
- this._batch = UndoManager.StartBatch("collectionErase");
+ document.addEventListener('pointermove', this.onEraserMove);
+ document.addEventListener('pointerup', this.onEraserUp);
+ this._batch = UndoManager.StartBatch('collectionErase');
e.stopPropagation();
e.preventDefault();
break;
case InkTool.None:
if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
+ document.addEventListener('pointermove', this.onPointerMove);
+ document.addEventListener('pointerup', this.onPointerUp);
}
break;
}
}
}
- }
+ };
@action
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
@@ -517,14 +561,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lastY = pt.pageY;
e.preventDefault();
e.stopPropagation();
- }
- else {
+ } else {
e.preventDefault();
}
}
}
}
- }
+ };
public unprocessedDocs: Doc[] = [];
public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
@@ -534,8 +577,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.Stroke:
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
- { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
+ const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points, {
+ title: 'ink stroke',
+ x: B.x - ActiveInkWidth() / 2,
+ y: B.y - ActiveInkWidth() / 2,
+ _width: B.width + ActiveInkWidth(),
+ _height: B.height + ActiveInkWidth(),
+ });
if (CurrentUserUtils.ActiveTool === InkTool.Write) {
this.unprocessedDocs.push(inkDoc);
CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
@@ -561,7 +609,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
return pass;
});
- this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
+ this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
sel.forEach(d => this.props.removeDocument?.(d));
e.stopPropagation();
break;
@@ -573,17 +621,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.EndBracket:
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
- const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
- const sets = setDocs.map((sd) => {
+ const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color);
+ const sets = setDocs.map(sd => {
return Cast(sd.text, RichTextField)?.Text as string;
});
if (sets.length && sets[0]) {
this._wordPalette.clear();
const colors = setDocs.map(sd => FieldValue(sd.color) as string);
- sets.forEach((st: string, i: number) => st.split(",").forEach(word => this._wordPalette.set(word, colors[i])));
+ sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i])));
}
const inks = this.getActiveDocuments().filter(doc => {
- if (doc.type === "ink") {
+ if (doc.type === 'ink') {
const l = NumCast(doc.x);
const r = l + doc[WidthSym]();
const t = NumCast(doc.y);
@@ -599,15 +647,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const d = Cast(i.data, InkField);
const x = NumCast(i.x);
const y = NumCast(i.y);
- const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
- const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+ const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+ const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
if (d) {
strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
}
});
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
- const wordResults = results.filter((r: any) => r.category === "inkWord");
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {
+ const wordResults = results.filter((r: any) => r.category === 'inkWord');
for (const word of wordResults) {
const indices: number[] = word.strokeIds;
indices.forEach(i => {
@@ -619,8 +667,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
inks[i].alternativeColors = new List<string>(uniqueColors);
if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
- }
- else if (word.alternates) {
+ } else if (word.alternates) {
for (const alt of word.alternates) {
if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
@@ -641,27 +688,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
}
}
- }
+ };
@action
onEraserUp = (e: PointerEvent): void => {
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
- document.removeEventListener("pointermove", this.onEraserMove);
- document.removeEventListener("pointerup", this.onEraserUp);
+ document.removeEventListener('pointermove', this.onEraserMove);
+ document.removeEventListener('pointerup', this.onEraserUp);
this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
this._deleteList = [];
this._batch?.end();
}
- }
+ };
@action
onPointerUp = (e: PointerEvent): void => {
if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
}
- }
+ };
onClick = (e: React.MouseEvent) => {
if (this.onBrowseClickHandler()) {
@@ -670,10 +717,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
e.stopPropagation();
e.preventDefault();
- }
- else if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+ } else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
if (e.shiftKey) {
- if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click
+ if (Date.now() - this._lastTap < 300) {
+ // reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
}
e.stopPropagation();
@@ -681,15 +728,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
this._lastTap = Date.now();
}
- }
+ };
@action
- pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
+ pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
- }
+ };
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
@@ -703,14 +750,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || "1");
- SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || "black");
+ SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1');
+ SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
- !e.shiftKey && this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke,
- segment.reduce((data, curve) => [...data, ...curve.points
- .map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })
- ], [] as PointData[])));
+ !e.shiftKey &&
+ this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
+ GestureOverlay.Instance.dispatchGesture(
+ GestureUtils.Gestures.Stroke,
+ segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
+ )
+ );
// Lower ink opacity to give the user a visual indicator of deletion.
intersect.inkView.layoutDoc.opacity = 0.5;
}
@@ -720,7 +769,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
- }
+ };
@action
onPointerMove = (e: PointerEvent): void => {
@@ -730,45 +779,54 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this.props.isContentActive(true)) e.stopPropagation();
} else if (!e.cancelBubble) {
if (this.tryDragCluster(e, this._hitCluster)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
- else this.pan(e);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
+ } else this.pan(e);
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
- }
+ };
/**
* Determines if the Eraser tool has intersected with an ink stroke in the current freeform collection.
* @returns an array of tuples containing the intersected ink DocumentView and the t-value where it was intersected
*/
- getEraserIntersections = (lastPoint: { X: number, Y: number }, currPoint: { X: number, Y: number }) => {
+ getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) };
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
- .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap
- eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom &&
- eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top)
+ .filter(
+ ({ inkViewBounds }) =>
+ inkViewBounds && // bounding box of eraser segment and ink stroke overlap
+ eraserMin.X <= inkViewBounds.right &&
+ eraserMin.Y <= inkViewBounds.bottom &&
+ eraserMax.X >= inkViewBounds.left &&
+ eraserMax.Y >= inkViewBounds.top
+ )
.reduce((intersections, { inkStroke, inkView }) => {
const { inkData } = inkStroke.inkScaledData();
// Convert from screen space to ink space for the intersection.
const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
for (var i = 0; i < inkData.length - 3; i += 4) {
- const intersects = Array.from(new Set(InkField.Segment(inkData, i).intersects({ // compute all unique intersections
- p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
- p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }
- }) as (number | string)[])); // convert to more manageable union array type
+ const intersects = Array.from(
+ new Set(
+ InkField.Segment(inkData, i).intersects({
+ // compute all unique intersections
+ p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
+ p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y },
+ }) as (number | string)[]
+ )
+ ); // convert to more manageable union array type
// return tuples of the inkingStroke intersected, and the t value of the intersection
- intersections.push(...intersects.map(t => ({ inkView, t: (+t) + Math.floor(i / 4) })));// convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
+ intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve
}
return intersections;
- }, [] as { t: number, inkView: DocumentView }[]);
- }
+ }, [] as { t: number; inkView: DocumentView }[]);
+ };
/**
* Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the
@@ -805,11 +863,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
segment.push(inkSegment);
}
}
- if (excludeT < startSegmentT || excludeT > (inkData.length / 4)) {
+ if (excludeT < startSegmentT || excludeT > inkData.length / 4) {
segment.length && segments.push(segment);
}
return segments;
- }
+ };
/**
* Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all
@@ -837,13 +895,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
curve.intersects(otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
- const t = +val.toString().split("/")[0];
- if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
+ const t = +val.toString().split('/')[0];
+ if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
});
}
});
return tVals;
- }
+ };
handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
if (!e.cancelBubble) {
@@ -853,8 +911,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this.tryDragCluster(e, this._hitCluster)) {
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
return;
}
// TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
@@ -864,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// e.stopPropagation();
e.preventDefault();
}
- }
+ };
handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
// pinch zooming
@@ -887,7 +945,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
// calculate the raw delta value
- const rawDelta = (dir * (d1 + d2));
+ const rawDelta = dir * (d1 + d2);
// this floors and ceils the delta value to prevent jitteriness
const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
@@ -902,7 +960,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
// const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
- if (!this._pullDirection) { // if we are not bezel movement
+ if (!this._pullDirection) {
+ // if we are not bezel movement
this.pan({ clientX: centerX, clientY: centerY });
} else {
this._pullCoords = [centerX, centerY];
@@ -916,7 +975,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// e.stopPropagation();
e.preventDefault();
}
- }
+ };
@action
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
@@ -934,21 +993,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._lastY = centerY;
const screenBox = this._mainCont?.getBoundingClientRect();
-
// determine if we are using a bezel movement
if (screenBox) {
- if ((screenBox.right - centerX) < 100) {
+ if (screenBox.right - centerX < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "right";
+ this._pullDirection = 'right';
} else if (centerX - screenBox.left < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "left";
+ this._pullDirection = 'left';
} else if (screenBox.bottom - centerY < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "bottom";
+ this._pullDirection = 'bottom';
} else if (centerY - screenBox.top < 100) {
this._pullCoords = [centerX, centerY];
- this._pullDirection = "top";
+ this._pullDirection = 'top';
}
}
@@ -959,27 +1017,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
}
}
- }
+ };
cleanUpInteractions = () => {
switch (this._pullDirection) {
- case "left": case "right": case "top": case "bottom":
- CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection);
+ case 'left':
+ case 'right':
+ case 'top':
+ case 'bottom':
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection);
}
- this._pullDirection = "";
+ this._pullDirection = '';
this._pullCoords = [0, 0];
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointermove', this.onPointerMove);
+ document.removeEventListener('pointerup', this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
- }
+ };
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
if (this.Document._isGroup) return;
- let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
+ let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
const invTransform = this.getLocalTransform().inverse();
@@ -996,47 +1057,65 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
}
- }
+ };
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline)
+ return;
+ if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
+ // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
- }
- else if (this.props.isContentActive(true) && !this.Document._isGroup) {
+ } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
e.stopPropagation();
e.preventDefault();
!this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
}
- }
+ };
@action
setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
+ // set the current respective FFview to the tab being panned.
+ Doc.UserDoc()?.presentationMode === 'recording' && RecordingApi.Instance.setRecordingFFView(this);
+ // TODO: make this based off the specific recording FFView
+ Doc.UserDoc()?.presentationMode === 'none' && RecordingApi.Instance.setPlayFFView(this);
+
+ // TODO: zzz + michael to figure out this merge in case of strange behaviour
+ // if (Doc.UserDoc()?.presentationMode === 'watching') {
+ // RecordingApi.Instance.pauseVideoAndMovements();
+ // Doc.UserDoc().presentationMode = 'none';
+ // // RecordingApi.Instance.pauseMovements()
+ // }
// this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
if (!this.isAnnotationOverlay && clamp) {
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
- const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") }))
- .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! }));
+ const measuredDocs = docs
+ .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') }))
+ .filter(({ pos, size }) => pos && size)
+ .map(({ pos, size }) => ({ pos: pos!, size: size! }));
if (measuredDocs.length) {
- const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
- yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }
- })
- , {
+ const ranges = measuredDocs.reduce(
+ (
+ { xrange, yrange },
+ { pos, size } // computes range of content
+ ) => ({
+ xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
+ yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
+ }),
+ {
xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
- });
+ yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ }
+ );
const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2;
- else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
+ if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
+ else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
+ if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
+ else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.Document)) {
@@ -1045,10 +1124,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
const minPanX = NumCast(this.rootDoc._panXMin, 0);
const minPanY = NumCast(this.rootDoc._panYMin, 0);
- const newPanX = Math.min(
- minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
- const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) :
- minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight)), Math.max(minPanY, panY));
+ const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
+ const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY));
!this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
!this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
}
@@ -1056,16 +1133,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ||
- this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out...
- this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
- NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), nudgeTime, true);
+ if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
+ // bcz: this isn't ideal, but want to try it out...
+ this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
this._lastNudge && clearTimeout(this._lastNudge);
- this._lastNudge = setTimeout(action(() => this._viewTransition = 0), nudgeTime);
+ this._lastNudge = setTimeout(
+ action(() => (this._viewTransition = 0)),
+ nudgeTime
+ );
return true;
}
return false;
- }
+ };
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
@@ -1083,12 +1162,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doc.zIndex = zlast + 1;
}
- }
+ };
@action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
- setTimeout(action(() => this._viewTransition = 0), this._viewTransition = transitionTime); // set transition to be smooth, then reset
+ setTimeout(
+ action(() => (this._viewTransition = 0)),
+ (this._viewTransition = transitionTime)
+ ); // set transition to be smooth, then reset
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -1103,7 +1185,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// TODO This technically isn't correct if type !== "doc", as
// currently nothing is done, but we should probably push a new state
- if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) {
+ if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) {
const init = state.initializers![this.Document[Id]];
if (!init) {
state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) };
@@ -1123,9 +1205,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
const newState = HistoryUtil.getState();
- const cantTransform = /*this.props.isAnnotationOverlay ||*/ ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
- const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
- if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
+ const cantTransform = /*this.props.isAnnotationOverlay ||*/ (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
+ const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 0.75 : undefined);
+ if (!cantTransform) {
+ // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
HistoryUtil.pushState(newState);
}
@@ -1145,30 +1228,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
if (resetView) {
const restoreState = (!LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document) && savedState;
- if (typeof restoreState !== "boolean") {
+ if (typeof restoreState !== 'boolean') {
this.Document._panX = restoreState.panX;
this.Document._panY = restoreState.panY;
this.Document[this.scaleFieldKey] = restoreState.scale;
}
- runInAction(() => this._viewTransition = 0);
+ runInAction(() => (this._viewTransition = 0));
}
return resetView;
};
- const xf = !cantTransform ? Transform.Identity() :
- this.props.isAnnotationOverlay ?
- new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
- :
- new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
- NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
+ const xf = !cantTransform
+ ? Transform.Identity()
+ : this.props.isAnnotationOverlay
+ ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
+ : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
this.props.focus(cantTransform ? doc : this.rootDoc, {
...options,
docTransform: xf,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res =>
- setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
+ afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))),
});
}
- }
+ };
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
const layoutdoc = Doc.Layout(doc);
@@ -1178,11 +1259,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (scale) {
const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
- const newScale = Math.min(maxZoom, 1 / (this.contentScaling || 1) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ const newScale = Math.min(maxZoom, (1 / (this.contentScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
- scale: newScale
+ scale: newScale,
};
}
const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
@@ -1190,19 +1271,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const cx = NumCast(this.layoutDoc._panX);
const cy = NumCast(this.layoutDoc._panY);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
- if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
- (screen.bot - screen.top) < (bounds.bot - bounds.top)) {
+ if (screen.right - screen.left < bounds.right - bounds.left || screen.bot - screen.top < bounds.bot - bounds.top) {
return {
panX: (bounds.left + bounds.right) / 2,
panY: (bounds.top + bounds.bot) / 2,
- scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1
+ scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
};
}
return {
panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
};
- }
+ };
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
@@ -1210,190 +1290,215 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) {
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
- const below = !e.altKey && e.key !== "Tab";
+ const below = !e.altKey && e.key !== 'Tab';
const layoutKey = StrCast(docView.LayoutFieldKey);
const newDoc = Doc.MakeCopy(docView.rootDoc, true);
const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
- if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) {
+ if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = docView.rootDoc[layoutKey];
}
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
return this.addDocument?.(newDoc);
}
- }
+ };
pointerEvents = () => {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- const pointerEvents = this.props.isContentActive() === false ? "none" :
- this.props.childPointerEvents ? "all" :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.();
+ const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ? 'all' : this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.();
return pointerEvents;
- }
+ };
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
- return <CollectionFreeFormDocumentView key={childLayout[Id] + (entry.replica || "")}
- DataDoc={childData}
- Document={childLayout}
- renderDepth={this.props.renderDepth + 1}
- replica={entry.replica}
- suppressSetHeight={this.layoutEngine ? true : false}
- renderCutoffProvider={this.renderCutoffProvider}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.Document}
- CollectionFreeFormView={this}
- LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
- LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
- rootSelected={childData ? this.rootSelected : returnFalse}
- onClick={this.onChildClickHandler}
- onKey={this.onKeyDown}
- onDoubleClick={this.onChildDoubleClickHandler}
- onBrowseClick={this.onBrowseClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
- PanelWidth={childLayout[WidthSym]}
- PanelHeight={childLayout[HeightSym]}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={emptyFunction}
- focus={this.focusDocument}
- addDocTab={this.addDocTab}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- moveDocument={this.props.moveDocument}
- pinToPres={this.props.pinToPres}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- docViewPath={this.props.docViewPath}
- styleProvider={this.getClusterColor}
- dataProvider={this.childDataProvider}
- sizeProvider={this.childSizeProvider}
- dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
- bringToFront={this.bringToFront}
- showTitle={this.props.childShowTitle}
- dontScaleFilter={this.props.dontScaleFilter}
- dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
- pointerEvents={this.pointerEvents}
- jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
- //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
- />;
+ return (
+ <CollectionFreeFormDocumentView
+ key={childLayout[Id] + (entry.replica || '')}
+ DataDoc={childData}
+ Document={childLayout}
+ renderDepth={this.props.renderDepth + 1}
+ replica={entry.replica}
+ suppressSetHeight={this.layoutEngine ? true : false}
+ renderCutoffProvider={this.renderCutoffProvider}
+ ContainingCollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.props.Document}
+ CollectionFreeFormView={this}
+ LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
+ LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+ rootSelected={childData ? this.rootSelected : returnFalse}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
+ PanelWidth={childLayout[WidthSym]}
+ PanelHeight={childLayout[HeightSym]}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={emptyFunction}
+ focus={this.focusDocument}
+ addDocTab={this.addDocTab}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.props.moveDocument}
+ pinToPres={this.props.pinToPres}
+ whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ docViewPath={this.props.docViewPath}
+ styleProvider={this.getClusterColor}
+ dataProvider={this.childDataProvider}
+ sizeProvider={this.childSizeProvider}
+ dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
+ bringToFront={this.bringToFront}
+ showTitle={this.props.childShowTitle}
+ dontScaleFilter={this.props.dontScaleFilter}
+ dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
+ pointerEvents={this.pointerEvents}
+ jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+ //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+ />
+ );
}
addDocTab = action((doc: Doc, where: string) => {
- if (where === "inParent") {
- ((doc instanceof Doc) ? [doc] : doc).forEach(doc => {
+ if (where === 'inParent') {
+ (doc instanceof Doc ? [doc] : doc).forEach(doc => {
const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
doc.y = pt[1];
});
return this.props.addDocument?.(doc) || false;
}
- if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
return true;
}
return this.props.addDocTab(doc, where);
});
- getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData {
+ getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
const layoutDoc = Doc.Layout(params.pair.layout);
const { z, color, zIndex } = params.pair.layout;
- const { x, y, opacity } = this.Document._currentFrame === undefined ?
- { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
+ const { x, y, opacity } =
+ this.Document._currentFrame === undefined
+ ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) }
+ : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
return {
- x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null),
- width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
+ x: NumCast(x),
+ y: NumCast(y),
+ z: Cast(z, 'number'),
+ color: StrCast(color),
+ zIndex: Cast(zIndex, 'number'),
+ transition: StrCast(layoutDoc.dataTransition),
+ opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
+ width: Cast(layoutDoc._width, 'number'),
+ height: Cast(layoutDoc._height, 'number'),
+ pair: params.pair,
+ replica: '',
};
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
(this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
e.stopPropagation();
- }
+ };
viewDefsToJSX = (views: ViewDefBounds[]) => {
return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
- }
+ };
viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
const { x, y, z } = viewDef;
const color = StrCast(viewDef.color);
- const width = Cast(viewDef.width, "number");
- const height = Cast(viewDef.height, "number");
+ const width = Cast(viewDef.width, 'number');
+ const height = Cast(viewDef.height, 'number');
const transform = `translate(${x}px, ${y}px)`;
- if (viewDef.type === "text") {
- const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const fontSize = Cast(viewDef.fontSize, "string");
- return [text, x, y].some(val => val === undefined) ? undefined :
- {
- ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
- {text}
- </div>,
- bounds: viewDef
- };
- } else if (viewDef.type === "div") {
- return [x, y].some(val => val === undefined) ? undefined :
- {
- ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z + viewDef.payload} onClick={e => this.onViewDefDivClick(e, viewDef)}
- style={{ width, height, backgroundColor: color, transform }} />,
- bounds: viewDef
- };
+ if (viewDef.type === 'text') {
+ const text = Cast(viewDef.text, 'string'); // don't use NumCast, StrCast, etc since we want to test for undefined below
+ const fontSize = Cast(viewDef.fontSize, 'string');
+ return [text, x, y].some(val => val === undefined)
+ ? undefined
+ : {
+ ele: (
+ <div className="collectionFreeform-customText" key={(text || '') + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
+ {text}
+ </div>
+ ),
+ bounds: viewDef,
+ };
+ } else if (viewDef.type === 'div') {
+ return [x, y].some(val => val === undefined)
+ ? undefined
+ : {
+ ele: (
+ <div
+ className="collectionFreeform-customDiv"
+ title={viewDef.payload?.join(' ')}
+ key={'div' + x + y + z + viewDef.payload}
+ onClick={e => this.onViewDefDivClick(e, viewDef)}
+ style={{ width, height, backgroundColor: color, transform }}
+ />
+ ),
+ bounds: viewDef,
+ };
}
}
-
- renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) {
- return !this._renderCutoffData.get(doc[Id] + "");
- }.bind(this));
-
+ renderCutoffProvider = computedFn(
+ function renderCutoffProvider(this: any, doc: Doc) {
+ return !this._renderCutoffData.get(doc[Id] + '');
+ }.bind(this)
+ );
childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutPoolData.get(doc[Id] + (replica || ""));
- }
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
- return this._layoutPoolData.get(doc[Id] + (replica || ""));
- }.bind(this));
+ return this._layoutPoolData.get(doc[Id] + (replica || ''));
+ };
+ childDataProvider = computedFn(
+ function childDataProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutPoolData.get(doc[Id] + (replica || ''));
+ }.bind(this)
+ );
childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
- return this._layoutSizeData.get(doc[Id] + (replica || ""));
- }
- childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
- return this._layoutSizeData.get(doc[Id] + (replica || ""));
- }.bind(this));
-
- doEngineLayout(poolData: Map<string, PoolData>,
- engine: (
- poolData: Map<string, PoolData>,
- pivotDoc: Doc,
- childPairs: { layout: Doc, data?: Doc }[],
- panelDim: number[],
- viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]),
- engineProps: any) => ViewDefResult[]
+ return this._layoutSizeData.get(doc[Id] + (replica || ''));
+ };
+ childSizeProvider = computedFn(
+ function childSizeProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutSizeData.get(doc[Id] + (replica || ''));
+ }.bind(this)
+ );
+
+ doEngineLayout(
+ poolData: Map<string, PoolData>,
+ engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
) {
return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) =>
- poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document })));
return [] as ViewDefResult[];
}
- @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); }
+ @computed get layoutEngine() {
+ return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ }
@computed get doInternalLayoutComputation() {
TraceMobx();
const newPool = new Map<string, PoolData>();
switch (this.layoutEngine) {
- case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
- case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
- case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
- case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
+ case 'pass':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
+ case 'timeline':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
+ case 'pivot':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
+ case 'starburst':
+ return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
}
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
@@ -1417,15 +1522,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
- Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) =>
- elements.push({
- ele: this.getChildDocView(entry[1]),
- bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
- }));
+ Array.from(newPool.entries())
+ .filter(entry => this.isCurrent(entry[1].pair.layout))
+ .forEach((entry, i) =>
+ elements.push({
+ ele: this.getChildDocView(entry[1]),
+ bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
+ })
+ );
- if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
+ if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
+ // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
- else this.props.Document[this.scaleFieldKey] = Math.max(1,this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
+ else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
}
this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
@@ -1446,60 +1555,69 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
return 0;
- }
+ };
getAnchor = () => {
if (this.props.Document.annotationOn) {
return this.rootDoc;
}
- const anchor = Docs.Create.TextanchorDocument({ title: "ViewSpec - " + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
+ const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
const proto = Doc.GetProto(anchor);
- proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
+ proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
- if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]);
+ this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
}
return anchor;
- }
+ };
@action
componentDidMount() {
super.componentDidMount?.();
this.props.setContentView?.(this);
- setTimeout(action(() => {
- this._firstRender = false;
- this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
- (elements) => this._layoutElements = elements || [],
- { fireImmediately: true, name: "doLayout" });
-
- this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
-
- this._disposers.groupBounds = reaction(() => {
- if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
- const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
- return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
- }
- return undefined;
- },
- (cbounds) => {
- if (cbounds) {
- const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
- const pbounds = {
- x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
- r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
- };
- this.layoutDoc._width = (pbounds.r - pbounds.x);
- this.layoutDoc._height = (pbounds.b - pbounds.y);
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
- this.layoutDoc.x = pbounds.x;
- this.layoutDoc.y = pbounds.y;
- }
- }, { fireImmediately: true });
- }));
+ setTimeout(
+ action(() => {
+ this._firstRender = false;
+ this._disposers.layoutComputation = reaction(
+ () => this.doLayoutComputation,
+ elements => (this._layoutElements = elements || []),
+ { fireImmediately: true, name: 'doLayout' }
+ );
+
+ this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+
+ this._disposers.groupBounds = reaction(
+ () => {
+ if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
+ return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
+ }
+ return undefined;
+ },
+ cbounds => {
+ if (cbounds) {
+ const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
+ const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const pbounds = {
+ x: (cbounds.x - p[0]) * this.zoomScaling() + c[0],
+ y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
+ r: (cbounds.r - p[0]) * this.zoomScaling() + c[0],
+ b: (cbounds.b - p[1]) * this.zoomScaling() + c[1],
+ };
+ this.layoutDoc._width = pbounds.r - pbounds.x;
+ this.layoutDoc._height = pbounds.b - pbounds.y;
+ this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc.x = pbounds.x;
+ this.layoutDoc.y = pbounds.y;
+ }
+ },
+ { fireImmediately: true }
+ );
+ })
+ );
}
static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
@@ -1509,15 +1627,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
if (oldDiv instanceof HTMLCanvasElement) {
- if (oldDiv.className === "collectionFreeFormView-grid") {
+ if (oldDiv.className === 'collectionFreeFormView-grid') {
const newCan = newDiv as HTMLCanvasElement;
const parEle = newCan.parentElement as HTMLElement;
parEle.removeChild(newCan);
- parEle.appendChild(document.createElement('div'))
+ parEle.appendChild(document.createElement('div'));
} else {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image source
+ img.src = canvas.toDataURL(); //image source
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -1528,27 +1646,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
- updateIcon = () => CollectionFreeFormView.UpdateIcon(
- this.layoutDoc[Id] + "-icon" + (new Date()).getTime(),
- this.props.docViewPath().lastElement().ContentDiv!,
- this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](),
- this.props.PanelWidth(), this.props.PanelHeight(), 0, 1, false, "",
- (iconFile, nativeWidth, nativeHeight) => {
- this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc["icon-nativeWidth"] = nativeWidth;
- this.dataDoc["icon-nativeHeight"] = nativeHeight;
- });
+ updateIcon = () =>
+ CollectionFreeFormView.UpdateIcon(
+ this.layoutDoc[Id] + '-icon' + new Date().getTime(),
+ this.props.docViewPath().lastElement().ContentDiv!,
+ this.layoutDoc[WidthSym](),
+ this.layoutDoc[HeightSym](),
+ this.props.PanelWidth(),
+ this.props.PanelHeight(),
+ 0,
+ 1,
+ false,
+ '',
+ (iconFile, nativeWidth, nativeHeight) => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc['icon-nativeWidth'] = nativeWidth;
+ this.dataDoc['icon-nativeHeight'] = nativeHeight;
+ }
+ );
public static UpdateIcon(
- filename:string, docViewContent:HTMLElement,
- width: number, height: number,
- panelWidth:number, panelHeight: number,
- scrollTop:number,
+ filename: string,
+ docViewContent: HTMLElement,
+ width: number,
+ height: number,
+ panelWidth: number,
+ panelHeight: number,
+ scrollTop: number,
realNativeHeight: number,
noSuffix: boolean,
- replaceRootFilename: string| undefined,
- cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any)
- {
+ replaceRootFilename: string | undefined,
+ cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any
+ ) {
const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
newDiv.style.width = width.toString();
newDiv.style.height = height.toString();
@@ -1556,15 +1685,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const htmlString = new XMLSerializer().serializeToString(newDiv);
const nativeWidth = width;
const nativeHeight = height;
- return CreateImage(
- Utils.prepend(""),
- document.styleSheets,
- htmlString,
- nativeWidth,
- nativeWidth * panelHeight / panelWidth,
- scrollTop * panelHeight / realNativeHeight
- ).then
- (async (data_url: any) => {
+ return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
+ .then(async (data_url: any) => {
const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
cb(returnedFilename as string, nativeWidth, nativeHeight);
})
@@ -1575,13 +1697,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
+ this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}
@action
onCursorMove = (e: React.PointerEvent) => {
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
- }
+ };
@action
onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
@@ -1601,7 +1723,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
e.stopPropagation();
- }
+ };
@undoBatch
promoteCollection = () => {
@@ -1611,9 +1733,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this.props.addDocTab(childDocs as any as Doc, "inParent");
+ this.props.addDocTab(childDocs as any as Doc, 'inParent');
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
- }
+ };
@undoBatch
layoutDocsInGrid = () => {
@@ -1622,73 +1744,88 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
docs.forEach((doc, i) => {
- doc.x = NumCast(this.Document._panX) + (i % dim) * width - width * dim / 2;
- doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - height * dim / 2;
+ doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
+ doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
});
- }
+ };
@undoBatch
- toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight)
+ toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight);
onContextMenu = (e: React.MouseEvent) => {
if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return;
- const appearance = ContextMenu.Instance.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
- !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
- appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" });
+ const appearance = ContextMenu.Instance.findByDescription('Appearance...');
+ const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ appearanceItems.push({
+ description: 'Reset View',
+ event: () => {
+ this.props.Document._panX = this.props.Document._panY = 0;
+ this.props.Document[this.scaleFieldKey] = 1;
+ },
+ icon: 'compress-arrows-alt',
+ });
+ !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+ appearanceItems.push({
+ description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
+ event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
+ icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
+ });
+ appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' });
//appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
- this.props.ContainingCollectionView &&
- appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" });
+ this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
- this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" });
+ this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
// this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" });
- !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
- !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
-
- const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
- const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
- !Doc.noviceMode ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? "Hide" : "Show") + " Snap Lines", event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: "compress-arrows-alt" }) : null;
- !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null;
- !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
-
- const options = ContextMenu.Instance.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- !this.props.isAnnotationOverlay && !Doc.noviceMode &&
- optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" });
- this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
+ !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+
+ const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...');
+ const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : [];
+ !Doc.noviceMode
+ ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' })
+ : null;
+ !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null;
+ !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
+
+ const options = ContextMenu.Instance.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ !this.props.isAnnotationOverlay &&
+ !Doc.noviceMode &&
+ optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
+ this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
if (!Doc.noviceMode) {
- optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
- !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
- const mores = ContextMenu.Instance.findByDescription("More...");
- const moreItems = mores && "subitems" in mores ? mores.subitems : [];
+ !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+ const mores = ContextMenu.Instance.findByDescription('More...');
+ const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
if (!Doc.noviceMode) {
e.persist();
- moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
- moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
+ moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
+ moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
}
- !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
- }
+ !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
+ };
importDocument = (x: number, y: number) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.zip';
input.onchange = _e => {
- input.files && Doc.importDocument(input.files[0]).then(doc => {
- if (doc instanceof Doc) {
- const [xx, yy] = this.getTransform().transformPoint(x, y);
- doc.x = xx, doc.y = yy;
- this.props.addDocument?.(doc);}
- });
+ input.files &&
+ Doc.importDocument(input.files[0]).then(doc => {
+ if (doc instanceof Doc) {
+ const [xx, yy] = this.getTransform().transformPoint(x, y);
+ (doc.x = xx), (doc.y = yy);
+ this.props.addDocument?.(doc);
+ }
+ });
};
input.click();
- }
+ };
@undoBatch
@action
@@ -1697,13 +1834,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!math) {
const text = StrCast(this.props.Document.transcription);
- const lines = text.split("\n");
+ const lines = text.split('\n');
const height = 30 + 15 * lines.length;
this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
}
}
- }
+ };
@action
setupDragLines = (snapToDraggedDoc: boolean = false) => {
@@ -1711,37 +1848,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
- const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect);
+ const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
- let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+ let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to
!snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs
const horizLines: number[] = [];
const vertLines: number[] = [];
const invXf = this.getTransform().inverse();
- snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => {
- const { left, top, width, height } = docDims(doc);
- const topLeftInScreen = invXf.transformPoint(left, top);
- const docSize = invXf.transformDirection(width, height);
+ snappableDocs
+ .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc))
+ .forEach(doc => {
+ const { left, top, width, height } = docDims(doc);
+ const topLeftInScreen = invXf.transformPoint(left, top);
+ const docSize = invXf.transformDirection(width, height);
- horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
- vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line
- });
+ horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
+ vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
+ });
DragManager.SetSnapLines(horizLines, vertLines);
- }
+ };
onPointerOver = (e: React.PointerEvent) => {
e.stopPropagation();
- }
+ };
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(unrendered[i][Id] + "", true);
+ this._renderCutoffData.set(unrendered[i][Id] + '', true);
}
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
@@ -1749,64 +1888,67 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
children = () => {
this.incrementalRender();
- const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
- return [
- ...children,
- ...this.views,
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
- ];
- }
+ const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+ return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
+ };
@computed get placeholder() {
- return <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
- </div>;
+ return (
+ <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
+ </div>
+ );
}
@computed get marqueeView() {
TraceMobx();
- return <MarqueeView
- {...this.props}
- ref={this._marqueeViewRef}
- ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
- nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
- addDocTab={this.addDocTab}
- trySelectCluster={this.trySelectCluster}
- activeDocuments={this.getActiveDocuments}
- selectDocuments={this.selectDocuments}
- addDocument={this.addDocument}
- addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform}
- getTransform={this.getTransform}
- isAnnotationOverlay={this.isAnnotationOverlay}>
- <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
- {this.layoutDoc._backgroundGridShow ?
- <div><CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
- panX={this.panX}
- panY={this.panY}
- zoomScaling={this.zoomScaling}
- layoutDoc={this.layoutDoc}
+ return (
+ <MarqueeView
+ {...this.props}
+ ref={this._marqueeViewRef}
+ ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
+ nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
+ addDocTab={this.addDocTab}
+ trySelectCluster={this.trySelectCluster}
+ activeDocuments={this.getActiveDocuments}
+ selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument}
+ addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform}
+ getTransform={this.getTransform}
+ isAnnotationOverlay={this.isAnnotationOverlay}>
+ <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
+ {this.layoutDoc._backgroundGridShow ? (
+ <div>
+ <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
+ panX={this.panX}
+ panY={this.panY}
+ zoomScaling={this.zoomScaling}
+ layoutDoc={this.layoutDoc}
+ isAnnotationOverlay={this.isAnnotationOverlay}
+ cachedCenteringShiftX={this.cachedCenteringShiftX}
+ cachedCenteringShiftY={this.cachedCenteringShiftY}
+ />
+ </div>
+ ) : null}
+ <CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
- cachedCenteringShiftX={this.cachedCenteringShiftX}
- cachedCenteringShiftY={this.cachedCenteringShiftY}
- /></div> : (null)}
- <CollectionFreeFormViewPannableContents
- isAnnotationOverlay={this.isAnnotationOverlay}
- isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
- transform={this.contentTransform}
- zoomScaling={this.zoomScaling}
- presPaths={BoolCast(this.Document.presPathView)}
- progressivize={BoolCast(this.Document.editProgressivize)}
- presPinView={BoolCast(this.Document.presPinView)}
- transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, "string", null)}
- viewDefDivClick={this.props.viewDefDivClick}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
- </div>
- {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
- </MarqueeView>;
+ isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
+ transform={this.contentTransform}
+ zoomScaling={this.zoomScaling}
+ presPaths={BoolCast(this.Document.presPathView)}
+ progressivize={BoolCast(this.Document.editProgressivize)}
+ presPinView={BoolCast(this.Document.presPinView)}
+ transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+ viewDefDivClick={this.props.viewDefDivClick}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents>
+ </div>
+ {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
+ </MarqueeView>
+ );
}
@computed get contentScaling() {
@@ -1819,66 +1961,83 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
private groupDropDisposer?: DragManager.DragDropDisposer;
- protected createGroupEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ protected createGroupEventsTarget = (ele: HTMLDivElement) => {
+ //used for stacking and masonry view
this.groupDropDisposer?.();
if (ele) {
this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
}
- }
+ };
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
- return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
- onPointerOver={this.onPointerOver}
- onWheel={this.onPointerWheel}
- onClick={this.onClick}
- onPointerDown={this.onPointerDown}
- onPointerMove={this.onCursorMove}
- onDrop={this.onExternalDrop.bind(this)}
- onDragOver={e => e.preventDefault()}
- onContextMenu={this.onContextMenu}
- style={{
- pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
- (SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())) ? "all" : this.props.pointerEvents?.() as any,
- transform: `scale(${this.contentScaling || 1})`,
- width: `${100 / (this.contentScaling || 1)}%`,
- height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
- }}>
- {this._firstRender ?
- this.placeholder : this.marqueeView}
- {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
-
- <div className={"pullpane-indicator"}
+ return (
+ <div
+ className={'collectionfreeformview-container'}
+ ref={this.createDashEventsTarget}
+ onPointerOver={this.onPointerOver}
+ onWheel={this.onPointerWheel}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}
+ onPointerMove={this.onCursorMove}
+ onDrop={this.onExternalDrop.bind(this)}
+ onDragOver={e => e.preventDefault()}
+ onContextMenu={this.onContextMenu}
style={{
- display: this._pullDirection ? "block" : "none",
- top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
- left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
- width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
- height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
-
+ pointerEvents:
+ this.props.Document.type === DocumentType.MARKER
+ ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
+ : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
+ ? 'all'
+ : (this.props.pointerEvents?.() as any),
+ transform: `scale(${this.contentScaling || 1})`,
+ width: `${100 / (this.contentScaling || 1)}%`,
+ height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
}}>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+ <div
+ className={'pullpane-indicator'}
+ style={{
+ display: this._pullDirection ? 'block' : 'none',
+ top: clientRect ? (this._pullDirection === 'bottom' ? this._pullCoords[1] - clientRect.y : 0) : 'auto',
+ left: clientRect ? (this._pullDirection === 'right' ? this._pullCoords[0] - clientRect.x : 0) : 'auto',
+ width: clientRect ? (this._pullDirection === 'left' ? this._pullCoords[0] - clientRect.left : this._pullDirection === 'right' ? clientRect.right - this._pullCoords[0] : clientRect.width) : 0,
+ height: clientRect ? (this._pullDirection === 'top' ? this._pullCoords[1] - clientRect.top : this._pullDirection === 'bottom' ? clientRect.bottom - this._pullCoords[1] : clientRect.height) : 0,
+ }}></div>
+ {
+ // uncomment to show snap lines
+ <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+ <svg style={{ width: '100%', height: '100%' }}>
+ {this._hLines?.map(l => (
+ <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
+ }
+
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
</div>
- {// uncomment to show snap lines
- <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
- <svg style={{ width: "100%", height: "100%" }}>
- {this._hLines?.map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />)}
- {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)}
- </svg>
- </div>}
-
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ?
- <div className="collectionFreeForm-groupDropper" ref={this.createGroupEventsTarget} style={{
- width: this.ChildDrag ? "10000" : "100%",
- height: this.ChildDrag ? "10000" : "100%",
- left: this.ChildDrag ? "-5000" : 0,
- top: this.ChildDrag ? "-5000" : 0,
- position: "absolute",
- background: "#0009930",
- pointerEvents: "all"
- }} /> : (null)}
- </div >;
+ );
}
}
@@ -1887,9 +2046,12 @@ interface CollectionFreeFormOverlayViewProps {
}
@observer
-class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
+class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps> {
render() {
- return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele);
+ return this.props
+ .elements()
+ .filter(ele => ele.bounds?.z)
+ .map(ele => ele.ele);
}
}
@@ -1907,50 +2069,50 @@ interface CollectionFreeFormViewPannableContentsProps {
}
@observer
-class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
@observable _drag: string = '';
//Adds event listener so knows pointer is down and moving
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
- this._drag = (e.target as any)?.id ?? "";
+ this._drag = (e.target as any)?.id ?? '';
document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction);
- }
+ };
//Adjusts the value in NodeStore
@action
onPointerMove = (e: PointerEvent) => {
const doc = document.getElementById('resizable');
- const toNumber = (original: number, delta: number) => original + (delta * this.props.zoomScaling());
+ const toNumber = (original: number, delta: number) => original + delta * this.props.zoomScaling();
if (doc) {
switch (this._drag) {
- case "resizer-br":
+ case 'resizer-br':
doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
break;
- case "resizer-bl":
+ case 'resizer-bl':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
break;
- case "resizer-tr":
+ case 'resizer-tr':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
- case "resizer-tl":
+ case 'resizer-tl':
doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
- case "resizable":
+ case 'resizable':
doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
}
return false;
}
return true;
- }
+ };
// scale: NumCast(targetDoc._viewScale),
@computed get zoomProgressivizeContainer() {
@@ -1961,66 +2123,73 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const top = NumCast(activeItem.presPinViewY);
const width = 100;
const height = 100;
- return !this.props.presPinView ? (null) :
+ return !this.props.presPinView ? null : (
<div key="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width, height, top, left, position: 'absolute' }}>
- <div className='resizers' key={'resizer' + activeItem.id}>
- <div className='resizer top-left' onPointerDown={this.onPointerDown} />
- <div className='resizer top-right' onPointerDown={this.onPointerDown} />
- <div className='resizer bottom-left' onPointerDown={this.onPointerDown} />
- <div className='resizer bottom-right' onPointerDown={this.onPointerDown} />
+ <div className="resizers" key={'resizer' + activeItem.id}>
+ <div className="resizer top-left" onPointerDown={this.onPointerDown} />
+ <div className="resizer top-right" onPointerDown={this.onPointerDown} />
+ <div className="resizer bottom-left" onPointerDown={this.onPointerDown} />
+ <div className="resizer bottom-right" onPointerDown={this.onPointerDown} />
</div>
- </div>;
+ </div>
+ );
}
}
@computed get zoomProgressivize() {
- return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : (null);
+ return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null;
}
@computed get progressivize() {
- return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null);
+ return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null;
}
@computed get presPaths() {
- const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden");
- return !PresBox.Instance || !this.props.presPaths ? (null) : <>
- <div key="presorder">{PresBox.Instance.order}</div>
- <svg key="svg" className={presPaths}>
- <defs>
- <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
- <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
- </marker>
- <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
- <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
- </marker>
- <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
- <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
- </marker>
- </defs>
- {PresBox.Instance.paths}
- </svg>
- </>;
+ const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden');
+ return !PresBox.Instance || !this.props.presPaths ? null : (
+ <>
+ <div key="presorder">{PresBox.Instance.order}</div>
+ <svg key="svg" className={presPaths}>
+ <defs>
+ <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+ <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
+ </marker>
+ <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+ <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
+ </marker>
+ <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
+ <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
+ </marker>
+ </defs>
+ {PresBox.Instance.paths}
+ </svg>
+ </>
+ );
}
render() {
- return <div className={"collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none")}
- onScroll={e => {
- const target = e.target as any;
- if (getComputedStyle(target)?.overflow === "visible") { // if collection is visible, then scrolling will mess things up since there are no scroll bars
- target.scrollTop = target.scrollLeft = 0;
- }
- }}
- style={{
- transform: this.props.transform(),
- transition: this.props.transition,
- width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
- //willChange: "transform"
- }}>
- {this.props.children()}
- {this.presPaths}
- {this.progressivize}
- {this.zoomProgressivize}
- </div>;
+ return (
+ <div
+ className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+ onScroll={e => {
+ const target = e.target as any;
+ if (getComputedStyle(target)?.overflow === 'visible') {
+ // if collection is visible, then scrolling will mess things up since there are no scroll bars
+ target.scrollTop = target.scrollLeft = 0;
+ }
+ }}
+ style={{
+ transform: this.props.transform(),
+ transition: this.props.transition,
+ width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+ //willChange: "transform"
+ }}>
+ {this.props.children()}
+ {this.presPaths}
+ {this.progressivize}
+ {this.zoomProgressivize}
+ </div>
+ );
}
}
@@ -2037,63 +2206,73 @@ interface CollectionFreeFormViewBackgroundGridProps {
}
@observer
class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
-
-
chooseGridSpace = (gridSpace: number): number => {
if (!this.props.zoomScaling()) return 50;
const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3;
return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
- }
+ };
render() {
- const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50));
- const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling();
- const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling();
+ const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
+ const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
+ const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
const renderGridSpace = gridSpace * this.props.zoomScaling();
const w = this.props.PanelWidth() + 2 * renderGridSpace;
const h = this.props.PanelHeight() + 2 * renderGridSpace;
- const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
- return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
- ref={(el) => {
- const ctx = el?.getContext('2d');
- if (ctx) {
- const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
- const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
- ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
- ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
- ctx.clearRect(0, 0, w, h);
+ const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
+ return (
+ <canvas
+ className="collectionFreeFormView-grid"
+ width={w}
+ height={h}
+ style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
+ ref={el => {
+ const ctx = el?.getContext('2d');
if (ctx) {
- ctx.strokeStyle = strokeStyle;
- ctx.beginPath();
- for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
- ctx.moveTo(x, Cy - h);
- ctx.lineTo(x, Cy + h);
+ const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
+ const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+ ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
+ ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
+ ctx.clearRect(0, 0, w, h);
+ if (ctx) {
+ ctx.strokeStyle = strokeStyle;
+ ctx.beginPath();
+ for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+ ctx.moveTo(x, Cy - h);
+ ctx.lineTo(x, Cy + h);
+ }
+ for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+ ctx.moveTo(Cx - w, y);
+ ctx.lineTo(Cx + w, y);
+ }
+ ctx.stroke();
}
- for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
- ctx.moveTo(Cx - w, y);
- ctx.lineTo(Cx + w, y);
- }
- ctx.stroke();
}
- }
- }} />;
+ }}
+ />
+ );
}
}
export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
SelectionManager.DeselectAll();
dv.props.focus(dv.props.Document, {
- willZoom: true, afterFocus: async (didMove) => {
+ willZoom: true,
+ afterFocus: async didMove => {
if (!didMove) {
const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || "_viewScale"] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5);
}
return ViewAdjustment.doNothing;
- }
+ },
});
Doc.linkFollowHighlight(dv?.props.Document, false);
}
ScriptingGlobals.add(CollectionBrowseClick);
-ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); });
-ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); \ No newline at end of file
+ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
+ !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
+});
+ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
+ !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
+});
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index 0e19ef3d9..f872637e5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -1,17 +1,16 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action } from "mobx";
-import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table";
-import { Doc } from "../../../../fields/Doc";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { Cast, FieldValue, StrCast } from "../../../../fields/Types";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { ContextMenu } from "../../ContextMenu";
-import "./CollectionSchemaView.scss";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action } from 'mobx';
+import * as React from 'react';
+import { ReactTableDefaults, RowInfo } from 'react-table';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, FieldValue, StrCast } from '../../../../fields/Types';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
+import { ContextMenu } from '../../ContextMenu';
+import './CollectionSchemaView.scss';
export interface MovableRowProps {
rowInfo: RowInfo;
@@ -25,7 +24,7 @@ export interface MovableRowProps {
addDocTab: any;
}
-export class MovableRow extends React.Component<MovableRowProps> {
+export class MovableRow extends React.Component<React.PropsWithChildren<MovableRowProps>> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _rowDropDisposer?: DragManager.DragDropDisposer;
@@ -33,28 +32,27 @@ export class MovableRow extends React.Component<MovableRowProps> {
// Create one when the mouse starts hovering...
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
- this._header!.current!.className = "collectionSchema-row-wrapper";
- document.addEventListener("pointermove", this.onDragMove, true);
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ document.addEventListener('pointermove', this.onDragMove, true);
}
- }
+ };
// ... and delete it when the mouse leaves
onPointerLeave = (e: React.PointerEvent): void => {
- this._header!.current!.className = "collectionSchema-row-wrapper";
- document.removeEventListener("pointermove", this.onDragMove, true);
- }
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ document.removeEventListener('pointermove', this.onDragMove, true);
+ };
// The method for the event listener, reorders columns when dragged to their new locations.
onDragMove = (e: PointerEvent): void => {
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const rect = this._header!.current!.getBoundingClientRect();
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
const before = x[1] < bounds[1];
- this._header!.current!.className = "collectionSchema-row-wrapper";
- if (before) this._header!.current!.className += " row-above";
- if (!before) this._header!.current!.className += " row-below";
+ this._header!.current!.className = 'collectionSchema-row-wrapper';
+ if (before) this._header!.current!.className += ' row-above';
+ if (!before) this._header!.current!.className += ' row-below';
e.stopPropagation();
- }
+ };
componentWillUnmount() {
-
this._rowDropDisposer?.();
}
//
@@ -63,7 +61,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (ele) {
this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
- }
+ };
// Controls what hppens when a row is dragged and dropped
rowDrop = (e: Event, de: DragManager.DropEvent) => {
this.onPointerLeave(e as any);
@@ -81,34 +79,34 @@ export class MovableRow extends React.Component<MovableRowProps> {
if (docDragData.draggedDocuments[0] === rowDoc) return true;
const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before);
const movedDocs = docDragData.draggedDocuments;
- return (docDragData.dropAction || docDragData.userDropAction) ?
- docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : (docDragData.moveDocument) ?
- movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
- : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+ return docDragData.dropAction || docDragData.userDropAction
+ ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+ : docDragData.moveDocument
+ ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+ : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
}
return false;
- }
+ };
onRowContextMenu = (e: React.MouseEvent): void => {
- const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row";
- ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" });
- }
+ const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row';
+ ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' });
+ };
@undoBatch
@action
move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => {
const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
- }
+ };
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- console.log("yes");
- if (e.key === "Backspace" || e.key === "Delete") {
+ console.log('yes');
+ if (e.key === 'Backspace' || e.key === 'Delete') {
undoBatch(() => this.props.removeDoc(this.props.rowInfo.original));
}
- }
+ };
render() {
const { children = null, rowInfo } = this.props;
@@ -120,23 +118,29 @@ export class MovableRow extends React.Component<MovableRowProps> {
const { original } = rowInfo;
const doc = FieldValue(Cast(original, Doc));
- if (!doc) return (null);
+ if (!doc) return null;
const reference = React.createRef<HTMLDivElement>();
const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType);
- let className = "collectionSchema-row";
- if (this.props.rowFocused) className += " row-focused";
- if (this.props.rowWrapped) className += " row-wrapped";
+ let className = 'collectionSchema-row';
+ if (this.props.rowFocused) className += ' row-focused';
+ if (this.props.rowWrapped) className += ' row-wrapped';
return (
<div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
<div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} >
+ <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown}>
<div className="row-dragger">
- <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
- <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
- <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+ <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}>
+ <FontAwesomeIcon icon="trash" size="sm" />
+ </div>
+ <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}>
+ <FontAwesomeIcon icon="grip-vertical" size="sm" />
+ </div>
+ <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, 'add:right')}>
+ <FontAwesomeIcon icon="external-link-alt" size="sm" />
+ </div>
</div>
{children}
</ReactTableDefaults.TrComponent>
@@ -144,4 +148,4 @@ export class MovableRow extends React.Component<MovableRowProps> {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 43266a571..fafea5ce3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -1,37 +1,47 @@
-import React = require("react");
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, trace } from "mobx";
-import { observer } from "mobx-react";
-import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
-import { DateField } from "../../../../fields/DateField";
-import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { ImageField } from "../../../../fields/URLField";
-import { GetEffectiveAcl } from "../../../../fields/util";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils";
-import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { CompileScript, Transformer, ts } from "../../../util/Scripting";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { ContextMenu } from "../../ContextMenu";
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table';
+import { DateField } from '../../../../fields/DateField';
+import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils';
+import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { CompileScript, Transformer, ts } from '../../../util/Scripting';
+import { Transform } from '../../../util/Transform';
+import { undoBatch } from '../../../util/UndoManager';
import '../../../views/DocumentDecorations.scss';
-import { DocumentView } from "../../nodes/DocumentView";
-import { DefaultStyleProvider } from "../../StyleProvider";
-import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
-import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
-import { MovableColumn } from "./CollectionSchemaMovableColumn";
-import { MovableRow } from "./CollectionSchemaMovableRow";
-import "./CollectionSchemaView.scss";
-import { CollectionView } from "../CollectionView";
-
+import { ContextMenu } from '../../ContextMenu';
+import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
+import { DocumentView } from '../../nodes/DocumentView';
+import { DefaultStyleProvider } from '../../StyleProvider';
+import { CollectionView } from '../CollectionView';
+import {
+ CellProps,
+ CollectionSchemaButtons,
+ CollectionSchemaCell,
+ CollectionSchemaCheckboxCell,
+ CollectionSchemaDateCell,
+ CollectionSchemaDocCell,
+ CollectionSchemaImageCell,
+ CollectionSchemaListCell,
+ CollectionSchemaNumberCell,
+ CollectionSchemaStringCell,
+} from './CollectionSchemaCells';
+import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders';
+import { MovableColumn } from './CollectionSchemaMovableColumn';
+import { MovableRow } from './CollectionSchemaMovableRow';
+import './CollectionSchemaView.scss';
enum ColumnType {
Any,
@@ -41,15 +51,22 @@ enum ColumnType {
Doc,
Image,
List,
- Date
+ Date,
}
// this map should be used for keys that should have a const type of value
const columnTypes: Map<string, ColumnType> = new Map([
- ["title", ColumnType.String],
- ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
- ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ['title', ColumnType.String],
+ ['x', ColumnType.Number],
+ ['y', ColumnType.Number],
+ ['_width', ColumnType.Number],
+ ['_height', ColumnType.Number],
+ ['_nativeWidth', ColumnType.Number],
+ ['_nativeHeight', ColumnType.Number],
+ ['isPrototype', ColumnType.Boolean],
+ ['_curPage', ColumnType.Number],
+ ['_currentTimecode', ColumnType.Number],
+ ['zIndex', ColumnType.Number],
]);
export interface SchemaTableProps {
@@ -92,18 +109,24 @@ export interface SchemaTableProps {
@observer
export class SchemaTable extends React.Component<SchemaTableProps> {
@observable _cellIsEditing: boolean = false;
- @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
- @observable _openCollections: Set<number> = new Set;
+ @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 };
+ @observable _openCollections: Set<number> = new Set();
@observable _showDoc: Doc | undefined;
- @observable _showDataDoc: any = "";
+ @observable _showDataDoc: any = '';
@observable _showDocPos: number[] = [];
@observable _showTitleDropdown: boolean = false;
- @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
- @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
- @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
+ @computed get previewWidth() {
+ return () => NumCast(this.props.Document.schemaPreviewWidth);
+ }
+ @computed get previewHeight() {
+ return () => this.props.PanelHeight() - 2 * this.borderWidth;
+ }
+ @computed get tableWidth() {
+ return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
+ }
@computed get childDocs() {
if (this.props.childDocs) return this.props.childDocs;
@@ -117,17 +140,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@computed get textWrappedRows() {
- return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
}
set textWrappedRows(textWrappedRows: string[]) {
this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
}
- @computed get resized(): { id: string, value: number }[] {
+ @computed get resized(): { id: string; value: number }[] {
return this.props.columns.reduce((resized, shf) => {
- (shf.width > -1) && resized.push({ id: shf.heading, value: shf.width });
+ shf.width > -1 && resized.push({ id: shf.heading, value: shf.width });
return resized;
- }, [] as { id: string, value: number }[]);
+ }, [] as { id: string; value: number }[]);
}
@computed get sorted(): SortingRule[] {
return this.props.columns.reduce((sorted, shf) => {
@@ -139,12 +162,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeSorting = (col: any) => {
this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true);
- }
+ };
@action
- changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown
+ changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown);
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get borderWidth() {
+ return Number(COLLECTION_BORDER_WIDTH);
+ }
@computed get tableColumns(): Column<Doc>[] {
const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
const columns: Column<Doc>[] = [];
@@ -154,149 +179,185 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isEditable = !this.props.headerIsEditing;
columns.push({
- expander: true, Header: "", width: 58,
- Expander: (rowInfo) => {
- return rowInfo.original.type !== DocumentType.COL ? (null) :
- <div className="collectionSchemaView-expander" onClick={action(() => (this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}>
- <FontAwesomeIcon icon={rowInfo.isExpanded ? "caret-down" : "caret-right"} size="lg" />
- </div>;
- }
+ expander: true,
+ Header: '',
+ width: 58,
+ Expander: rowInfo => {
+ return rowInfo.original.type !== DocumentType.COL ? null : (
+ <div className="collectionSchemaView-expander" onClick={action(() => this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}>
+ <FontAwesomeIcon icon={rowInfo.isExpanded ? 'caret-down' : 'caret-right'} size="lg" />
+ </div>
+ );
+ },
});
- columns.push(...this.props.columns.map(col => {
- const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" :
- this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" :
- this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" :
- this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify";
-
- const keysDropdown = <KeysDropdown
- keyValue={col.heading}
- possibleKeys={possibleKeys}
- existingKeys={this.props.columns.map(c => c.heading)}
- canAddNew={true}
- addNew={false}
- onSelect={this.props.changeColumns}
- setIsEditing={this.props.setHeaderIsEditing}
- docs={this.props.childDocs}
- Document={this.props.Document}
- dataDoc={this.props.dataDoc}
- fieldKey={this.props.fieldKey}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- active={this.props.active}
- openHeader={this.props.openHeader}
- icon={icon}
- col={col}
- // try commenting this out
- width={"100%"}
- />;
-
- const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
- const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
- {keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}>
- <FontAwesomeIcon icon={sortIcon} size="lg" />
- </div>
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
- </div>;
-
- return {
- Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
- accessor: (doc: Doc) => doc ? Field.toString(doc[col.heading] as Field) : 0,
- id: col.heading,
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
-
- const props: CellProps = {
- row: rowIndex,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- };
-
-
- switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
- case ColumnType.Number: return <CollectionSchemaNumberCell {...props} />;
- case ColumnType.String: return <CollectionSchemaStringCell {...props} />;
- case ColumnType.Boolean: return <CollectionSchemaCheckboxCell {...props} />;
- case ColumnType.Doc: return <CollectionSchemaDocCell {...props} />;
- case ColumnType.Image: return <CollectionSchemaImageCell {...props} />;
- case ColumnType.List: return <CollectionSchemaListCell {...props} />;
- case ColumnType.Date: return <CollectionSchemaDateCell {...props} />;
- default:
- return <CollectionSchemaCell {...props} />;
- }
- },
- minWidth: 200,
- };
- }));
+ columns.push(
+ ...this.props.columns.map(col => {
+ const icon: IconProp =
+ this.getColumnType(col) === ColumnType.Number
+ ? 'hashtag'
+ : this.getColumnType(col) === ColumnType.String
+ ? 'font'
+ : this.getColumnType(col) === ColumnType.Boolean
+ ? 'check-square'
+ : this.getColumnType(col) === ColumnType.Doc
+ ? 'file'
+ : this.getColumnType(col) === ColumnType.Image
+ ? 'image'
+ : this.getColumnType(col) === ColumnType.List
+ ? 'list-ul'
+ : this.getColumnType(col) === ColumnType.Date
+ ? 'calendar'
+ : 'align-justify';
+
+ const keysDropdown = (
+ <KeysDropdown
+ keyValue={col.heading}
+ possibleKeys={possibleKeys}
+ existingKeys={this.props.columns.map(c => c.heading)}
+ canAddNew={true}
+ addNew={false}
+ onSelect={this.props.changeColumns}
+ setIsEditing={this.props.setHeaderIsEditing}
+ docs={this.props.childDocs}
+ Document={this.props.Document}
+ dataDoc={this.props.dataDoc}
+ fieldKey={this.props.fieldKey}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ active={this.props.active}
+ openHeader={this.props.openHeader}
+ icon={icon}
+ col={col}
+ // try commenting this out
+ width={'100%'}
+ />
+ );
+
+ const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up';
+ const header = (
+ <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: '2px', display: 'flex', cursor: 'default', height: '100%' }}>
+ {keysDropdown}
+ <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}>
+ <FontAwesomeIcon icon={sortIcon} size="lg" />
+ </div>
+ {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
+ </div>
+ );
+
+ return {
+ Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
+ accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0),
+ id: col.heading,
+ Cell: (rowProps: CellInfo) => {
+ const rowIndex = rowProps.index;
+ const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
+ const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
+
+ const props: CellProps = {
+ row: rowIndex,
+ col: columnIndex,
+ rowProps: rowProps,
+ isFocused: isFocused,
+ changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+ CollectionView: this.props.CollectionView,
+ ContainingCollection: this.props.ContainingCollectionView,
+ Document: this.props.Document,
+ fieldKey: this.props.fieldKey,
+ renderDepth: this.props.renderDepth,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ moveDocument: this.props.moveDocument,
+ setIsEditing: this.setCellIsEditing,
+ isEditable: isEditable,
+ setPreviewDoc: this.props.setPreviewDoc,
+ setComputed: this.setComputed,
+ getField: this.getField,
+ showDoc: this.showDoc,
+ };
+
+ switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) {
+ case ColumnType.Number:
+ return <CollectionSchemaNumberCell {...props} />;
+ case ColumnType.String:
+ return <CollectionSchemaStringCell {...props} />;
+ case ColumnType.Boolean:
+ return <CollectionSchemaCheckboxCell {...props} />;
+ case ColumnType.Doc:
+ return <CollectionSchemaDocCell {...props} />;
+ case ColumnType.Image:
+ return <CollectionSchemaImageCell {...props} />;
+ case ColumnType.List:
+ return <CollectionSchemaListCell {...props} />;
+ case ColumnType.Date:
+ return <CollectionSchemaDateCell {...props} />;
+ default:
+ return <CollectionSchemaCell {...props} />;
+ }
+ },
+ minWidth: 200,
+ };
+ })
+ );
columns.push({
Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
accessor: (doc: Doc) => 0,
- id: "add",
+ id: 'add',
Cell: (rowProps: CellInfo) => {
const rowIndex = rowProps.index;
const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
- return <CollectionSchemaButtons {...{
- row: rowProps.index,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- showDoc: this.showDoc,
- }} />;
+ return (
+ <CollectionSchemaButtons
+ {...{
+ row: rowProps.index,
+ col: columnIndex,
+ rowProps: rowProps,
+ isFocused: isFocused,
+ changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+ CollectionView: this.props.CollectionView,
+ ContainingCollection: this.props.ContainingCollectionView,
+ Document: this.props.Document,
+ fieldKey: this.props.fieldKey,
+ renderDepth: this.props.renderDepth,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ moveDocument: this.props.moveDocument,
+ setIsEditing: this.setCellIsEditing,
+ isEditable: isEditable,
+ setPreviewDoc: this.props.setPreviewDoc,
+ setComputed: this.setComputed,
+ getField: this.getField,
+ showDoc: this.showDoc,
+ }}
+ />
+ );
},
width: 28,
- resizable: false
+ resizable: false,
});
return columns;
}
-
constructor(props: SchemaTableProps) {
super(props);
if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date),
- new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]);
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([
+ new SchemaHeaderField('title', '#f1efeb'),
+ new SchemaHeaderField('author', '#f1efeb'),
+ new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date),
+ new SchemaHeaderField('text', '#f1efeb', ColumnType.String),
+ new SchemaHeaderField('type', '#f1efeb'),
+ new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc),
+ ]);
}
}
componentDidMount() {
- document.addEventListener("keydown", this.onKeyDown);
+ document.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
- document.removeEventListener("keydown", this.onKeyDown);
+ document.removeEventListener('keydown', this.onKeyDown);
}
tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
@@ -305,25 +366,27 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) {
doc.context = this.props.Document;
- tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+ tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()));
return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
}
return false;
- }
+ };
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
- return !rowInfo ? {} : {
- ScreenToLocalTransform: this.props.ScreenToLocalTransform,
- addDoc: this.tableAddDoc,
- removeDoc: this.props.deleteDocument,
- rowInfo,
- rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true),
- textWrapRow: this.toggleTextWrapRow,
- rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
- dropAction: StrCast(this.props.Document.childDropAction),
- addDocTab: this.props.addDocTab
- };
- }
+ return !rowInfo
+ ? {}
+ : {
+ ScreenToLocalTransform: this.props.ScreenToLocalTransform,
+ addDoc: this.tableAddDoc,
+ removeDoc: this.props.deleteDocument,
+ rowInfo,
+ rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true),
+ textWrapRow: this.toggleTextWrapRow,
+ rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
+ dropAction: StrCast(this.props.Document.childDropAction),
+ addDocTab: this.props.addDocTab,
+ };
+ };
private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
if (!rowInfo || column) return {};
@@ -334,16 +397,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true);
// TODO: editing border doesn't work :(
return {
- style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" }
+ style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' },
};
- }
+ };
- @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
+ @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing);
@action
onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) {
- const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
+ if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {
+ // && this.props.isSelected(true)) {
+ const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : '';
this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
if (direction) {
@@ -353,20 +417,25 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
} else if (e.keyCode === 27) {
this.props.setPreviewDoc(undefined);
- e.stopPropagation(); // stopPropagation for left/right arrows
+ e.stopPropagation(); // stopPropagation for left/right arrows
}
- }
+ };
changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
switch (direction) {
- case "tab": return { row: (curRow + 1 === this.childDocs.length ? 0 : curRow + 1), col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
- case "right": return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
- case "left": return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
- case "up": return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
- case "down": return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
+ case 'tab':
+ return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
+ case 'right':
+ return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
+ case 'left':
+ return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
+ case 'up':
+ return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
+ case 'down':
+ return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
}
return this._focusedCell;
- }
+ };
@action
changeFocusedCellByIndex = (row: number, col: number): void => {
@@ -374,25 +443,25 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
this._focusedCell = { row: row, col: col };
}
this.props.setFocused(this.props.Document);
- }
+ };
@undoBatch
createRow = action(() => {
- this.props.addDocument?.(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
+ this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 }));
this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col };
});
@undoBatch
@action
createColumn = () => {
- const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`;
+ const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`;
for (let index = 0; index < 100; index++) {
if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
- this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb"));
+ this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb'));
break;
}
}
- }
+ };
@action
getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => {
@@ -407,15 +476,15 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return column.type;
}
if (columnTypes.get(column.heading)) {
- return column.type = columnTypes.get(column.heading)!;
+ return (column.type = columnTypes.get(column.heading)!);
}
- return column.type = ColumnType.Any;
- }
+ return (column.type = ColumnType.Any);
+ };
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []);
if (textwrappedRows.length) {
this.props.Document.textwrappedSchemaRows = new List<string>([]);
} else {
@@ -423,7 +492,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
}
- }
+ };
@action
toggleTextWrapRow = (doc: Doc): void => {
@@ -433,41 +502,50 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
this.textWrappedRows = textWrapped;
- }
+ };
@computed
get reactTable() {
const children = this.childDocs;
const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false);
const expanded: { [name: string]: any } = {};
- Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true);
+ Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true));
const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
- return <ReactTable
- style={{ position: "relative" }}
- data={children}
- page={0}
- pageSize={children.length}
- showPagination={false}
- columns={this.tableColumns}
- getTrProps={this.getTrProps}
- getTdProps={this.getTdProps}
- sortable={false}
- TrComponent={MovableRow}
- sorted={this.sorted}
- expanded={expanded}
- resized={this.resized}
- onResizedChange={this.props.onResizedChange}
- // if it has a child, render another table with the children
- SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== DocumentType.COL) ? (null) :
- <div style={{ paddingLeft: 57 + "px" }} className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
-
- />;
+ return (
+ <ReactTable
+ style={{ position: 'relative' }}
+ data={children}
+ page={0}
+ pageSize={children.length}
+ showPagination={false}
+ columns={this.tableColumns}
+ getTrProps={this.getTrProps}
+ getTdProps={this.getTdProps}
+ sortable={false}
+ TrComponent={MovableRow}
+ sorted={this.sorted}
+ expanded={expanded}
+ resized={this.resized}
+ onResizedChange={this.props.onResizedChange}
+ // if it has a child, render another table with the children
+ SubComponent={
+ !hasCollectionChild
+ ? undefined
+ : row =>
+ row.original.type !== DocumentType.COL ? null : (
+ <div style={{ paddingLeft: 57 + 'px' }} className="reactTable-sub">
+ <SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} />
+ </div>
+ )
+ }
+ />
+ );
}
onContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
- }
+ ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' });
+ };
getField = (row: number, col?: number) => {
const docs = this.childDocs;
@@ -484,7 +562,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return doc[column];
}
return undefined;
- }
+ };
createTransformer = (row: number, col: number): Transformer => {
const self = this;
@@ -498,11 +576,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
if (isntPropAccess && isntPropAssign) {
- if (node.text === "$r") {
+ if (node.text === '$r') {
return ts.createNumericLiteral(row.toString());
- } else if (node.text === "$c") {
+ } else if (node.text === '$c') {
return ts.createNumericLiteral(col.toString());
- } else if (node.text === "$") {
+ } else if (node.text === '$') {
if (ts.isCallExpression(node.parent)) {
// captures.doc = self.props.Document;
// captures.key = self.props.fieldKey;
@@ -521,12 +599,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
// return { capturedVariables: captures };
// };
- return { transformer, /*getVars*/ };
- }
+ return { transformer /*getVars*/ };
+ };
setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
- script =
- `const $ = (row:number, col?:number) => {
+ script = `const $ = (row:number, col?:number) => {
const rval = (doc as any)[key][row + ${row}];
return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading];
}
@@ -537,7 +614,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return true;
}
return false;
- }
+ };
@action
showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
@@ -545,55 +622,72 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (dataDoc && screenX && screenY) {
this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
}
- }
+ };
onOpenClick = () => {
- this._showDoc && this.props.addDocTab(this._showDoc, "add:right");
- }
+ this._showDoc && this.props.addDocTab(this._showDoc, 'add:right');
+ };
getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- this.borderWidth - 4 - this.tableWidth, - this.borderWidth);
- }
+ return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth);
+ };
render() {
- const preview = "";
- return <div className="collectionSchemaView-table"
- onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()}
- onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
- {this.reactTable}
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>}
- {!this._showDoc ? (null) :
- <div className="collectionSchemaView-documentPreview" ref="overlay"
- style={{
- position: "absolute", width: 150, height: 150,
- background: "dimgray", display: "block", top: 0, left: 0,
- transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`
- }} >
- <DocumentView
- Document={this._showDoc}
- DataDoc={this._showDataDoc}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={returnFalse}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- PanelWidth={() => 150}
- PanelHeight={() => 150}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- whenChildContentsActiveChanged={emptyFunction}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}>
- </DocumentView>
- </div>}
- </div>;
+ const preview = '';
+ return (
+ <div
+ className="collectionSchemaView-table"
+ onPointerDown={this.props.onPointerDown}
+ onClick={this.props.onClick}
+ onWheel={e => this.props.active(true) && e.stopPropagation()}
+ onDrop={e => this.props.onDrop(e, {})}
+ onContextMenu={this.onContextMenu}>
+ {this.reactTable}
+ {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : (
+ <div className="collectionSchemaView-addRow" onClick={this.createRow}>
+ + new
+ </div>
+ )}
+ {!this._showDoc ? null : (
+ <div
+ className="collectionSchemaView-documentPreview"
+ ref="overlay"
+ style={{
+ position: 'absolute',
+ width: 150,
+ height: 150,
+ background: 'dimgray',
+ display: 'block',
+ top: 0,
+ left: 0,
+ transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`,
+ }}>
+ <DocumentView
+ Document={this._showDoc}
+ DataDoc={this._showDataDoc}
+ styleProvider={DefaultStyleProvider}
+ docViewPath={returnEmptyDoclist}
+ focus={DocUtils.DefaultFocus}
+ renderDepth={this.props.renderDepth}
+ rootSelected={returnFalse}
+ isContentActive={returnTrue}
+ isDocumentActive={returnFalse}
+ PanelWidth={() => 150}
+ PanelHeight={() => 150}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ whenChildContentsActiveChanged={emptyFunction}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}></DocumentView>
+ </div>
+ )}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 56779c37c..610c2b102 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -8,6 +8,7 @@ export enum Colors {
MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
LIGHT_BLUE = "#BDDDF5",
PINK = "#E0217D",
+ ERROR_RED = "#ff0033",
YELLOW = "#F5D747",
DROP_SHADOW = "#32323215",
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index bedc97575..284584a3d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -170,6 +170,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.panelWidth(),
height: this.panelHeight(),
transform: this.transform,
+ transformOrigin: '50% 50%',
transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
zIndex: this.ZInd,
mixBlendMode: mixBlendMode,
diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx
new file mode 100644
index 000000000..df4c8f937
--- /dev/null
+++ b/src/client/views/nodes/DataViz.tsx
@@ -0,0 +1,20 @@
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import './DataViz.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+
+@observer
+export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(DataVizBox, fieldKey);
+ }
+
+ render() {
+ return (
+ <div>
+ <div>Hi</div>
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 798759c01..28874220a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,14 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
-import { StringIterator } from 'lodash';
+import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
-import { createSchema } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
@@ -33,9 +32,6 @@ import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
import './FontIconBox.scss';
-const FontIconSchema = createSchema({
- icon: "string",
-});
export enum ButtonType {
TextButton = "textBtn",
@@ -630,11 +626,7 @@ ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", chec
if (active === mapStyle) return Colors.MEDIUM_BLUE;
return "transparent";
}
- if (editorView) {
- const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
- editorView?.state && RichTextMenu.Instance.changeListType(
- editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }));
- }
+ editorView?.state && RichTextMenu.Instance.changeListType(mapStyle);
});
// toggle: Set overlay status of selected document
diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx
index e18590a95..5d7d55863 100644
--- a/src/client/views/nodes/button/textButton/TextButton.tsx
+++ b/src/client/views/nodes/button/textButton/TextButton.tsx
@@ -9,9 +9,22 @@ export class TextButton extends Component<IButtonProps> {
// Determine the type of toggle button
const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle);
- return (<div className={`menuButton ${this.props.type}`} style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}>
- <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} />
- {this.props.label}
- </div>);
+ return (
+ <div
+ className={`menuButton ${this.props.type}`}
+ style={{
+ opacity: 1,
+ backgroundColor: this.props.backgroundColor,
+ color: this.props.color,
+ }}
+ >
+ <FontAwesomeIcon
+ className={`fontIconBox-icon-${this.props.type}`}
+ icon={this.props.icon}
+ color={this.props.color}
+ />
+ {this.props.label}
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 5c75a589a..40dd6fbc7 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,37 +1,44 @@
-import { TextSelection } from "prosemirror-state";
+import { TextSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import React = require("react");
-
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import React = require('react');
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
// the comment can be toggled on/off with the '<-' text anchor.
export class DashDocCommentView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this.dom);
+ (this as any).dom = this.dom;
}
destroy() {
- ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+ ReactDOM.unmountComponentAtNode(this.dom);
}
- selectNode() { }
+ selectNode() {}
}
interface IDashDocCommentViewInternal {
@@ -40,8 +47,7 @@ interface IDashDocCommentViewInternal {
getPos: any;
}
-export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal>{
-
+export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
constructor(props: IDashDocCommentViewInternal) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -71,7 +77,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
- try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
+ try {
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
+ } catch (e) {}
}, 0);
}
e.stopPropagation();
@@ -81,32 +89,35 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
e.stopPropagation();
}
- targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ targetNode = () => {
+ // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
const state = this.props.view.state;
for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
const m = state.doc.nodeAt(i);
if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean };
}
}
- const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" });
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' });
this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
- setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+ setTimeout(() => {
+ try {
+ this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
+ } catch (e) {}
+ }, 0);
return undefined;
- }
+ };
render() {
return (
<span
className="formattedTextBox-inlineComment"
- id={"DashDocCommentView-" + this.props.docid}
+ id={'DashDocCommentView-' + this.props.docid}
onPointerLeave={this.onPointerLeaveCollapsed}
onPointerEnter={this.onPointerEnterCollapsed}
onPointerUp={this.onPointerUpCollapsed}
- onPointerDown={this.onPointerDownCollapsed}
- >
- </span>
+ onPointerDown={this.onPointerDownCollapsed}></span>
);
}
}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1d8e3a2cf..9d203b6cc 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,52 +1,54 @@
-import { IReactionDisposer, reaction, observable, action } from "mobx";
-import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Cast, StrCast, NumCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { Transform } from "../../../util/Transform";
-import { DocumentView } from "../DocumentView";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { IReactionDisposer, reaction, observable, action } from 'mobx';
+import { NodeSelection } from 'prosemirror-state';
+import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc';
+import { Cast, StrCast, NumCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
+import { Transform } from '../../../util/Transform';
+import { DocumentView } from '../DocumentView';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
import * as ReactDOM from 'react-dom';
-import { observer } from "mobx-react";
-import { ColorScheme } from "../../../util/SettingsManager";
+import { observer } from 'mobx-react';
+import { ColorScheme } from '../../../util/SettingsManager';
export class DashDocView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.textIndent = "0";
- this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray"));
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
- (this._fieldWrapper.style as any).float = node.attrs.float;
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
- ReactDOM.render(<DashDocViewInternal
- docid={node.attrs.docid}
- alias={node.attrs.alias}
- width={node.attrs.width}
- height={node.attrs.height}
- hidden={node.attrs.hidden}
- fieldKey={node.attrs.fieldKey}
- tbox={tbox}
- view={view}
- node={node}
- getPos={getPos}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ this.dom = document.createElement('span');
+ this.dom.style.position = 'relative';
+ this.dom.style.textIndent = '0';
+ this.dom.style.border = '1px solid ' + StrCast(tbox.layoutDoc.color, CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'lightGray');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+ (this.dom.style as any).float = node.attrs.float;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
+
+ ReactDOM.render(
+ <DashDocViewInternal docid={node.attrs.docid} alias={node.attrs.alias} width={node.attrs.width} height={node.attrs.height} hidden={node.attrs.hidden} fieldKey={node.attrs.fieldKey} tbox={tbox} view={view} node={node} getPos={getPos} />,
+ this.dom
+ );
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashDocViewInternal {
@@ -70,7 +72,6 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
@observable _finalLayout: any;
@observable _resolvedDataDoc: any;
-
updateDoc = action((dashDoc: Doc) => {
this._dashDoc = dashDoc;
this._finalLayout = this.props.docid ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey);
@@ -81,13 +82,18 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null);
}
- if (this.props.width !== (this._dashDoc?._width ?? "") + "px" || this.props.height !== (this._dashDoc?._height ?? "") + "px") {
- try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') {
+ try {
+ // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+ this.props.view.dispatch(
+ this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
+ ...this.props.node.attrs,
+ width: (this._dashDoc?._width ?? '') + 'px',
+ height: (this._dashDoc?._height ?? '') + 'px',
+ })
+ );
} catch (e) {
- console.log("DashDocView:" + e);
+ console.log('DashDocView:' + e);
}
}
});
@@ -98,14 +104,15 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
DocServer.GetRefField(this.props.docid + this.props.alias).then(async dashDoc => {
if (!(dashDoc instanceof Doc)) {
- this.props.alias && DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
- if (dashDocBase instanceof Doc) {
- const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
- aliasedDoc.layoutKey = "layout";
- this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
- this.updateDoc(aliasedDoc);
- }
- });
+ this.props.alias &&
+ DocServer.GetRefField(this.props.docid).then(async dashDocBase => {
+ if (dashDocBase instanceof Doc) {
+ const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias);
+ aliasedDoc.layoutKey = 'layout';
+ this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined);
+ this.updateDoc(aliasedDoc);
+ }
+ });
} else {
this.updateDoc(dashDoc);
}
@@ -113,67 +120,70 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
}
componentDidMount() {
- this._disposers.upater = reaction(() => this._dashDoc && (NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width)),
+ this._disposers.upater = reaction(
+ () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width),
() => {
if (this._dashDoc) {
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
- ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px"
- }));
+ this.props.view.dispatch(
+ this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, {
+ ...this.props.node.attrs,
+ width: (this._dashDoc?._width ?? '') + 'px',
+ height: (this._dashDoc?._height ?? '') + 'px',
+ })
+ );
}
- });
+ }
+ );
}
-
removeDoc = () => {
- this.props.view.dispatch(this.props.view.state.tr
- .setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos())))
- .deleteSelection());
+ this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos()))).deleteSelection());
return true;
- }
+ };
getDocTransform = () => {
if (!this._spanRef.current) return Transform.Identity();
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
- }
- outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
+ };
+ outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
onKeyDown = (e: any) => {
e.stopPropagation();
- if (e.key === "Tab" || e.key === "Enter") {
+ if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
}
- }
+ };
onPointerLeave = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = '');
+ };
onPointerEnter = () => {
- const ele = document.getElementById("DashDocCommentView-" + this.props.docid) as HTMLDivElement;
- ele && (ele.style.backgroundColor = "orange");
- }
+ const ele = document.getElementById('DashDocCommentView-' + this.props.docid) as HTMLDivElement;
+ ele && (ele.style.backgroundColor = 'orange');
+ };
componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
render() {
- return !this._dashDoc || !this._finalLayout || this.props.hidden ? null :
- <div ref={this._spanRef}
+ return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : (
+ <div
+ ref={this._spanRef}
className="dash-span"
style={{
width: this.props.width,
height: this.props.height,
position: 'absolute',
- display: 'inline-block'
+ display: 'inline-block',
}}
onPointerLeave={this.onPointerLeave}
onPointerEnter={this.onPointerEnter}
onKeyDown={this.onKeyDown}
onKeyPress={e => e.stopPropagation()}
onKeyUp={e => e.stopPropagation()}
- onWheel={e => e.preventDefault()}
- >
+ onWheel={e => e.preventDefault()}>
<DocumentView
Document={this._finalLayout}
DataDoc={this._resolvedDataDoc}
@@ -200,6 +210,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
ContainingCollectionView={this._textBox.props.ContainingCollectionView}
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
/>
- </div>;
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index bb3791f1e..940ed6386 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,52 +1,55 @@
-import { action, computed, IReactionDisposer, observable } from "mobx";
-import { observer } from "mobx-react";
+import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc";
-import { List } from "../../../../fields/List";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { DocServer } from "../../../DocServer";
-import { CollectionViewType } from "../../collections/CollectionView";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import { Tooltip } from "@material-ui/core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { DocServer } from '../../../DocServer';
+import { CollectionViewType } from '../../collections/CollectionView';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { Tooltip } from '@material-ui/core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
export class DashFieldView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey);
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.fontWeight = "bold";
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal;
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.fontWeight = 'bold';
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal;
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
- setTimeout(() => ReactDOM.render(<DashFieldViewInternal
- fieldKey={node.attrs.fieldKey}
- docid={node.attrs.docid}
- width={node.attrs.width}
- height={node.attrs.height}
- hideKey={node.attrs.hideKey}
- tbox={tbox}
- />, this._fieldWrapper));
- (this as any).dom = this._fieldWrapper;
+ setTimeout(() => ReactDOM.render(<DashFieldViewInternal fieldKey={node.attrs.fieldKey} docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} tbox={tbox} />, this.dom));
+ (this as any).dom = this.dom;
}
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
}
interface IDashFieldViewInternal {
@@ -72,8 +75,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._textBoxDoc = this.props.tbox.props.Document;
if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).
- then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
@@ -82,11 +84,11 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._reactionDisposer?.();
}
- public static multiValueDelimeter = ";";
+ public static multiValueDelimeter = ';';
public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
- const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === "PARAMS" ? textBoxDoc[fieldKey] : "");
- const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
- return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" };
+ const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === 'PARAMS' ? textBoxDoc[fieldKey] : '');
+ const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
+ return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' };
}
// set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -95,30 +97,40 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey);
// field value is a boolean, so use a checkbox or similar widget to display it
if (boolVal === true || boolVal === false) {
- return <input
- className="dashFieldView-fieldCheck"
- type="checkbox" checked={boolVal}
- onChange={e => {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
- Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
- }}
- />;
- }
- else // field value is a string, so display it as an editable span
- {
+ return (
+ <input
+ className="dashFieldView-fieldCheck"
+ type="checkbox"
+ checked={boolVal}
+ onChange={e => {
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked;
+ Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true);
+ }}
+ />
+ );
+ } // field value is a string, so display it as an editable span
+ else {
// bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
// use React events. Essentially, React events occur after native events have been processed, so corresponding React events
// will never fire because Prosemirror has handled the native events. So we add listeners for native events here.
- return <span className="dashFieldView-fieldSpan" contentEditable={true}
- style={{ display: strVal.length < 2 ? "inline-block" : undefined }}
- suppressContentEditableWarning={true} defaultValue={strVal}
- ref={r => {
- r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
- r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
- r?.addEventListener("pointerdown", action(e => e.stopPropagation()));
- }} >
- {strVal}
- </span>;
+ return (
+ <span
+ className="dashFieldView-fieldSpan"
+ contentEditable={true}
+ style={{ display: strVal.length < 2 ? 'inline-block' : undefined }}
+ suppressContentEditableWarning={true}
+ defaultValue={strVal}
+ ref={r => {
+ r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r));
+ r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false));
+ r?.addEventListener(
+ 'pointerdown',
+ action(e => e.stopPropagation())
+ );
+ }}>
+ {strVal}
+ </span>
+ );
}
}
}
@@ -126,11 +138,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
// we need to handle all key events on the input span or else they will propagate to prosemirror.
@action
fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
- if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database.
+ if (e.key === 'Enter') {
+ // handle the enter key by "submitting" the current text to Dash's database.
this.updateText(span.textContent!, true);
- e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
+ e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view
}
- if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
+ if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+ // handle ctrl-A to select all the text within the span
if (window.getSelection) {
const range = document.createRange();
range.selectNodeContents(span);
@@ -139,44 +153,44 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
}
e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
}
- e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
- }
+ e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror.
+ };
@action
updateText = (nodeText: string, forceMatch: boolean) => {
if (nodeText) {
- const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
+ const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText;
// look for a document whose id === the fieldKey being displayed. If there's a match, then that document
// holds the different enumerated values for the field in the titles of its collected documents.
// if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
DocServer.GetRefField(this._fieldKey).then(options => {
- let modText = "";
- (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+ let modText = '';
+ options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
if (modText) {
// elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true);
} // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
- else if (nodeText.startsWith(":=")) {
+ else if (nodeText.startsWith(':=')) {
this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
- } else if (nodeText.startsWith("=:=")) {
+ } else if (nodeText.startsWith('=:=')) {
Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
} else {
if (Number(newText).toString() === newText) {
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText);
Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true);
} else {
const splits = newText.split(DashFieldViewInternal.multiValueDelimeter);
- if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
+ if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) {
const strVal = splits.length > 1 ? new List<string>(splits) : newText;
- if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
+ if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal;
Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true);
}
}
}
});
}
- }
+ };
createPivotForField = (e: React.MouseEvent) => {
let container = this.props.tbox.props.ContainingCollectionView;
@@ -190,36 +204,39 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
if (!list) {
alias._columnHeaders = list = new List<SchemaHeaderField>();
}
- list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
- list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
- alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey;
- this.props.tbox.props.addDocTab(alias, "add:right");
+ list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
+ list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
+ alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
+ this.props.tbox.props.addDocTab(alias, 'add:right');
}
- }
-
+ };
// clicking on the label creates a pivot view collection of all documents
// in the same collection. The pivot field is the fieldKey of this label
onPointerDownLabelSpan = (e: any) => {
- setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
DashFieldViewMenu.createFieldView = this.createPivotForField;
DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16);
});
- }
+ };
render() {
- return <div className="dashFieldView" style={{
- width: this.props.width,
- height: this.props.height,
- }}>
- {this.props.hideKey ? (null) :
- <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {this._fieldKey}
- </span>}
-
- {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
+ return (
+ <div
+ className="dashFieldView"
+ style={{
+ width: this.props.width,
+ height: this.props.height,
+ }}>
+ {this.props.hideKey ? null : (
+ <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+ {this._fieldKey}
+ </span>
+ )}
- </div >;
+ {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ </div>
+ );
}
}
@observer
@@ -234,19 +251,19 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
showFields = (e: React.MouseEvent) => {
DashFieldViewMenu.createFieldView(e);
DashFieldViewMenu.Instance.fadeOut(true);
- }
+ };
public show = (x: number, y: number) => {
this.jumpTo(x, y, true);
const hideMenu = () => {
this.fadeOut(true);
- document.removeEventListener("pointerdown", hideMenu);
+ document.removeEventListener('pointerdown', hideMenu);
};
- document.addEventListener("pointerdown", hideMenu);
- }
+ document.addEventListener('pointerdown', hideMenu);
+ };
render() {
const buttons = [
- <Tooltip key="trash" title={<div className="dash-tooltip">{"Remove Link Anchor"}</div>}>
+ <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}>
<button className="antimodeMenu-button" onPointerDown={this.showFields}>
<FontAwesomeIcon icon="eye" size="lg" />
</button>
@@ -255,4 +272,4 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
return this.getElement(buttons);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 508500ab6..98d611ca6 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,38 +1,38 @@
-import EquationEditor from "equation-editor-react";
-import { IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
+import EquationEditor from 'equation-editor-react';
+import { IReactionDisposer } from 'mobx';
+import { observer } from 'mobx-react';
import * as ReactDOM from 'react-dom';
-import { Doc } from "../../../../fields/Doc";
-import { StrCast } from "../../../../fields/Types";
-import "./DashFieldView.scss";
-import { FormattedTextBox } from "./FormattedTextBox";
-import React = require("react");
+import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import React = require('react');
export class EquationView {
- _fieldWrapper: HTMLDivElement; // container for label and value
+ dom: HTMLDivElement; // container for label and value
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
- this._fieldWrapper = document.createElement("div");
- this._fieldWrapper.style.width = node.attrs.width;
- this._fieldWrapper.style.height = node.attrs.height;
- this._fieldWrapper.style.position = "relative";
- this._fieldWrapper.style.display = "inline-block";
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('div');
+ this.dom.style.width = node.attrs.width;
+ this.dom.style.height = node.attrs.height;
+ this.dom.style.position = 'relative';
+ this.dom.style.display = 'inline-block';
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
- ReactDOM.render(<EquationViewInternal
- fieldKey={node.attrs.fieldKey}
- width={node.attrs.width}
- height={node.attrs.height}
- setEditor={this.setEditor}
- tbox={tbox}
- />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} setEditor={this.setEditor} tbox={tbox} />, this.dom);
+ (this as any).dom = this.dom;
}
_editor: EquationEditor | undefined;
- setEditor = (editor?: EquationEditor) => this._editor = editor;
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { this._editor?.mathField.focus(); }
- deselectNode() { }
+ setEditor = (editor?: EquationEditor) => (this._editor = editor);
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {
+ this._editor?.mathField.focus();
+ }
+ deselectNode() {}
}
interface IEquationViewInternal {
@@ -56,24 +56,33 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
this._textBoxDoc = this.props.tbox.props.Document;
}
- componentWillUnmount() { this._reactionDisposer?.(); }
- componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); }
+ componentWillUnmount() {
+ this._reactionDisposer?.();
+ }
+ componentDidMount() {
+ this.props.setEditor(this._ref.current ?? undefined);
+ }
render() {
- return <div className="equationView" style={{
- position: "relative",
- display: "inline-block",
- width: this.props.width,
- height: this.props.height,
- bottom: 3,
- }}>
- <EquationEditor ref={this._ref}
- value={StrCast(this._textBoxDoc[this._fieldKey], "y=")}
- onChange={str => this._textBoxDoc[this._fieldKey] = str}
- autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
- autoOperatorNames="sin cos tan"
- spaceBehavesLikeTab={true}
- />
- </div >;
+ return (
+ <div
+ className="equationView"
+ style={{
+ position: 'relative',
+ display: 'inline-block',
+ width: this.props.width,
+ height: this.props.height,
+ bottom: 3,
+ }}>
+ <EquationEditor
+ ref={this._ref}
+ value={StrCast(this._textBoxDoc[this._fieldKey], 'y=')}
+ onChange={str => (this._textBoxDoc[this._fieldKey] = str)}
+ autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
+ autoOperatorNames="sin cos tan"
+ spaceBehavesLikeTab={true}
+ />
+ </div>
+ );
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 27227c152..61940ac09 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,89 +1,90 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { baseKeymap, selectAll } from "prosemirror-commands";
-import { history } from "prosemirror-history";
+import { isEqual } from 'lodash';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { baseKeymap, selectAll } from 'prosemirror-commands';
+import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
-import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
+import { keymap } from 'prosemirror-keymap';
+import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
-import { RichTextField } from "../../../../fields/RichTextField";
+import { RichTextField } from '../../../../fields/RichTextField';
import { RichTextUtils } from '../../../../fields/RichTextUtils';
import { ComputedField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
-import { DocServer } from "../../../DocServer";
+import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from "../../../util/DragManager";
+import { DragManager } from '../../../util/DragManager';
import { MakeTemplate } from '../../../util/DropConverter';
import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from "../../../util/SelectionManager";
+import { SelectionManager } from '../../../util/SelectionManager';
import { SnappingManager } from '../../../util/SnappingManager';
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from "../../DocComponent";
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { DocumentButtonBar } from '../../DocumentButtonBar';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { FieldView, FieldViewProps } from "../FieldView";
+import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
-import { DashDocCommentView } from "./DashDocCommentView";
-import { DashDocView } from "./DashDocView";
-import { DashFieldView } from "./DashFieldView";
-import { EquationView } from "./EquationView";
-import { FootnoteView } from "./FootnoteView";
-import "./FormattedTextBox.scss";
+import { DashDocCommentView } from './DashDocCommentView';
+import { DashDocView } from './DashDocView';
+import { DashFieldView } from './DashFieldView';
+import { EquationView } from './EquationView';
+import { FootnoteView } from './FootnoteView';
+import './FormattedTextBox.scss';
import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { OrderedListView } from "./OrderedListView";
-import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer";
-import { removeMarkWithAttrs } from "./prosemirrorPatches";
+import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+import { removeMarkWithAttrs } from './prosemirrorPatches';
import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
-import { RichTextRules } from "./RichTextRules";
-import { schema } from "./schema_rts";
-import { SummaryView } from "./SummaryView";
-import applyDevTools = require("prosemirror-dev-tools");
-import React = require("react");
-const translateGoogleApi = require("translate-google-api");
+import { RichTextRules } from './RichTextRules';
+import { schema } from './schema_rts';
+import { SummaryView } from './SummaryView';
+import applyDevTools = require('prosemirror-dev-tools');
+import React = require('react');
+const translateGoogleApi = require('translate-google-api');
export interface FormattedTextBoxProps {
- makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
- xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
+ makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
+ xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yPadding?: number;
noSidebar?: boolean;
dontScale?: boolean;
dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field)
}
-export const GoogleRef = "googleDocId";
+export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() {
- public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+ public static LayoutString(fieldStr: string) {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+ static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items'];
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: string = "";
+ private _applyingChange: string = '';
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
private _cachedLinks: Doc[] = [];
@@ -102,7 +103,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _ignoreScroll = false;
- private _lastText = "";
+ private _lastText = '';
private _focusSpeed: Opt<number>;
private _keymap: any = undefined;
private _rules: RichTextRules | undefined;
@@ -113,21 +114,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _downY = 0;
private _break = true;
public ProseRef?: HTMLDivElement;
- public get EditorView() { return this._editorView; }
- public get SidebarKey() { return this.fieldKey + "-sidebar"; }
- @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); }
-
- @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
- @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
- @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; }
- @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); }
- @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
- @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
- @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); }
- @computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
+ public get EditorView() {
+ return this._editorView;
+ }
+ public get SidebarKey() {
+ return this.fieldKey + '-sidebar';
+ }
+ @computed get allSidebarDocs() {
+ return DocListCast(this.dataDoc[this.SidebarKey]);
+ }
+
+ @computed get sidebarWidthPercent() {
+ return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%');
+ }
+ @computed get sidebarColor() {
+ return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4'));
+ }
+ @computed get autoHeight() {
+ return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight;
+ }
+ @computed get textHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-height']);
+ }
+ @computed get scrollHeight() {
+ return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']);
+ }
+ @computed get sidebarHeight() {
+ return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']);
+ }
+ @computed get titleHeight() {
+ return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+ }
+ @computed get autoHeightMargins() {
+ return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins);
+ }
+ @computed get _recording() {
+ return this.dataDoc?.mediaState === 'recording';
+ }
set _recording(value) {
- !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
+ !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined);
}
@computed get config() {
this._keymap = buildKeymap(schema, this.props);
@@ -140,28 +165,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
history(),
keymap(this._keymap),
keymap(baseKeymap),
- new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }),
- new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
- ]
+ new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
+ new Plugin({
+ view(editorView) {
+ return new FormattedTextBoxComment(editorView);
+ },
+ }),
+ ],
};
}
public static PasteOnLoad: ClipboardEvent | undefined;
- public static SelectOnLoad = "";
+ public static SelectOnLoad = '';
public static DontSelectInitialText = false; // whether initial text should be selected or not
- public static SelectOnLoadChar = "";
- public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; }
+ public static SelectOnLoadChar = '';
+ public static IsFragment(html: string) {
+ return html.indexOf('data-pm-slice') !== -1;
+ }
public static GetHref(html: string): string {
const parser = new DOMParser();
const parsedHtml = parser.parseFromString(html, 'text/html');
- if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
- (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+ if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
}
- return "";
+ return '';
}
public static GetDocFromUrl(url: string) {
- return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid
}
constructor(props: any) {
@@ -172,7 +202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
// removes all hyperlink anchors for the removed linkDoc
- // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
+ // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
public RemoveLinkFromDoc(linkDoc?: Doc) {
this.unhighlightSearchTerms();
@@ -195,9 +225,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- // removes all the specified link references from the selection.
+ // removes all the specified link references from the selection.
// NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
- public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) {
+ public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) {
const state = this._editorView?.state;
if (state && this._editorView) {
this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
@@ -205,11 +235,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
- getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+ getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection');
@action
setupAnchorMenu = () => {
- AnchorMenu.Instance.Status = "marquee";
+ AnchorMenu.Instance.Status = 'marquee';
AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
!this.layoutDoc.showSidebar && this.toggleSidebar();
@@ -222,14 +252,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.onMakeAnchor = this.getAnchor;
AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
/**
- * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
+ * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation.
* It also initiates a Drag/Drop interaction to place the text annotation.
*/
AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => {
e.preventDefault();
e.stopPropagation();
const targetCreator = (annotationOn?: Doc) => {
- const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
+ const target = CurrentUserUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn);
FormattedTextBox.SelectOnLoad = target[Id];
return target;
};
@@ -238,55 +268,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
- }
+ };
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
- const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box
+ const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
- const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ?
- json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
+ const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"'));
if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
const accumTags = [] as string[];
state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
- if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
+ if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
accumTags.push(node.attrs.fieldKey);
}
});
- const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#"));
+ const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#'));
const added = accumTags.filter(tag => !curTags.includes(tag));
const removed = curTags.filter(tag => !accumTags.includes(tag));
- removed.forEach(r => this.dataDoc[r] = undefined);
- added.forEach(a => this.dataDoc[a] = a);
+ removed.forEach(r => (this.dataDoc[r] = undefined));
+ added.forEach(a => (this.dataDoc[a] = a));
let unchanged = true;
if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
this._applyingChange = this.fieldKey;
- (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
- if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+ curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())));
+ if ((!curTemp && !curProto) || curText || json.includes('dash')) {
+ // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+ } else {
+ // if we've deleted all the text in a note driven by a template, then restore the template data
this.dataDoc[this.props.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
- this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have
ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
- this._applyingChange = "";
+ this._applyingChange = '';
if (!unchanged) {
this.updateTitle();
this.tryUpdateScrollHeight();
@@ -304,16 +335,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
AnchorMenu.Instance.fadeOut(true);
}
}
- }
+ };
- // for inserting timestamps
+ // for inserting timestamps
insertTime = () => {
let linkTime;
let linkAnchor;
let link;
DocListCast(this.dataDoc.links).forEach((l, i) => {
- const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
- if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") {
+ const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
+ if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
linkAnchor = anchor;
link = l;
@@ -344,7 +375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
}
}
- }
+ };
autoLink = () => {
if (this._editorView?.state.doc.textContent) {
@@ -355,38 +386,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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);
- DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks));
+ DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
this._editorView?.dispatch(tr);
oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
}
- }
+ };
updateTitle = () => {
const title = StrCast(this.dataDoc.title);
- if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
- (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
- (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
+ if (
+ !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ (title.startsWith('-') || title.startsWith('@')) &&
+ this._editorView &&
+ !this.dataDoc['title-custom'] &&
+ (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text')
+ ) {
let node = this._editorView.state.doc;
- while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild;
+ while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
const str = node.textContent;
- const prefix = str.startsWith("@") ? "" : "-";
+ const prefix = str.startsWith('@') ? '' : '-';
const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
if (!(cfield instanceof ComputedField)) {
- this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : "");
- if (str.startsWith("@") && str.length > 1) {
+ this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '');
+ if (str.startsWith('@') && str.length > 1) {
Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc);
}
}
}
- }
+ };
// creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
- const autoLinkTerm = StrCast(target.title).replace(/^@/, "");
+ const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
var alink: Doc | undefined;
flattened1.forEach((flat, i) => {
@@ -397,14 +432,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(sel.from, sel.to, splitter);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
- alink = alink ?? (DocListCast(this.Document.links).find(link =>
- Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
- Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target },
- LinkManager.AutoKeywords)!);
+ alink =
+ alink ??
+ (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
newAutoLinks.add(alink);
- const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
- const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" });
+ const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
@@ -412,13 +447,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
}
return tr;
- }
+ };
@action
search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
if (clear) this.unhighlightSearchTerms();
else this.highlightSearchTerms([searchString], bwd!);
return true;
- }
+ };
highlightSearchTerms = (terms: string[], backward: boolean) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -432,21 +467,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (backward === true) {
if (this._searchIndex > 1) {
this._searchIndex += -2;
- }
- else if (this._searchIndex === 1) {
+ } else if (this._searchIndex === 1) {
this._searchIndex = length - 1;
- }
- else if (this._searchIndex === 0 && length !== 1) {
+ } else if (this._searchIndex === 0 && length !== 1) {
this._searchIndex = length - 2;
}
-
}
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
- flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
+ flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
}
- }
+ };
unhighlightSearchTerms = () => {
if (window.screen.width < 600) null;
@@ -455,20 +487,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
-
}
if (FormattedTextBox.PasteOnLoad) {
- const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion');
FormattedTextBox.PasteOnLoad = undefined;
setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10);
}
- }
+ };
adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
- }
+ };
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer?.();
this.ProseRef = ele;
@@ -476,8 +507,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.setupEditor(this.config, this.props.fieldKey);
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
}
- // if (this.autoHeight) this.tryUpdateScrollHeight();
- }
+ // if (this.autoHeight) this.tryUpdateScrollHeight();
+ };
@undoBatch
@action
@@ -499,18 +530,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const node = schema.nodes.dashDoc.create({
width: target[WidthSym](),
height: target[HeightSym](),
- title: "dashDoc",
+ title: 'dashDoc',
docid: target[Id],
- float: "unset"
+ float: 'unset',
});
const view = this._editorView!;
view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
e.stopPropagation();
} // otherwise, fall through to outer collection to handle drop
}
- }
+ };
- getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
+ getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null {
let offset = 0;
if (context === node) return { from: offset, to: offset + node.nodeSize };
@@ -521,8 +552,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const result = this.getNodeEndpoints((context.content as any).content[i], node);
if (result) {
return {
- from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
- to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
+ from: result.from + offset + (context.type.name === 'doc' ? 0 : 1),
+ to: result.to + offset + (context.type.name === 'doc' ? 0 : 1),
};
}
offset += (context.content as any).content[i].nodeSize;
@@ -538,9 +569,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0, foundAt;
+ let index = 0,
+ foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- const regexp = new RegExp(find.replace("*", ""), "i");
+ const regexp = new RegExp(find.replace('*', ''), 'i');
if (regexp) {
while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
@@ -549,7 +581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
} else {
- node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
+ node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
}
return ret;
}
@@ -557,83 +589,84 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
updateHighlights = () => {
const highlights = FormattedTextBox._globalHighlights;
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (highlights.indexOf("Audio Tags") === -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, "");
+ if (highlights.indexOf('Audio Tags') === -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
}
- if (highlights.indexOf("Text from Others") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
+ if (highlights.indexOf('Text from Others') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
}
- if (highlights.indexOf("My Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+ if (highlights.indexOf('My Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' });
}
- if (highlights.indexOf("Todo Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
+ if (highlights.indexOf('Todo Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' });
}
- if (highlights.indexOf("Important Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
+ if (highlights.indexOf('Important Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' });
}
- if (highlights.indexOf("Bold Text") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, "");
- addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, "");
+ if (highlights.indexOf('Bold Text') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, '');
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
}
- if (highlights.indexOf("Disagree Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
+ if (highlights.indexOf('Disagree Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' });
}
- if (highlights.indexOf("Ignore Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
+ if (highlights.indexOf('Ignore Items') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' });
}
- if (highlights.indexOf("By Recent Minute") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Minute') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const min = Math.round(Date.now() / 1000 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(this.updateHighlights);
}
- if (highlights.indexOf("By Recent Hour") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ if (highlights.indexOf('By Recent Hour') !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
- numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
- }
+ };
@observable _showSidebar = false;
- @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; }
+ @computed get SidebarShown() {
+ return this._showSidebar || this.layoutDoc._showSidebar ? true : false;
+ }
@action
toggleSidebar = (preview: boolean = false) => {
const prevWidth = this.sidebarWidth();
if (preview) this._showSidebar = true;
- else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%";
+ else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
- }
+ };
sidebarDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false);
- }
+ };
sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
const bounds = this._ref.current!.getBoundingClientRect();
- this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
- this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
+ this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%';
e.preventDefault();
return false;
- }
-
+ };
@undoBatch
- deleteAnnotation = (anchor:Doc) => {
+ deleteAnnotation = (anchor: Doc) => {
LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]);
// const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
// this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
// AnchorMenu.Instance.fadeOut(true);
this.props.select(false);
- }
+ };
@undoBatch
- pinToPres = (anchor:Doc) => this.props.pinToPres(anchor)
+ pinToPres = (anchor: Doc) => this.props.pinToPres(anchor);
@undoBatch
- makePushpin = (anchor:Doc) => anchor.isPushpin = !anchor.isPushpin
+ makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
- isPushpin = (anchor:Doc) => BoolCast(anchor.isPushpin);
+ isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
@@ -643,115 +676,135 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
if (target) {
- const hrefs = (target.dataset?.targethrefs as string)?.trim().split(" ").filter(h => h);
- const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), "").split("?")[0];
+ const hrefs = (target.dataset?.targethrefs as string)
+ ?.trim()
+ .split(' ')
+ .filter(h => h);
+ const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0];
e.persist();
- anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
- AnchorMenu.Instance.Status = "annotation";
- AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc);
- AnchorMenu.Instance.Pinned = false;
- AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc);
- AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc);
- AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc);
- AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
- }));
+ anchorDoc &&
+ DocServer.GetRefField(anchorDoc).then(
+ action(anchor => {
+ AnchorMenu.Instance.Status = 'annotation';
+ AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc);
+ AnchorMenu.Instance.Pinned = false;
+ AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc);
+ AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc);
+ AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc);
+ AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+ })
+ );
e.stopPropagation();
return;
}
const changeItems: ContextMenuProps[] = [];
changeItems.push({
- description: "plain", event: undoBatch(() => {
+ description: 'plain',
+ event: undoBatch(() => {
Doc.setNativeView(this.rootDoc);
this.layoutDoc.autoHeightMargins = undefined;
- }), icon: "eye"
+ }),
+ icon: 'eye',
});
changeItems.push({
- description: "metadata", event: undoBatch(() => {
+ description: 'metadata',
+ event: undoBatch(() => {
this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
- this.rootDoc.layoutKey = "layout_meta";
- setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50);
- }), icon: "eye"
+ this.rootDoc.layoutKey = 'layout_meta';
+ setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50);
+ }),
+ icon: 'eye',
});
- const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+ const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null);
DocListCast(noteTypesDoc?.data).forEach(note => {
const icon: IconProp = StrCast(note.icon) as IconProp;
changeItems.push({
- description: StrCast(note.title), event: undoBatch(() => {
+ description: StrCast(note.title),
+ event: undoBatch(() => {
this.layoutDoc.autoHeightMargins = undefined;
Doc.setNativeView(this.rootDoc);
DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
- }), icon: icon
+ }),
+ icon: icon,
});
});
const highlighting: ContextMenuProps[] = [];
- const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"];
- const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
+ const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
+ const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
(Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option,
+ event: () => {
e.stopPropagation();
if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
FormattedTextBox._globalHighlights.push(option);
} else {
FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
}
- runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(""));
+ runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join('')));
this.updateHighlights();
- }, icon: "expand-arrows-alt"
- }));
+ },
+ icon: 'expand-arrows-alt',
+ })
+ );
const uicontrols: ContextMenuProps[] = [];
- !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" });
- uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" });
- uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
- uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
- !Doc.noviceMode && uicontrols.push({
- description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
- proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
- });
- cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+ !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
+ uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+ !Doc.noviceMode &&
+ uicontrols.push({
+ description: 'Broadcast Message',
+ event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)),
+ icon: 'expand-arrows-alt',
+ });
+ cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' });
- const appearance = cm.findByDescription("Appearance...");
- const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
- appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
+ const appearance = cm.findByDescription('Appearance...');
+ const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
+ appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
// this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
- !Doc.noviceMode && appearanceItems.push({
- description: "Make Default Layout", event: () => {
- if (!this.layoutDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- MakeTemplate(this.rootDoc, true, title);
- } else if (!this.rootDoc.isTemplateDoc) {
- const title = StrCast(this.rootDoc.title);
- this.rootDoc.title = "text";
- this.rootDoc.layout = this.layoutDoc.layout as string;
- this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
- this.rootDoc.isTemplateDoc = false;
- this.rootDoc.isTemplateForField = "";
- this.rootDoc.layoutKey = "layout";
- MakeTemplate(this.rootDoc, true, title);
- setTimeout(() => {
- this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
- this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
- this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
- this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
- this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null);
- }, 10);
- }
- Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
- Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
- }, icon: "eye"
- });
- cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Make Default Layout',
+ event: () => {
+ if (!this.layoutDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ MakeTemplate(this.rootDoc, true, title);
+ } else if (!this.rootDoc.isTemplateDoc) {
+ const title = StrCast(this.rootDoc.title);
+ this.rootDoc.title = 'text';
+ this.rootDoc.layout = this.layoutDoc.layout as string;
+ this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
+ this.rootDoc.isTemplateDoc = false;
+ this.rootDoc.isTemplateForField = '';
+ this.rootDoc.layoutKey = 'layout';
+ MakeTemplate(this.rootDoc, true, title);
+ setTimeout(() => {
+ this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
+ this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template
+ this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields
+ this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null);
+ this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null);
+ }, 10);
+ }
+ Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc);
+ Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc);
+ },
+ icon: 'eye',
+ });
+ cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
- const options = cm.findByDescription("Options...");
- const optionItems = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
- optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+ const options = cm.findByDescription('Options...');
+ const optionItems = options && 'subitems' in options ? options.subitems : [];
+ optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' });
+ optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' });
+ !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
- }
+ };
breakupDictation = () => {
if (this._editorView && this._recording) {
@@ -760,12 +813,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const state = this._editorView.state;
const to = state.selection.to;
const updated = TextSelection.create(state.doc, to, to);
- this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to));
+ this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
if (this._recording) {
this.recordDictation();
}
}
- }
+ };
recordDictation = () => {
DictationManager.Controls.listen({
interimHandler: this.setDictationContent,
@@ -775,26 +828,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DictationManager.Controls.stop();
}
});
- }
+ };
stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
setDictationContent = (value: string) => {
if (this._editorView && this._recordingStart) {
if (this._break) {
- const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" });
+ const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
this.addDocument(textanchor);
const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();
link && (Doc.GetProto(link).isDictation = true);
if (!link) return;
const audioanchor = Cast(link.anchor2, Doc, null);
if (!audioanchor) return;
- audioanchor.backgroundColor = "tan";
+ audioanchor.backgroundColor = 'tan';
const audiotag = this._editorView.state.schema.nodes.audiotag.create({
timeCode: NumCast(audioanchor._timecodeToShow),
audioId: audioanchor[Id],
- textId: textanchor[Id]
+ textId: textanchor[Id],
});
- Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode;
+ Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -804,7 +857,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tr = this._editorView.state.tr.insertText(value);
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
}
- }
+ };
// TODO: nda -- Look at how link anchors are added
makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
@@ -814,7 +867,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
@@ -825,7 +878,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
- this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
+ this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
return anchor;
@@ -837,7 +890,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
scrollFocus = (textAnchor: Doc, smooth: boolean) => {
let didToggle = false;
- if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) {
+ if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
this.toggleSidebar(!smooth);
didToggle = true;
}
@@ -868,7 +921,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
};
let start = 0;
@@ -886,59 +939,69 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
- addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow", transform: "scale(3)", "transform-origin": "left bottom" });
- setTimeout(() => this._focusSpeed = undefined, this._focusSpeed);
+ addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
+ setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
}
}
- return this._didScroll ? this._focusSpeed : didToggle ? 1: undefined; // if we actually scrolled, then return some focusSpeed
- }
+ return this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; // if we actually scrolled, then return some focusSpeed
+ };
getScrollHeight = () => this.scrollHeight;
// if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
// Since we also monitor all component height changes, this will update the document's height.
resetNativeHeight = (scrollHeight: number) => {
const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
- this.rootDoc[this.fieldKey + "-height"] = scrollHeight;
+ this.rootDoc[this.fieldKey + '-height'] = scrollHeight;
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
- }
+ };
- @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;}
+ @computed get contentScaling() {
+ return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;
+ }
componentDidMount() {
!this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation);
- this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
- this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
+ this._disposers.autoHeight = reaction(
+ () => this.autoHeight,
+ autoHeight => autoHeight && this.tryUpdateScrollHeight()
+ );
+ this._disposers.scrollHeight = reaction(
+ () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
({ width, scrollHeight, autoHeight }) => {
width && autoHeight && this.resetNativeHeight(scrollHeight);
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
- this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
+ this._disposers.componentHeights = reaction(
+ // set the document height when one of the component heights changes and autoHeight is on
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)));
- }, { fireImmediately: true });
- this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ },
+ { fireImmediately: true }
+ );
+ this._disposers.links = reaction(
+ () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
- });
+ }
+ );
this._disposers.buttonBar = reaction(
() => DocumentButtonBar.Instance,
instance => {
if (instance) {
this.pullFromGoogleDoc(this.checkState);
- this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true);
+ this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));
}
}
);
this._disposers.editorState = reaction(
() => {
- const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined :
- this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ?
- this.dataDoc : this.layoutDoc;
+ const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc;
return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };
},
incomingValue => {
@@ -953,7 +1016,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
}
}
- },
+ }
);
this._disposers.pullDoc = reaction(
() => this.props.Document[Pulls],
@@ -974,13 +1037,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
- this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc),
- search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(),
- { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false });
+ this._disposers.search = reaction(
+ () => Doc.IsSearchMatch(this.rootDoc),
+ search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
+ { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }
+ );
- this._disposers.selected = reaction(() => this.props.isSelected(),
+ this._disposers.selected = reaction(
+ () => this.props.isSelected(),
action(selected => {
- this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : "";
+ this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : '';
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -988,21 +1054,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
this.autoLink();
}
- }), { fireImmediately: true });
+ }),
+ { fireImmediately: true }
+ );
if (!this.props.dontRegisterView) {
- this._disposers.record = reaction(() => this._recording,
+ this._disposers.record = reaction(
+ () => this._recording,
() => {
this.stopDictation(true);
if (this._recording) {
this.recordDictation();
}
- },
+ }
);
if (this._recording) setTimeout(this.recordDictation);
}
- var quickScroll: string | undefined = "";
- this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
+ var quickScroll: string | undefined = '';
+ this._disposers.scroll = reaction(
+ () => NumCast(this.layoutDoc._scrollTop),
pos => {
if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
@@ -1015,7 +1085,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._scrollRef.current.scrollTo({ top: pos });
}
}
- }, { fireImmediately: true }
+ },
+ { fireImmediately: true }
);
quickScroll = undefined;
this.tryUpdateScrollHeight();
@@ -1025,7 +1096,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
const modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
- let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');
if (!reference) {
mode = modes.Insert;
reference = { title: StrCast(this.dataDoc.title) };
@@ -1035,7 +1106,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
response && (this.dataDoc[GoogleRef] = response.documentId);
- const pushSuccess = response !== undefined && !("errors" in response);
+ const pushSuccess = response !== undefined && !('errors' in response);
dataDoc.googleDocUnchanged = pushSuccess;
DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
}
@@ -1044,15 +1115,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (exportState && reference) {
const content: GoogleApiClientUtils.Docs.Content = {
text: exportState.text,
- requests: []
+ requests: [],
};
GoogleApiClientUtils.Docs.write({ reference, content, mode });
}
};
- UndoManager.AddEvent({ undo, redo, prop: "" });
+ UndoManager.AddEvent({ undo, redo, prop: '' });
redo();
});
- }
+ };
pullFromGoogleDoc = async (handler: PullHandler) => {
const dataDoc = this.dataDoc;
@@ -1062,7 +1133,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
}
exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
- }
+ };
updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
let pullSuccess = false;
@@ -1077,13 +1148,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}, 0);
dataDoc.title = exportState.title;
- this.dataDoc["title-custom"] = true;
+ this.dataDoc['title-custom'] = true;
dataDoc.googleDocUnchanged = true;
} else {
delete dataDoc[GoogleRef];
}
DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
- }
+ };
checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
if (exportState && this._editorView) {
@@ -1093,56 +1164,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
dataDoc.googleDocUnchanged = unchanged;
DocumentButtonBar.Instance.setPullState(unchanged);
}
- }
+ };
clipboardTextSerializer = (slice: Slice): string => {
- let text = "", separated = true;
- const from = 0, to = slice.content.size;
- slice.content.nodesBetween(from, to, (node, pos) => {
- if (node.isText) {
- text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
- separated = false;
- } else if (!separated && node.isBlock) {
- text += "\n";
- separated = true;
- } else if (node.type.name === "hard_break") {
- text += "\n";
- }
- }, 0);
+ let text = '',
+ separated = true;
+ const from = 0,
+ to = slice.content.size;
+ slice.content.nodesBetween(
+ from,
+ to,
+ (node, pos) => {
+ if (node.isText) {
+ text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
+ separated = false;
+ } else if (!separated && node.isBlock) {
+ text += '\n';
+ separated = true;
+ } else if (node.type.name === 'hard_break') {
+ text += '\n';
+ }
+ },
+ 0
+ );
return text;
- }
+ };
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
const cbe = event as ClipboardEvent;
- const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin");
- const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion");
+ const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin');
+ const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion');
return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false;
- }
+ };
addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => {
const view = this._editorView!;
if (pdfDocId && pdfRegionId) {
DocServer.GetRefField(pdfDocId).then(pdfDoc => {
DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
- if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+ if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) {
setTimeout(async () => {
const targetField = Doc.LayoutFieldKey(pdfDoc);
- const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+ const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations
if (targetAnnotations) targetAnnotations.push(pdfRegion);
- else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion);
+ else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion);
});
- const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted");
+ const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted');
if (link) {
const linkId = link[Id];
- const quote = view.state.schema.nodes.blockquote.create();
- quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+ const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) });
const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0);
if (slice) {
- view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+ view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste'));
} else {
selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView()));
-
}
}
}
@@ -1152,31 +1228,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return false;
-
function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
const nodes: Node[] = [];
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
-
function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
return node.copy(content);
}
const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type.name === "link");
+ const linkIndex = marks.findIndex(mark => mark.type.name === 'link');
const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
- }
+ };
isActiveTab(el: Element | null | undefined) {
while (el && el !== document.body) {
- if (getComputedStyle(el).display === "none") return false;
+ if (getComputedStyle(el).display === 'none') return false;
el = el.parentNode as any;
}
return true;
@@ -1188,7 +1262,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
view(newView) {
runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
return new RichTextMenuPlugin({ editorProps: this.props });
- }
+ },
});
}
_didScroll = false;
@@ -1200,7 +1274,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView?.destroy();
this._editorView = new EditorView(this.ProseRef, {
state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
- handleScrollToSelection: (editorView) => {
+ handleScrollToSelection: editorView => {
const docPos = editorView.coordsAtPos(editorView.state.selection.to);
const viewRect = self._ref.current!.getBoundingClientRect();
const scrollRef = self._scrollRef.current;
@@ -1220,13 +1294,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); },
- dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); },
- dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); },
- equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); },
- summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); },
- ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
- footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }
+ dashComment(node: any, view: any, getPos: any) {
+ return new DashDocCommentView(node, view, getPos);
+ },
+ dashDoc(node: any, view: any, getPos: any) {
+ return new DashDocView(node, view, getPos, self);
+ },
+ dashField(node: any, view: any, getPos: any) {
+ return new DashFieldView(node, view, getPos, self);
+ },
+ equation(node: any, view: any, getPos: any) {
+ return new EquationView(node, view, getPos, self);
+ },
+ summary(node: any, view: any, getPos: any) {
+ return new SummaryView(node, view, getPos);
+ },
+ //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
+ footnote(node: any, view: any, getPos: any) {
+ return new FootnoteView(node, view, getPos);
+ },
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
@@ -1237,9 +1323,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (startupText) {
dispatch(state.tr.insertText(startupText));
}
- const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left"));
- if (textAlign !== "left") {
- selectAll(this._editorView.state, (tr) => {
+ const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left'));
+ if (textAlign !== 'left') {
+ selectAll(this._editorView.state, tr => {
this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign })));
});
}
@@ -1250,14 +1336,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
- FormattedTextBox.SelectOnLoad = "";
+ FormattedTextBox.SelectOnLoad = '';
this.props.select(false);
if (selLoadChar && this._editorView) {
const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
const storedMarks = [...curMarks.filter(m => 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 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);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
selectAll(this._editorView.state, this._editorView?.dispatch);
@@ -1270,14 +1359,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
selectOnLoad && this._editorView!.focus();
// 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.
if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []),
- schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
- ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
- ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []),
- ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []),
- ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
- ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
- ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])];
+ this._editorView.state.storedMarks = [
+ ...(this._editorView.state.storedMarks ?? []),
+ schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }),
+ ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
+ ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
+ ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
+ ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []),
+ ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []),
+ ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
+ ];
}
}
@@ -1288,17 +1379,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.unhighlightSearchTerms();
this._editorView?.destroy();
RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
}
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.nativeEvent as any).handledByInnerReactInstance)) {
+ if ((e.nativeEvent as any).handledByInnerReactInstance) {
return e.stopPropagation();
} else (e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
- if ((e.target as any).tagName === "AUDIOTAG") {
+ if ((e.target as any).tagName === 'AUDIOTAG') {
e.preventDefault();
e.stopPropagation();
const timecode = Number((e.target as any)?.dataset?.timecode);
@@ -1309,10 +1400,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const func = () => {
const docView = DocumentManager.Instance.getDocumentView(audiodoc);
if (!docView) {
- this.props.addDocTab(audiodoc, "add:bottom");
+ this.props.addDocTab(audiodoc, 'add:bottom');
setTimeout(func);
- }
- else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that
+ } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
};
func();
}
@@ -1328,23 +1418,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
+ // stop propagation if not in sidebar
// bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView,
// but that's changed, so this shouldn't be needed.
//e.stopPropagation(); // if the text box is selected, then it consumes all down events
- document.addEventListener("pointerup", this.onSelectEnd);
- document.addEventListener("pointermove", this.onSelectMove);
+ document.addEventListener('pointerup', this.onSelectEnd);
+ document.addEventListener('pointermove', this.onSelectMove);
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
- }
+ };
onSelectMove = (e: PointerEvent) => e.stopPropagation();
onSelectEnd = (e: PointerEvent) => {
- document.removeEventListener("pointerup", this.onSelectEnd);
- document.removeEventListener("pointermove", this.onSelectMove);
- }
+ document.removeEventListener('pointerup', this.onSelectEnd);
+ document.removeEventListener('pointermove', this.onSelectMove);
+ };
onPointerUp = (e: React.PointerEvent): void => {
if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu();
if (!this._downEvent) return;
@@ -1361,13 +1452,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.button === 0 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
@action
onDoubleClick = (e: React.MouseEvent): void => {
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
- e.stopPropagation(); // if the text box is selected, then it consumes all click events
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
+ // stop propagation if not in sidebar
+ e.stopPropagation(); // if the text box is selected, then it consumes all click events
}
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -1378,18 +1470,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
- }
+ };
setFocus = () => {
const pos = this._editorView?.state.selection.$from.pos || 1;
(this.ProseRef?.children?.[0] as any).focus();
setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
- }
+ };
@action
onFocused = (e: React.FocusEvent): void => {
//applyDevTools.applyDevTools(this._editorView);
FormattedTextBox.Focused = this;
this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
- }
+ };
@observable public static Focused: FormattedTextBox | undefined;
onClick = (e: React.MouseEvent): void => {
@@ -1401,7 +1493,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._forceDownNode = undefined;
return;
}
- if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+ if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
+ // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
@@ -1411,17 +1504,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (!node && this.ProseRef) {
const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
const boundsRect = lastNode?.getBoundingClientRect();
- if (e.clientX > boundsRect.left && e.clientX < boundsRect.right &&
- e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) {
+ // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView?.focus();
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
- } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
- node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
+ } else if (node && [this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
- if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
+ if (this.props.isSelected(true)) {
+ // if text box is selected, then it consumes all click events
(e.nativeEvent as any).handledByInnerReactInstance = true;
if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it.
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment)
@@ -1429,7 +1522,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
- }
+ };
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
this._forceUncollapse = false;
@@ -1459,12 +1552,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
}
}
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' });
}
}
}
startUndoTypingBatch() {
- !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping"));
+ !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping'));
}
public endUndoTypingBatch() {
const wasUndoing = this._undoTyping;
@@ -1480,33 +1573,39 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
- FormattedTextBox._hadSelection = window.getSelection()?.toString() !== "";
+ FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
this.endUndoTypingBatch();
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
- if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+ const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) {
try {
- translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => {
- setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => {
- this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
- }), 1000);
+ translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => {
+ setTimeout(
+ () =>
+ translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => {
+ this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0];
+ }),
+ 1000
+ );
});
- } catch (e: any) { console.log(e.message); }
+ } catch (e: any) {
+ console.log(e.message);
+ }
this._lastText = curText;
}
- if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) {
+ if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) {
UndoManager.RunInBatch(() => {
- this.dataDoc["title-custom"] = true;
- this.dataDoc.showTitle = "title";
+ this.dataDoc['title-custom'] = true;
+ this.dataDoc.showTitle = 'title';
const tr = this._editorView!.state.tr;
this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
- }, "titler");
+ }, 'titler');
}
- }
+ };
onKeyDown = (e: React.KeyboardEvent) => {
if (e.altKey) {
@@ -1514,7 +1613,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return;
}
const state = this._editorView!.state;
- if (!state.selection.empty && e.key === "%") {
+ if (!state.selection.empty && e.key === '%') {
this._rules!.EnteringStyle = true;
e.preventDefault();
e.stopPropagation();
@@ -1527,32 +1626,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.stopPropagation();
for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
- mark.attrs.userid !== Doc.CurrentUserEmail) &&
- [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
e.preventDefault();
}
}
switch (e.key) {
- case "Escape":
+ case 'Escape':
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
return;
- case "Enter": this.insertTime();
- case "Tab": e.preventDefault(); break;
- default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
- case " ":
- [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({}))
- .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+ case 'Enter':
+ this.insertTime();
+ case 'Tab':
+ e.preventDefault();
+ break;
+ default:
+ if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
+ case ' ':
+ [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) &&
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
}
this.startUndoTypingBatch();
- }
+ };
ondrop = (e: React.DragEvent) => {
this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
- }
+ };
onScroll = (e: React.UIEvent) => {
if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
if (!this.props.dontSelectOnLoad) {
@@ -1561,15 +1662,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._ignoreScroll = false;
}
}
- }
+ };
tryUpdateScrollHeight = () => {
const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children) {
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+ const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (this.props.setHeight && scrollHeight && this.props.renderDepth && !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.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+ if (this.props.setHeight && scrollHeight && this.props.renderDepth && !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.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight);
if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
} else {
@@ -1577,7 +1679,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
}
- }
+ };
fitContentsToBox = () => this.props.Document._fitContentsToBox;
sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
@@ -1585,40 +1687,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// console.log("printting allSideBarDocs");
// console.log(this.allSidebarDocs);
return this.addDocument(doc, sidebarKey);
- }
+ };
sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
- setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height;
- sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
- sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1));
+ setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height);
+ sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+ sidebarScreenToLocal = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0)
+ .scale(1 / NumCast(this.layoutDoc._viewScale, 1));
@computed get audioHandle() {
- return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} >
- <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" />
- </div>;
+ return (
+ <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}>
+ <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+ </div>
+ );
}
@computed get sidebarHandle() {
TraceMobx();
const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
const color = !annotated ? Colors.WHITE : Colors.BLACK;
- const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : ""));
+ const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
- return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) :
- <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown}
+ return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : (
+ <div
+ className="formattedTextBox-sidebar-handle"
+ onPointerDown={this.sidebarDown}
style={{
left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`,
backgroundColor: backgroundColor,
color: color,
- opacity: annotated ? 1 : undefined
- }} >
- <FontAwesomeIcon icon={"comment-alt"} />
- </div>;
+ opacity: annotated ? 1 : undefined,
+ }}>
+ <FontAwesomeIcon icon={'comment-alt'} />
+ </div>
+ );
}
@computed get sidebarCollection() {
const renderComponent = (tag: string) => {
- const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView;
- return ComponentTag === CollectionStackingView ?
- <SidebarAnnos ref={this._sidebarRef}
+ const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+ return ComponentTag === CollectionStackingView ? (
+ <SidebarAnnos
+ ref={this._sidebarRef}
{...this.props}
fieldKey={this.fieldKey}
rootDoc={this.rootDoc}
@@ -1633,16 +1745,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
sidebarAddDocument={this.sidebarAddDocument}
moveDocument={this.moveDocument}
removeDocument={this.removeDocument}
- /> :
+ />
+ ) : (
<ComponentTag
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.sidebarWidth}
xPadding={0}
yPadding={0}
- scaleField={this.SidebarKey + "-scale"}
+ scaleField={this.SidebarKey + '-scale'}
isAnnotationOverlay={false}
select={emptyFunction}
scaling={this.sidebarContentScaling}
@@ -1656,48 +1769,54 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setHeight={this.setSidebarHeight}
fitContentsToBox={this.fitContentsToBox}
noSidebar={true}
- fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />;
+ fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`}
+ />
+ );
};
- return <div className={"formattedTextBox-sidebar" + (CurrentUserUtils.ActiveTool !== InkTool.None ? "-inking" : "")}
- style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
- </div>;
+ return (
+ <div className={'formattedTextBox-sidebar' + (CurrentUserUtils.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+ {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+ </div>
+ );
}
render() {
TraceMobx();
const active = this.props.isContentActive() || this.props.isSelected();
const selected = active;
const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
- const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+ const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : '';
const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition);
if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide);
const minimal = this.props.ignoreAutoHeight;
const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
- const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0);
- const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : "";
- const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
- return (styleFromString?.height === "0px" ? (null) :
- <div className="formattedTextBox-cont"
+ const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0;
+ const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : '';
+ const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
+ return styleFromString?.height === '0px' ? null : (
+ <div
+ className="formattedTextBox-cont"
onWheel={e => this.props.isContentActive() && e.stopPropagation()}
style={{
transform: this.props.dontScale ? undefined : `scale(${scale})`,
- transformOrigin: this.props.dontScale ? undefined : "top left",
+ transformOrigin: this.props.dontScale ? undefined : 'top left',
width: this.props.dontScale ? undefined : `${100 / scale}%`,
height: this.props.dontScale ? undefined : `${100 / scale}%`,
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
- ...styleFromString
+ ...styleFromString,
}}>
- <div className={`formattedTextBox-cont`} ref={this._ref}
+ <div
+ className={`formattedTextBox-cont`}
+ ref={this._ref}
style={{
- overflow: this.autoHeight ? "hidden" : undefined,
- height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? "max-content" : undefined),
+ overflow: this.autoHeight ? 'hidden' : undefined,
+ height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize),
- fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any,
- fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
- pointerEvents: interactive ? undefined : "none",
+ fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any,
+ fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'),
+ pointerEvents: interactive ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
@@ -1707,31 +1826,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
- onDoubleClick={this.onDoubleClick}
- >
- <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
+ onDoubleClick={this.onDoubleClick}>
+ <div
+ className={`formattedTextBox-outer${selected ? '-selected' : ''}`}
+ ref={this._scrollRef}
style={{
- width: this.props.dontSelectOnLoad ? "100%" : `calc(100% - ${this.sidebarWidthPercent})`,
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
- overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
+ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined,
+ overflow: this.layoutDoc._singleLine ? 'hidden' : undefined,
}}
- onScroll={this.onScroll} onDrop={this.ondrop} >
- <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
+ onScroll={this.onScroll}
+ onDrop={this.ondrop}>
+ <div
+ className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`}
+ ref={this.createDropTarget}
style={{
padding: StrCast(this.layoutDoc._textBoxPadding),
paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`),
paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`),
- pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined
+ pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? 'none' : undefined) : undefined,
}}
/>
</div>
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection}
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle}
- {!this.layoutDoc._showAudio ? (null) : this.audioHandle}
+ {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+ {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
+ {!this.layoutDoc._showAudio ? null : this.audioHandle}
</div>
- </div >
+ </div>
);
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 3e673c0b2..bdf59863b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,18 +1,25 @@
-import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import { LinkDocPreview } from "../LinkDocPreview";
-import { FormattedTextBox } from "./FormattedTextBox";
+import { Mark, ResolvedPos } from 'prosemirror-model';
+import { EditorState } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
+import { LinkDocPreview } from '../LinkDocPreview';
+import { FormattedTextBox } from './FormattedTextBox';
import './FormattedTextBoxComment.scss';
-import { schema } from "./schema_rts";
+import { schema } from './schema_rts';
-export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
-export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
-export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); }
-export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let before = 0, nbef = rpos.nodeBefore;
+export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid);
+}
+export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
+ return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
+ let before = 0,
+ nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -20,8 +27,9 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
}
return before;
}
-export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let after = 0, naft = rpos.nodeAfter;
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
+ let after = 0,
+ naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -32,7 +40,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
// this will also display metadata information about text when the view is configured to display things like other people who authored text.
-//
+//
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
@@ -43,11 +51,11 @@ export class FormattedTextBoxComment {
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
- const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
- const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
- tooltip.className = "FormattedTextBox-tooltip";
- tooltipText.className = "FormattedTextBox-tooltipText";
- tooltip.style.display = "none";
+ const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
+ const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
+ tooltip.className = 'FormattedTextBox-tooltip';
+ tooltipText.className = 'FormattedTextBox-tooltipText';
+ tooltip.style.display = 'none';
tooltip.appendChild(tooltipText);
tooltip.onpointerdown = (e: PointerEvent) => {
const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
@@ -55,38 +63,47 @@ export class FormattedTextBoxComment {
e.stopPropagation();
e.preventDefault();
};
- document.getElementById("root")?.appendChild(tooltip);
+ document.getElementById('root')?.appendChild(tooltip);
}
}
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
- FormattedTextBoxComment.tooltip.style.display = "none";
+ FormattedTextBoxComment.tooltip.style.display = 'none';
}
public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.startUserMarkRegion = start;
FormattedTextBoxComment.endUserMarkRegion = end;
FormattedTextBoxComment.userMark = mark;
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.display = '';
}
static showCommentbox(view: EditorView, nbef: number) {
const state = view.state;
// These are in screen coordinates
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ const start = view.coordsAtPos(state.selection.from - nbef),
+ end = view.coordsAtPos(state.selection.from - nbef);
// The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- FormattedTextBoxComment.tooltip.style.display = "";
+ FormattedTextBoxComment.tooltip.style.left = left - box.left + 'px';
+ FormattedTextBoxComment.tooltip.style.bottom = box.bottom - start.top + 'px';
+ FormattedTextBoxComment.tooltip.style.display = '';
}
- static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") {
+ static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') {
FormattedTextBoxComment.textBox = textBox;
- if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
- FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc);
+ if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) {
+ FormattedTextBoxComment.setupPreview(
+ view,
+ textBox,
+ hrefs
+ ?.trim()
+ .split(' ')
+ .filter(h => h),
+ linkDoc
+ );
}
}
@@ -104,25 +121,27 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
- FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
+ FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + ' on ' + new Date(mark.attrs.modified * 1000).toLocaleString();
FormattedTextBoxComment.showCommentbox(view, nbef);
} else FormattedTextBoxComment.Hide();
}
- // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
+ // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
if (state.selection.$from && hrefs?.length) {
const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- nbef && naft && LinkDocPreview.SetLinkInfo({
- docProps: textBox.props,
- linkSrc: textBox.rootDoc,
- linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined,
- location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
- hrefs,
- showHeader: true
- });
+ nbef &&
+ naft &&
+ LinkDocPreview.SetLinkInfo({
+ docProps: textBox.props,
+ linkSrc: textBox.rootDoc,
+ linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
+ location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
+ hrefs,
+ showHeader: true,
+ });
}
}
- destroy() { }
-} \ No newline at end of file
+ destroy() {}
+}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c66cb502e..31552cf1b 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,17 +1,17 @@
-import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { Schema } from "prosemirror-model";
-import { splitListItem, wrapInList } from "prosemirror-schema-list";
-import { EditorState, TextSelection, Transaction } from "prosemirror-state";
-import { liftTarget } from "prosemirror-transform";
-import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc";
-import { GetEffectiveAcl } from "../../../../fields/util";
-import { Utils } from "../../../../Utils";
-import { Docs } from "../../../documents/Documents";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
-
-const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { Schema } from 'prosemirror-model';
+import { splitListItem, wrapInList } from 'prosemirror-schema-list';
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { liftTarget } from 'prosemirror-transform';
+import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
+import { GetEffectiveAcl } from '../../../../fields/util';
+import { Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
+
+const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
export type KeyMap = { [key: string]: any };
@@ -20,12 +20,12 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?:
tx2.doc.descendants((node: any, offset: any, index: any) => {
if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
const path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) {
if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
depth++;
}
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks);
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth }, node.marks);
}
});
return tx2;
@@ -45,7 +45,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const canEdit = (state: any) => {
switch (GetEffectiveAcl(props.DataDoc)) {
- case AclAugment: return false;
+ case AclAugment:
+ return false;
case AclSelfEdit:
for (var i = state.selection.from; i < state.selection.to; i++) {
const marks = state.doc.resolve(i)?.marks?.();
@@ -58,95 +59,102 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return true;
};
- const toggleEditableMark = (mark: any) => (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+ const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
//History commands
- bind("Mod-z", undo);
- bind("Shift-Mod-z", redo);
- !mac && bind("Mod-y", redo);
+ bind('Mod-z', undo);
+ bind('Shift-Mod-z', redo);
+ !mac && bind('Mod-y', redo);
//Commands to modify Mark
- bind("Mod-b", toggleEditableMark(schema.marks.strong));
- bind("Mod-B", toggleEditableMark(schema.marks.strong));
+ bind('Mod-b', toggleEditableMark(schema.marks.strong));
+ bind('Mod-B', toggleEditableMark(schema.marks.strong));
- bind("Mod-e", toggleEditableMark(schema.marks.em));
- bind("Mod-E", toggleEditableMark(schema.marks.em));
+ bind('Mod-e', toggleEditableMark(schema.marks.em));
+ bind('Mod-E', toggleEditableMark(schema.marks.em));
- bind("Mod-*", toggleEditableMark(schema.marks.code));
+ bind('Mod-*', toggleEditableMark(schema.marks.code));
- bind("Mod-u", toggleEditableMark(schema.marks.underline));
- bind("Mod-U", toggleEditableMark(schema.marks.underline));
+ bind('Mod-u', toggleEditableMark(schema.marks.underline));
+ bind('Mod-U', toggleEditableMark(schema.marks.underline));
//Commands for lists
- bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+ bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
- bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true);
- bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true);
- bind("Meta-Tab", () => props.onKey?.(event, props) ? true : true);
- bind("Meta-Enter", () => props.onKey?.(event, props) ? true : true);
- bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
+ bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const ref = state.selection;
const range = ref.$from.blockRange(ref.$to);
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) { // couldn't sink into an existing list, so wrap in a new one
- const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
- if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ if (
+ !sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
- // when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
- })) {
- console.log("bullet promote fail");
+ })
+ ) {
+ // couldn't sink into an existing list, so wrap in a new one
+ const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+ if (
+ !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ // when promoting to a list, assume list will format things so don't copy the stored marks.
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet promote fail');
}
}
});
- bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
- console.log("bullet demote fail");
+ if (
+ !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
+ console.log('bullet demote fail');
}
});
//Command to create a new Tab with a PDF of all the command shortcuts
- bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 });
- props.addDocTab(newDoc, "add:right");
+ bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+ props.addDocTab(newDoc, 'add:right');
});
//Commands to modify BlockType
- bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
- bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
- bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
+ bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
+ bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
+ bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
- bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))));
+ bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }))));
for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
+ bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
}
//Command to create a horizontal break line
const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
+ bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
//Command to unselect all
- bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
@@ -158,24 +166,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return tx;
};
-
- bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true);
- bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true);
+ bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
+ bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
// backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
- bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
- if (!deleteSelection(state, (tx: Transaction<S>) => {
- dispatch(updateBullets(tx, schema));
- })) {
- if (!joinBackward(state, (tx: Transaction<S>) => {
+ if (
+ !deleteSelection(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
- if (!selectNodeBackward(state, (tx: Transaction<S>) => {
+ })
+ ) {
+ if (
+ !joinBackward(state, (tx: Transaction) => {
dispatch(updateBullets(tx, schema));
- })) {
+ })
+ ) {
+ if (
+ !selectNodeBackward(state, (tx: Transaction) => {
+ dispatch(updateBullets(tx, schema));
+ })
+ ) {
return false;
}
}
@@ -185,8 +198,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
//newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
//command to break line
- bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-
+ bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (props.onKey?.(event, props)) return true;
if (!canEdit(state)) return true;
@@ -200,25 +212,29 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
}
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- const cr = state.selection.$from.node().textContent.endsWith("\n");
+ const cr = state.selection.$from.node().textContent.endsWith('\n');
if (cr || !newlineInCode(state, dispatch as any)) {
- if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
- const tx3 = updateBullets(tx2, schema);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
- dispatch(tx3);
- })) {
+ if (
+ !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
+ const tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
+ })
+ ) {
const fromattrs = state.selection.$from.node().attrs;
- if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
- const tonode = tx3.selection.$to.node();
- if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
- const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
- splitMetadata(marks, tx4);
- if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
- dispatch(tx4);
- }
- } else dispatch(tx3.insertText("\r\n"));
- })) {
+ if (
+ !splitBlockKeepMarks(state, (tx3: Transaction) => {
+ const tonode = tx3.selection.$to.node();
+ if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ splitMetadata(marks, tx4);
+ if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) {
+ dispatch(tx4);
+ }
+ } else dispatch(tx3.insertText('\r\n'));
+ })
+ ) {
return false;
}
}
@@ -227,16 +243,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
});
//Command to create a blank space
- bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
if (!canEdit(state)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
- bind("Alt-ArrowUp", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinUp(state, dispatch as any));
- bind("Alt-ArrowDown", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && joinDown(state, dispatch as any));
- bind("Mod-BracketLeft", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && lift(state, dispatch as any));
+ bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any));
+ bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any));
+ bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any));
const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
@@ -246,8 +262,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
return false;
});
- bind("Shift-Enter", cmd);
+ bind('Shift-Enter', cmd);
return keys;
}
-
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 98343a261..22ca76b2e 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,36 +1,35 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { lift, wrapIn } from "prosemirror-commands";
-import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model";
-import { wrapInList } from "prosemirror-schema-list";
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import { Doc } from "../../../../fields/Doc";
-import { Cast, StrCast } from "../../../../fields/Types";
-import { DocServer } from "../../../DocServer";
-import { LinkManager } from "../../../util/LinkManager";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import { FieldViewProps } from "../FieldView";
-import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import "./RichTextMenu.scss";
-import { schema } from "./schema_rts";
-const { toggleMark } = require("prosemirror-commands");
-
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { lift, wrapIn } from 'prosemirror-commands';
+import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
+import { wrapInList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { Doc } from '../../../../fields/Doc';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { DocServer } from '../../../DocServer';
+import { LinkManager } from '../../../util/LinkManager';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { undoBatch, UndoManager } from '../../../util/UndoManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { FieldViewProps } from '../FieldView';
+import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox';
+import { updateBullets } from './ProsemirrorExampleTransfer';
+import './RichTextMenu.scss';
+import { schema } from './schema_rts';
+const { toggleMark } = require('prosemirror-commands');
@observer
-export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable static Instance: RichTextMenu;
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
@observable public view?: EditorView;
- public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+ public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined;
public _brushMap: Map<string, Set<Mark>> = new Map();
@@ -43,21 +42,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable private _subscriptActive: boolean = false;
@observable private _superscriptActive: boolean = false;
- @observable private _activeFontSize: string = "13px";
- @observable private _activeFontFamily: string = "";
- @observable private activeListType: string = "";
- @observable private _activeAlignment: string = "left";
+ @observable private _activeFontSize: string = '13px';
+ @observable private _activeFontFamily: string = '';
+ @observable private activeListType: string = '';
+ @observable private _activeAlignment: string = 'left';
@observable private brushMarks: Set<Mark> = new Set();
@observable private showBrushDropdown: boolean = false;
- @observable private _activeFontColor: string = "black";
+ @observable private _activeFontColor: string = 'black';
@observable private showColorDropdown: boolean = false;
- @observable private activeHighlightColor: string = "transparent";
+ @observable private activeHighlightColor: string = 'transparent';
@observable private showHighlightDropdown: boolean = false;
- @observable private currentLink: string | undefined = "";
+ @observable private currentLink: string | undefined = '';
@observable private showLinkDropdown: boolean = false;
_reaction: IReactionDisposer | undefined;
@@ -72,24 +71,44 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
componentDidMount() {
- this._reaction = reaction(() => SelectionManager.Views(),
- () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true));
+ this._reaction = reaction(
+ () => SelectionManager.Views(),
+ () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)
+ );
}
componentWillUnmount() {
this._reaction?.();
}
- @computed get noAutoLink() { return this._noLinkActive; }
- @computed get bold() { return this._boldActive; }
- @computed get underline() { return this._underlineActive; }
- @computed get italics() { return this._italicsActive; }
- @computed get strikeThrough() { return this._strikethroughActive; }
- @computed get fontColor() { return this._activeFontColor; }
- @computed get fontFamily() { return this._activeFontFamily; }
- @computed get fontSize() { return this._activeFontSize; }
- @computed get textAlign() { return this._activeAlignment; }
+ @computed get noAutoLink() {
+ return this._noLinkActive;
+ }
+ @computed get bold() {
+ return this._boldActive;
+ }
+ @computed get underline() {
+ return this._underlineActive;
+ }
+ @computed get italics() {
+ return this._italicsActive;
+ }
+ @computed get strikeThrough() {
+ return this._strikethroughActive;
+ }
+ @computed get fontColor() {
+ return this._activeFontColor;
+ }
+ @computed get fontFamily() {
+ return this._activeFontFamily;
+ }
+ @computed get fontSize() {
+ return this._activeFontSize;
+ }
+ @computed get textAlign() {
+ return this._activeAlignment;
+ }
- public delayHide = () => this._delayHide = true;
+ public delayHide = () => (this._delayHide = true);
@action
public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
@@ -118,16 +137,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "...";
- this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "...";
+ this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle));
}
- setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => {
+ setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
if (mark) {
const node = (state.selection as NodeSelection).node;
if (node?.type === schema.nodes.ordered_list) {
@@ -140,7 +159,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
} else if (dontToggle) {
toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
const { from, $from, to, empty } = tx.selection;
- if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place
+ if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+ // hack -- should have just set the mark in the first place
toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
} else dispatch(tx);
});
@@ -148,7 +168,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
toggleMark(mark.type, mark.attrs)(state, dispatch);
}
}
- }
+ };
// finds font sizes and families in selection
getActiveAlignment() {
@@ -156,11 +176,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const path = (this.view.state.selection.$from as any).path;
for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
- return path[i].attrs.align || "left";
+ return path[i].attrs.align || 'left';
}
}
}
- return "left";
+ return 'left';
}
// finds font sizes and families in selection
@@ -176,7 +196,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
}
}
- return "";
+ return '';
}
// finds font sizes and families in selection
@@ -190,7 +210,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.TextView.props.isSelected(true)) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
- const marks: Mark<any>[] = [...(state.storedMarks ?? [])];
+ const marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.selection.empty) {
const ref_node = this.reference_node(pos);
marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
@@ -209,10 +229,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { activeFamilies, activeSizes, activeColors, activeHighlights };
}
- getMarksInSelection(state: EditorState<any>) {
+ getMarksInSelection(state: EditorState) {
const found = new Set<Mark>();
const { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+ state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m)));
return found;
}
@@ -234,14 +254,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
return false;
});
- }
- else {
+ } else {
const pos = this.view.state.selection.$from;
const ref_node: ProsNode | null = this.reference_node(pos);
if (ref_node !== null && ref_node !== this.view.state.doc) {
if (ref_node.isText) {
- }
- else {
+ } else {
return [];
}
activeMarks = markGroup.filter(mark_type => {
@@ -275,13 +293,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
activeMarks.forEach(mark => {
switch (mark.name) {
- case "noAutoLinkAnchor": this._noLinkActive = true; break;
- case "strong": this._boldActive = true; break;
- case "em": this._italicsActive = true; break;
- case "underline": this._underlineActive = true; break;
- case "strikethrough": this._strikethroughActive = true; break;
- case "subscript": this._subscriptActive = true; break;
- case "superscript": this._superscriptActive = true; break;
+ case 'noAutoLinkAnchor':
+ this._noLinkActive = true;
+ break;
+ case 'strong':
+ this._boldActive = true;
+ break;
+ case 'em':
+ this._italicsActive = true;
+ break;
+ case 'underline':
+ this._underlineActive = true;
+ break;
+ case 'strikethrough':
+ this._strikethroughActive = true;
+ break;
+ case 'subscript':
+ this._subscriptActive = true;
+ break;
+ case 'superscript':
+ this._superscriptActive = true;
+ break;
}
});
}
@@ -293,14 +325,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.TextView.autoLink();
this.view.focus();
}
- }
+ };
toggleBold = () => {
if (this.view) {
const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong);
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleUnderline = () => {
if (this.view) {
@@ -308,7 +340,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
+ };
toggleItalics = () => {
if (this.view) {
@@ -316,13 +348,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(mark, this.view.state, this.view.dispatch, false);
this.view.focus();
}
- }
-
+ };
setFontSize = (fontSize: string) => {
if (this.view) {
- if (this.view.state.selection.from === 1 && this.view.state.selection.empty &&
- (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
+ if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
this.TextView.dataDoc.fontSize = fontSize;
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
@@ -333,7 +363,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.updateMenu(this.view, undefined, this.props);
}
}
- }
+ };
setFontFamily = (family: string) => {
if (this.view) {
@@ -342,7 +372,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
}
- }
+ };
setHighlight(color: String, view: EditorView, dispatch: any) {
const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color });
@@ -362,8 +392,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
- changeListType = (nodeType: Node | undefined) => {
- if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+ changeListType = (mapStyle: string) => {
+ const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+ const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle });
+ if (!this.view || nodeType?.attrs.mapStyle === '') return;
const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
let inList: any = undefined;
@@ -377,17 +409,19 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
+ if (
+ inList ||
+ !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
- this.view!.dispatch(tx2);
- })) {
+ this.view!.dispatch(tx2);
+ })
+ ) {
const tx2 = this.view.state.tr;
if (nodeType && (inList || nextIsOL)) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
- inList ? fromList + inList.nodeSize : this.view.state.selection.to);
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
this.view.dispatch(tx3);
@@ -395,9 +429,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
this.view.focus();
this.updateMenu(this.view, undefined, this.props);
- }
+ };
- insertSummarizer(state: EditorState<any>, dispatch: any) {
+ insertSummarizer(state: EditorState, dispatch: any) {
if (state.selection.empty) return false;
const mark = state.schema.marks.summarize.create();
const tr = state.tr;
@@ -408,7 +442,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => {
+ align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
if (this.TextView.props.isSelected(true)) {
var tr = view.state.tr;
view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
@@ -422,9 +456,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
view.focus();
dispatch?.(tr);
}
- }
+ };
- insetParagraph(state: EditorState<any>, dispatch: any) {
+ insetParagraph(state: EditorState, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
@@ -437,7 +471,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
dispatch?.(tr);
return true;
}
- outsetParagraph(state: EditorState<any>, dispatch: any) {
+ outsetParagraph(state: EditorState, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
@@ -451,7 +485,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- indentParagraph(state: EditorState<any>, dispatch: any) {
+ indentParagraph(state: EditorState, dispatch: any) {
var tr = state.tr;
const heading = false;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
@@ -467,7 +501,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
+ hangingIndentParagraph(state: EditorState, dispatch: any) {
var tr = state.tr;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
@@ -482,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertBlockquote(state: EditorState<any>, dispatch: any) {
+ insertBlockquote(state: EditorState, dispatch: any) {
const path = (state.selection.$from as any).path;
if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
lift(state, dispatch);
@@ -492,20 +526,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return true;
}
- insertHorizontalRule(state: EditorState<any>, dispatch: any) {
+ insertHorizontalRule(state: EditorState, dispatch: any) {
dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
return true;
}
- @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+ @action toggleBrushDropdown() {
+ this.showBrushDropdown = !this.showBrushDropdown;
+ }
// todo: add brushes to brushMap to save with a style name
onBrushNameKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
+ if (e.key === 'Enter') {
RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
- this._brushNameRef.current!.style.background = "lightGray";
+ this._brushNameRef.current!.style.background = 'lightGray';
}
- }
+ };
_brushNameRef = React.createRef<HTMLInputElement>();
@action
@@ -514,7 +550,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
@action
- fillBrush(state: EditorState<any>, dispatch: any) {
+ fillBrush(state: EditorState, dispatch: any) {
if (!this.view) return;
if (!Array.from(this.brushMarks.keys()).length) {
@@ -522,68 +558,81 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (selected_marks.size >= 0) {
this.brushMarks = selected_marks;
}
- }
- else {
+ } else {
const { from, to, $from } = this.view.state.selection;
if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
if (to - from > 0) {
this.view.dispatch(this.view.state.tr.removeMark(from, to));
- Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- this.setMark(mark, this.view!.state, this.view!.dispatch);
- });
+ Array.from(this.brushMarks)
+ .filter(m => m.type !== schema.marks.user_mark)
+ .forEach((mark: Mark) => {
+ this.setMark(mark, this.view!.state, this.view!.dispatch);
+ });
}
}
}
}
- get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
- get TextViewFieldKey() { return this.TextView?.props.fieldKey; }
-
-
-
- @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+ get TextView() {
+ return (this.view as any)?.TextView as FormattedTextBox;
+ }
+ get TextViewFieldKey() {
+ return this.TextView?.props.fieldKey;
+ }
+ @action setActiveHighlight(color: string) {
+ this.activeHighlightColor = color;
+ }
- @action setCurrentLink(link: string) { this.currentLink = link; }
+ @action setCurrentLink(link: string) {
+ this.currentLink = link;
+ }
createLinkButton() {
const self = this;
function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
self.TextView?.endUndoTypingBatch();
- UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change");
+ UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change');
}
- const link = this.currentLink ? this.currentLink : "";
+ const link = this.currentLink ? this.currentLink : '';
- const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
- <button className="antimodeMenu-button color-preview-button">
- <FontAwesomeIcon icon="link" size="lg" />
- </button>
- </Tooltip>;
+ const button = (
+ <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
+ <button className="antimodeMenu-button color-preview-button">
+ <FontAwesomeIcon icon="link" size="lg" />
+ </button>
+ </Tooltip>
+ );
- const dropdownContent =
+ const dropdownContent = (
<div className="dropdown link-menu">
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
- <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
+ <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+ Apply hyperlink
+ </button>
<div className="divider" />
- <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
- </div>;
+ <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+ Remove link
+ </button>
+ </div>
+ );
- return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+ return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
}
async getTextLinkTargetTitle() {
if (!this.view) return;
const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type.name === "link");
+ const link = node && node.marks.find(m => m.type.name === 'link');
if (link) {
const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
if (href.indexOf(Doc.localServerPath()) === 0) {
- const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0];
+ const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0];
if (linkclicked) {
const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
@@ -612,8 +661,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
- }
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
+ };
@undoBatch
@action
@@ -624,13 +673,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const allAnchors = linkAnchor.attrs.allAnchors.slice();
this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => {
- const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0];
- anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
- });
+ allAnchors
+ .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
+ .forEach((aref: any) => {
+ const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ });
}
}
- }
+ };
linkExtend($start: ResolvedPos, href: string) {
const mark = this.view!.state.schema.marks.linkAnchor;
@@ -651,7 +702,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
return { from: startPos, to: endPos };
}
- reference_node(pos: ResolvedPos<any>): ProsNode | null {
+ reference_node(pos: ResolvedPos): ProsNode | null {
if (!this.view) return null;
let ref_node: ProsNode = this.view.state.doc;
@@ -671,7 +722,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
ref_node = node;
skip = true;
}
-
});
}
}
@@ -755,21 +805,19 @@ interface ButtonDropdownProps {
openDropdownOnButton?: boolean;
link?: boolean;
pdf?: boolean;
-
}
@observer
export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
-
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
componentDidMount() {
- document.addEventListener("pointerdown", this.onBlur);
+ document.addEventListener('pointerdown', this.onBlur);
}
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onBlur);
+ document.removeEventListener('pointerdown', this.onBlur);
}
@action
@@ -785,7 +833,7 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
e.preventDefault();
e.stopPropagation();
this.toggleDropdown();
- }
+ };
onBlur = (e: PointerEvent) => {
setTimeout(() => {
@@ -793,37 +841,40 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
this.setShowDropdown(false);
}
}, 0);
- }
-
+ };
render() {
return (
- <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
- {!this.props.pdf ?
+ <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+ {!this.props.pdf ? (
<div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
{this.props.button}
- <div style={{ marginTop: "-8.5", position: "relative" }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+ <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</div>
</div>
- :
+ ) : (
<>
{this.props.button}
<button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
<FontAwesomeIcon icon="caret-down" size="sm" />
</button>
- </>}
- {this.showDropdown ? this.props.dropdownContent : (null)}
+ </>
+ )}
+ {this.showDropdown ? this.props.dropdownContent : null}
</div>
);
}
}
-
interface RichTextMenuPluginProps {
editorProps: any;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- render() { return null; }
- update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); }
-} \ No newline at end of file
+ render() {
+ return null;
+ }
+ update(view: EditorView, lastState: EditorState | undefined) {
+ RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps);
+ }
+}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 8851d52e4..1916b94bf 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,17 +1,17 @@
-import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
-import { NodeSelection, TextSelection } from "prosemirror-state";
-import { DataSym, Doc } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { normalizeEmail } from "../../../../fields/util";
-import { returnFalse, Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { wrappingInputRule } from "./prosemirrorPatches";
-import { RichTextMenu } from "./RichTextMenu";
-import { schema } from "./schema_rts";
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
+import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { DataSym, Doc } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { normalizeEmail } from '../../../../fields/util';
+import { returnFalse, Utils } from '../../../../Utils';
+import { DocServer } from '../../../DocServer';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { FormattedTextBox } from './FormattedTextBox';
+import { wrappingInputRule } from './prosemirrorPatches';
+import { RichTextMenu } from './RichTextMenu';
+import { schema } from './schema_rts';
export class RichTextRules {
public Document: Doc;
@@ -34,9 +34,9 @@ export class RichTextRules {
wrappingInputRule(
/^1\.\s$/,
schema.nodes.ordered_list,
- () => ({ mapStyle: "decimal", bulletStyle: 1 }),
+ () => ({ mapStyle: 'decimal', bulletStyle: 1 }),
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as any
),
// A. create alphabetical ordered list
@@ -45,360 +45,324 @@ export class RichTextRules {
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "multi", bulletStyle: 1 });
+ return { mapStyle: 'multi', bulletStyle: 1 };
// return ({ order: +match[1] })
},
(match: any, node: any) => {
return node.childCount + node.attrs.order === +match[1];
},
- ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
),
// * + - create bullet list
- wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list,
+ wrappingInputRule(
+ /^\s*([-+*])\s$/,
+ schema.nodes.ordered_list,
// match => {
- () => ({ mapStyle: "bullet" }), // ({ order: +match[1] })
+ () => ({ mapStyle: 'bullet' }), // ({ order: +match[1] })
(match: any, node: any) => node.childCount + node.attrs.order === +match[1],
- ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any
+ ((type: any) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as any
),
// ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
- // %<font-size> set the font size
- new InputRule(
- new RegExp(/%([0-9]+)\s$/),
- (state, match, start, end) => {
- const size = Number(match[1]);
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
- }),
+ // %<font-size> set the font size
+ new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+ const size = Number(match[1]);
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
+ }),
//Create annotation to a field on the text document
- new InputRule(
- new RegExp(/>>$/),
- (state, match, start, end) => {
- const textDoc = this.Document[DataSym];
- const numInlines = NumCast(textDoc.inlineTextCount);
- textDoc.inlineTextCount = numInlines + 1;
- const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
- const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" });
- textDocInline.title = inlineFieldKey; // give the annotation its own title
- textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
- textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
- textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
- textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
- textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
- textDoc[inlineFieldKey] = ""; // set a default value for the annotation
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced;
- }),
-
+ new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+ const textDoc = this.Document[DataSym];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
+ const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+ const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' });
+ textDocInline.title = inlineFieldKey; // give the annotation its own title
+ textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+ textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+ textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+ textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+ textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+ textDoc[inlineFieldKey] = ''; // set a default value for the annotation
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ const replaced = node
+ ? state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 1, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ : state.tr;
+ return replaced;
+ }),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%d|d)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%h|h)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/(%q|q)$/),
- (state, match, start, end) => {
- if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
- const pos = (state.doc.resolve(start) as any);
- if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
- const node = state.selection.node;
- return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
- }
- for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
- const node = pos.node(depth);
- if (node.type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
- const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
- return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
- }
+ new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+ if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+ const pos = state.doc.resolve(start) as any;
+ if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+ const node = state.selection.node;
+ return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
+ }
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith('%') ? result.deleteRange(start, end) : result;
}
- return null;
- }),
+ }
+ return null;
+ }),
// center justify text
- new InputRule(
- new RegExp(/%\^/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
+ new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// left justify text
- new InputRule(
- new RegExp(/%\[/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
+ new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// right justify text
- new InputRule(
- new RegExp(/%\]/),
- (state, match, start, end) => {
- const resolved = state.doc.resolve(start) as any;
- if (resolved?.parent.type.name === "paragraph") {
- return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks);
- } else {
- const node = resolved.nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }
- }),
-
+ new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === 'paragraph') {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
+ }),
// %f create footnote
- new InputRule(
- new RegExp(/%f$/),
- (state, match, start, end) => {
- const newNode = schema.nodes.footnote.create({});
- const tr = state.tr;
- tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
- return tr.setSelection(new NodeSelection( // select the footnote node to open its display
- tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0))));
- }),
+ new InputRule(new RegExp(/%f$/), (state, match, start, end) => {
+ const newNode = schema.nodes.footnote.create({});
+ const tr = state.tr;
+ tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+ return tr.setSelection(
+ new NodeSelection( // select the footnote node to open its display
+ tr.doc.resolve(
+ // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)
+ )
+ )
+ );
+ }),
// activate a style by name using prefix '%<color name>'
- new InputRule(
- new RegExp(/%[a-z]+$/),
- (state, match, start, end) => {
-
- const color = match[0].substring(1, match[0].length);
- const marks = RichTextMenu.Instance._brushMap.get(color);
+ new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
+ const color = match[0].substring(1, match[0].length);
+ const marks = RichTextMenu.Instance._brushMap.get(color);
- if (marks) {
- const tr = state.tr.deleteRange(start, end);
- return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
- }
+ if (marks) {
+ const tr = state.tr.deleteRange(start, end);
+ return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ }
- const isValidColor = (strColor: string) => {
- const s = new Option().style;
- s.color = strColor;
- return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
- };
+ const isValidColor = (strColor: string) => {
+ const s = new Option().style;
+ s.color = strColor;
+ return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+ };
- if (isValidColor(color)) {
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
- }
+ if (isValidColor(color)) {
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ }
- return null;
- }),
+ return null;
+ }),
// stop using active style
- new InputRule(
- new RegExp(/%%$/),
- (state, match, start, end) => {
-
- const tr = state.tr.deleteRange(start, end);
- const marks = state.tr.selection.$anchor.nodeBefore?.marks;
-
- return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
- }),
-
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
- // [[<fieldKey> : <Doc>]]
- // [[:Doc]] => hyperlink
- // [[fieldKey]] => show field
+ new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+ const tr = state.tr.deleteRange(start, end);
+ const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+
+ return marks
+ ? Array.from(marks)
+ .filter(m => m.type !== state.schema.marks.user_mark)
+ .reduce((tr, m) => tr.removeStoredMark(m), tr)
+ : tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [[<fieldKey> : <Doc>]]
+ // [[:Doc]] => hyperlink
+ // [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:Doc]] => show field of doc
- new InputRule(
- new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const rawdocid = match[3];
- const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined;
- const value = match[2]?.substring(1);
- if (!fieldKey) {
- if (docid) {
- DocServer.GetRefField(docid).then(docx => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid);
- DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined);
-
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- });
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
- }
- return state.tr;
+ new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
+ const fieldKey = match[1];
+ const rawdocid = match[3];
+ const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined;
+ const value = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
+ const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid);
+ DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined);
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+ }
+ });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
}
- if (value !== "" && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
-
+ return state.tr;
+ }
+ if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// wiki:title
- new InputRule(
- new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/),
- (state, match, start, end) => {
- const title = match[1];
- this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
-
- this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference");
-
- const fstate = this.TextBox.EditorView?.state;
- if (fstate) {
- const tr = fstate?.tr.deleteRange(start, start + 5);
- return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" ");
- }
- return state.tr;
- }),
+ new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => {
+ const title = match[1];
+ this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
+
+ this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference');
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate) {
+ const tr = fstate?.tr.deleteRange(start, start + 5);
+ return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' ');
+ }
+ return state.tr;
+ }),
- // create an inline view of a document {{ <layoutKey> : <Doc> }}
- // {{:Doc}} => show default view of document
- // {{<layout>}} => show layout for this doc
+ // create an inline view of a document {{ <layoutKey> : <Doc> }}
+ // {{:Doc}} => show default view of document
+ // {{<layout>}} => show layout for this doc
// {{<layout> : Doc}} => show layout for another doc
- new InputRule(
- new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/),
- (state, match, start, end) => {
- const fieldKey = match[1] || "";
- const fieldParam = match[2]?.replace("…", "...") || "";
- const rawdocid = match[3]?.substring(1);
- const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined;
- if (!fieldKey && !docid) return state.tr;
- docid && DocServer.GetRefField(docid).then(docx => {
+ new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => {
+ const fieldKey = match[1] || '';
+ const fieldParam = match[2]?.replace('…', '...') || '';
+ const rawdocid = match[3]?.substring(1);
+ const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined;
+ if (!fieldKey && !docid) return state.tr;
+ docid &&
+ DocServer.GetRefField(docid).then(docx => {
if (!(docx instanceof Doc && docx)) {
Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid);
}
});
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
- const sm = state.storedMarks || undefined;
- return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }),
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ }),
// create an inline view of a tag stored under the '#' field
- new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
- (state, match, start, end) => {
- const tag = match[1];
- if (!tag) return state.tr;
- this.Document[DataSym]["#" + tag] = "#" + tag;
- const tags = StrCast(this.Document[DataSym].tags, ":");
- if (!tags.includes(`#${tag}:`)) {
- this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`;
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
- return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" ");
- }),
-
+ new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+ const tag = match[1];
+ if (!tag) return state.tr;
+ this.Document[DataSym]['#' + tag] = '#' + tag;
+ const tags = StrCast(this.Document[DataSym].tags, ':');
+ if (!tags.includes(`#${tag}:`)) {
+ this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`;
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
+ return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' ');
+ }),
// # heading
- textblockTypeInputRule(
- new RegExp(/^(#{1,6})\s$/),
- schema.nodes.heading,
- match => {
- return ({ level: match[1].length });
- }
- ),
+ textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
+ return { level: match[1].length };
+ }),
// set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/[ti!x]$/),
- (state, match, start, end) => {
-
- if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
-
- const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
- const node = (state.doc.resolve(start) as any).nodeAfter;
+ new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+ if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+ const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
+ const node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- new InputRule(
- new RegExp(/%\(/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || [];
- const mark = state.schema.marks.summarizeInclusive.create();
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
- sm.push(mark);
- const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- const content = selected.selection.content();
- const replaced = node ? selected.replaceRangeWith(start, end,
- schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
- state.tr;
+ new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks?.slice() || [];
+ const mark = state.schema.marks.summarizeInclusive.create();
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
- }),
+ sm.push(mark);
+ const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ const content = selected.selection.content();
+ const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr;
- new InputRule(
- new RegExp(/%\)/),
- (state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
- }),
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ }),
- ]
+ new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
+ return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+ }),
+ ],
};
}
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index c017db034..01acc3de9 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,35 +1,49 @@
-import { TextSelection } from "prosemirror-state";
-import { Fragment, Node, Slice } from "prosemirror-model";
+import { TextSelection } from 'prosemirror-state';
+import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom';
-import React = require("react");
+import React = require('react');
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
- _fieldWrapper: HTMLSpanElement; // container for label and value
+ dom: HTMLSpanElement; // container for label and value
constructor(node: any, view: any, getPos: any) {
const self = this;
- this._fieldWrapper = document.createElement("span");
- this._fieldWrapper.className = this.className(node.attrs.visibility);
- this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); };
- this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
- this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+ this.dom = document.createElement('span');
+ this.dom.className = this.className(node.attrs.visibility);
+ this.dom.onpointerdown = function (e: any) {
+ self.onPointerDown(e, node, view, getPos);
+ };
+ this.dom.onkeypress = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeydown = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onkeyup = function (e: any) {
+ e.stopPropagation();
+ };
+ this.dom.onmousedown = function (e: any) {
+ e.stopPropagation();
+ };
const js = node.toJSON;
- node.toJSON = function () { return js.apply(this, arguments); };
+ node.toJSON = function () {
+ return js.apply(this, arguments);
+ };
- ReactDOM.render(<SummaryViewInternal />, this._fieldWrapper);
- (this as any).dom = this._fieldWrapper;
+ ReactDOM.render(<SummaryViewInternal />, this.dom);
+ (this as any).dom = this.dom;
}
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
- destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
- selectNode() { }
+ className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this.dom);
+ }
+ selectNode() {}
updateSummarizedText(start: any, view: any) {
const mtype = view.state.schema.marks.summarize;
@@ -44,8 +58,7 @@ export class SummaryView {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
- }
- else skip = true;
+ } else skip = true;
}
});
}
@@ -56,26 +69,28 @@ export class SummaryView {
const visible = !node.attrs.visibility;
const attrs = { ...node.attrs, visibility: visible };
let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
+ if (!visible) {
+ // update summarized text and save in attrs
textSelection = this.updateSummarizedText(getPos() + 1, view);
attrs.text = textSelection.content();
attrs.textslice = attrs.text.toJSON();
}
- view.dispatch(view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+ view.dispatch(
+ view.state.tr
+ .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
+ .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
+ .setNodeMarkup(getPos(), undefined, attrs)
+ ); // update the attrs
e.preventDefault();
e.stopPropagation();
- this._fieldWrapper.className = this.className(visible);
- }
+ this.dom.className = this.className(visible);
+ };
}
-interface ISummaryView {
-}
+interface ISummaryView {}
// currently nothing needs to be rendered for the internal view of a summary.
export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
return <> </>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 2fde5c7ba..00c41e187 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,66 +1,70 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../../../fields/Doc";
+import React = require('react');
+import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
+import { Doc } from '../../../../fields/Doc';
-
-const emDOM: DOMOutputSpecArray = ["em", 0];
-const strongDOM: DOMOutputSpecArray = ["strong", 0];
-const codeDOM: DOMOutputSpecArray = ["code", 0];
+const emDOM: DOMOutputSpec = ['em', 0];
+const strongDOM: DOMOutputSpec = ['strong', 0];
+const codeDOM: DOMOutputSpec = ['code', 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
splitter: {
attrs: {
- id: { default: "" }
+ id: { default: '' },
},
toDOM(node: any) {
- return ["div", { className: "dummy" }, 0];
- }
+ return ['div', { className: 'dummy' }, 0];
+ },
},
-
// :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the
- // published document's title.
+ // published document's title.
// NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because
- // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
+ // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
// multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to
// disambiguate links from one another.
// Rendered and parsed as an `<a>`
// element.
autoLinkAnchor: {
attrs: {
- allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
},
inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return {
- location: dom.getAttribute("location"),
- title: dom.getAttribute("title")
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
toDOM(node: any) {
- const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
- const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
- return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
- }
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
+ return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+ },
},
noAutoLinkAnchor: {
attrs: {},
inclusive: false,
- parseDOM: [{
- tag: "div", getAttrs(dom: any) {
- return {
- noAutoLink: dom.getAttribute("data-noAutoLink"),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'div',
+ getAttrs(dom: any) {
+ return {
+ noAutoLink: dom.getAttribute('data-noAutoLink'),
+ };
+ },
+ },
+ ],
toDOM(node: any) {
- return ["span", { "data-noAutoLink": "true" }, 0];
- }
+ return ['span', { 'data-noAutoLink': 'true' }, 0];
+ },
},
// :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
// and a title for use in menus and hover. `title`
@@ -68,31 +72,46 @@ export const marks: { [index: string]: MarkSpec } = {
// element.
linkAnchor: {
attrs: {
- allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
+ allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
location: { default: null },
title: { default: null },
- docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
+ docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return {
- location: dom.getAttribute("location"),
- title: dom.getAttribute("title")
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'a[href]',
+ getAttrs(dom: any) {
+ return {
+ location: dom.getAttribute('location'),
+ title: dom.getAttribute('title'),
+ };
+ },
+ },
+ ],
toDOM(node: any) {
- const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
- const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
- return node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
- ...node.attrs,
- class: "prosemirror-attribution",
- href: node.attrs.allAnchors[0].href,
- }, node.attrs.title], ["br"]] :
- //node.attrs.allLinks.length === 1 ?
- ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
+ return node.attrs.docref && node.attrs.title
+ ? [
+ 'div',
+ ['span', `"`],
+ ['span', 0],
+ ['span', `"`],
+ ['br'],
+ [
+ 'a',
+ {
+ ...node.attrs,
+ class: 'prosemirror-attribution',
+ href: node.attrs.allAnchors[0].href,
+ },
+ node.attrs.title,
+ ],
+ ['br'],
+ ]
+ : //node.attrs.allLinks.length === 1 ?
+ ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
@@ -102,254 +121,273 @@ export const marks: { [index: string]: MarkSpec } = {
// ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
// )]
// ];
- }
+ },
},
/** FONT SIZES */
pFontSize: {
- attrs: { fontSize: { default: "10px" } },
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" };
- }
- }],
- toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]
+ attrs: { fontSize: { default: '10px' } },
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]),
},
/* FONTS */
pFontFamily: {
- attrs: { family: { default: "" } },
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- const cstyle = getComputedStyle(dom);
- if (cstyle.font) {
- if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
- if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
- if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
- if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
- if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
- if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
- }
- }
- }],
- toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]
+ attrs: { family: { default: '' } },
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ const cstyle = getComputedStyle(dom);
+ if (cstyle.font) {
+ if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
+ if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
+ if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
+ if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
+ if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
+ if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+ }
+ return { family: '' };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
},
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
pFontColor: {
- attrs: { color: { default: "" } },
+ attrs: { color: { default: '' } },
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { color: dom.getAttribute("color") };
- }
- }],
- toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { color: dom.getAttribute('color') };
+ },
+ },
+ ],
+ toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
},
marker: {
attrs: {
- highlight: { default: "transparent" }
+ highlight: { default: 'transparent' },
},
inclusive: true,
- parseDOM: [{
- tag: "span", getAttrs(dom: any) {
- return { highlight: dom.getAttribute("backgroundColor") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'span',
+ getAttrs(dom: any) {
+ return { highlight: dom.getAttribute('backgroundColor') };
+ },
+ },
+ ],
toDOM(node: any) {
return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
- }
+ },
},
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
- toDOM() { return emDOM; }
+ parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }],
+ toDOM() {
+ return emDOM;
+ },
},
// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
// also match `<b>` and `font-weight: bold`.
strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM; }
+ parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }],
+ toDOM() {
+ return strongDOM;
+ },
},
strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
+ parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }],
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration-line:line-through',
+ },
],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
},
subscript: {
excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
+ parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }],
+ toDOM: () => ['sub'],
},
superscript: {
excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
+ parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }],
+ toDOM: () => ['sup'],
},
mbulletType: {
attrs: {
- bulletType: { default: "decimal" }
+ bulletType: { default: 'decimal' },
},
toDOM(node: any) {
- return ['span', {
- style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
- }];
- }
+ return [
+ 'span',
+ {
+ style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`,
+ },
+ ];
+ },
},
metadata: {
toDOM() {
return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
- }
+ },
},
metadataKey: {
toDOM() {
return ['span', { style: 'font-style:italic; ' }];
- }
+ },
},
metadataVal: {
toDOM() {
return ['span'];
- }
+ },
},
summarizeInclusive: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
inclusive: true,
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
summarize: {
inclusive: false,
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline") return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+ if (style.textDecoration === 'underline') return null;
+ if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) {
return null;
}
}
return false;
- }
+ },
},
],
toDOM() {
- return ['span', {
- style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
- }];
- }
+ return [
+ 'span',
+ {
+ style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)',
+ },
+ ];
+ },
},
underline: {
parseDOM: [
{
- tag: "span",
+ tag: 'span',
getAttrs: (p: any) => {
- if (typeof (p) !== "string") {
+ if (typeof p !== 'string') {
const style = getComputedStyle(p);
- if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
+ if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) {
return null;
}
}
return false;
- }
- }
+ },
+ },
// { style: "text-decoration=underline" }
],
- toDOM: () => ['span', {
- style: 'text-decoration:underline;text-decoration-style:line'
- }]
+ toDOM: () => [
+ 'span',
+ {
+ style: 'text-decoration:underline;text-decoration-style:line',
+ },
+ ],
},
search_highlight: {
attrs: {
- selected: { default: false }
+ selected: { default: false },
},
parseDOM: [{ style: 'background: yellow' }],
toDOM(node: any) {
- return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];
- }
+ return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }];
+ },
},
// the id of the user who entered the text
user_mark: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
},
- excludes: "user_mark",
- group: "inline",
+ excludes: 'user_mark',
+ group: 'inline',
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
const min = Math.round(node.attrs.modified / 12);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
- const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
- return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
- }
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+ return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
+ },
},
// the id of the user who entered the text
user_tag: {
attrs: {
- userid: { default: "" },
- modified: { default: "when?" }, // 1 second intervals since 1970
- tag: { default: "" }
+ userid: { default: '' },
+ modified: { default: 'when?' }, // 1 second intervals since 1970
+ tag: { default: '' },
},
- group: "inline",
+ group: 'inline',
inclusive: false,
toDOM(node: any) {
- const uid = node.attrs.userid.replace(".", "").replace("@", "");
- return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];
- }
+ const uid = node.attrs.userid.replace('.', '').replace('@', '');
+ return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0];
+ },
},
-
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM; }
+ parseDOM: [{ tag: 'code' }],
+ toDOM() {
+ return codeDOM;
+ },
},
};
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 2fe0a67cb..5142b7da6 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,15 +1,18 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec";
+import React = require('react');
+import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
+import { listItem, orderedList } from 'prosemirror-schema-list';
+import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
- preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
+ hrDOM: DOMOutputSpec = ['hr'],
+ preDOM: DOMOutputSpec = ['pre', ['code', 0]],
+ brDOM: DOMOutputSpec = ['br'],
+ ulDOM: DOMOutputSpec = ['ul', 0];
function formatAudioTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
- const minutes = Math.floor(time / 60) - (hours * 60);
+ const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
@@ -19,67 +22,70 @@ function formatAudioTime(time: number) {
export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec The top level document node.
doc: {
- content: "block+"
+ content: 'block+',
},
paragraph: ParagraphNodeSpec,
audiotag: {
- group: "block",
+ group: 'block',
attrs: {
timeCode: { default: 0 },
- audioId: { default: "" },
- textId: { default: "" }
+ audioId: { default: '' },
+ textId: { default: '' },
},
toDOM(node) {
- return ['audiotag',
+ return [
+ 'audiotag',
{
class: node.attrs.textId,
// style: see FormattedTextBox.scss
- "data-timecode": node.attrs.timeCode,
- "data-audioid": node.attrs.audioId,
- "data-textid": node.attrs.textId,
+ 'data-timecode': node.attrs.timeCode,
+ 'data-audioid': node.attrs.audioId,
+ 'data-textid': node.attrs.textId,
},
- formatAudioTime(node.attrs.timeCode.toString())
+ formatAudioTime(node.attrs.timeCode.toString()),
];
},
parseDOM: [
{
- tag: "audiotag", getAttrs(dom: any) {
+ tag: 'audiotag',
+ getAttrs(dom: any) {
return {
- timeCode: dom.getAttribute("data-timecode"),
- audioId: dom.getAttribute("data-audioid"),
- textId: dom.getAttribute("data-textid")
+ timeCode: dom.getAttribute('data-timecode'),
+ audioId: dom.getAttribute('data-audioid'),
+ textId: dom.getAttribute('data-textid'),
};
- }
+ },
},
- ]
+ ],
},
footnote: {
- group: "inline",
- content: "inline*",
+ group: 'inline',
+ content: 'inline*',
inline: true,
attrs: {
- visibility: { default: false }
+ visibility: { default: false },
},
// This makes the view treat the node as a leaf, even though it
// technically has content
atom: true,
- toDOM: () => ["footnote", 0],
- parseDOM: [{ tag: "footnote" }]
+ toDOM: () => ['footnote', 0],
+ parseDOM: [{ tag: 'footnote' }],
},
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
- content: "block*",
- group: "block",
+ content: 'block*',
+ group: 'block',
defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM; }
+ parseDOM: [{ tag: 'blockquote' }],
+ toDOM() {
+ return blockquoteDOM;
+ },
},
-
// blockquote: {
// ...ParagraphNodeSpec,
// defining: true,
@@ -97,9 +103,11 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec A horizontal rule (`<hr>`).
horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM; }
+ group: 'block',
+ parseDOM: [{ tag: 'hr' }],
+ toDOM() {
+ return hrDOM;
+ },
},
// :: NodeSpec A heading textblock, with a `level` attribute that
@@ -112,12 +120,14 @@ export const nodes: { [index: string]: NodeSpec } = {
level: { default: 1 },
},
defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
+ parseDOM: [
+ { tag: 'h1', attrs: { level: 1 } },
+ { tag: 'h2', attrs: { level: 2 } },
+ { tag: 'h3', attrs: { level: 3 } },
+ { tag: 'h4', attrs: { level: 4 } },
+ { tag: 'h5', attrs: { level: 5 } },
+ { tag: 'h6', attrs: { level: 6 } },
+ ],
toDOM(node) {
const dom = toParagraphDOM(node) as any;
const level = node.attrs.level || 1;
@@ -129,36 +139,38 @@ export const nodes: { [index: string]: NodeSpec } = {
const level = Number(dom.nodeName.substring(1)) || 1;
attrs.level = level;
return attrs;
- }
+ },
},
// :: NodeSpec A code listing. Disallows marks or non-text inline
// nodes by default. Represented as a `<pre>` element with a
// `<code>` element inside of it.
code_block: {
- content: "inline*",
- marks: "_",
- group: "block",
+ content: 'inline*',
+ marks: '_',
+ group: 'block',
code: true,
defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM; }
+ parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }],
+ toDOM() {
+ return preDOM;
+ },
},
// :: NodeSpec The text node.
text: {
- group: "inline"
+ group: 'inline',
},
dashComment: {
attrs: {
- docid: { default: "" },
+ docid: { default: '' },
},
inline: true,
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }, "←"];
+ return ['span', { ...node.attrs, ...attrs }, '←'];
},
},
@@ -169,10 +181,10 @@ export const nodes: { [index: string]: NodeSpec } = {
text: { default: undefined },
textslice: { default: undefined },
},
- group: "inline",
+ group: 'inline',
toDOM(node) {
const attrs = { style: `width: 40px` };
- return ["span", { ...node.attrs, ...attrs }];
+ return ['span', { ...node.attrs, ...attrs }];
},
},
@@ -187,27 +199,30 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 100 },
alt: { default: null },
title: { default: null },
- float: { default: "left" },
- location: { default: "add:right" },
- docid: { default: "" }
+ float: { default: 'left' },
+ location: { default: 'add:right' },
+ docid: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'img[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
// TODO if we don't define toDom, dragging the image crashes. Why?
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["img", { ...node.attrs, ...attrs }];
- }
+ return ['img', { ...node.attrs, ...attrs }];
+ },
},
dashDoc: {
@@ -216,82 +231,87 @@ export const nodes: { [index: string]: NodeSpec } = {
width: { default: 200 },
height: { default: 100 },
title: { default: null },
- float: { default: "right" },
+ float: { default: 'right' },
hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
- fieldKey: { default: "" },
- docid: { default: "" },
- alias: { default: "" }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ alias: { default: '' },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
dashField: {
inline: true,
attrs: {
- fieldKey: { default: "" },
- docid: { default: "" },
- hideKey: { default: false }
+ fieldKey: { default: '' },
+ docid: { default: '' },
+ hideKey: { default: false },
},
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
equation: {
inline: true,
attrs: {
- fieldKey: { default: "" },
+ fieldKey: { default: '' },
},
atom: true,
- group: "inline",
+ group: 'inline',
draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
- return ["div", { ...node.attrs, ...attrs }];
- }
+ return ['div', { ...node.attrs, ...attrs }];
+ },
},
video: {
inline: true,
attrs: {
src: {},
- width: { default: "100px" },
+ width: { default: '100px' },
alt: { default: null },
- title: { default: null }
+ title: { default: null },
},
- group: "inline",
+ group: 'inline',
draggable: true,
- parseDOM: [{
- tag: "video[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt"),
- width: Math.min(100, Number(dom.getAttribute("width"))),
- };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'video[src]',
+ getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute('src'),
+ title: dom.getAttribute('title'),
+ alt: dom.getAttribute('alt'),
+ width: Math.min(100, Number(dom.getAttribute('width'))),
+ };
+ },
+ },
+ ],
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}` };
- return ["video", { ...node.attrs, ...attrs }];
- }
+ return ['video', { ...node.attrs, ...attrs }];
+ },
},
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
hard_break: {
inline: true,
- group: "inline",
+ group: 'inline',
selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM; }
+ parseDOM: [{ tag: 'br' }],
+ toDOM() {
+ return brDOM;
+ },
},
ordered_list: {
@@ -300,85 +320,108 @@ export const nodes: { [index: string]: NodeSpec } = {
group: 'block',
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" },// "decimal", "multi", "bullet"
- fontColor: { default: "inherit" },
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ fontColor: { default: 'inherit' },
fontSize: { default: undefined },
fontFamily: { default: undefined },
visibility: { default: true },
- indent: { default: undefined }
+ indent: { default: undefined },
},
parseDOM: [
{
- tag: "ul", getAttrs(dom: any) {
+ tag: 'ul',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
+ },
},
{
- style: 'list-style-type=disc', getAttrs(dom: any) {
- return { mapStyle: "bullet" };
- }
+ style: 'list-style-type=disc',
+ getAttrs(dom: any) {
+ return { mapStyle: 'bullet' };
+ },
},
{
- tag: "ol", getAttrs(dom: any) {
+ tag: 'ol',
+ getAttrs(dom: any) {
return {
- bulletStyle: dom.getAttribute("data-bulletStyle"),
- mapStyle: dom.getAttribute("data-mapStyle"),
+ bulletStyle: dom.getAttribute('data-bulletStyle'),
+ mapStyle: dom.getAttribute('data-mapStyle'),
fontColor: dom.style.color,
- fontSize: dom.style["font-size"],
- fontFamily: dom.style["font-family"],
- indent: dom.style["margin-left"]
+ fontSize: dom.style['font-size'],
+ fontFamily: dom.style['font-family'],
+ indent: dom.style['margin-left'],
};
- }
- }],
- toDOM(node: Node<any>) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : "";
- const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : "";
- const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : "";
- const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : "";
- if (node.attrs.mapStyle === "bullet") {
- return ['ul', {
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `${fsize} ${ffam} ${fcol} ${marg}`
- }, 0];
+ },
+ },
+ ],
+ toDOM(node: Node) {
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
+ const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
+ const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
+ const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+ const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
+ if (node.attrs.mapStyle === 'bullet') {
+ return [
+ 'ul',
+ {
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ];
}
- return node.attrs.visibility ?
- ['ol', {
- class: `${map}-ol`,
- "data-mapStyle": node.attrs.mapStyle,
- "data-bulletStyle": node.attrs.bulletStyle,
- style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`
- }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
- }
+ return node.attrs.visibility
+ ? [
+ 'ol',
+ {
+ class: `${map}-ol`,
+ 'data-mapStyle': node.attrs.mapStyle,
+ 'data-bulletStyle': node.attrs.bulletStyle,
+ style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+ },
+ 0,
+ ]
+ : ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+ },
},
list_item: {
...listItem,
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet"
- visibility: { default: true }
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
+ visibility: { default: true },
},
content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)',
- parseDOM: [{
- tag: "li", getAttrs(dom: any) {
- return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") };
- }
- }],
+ parseDOM: [
+ {
+ tag: 'li',
+ getAttrs(dom: any) {
+ return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') };
+ },
+ },
+ ],
toDOM(node: any) {
- const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 :
- ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` },
- `${node.firstChild?.textContent}...`]];
- }
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
+ return [
+ 'li',
+ { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+ node.attrs.visibility
+ ? 0
+ : [
+ 'span',
+ { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+ `${node.firstChild?.textContent}...`,
+ ],
+ ];
+ },
},
-}; \ No newline at end of file
+};
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index b30ca644d..31a50301a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,59 +1,54 @@
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { saveAs } from "file-saver";
-import { action, computed, observable, ObservableMap, runInAction } from "mobx";
-import { computedFn } from "mobx-utils";
-import { alias, map, serializable } from "serializr";
-import { DocServer } from "../client/DocServer";
-import { DocumentType } from "../client/documents/DocumentTypes";
-import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
-import { LinkManager } from "../client/util/LinkManager";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { SelectionManager } from "../client/util/SelectionManager";
-import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
-import { UndoManager } from "../client/util/UndoManager";
-import { DashColor, incrementTitleCopy, intersectRect, Utils } from "../Utils";
-import { DateField } from "./DateField";
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
-import { List } from "./List";
-import { ObjectField } from "./ObjectField";
-import { PrefetchProxy, ProxyField } from "./Proxy";
-import { FieldId, RefField } from "./RefField";
-import { RichTextField } from "./RichTextField";
-import { listSpec } from "./Schema";
-import { ComputedField, ScriptField } from "./ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField";
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
-import JSZip = require("jszip");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { saveAs } from 'file-saver';
+import { action, computed, observable, ObservableMap, runInAction } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { alias, map, serializable } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { DocumentType } from '../client/documents/DocumentTypes';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
+import { LinkManager } from '../client/util/LinkManager';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { SelectionManager } from '../client/util/SelectionManager';
+import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
+import { UndoManager } from '../client/util/UndoManager';
+import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils';
+import { DateField } from './DateField';
+import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { FieldId, RefField } from './RefField';
+import { RichTextField } from './RichTextField';
+import { listSpec } from './Schema';
+import { ComputedField, ScriptField } from './ScriptField';
+import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
+import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from './URLField';
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
+import JSZip = require('jszip');
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
const onDelegate = Object.keys(doc).includes(key);
const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
+ return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
}
export function toScriptString(field: Field): string {
- if (typeof field === "string") return `"${field}"`;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
- if (field === undefined || field === null) return "null";
+ if (typeof field === 'string') return `"${field}"`;
+ if (typeof field === 'number' || typeof field === 'boolean') return String(field);
+ if (field === undefined || field === null) return 'null';
return field[ToScriptString]();
}
export function toString(field: Field): string {
- if (typeof field === "string") return field;
- if (typeof field === "number" || typeof field === "boolean") return String(field);
+ if (typeof field === 'string') return field;
+ if (typeof field === 'number' || typeof field === 'boolean') return String(field);
if (field instanceof ObjectField) return field[ToString]();
if (field instanceof RefField) return field[ToString]();
- return "";
+ return '';
}
export function IsField(field: any): field is Field;
export function IsField(field: any, includeUndefined: true): field is Field | undefined;
export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
- return (typeof field === "string")
- || (typeof field === "number")
- || (typeof field === "boolean")
- || (field instanceof ObjectField)
- || (field instanceof RefField)
- || (includeUndefined && field === undefined);
+ return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
}
export function Copy(field: any) {
return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
@@ -77,40 +72,50 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
-export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> { return Cast(field, Doc); }
-
-export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); }
-export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); }
-export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; }
-export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; }
-
-export const WidthSym = Symbol("Width");
-export const HeightSym = Symbol("Height");
-export const DataSym = Symbol("Data");
-export const LayoutSym = Symbol("Layout");
-export const FieldsSym = Symbol("Fields");
-export const AclSym = Symbol("Acl");
-export const DirectLinksSym = Symbol("DirectLinks");
-export const AclUnset = Symbol("AclUnset");
-export const AclPrivate = Symbol("AclOwnerOnly");
-export const AclReadonly = Symbol("AclReadOnly");
-export const AclAugment = Symbol("AclAugment");
-export const AclSelfEdit = Symbol("AclSelfEdit");
-export const AclEdit = Symbol("AclEdit");
-export const AclAdmin = Symbol("AclAdmin");
-export const UpdatingFromServer = Symbol("UpdatingFromServer");
-export const Initializing = Symbol("Initializing");
-export const ForceServerWrite = Symbol("ForceServerWrite");
-export const CachedUpdates = Symbol("Cached updates");
+export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
+ return Cast(field, Doc);
+}
+
+export function NumListCast(field: FieldResult) {
+ return Cast(field, listSpec('number'), []);
+}
+export function StrListCast(field: FieldResult) {
+ return Cast(field, listSpec('string'), []);
+}
+export function DocListCast(field: FieldResult) {
+ return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
+}
+export function DocListCastOrNull(field: FieldResult) {
+ return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined;
+}
+
+export const WidthSym = Symbol('Width');
+export const HeightSym = Symbol('Height');
+export const DataSym = Symbol('Data');
+export const LayoutSym = Symbol('Layout');
+export const FieldsSym = Symbol('Fields');
+export const AclSym = Symbol('Acl');
+export const DirectLinksSym = Symbol('DirectLinks');
+export const AclUnset = Symbol('AclUnset');
+export const AclPrivate = Symbol('AclOwnerOnly');
+export const AclReadonly = Symbol('AclReadOnly');
+export const AclAugment = Symbol('AclAugment');
+export const AclSelfEdit = Symbol('AclSelfEdit');
+export const AclEdit = Symbol('AclEdit');
+export const AclAdmin = Symbol('AclAdmin');
+export const UpdatingFromServer = Symbol('UpdatingFromServer');
+export const Initializing = Symbol('Initializing');
+export const ForceServerWrite = Symbol('ForceServerWrite');
+export const CachedUpdates = Symbol('Cached updates');
const AclMap = new Map<string, symbol>([
- ["None", AclUnset],
+ ['None', AclUnset],
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
[SharingPermissions.Augment, AclAugment],
[SharingPermissions.SelfEdit, AclSelfEdit],
[SharingPermissions.Edit, AclEdit],
- [SharingPermissions.Admin, AclAdmin]
+ [SharingPermissions.Admin, AclAdmin],
]);
// caches the document access permissions for the current user.
@@ -120,7 +125,7 @@ export function updateCachedAcls(doc: Doc) {
const permissions: { [key: string]: symbol } = {};
doc[UpdatingFromServer] = true;
- Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
+ Object.keys(doc).filter(key => key.startsWith('acl') && (permissions[key] = AclMap.get(StrCast(doc[key]))!));
doc[UpdatingFromServer] = false;
if (Object.keys(permissions).length) {
@@ -134,7 +139,7 @@ export function updateCachedAcls(doc: Doc) {
}
@scriptingGlobal
-@Deserializable("Doc", updateCachedAcls).withFields(["id"])
+@Deserializable('Doc', updateCachedAcls).withFields(['id'])
export class Doc extends RefField {
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
@@ -146,24 +151,26 @@ export class Doc extends RefField {
ownKeys: target => {
const obj = {} as any;
if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
- runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
+ runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__));
return Object.keys(obj);
},
getOwnPropertyDescriptor: (target, prop) => {
- if (prop.toString() === "__LAYOUT__") {
+ if (prop.toString() === '__LAYOUT__') {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
if (prop in target.__fieldKeys) {
return {
- configurable: true,//TODO Should configurable be true?
+ configurable: true, //TODO Should configurable be true?
enumerable: true,
- value: 0//() => target.__fields[prop])
+ value: 0, //() => target.__fields[prop])
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
},
deleteProperty: deleteProperty,
- defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ defineProperty: () => {
+ throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
+ },
});
this[SelfProxy] = doc;
if (!id || forceSave) {
@@ -175,24 +182,30 @@ export class Doc extends RefField {
proto: Opt<Doc>;
[key: string]: FieldResult;
- @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
- private get __fields() { return this.___fields; }
+ @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ private get __fields() {
+ return this.___fields;
+ }
private set __fields(value) {
this.___fields = value;
for (const key in value) {
const field = value[key];
- (field !== undefined) && (this.__fieldKeys[key] = true);
+ field !== undefined && (this.__fieldKeys[key] = true);
if (!(field instanceof ObjectField)) continue;
field[Parent] = this[Self];
field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
}
}
- private get __fieldKeys() { return this.___fieldKeys; }
- private set __fieldKeys(value) { this.___fieldKeys = value; }
+ private get __fieldKeys() {
+ return this.___fieldKeys;
+ }
+ private set __fieldKeys(value) {
+ this.___fieldKeys = value;
+ }
@observable private ___fields: any = {};
@observable private ___fieldKeys: any = {};
- @observable public [AclSym]: { [key: string]: symbol };
+ @observable public [AclSym]: { [key: string]: symbol } = {};
@observable public [DirectLinksSym]: Set<Doc> = new Set();
private [UpdatingFromServer]: boolean = false;
@@ -201,7 +214,7 @@ export class Doc extends RefField {
private [Update] = (diff: any) => {
(!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- }
+ };
private [Self] = this;
private [SelfProxy]: any;
@@ -209,42 +222,52 @@ export class Doc extends RefField {
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
- public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`;
- public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
+ public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
+ public get [LayoutSym]() {
+ return this[SelfProxy].__LAYOUT__;
+ }
public get [DataSym]() {
const self = this[SelfProxy];
- return self.resolvedDataDoc && !self.isTemplateForField ? self :
- Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+ return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
}
@computed get __LAYOUT__(): Doc | undefined {
const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
if (templateLayoutDoc) {
let renderFieldKey: any;
- const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")];
- if (typeof layoutField === "string") {
- renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1];
+ const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, 'layout')];
+ if (typeof layoutField === 'string') {
+ renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1];
} else {
return Cast(layoutField, Doc, null);
}
- return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
+ return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc;
}
return undefined;
-
}
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
- public static get noviceMode() { return Doc.UserDoc().noviceMode as boolean; }
- public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; }
- public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; }
- public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; }
- public static CurrentUserEmail: string = "";
- public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); }
+ public static get noviceMode() {
+ return Doc.UserDoc().noviceMode as boolean;
+ }
+ public static set noviceMode(val) {
+ Doc.UserDoc().noviceMode = val;
+ }
+ public static get defaultAclPrivate() {
+ return Doc.UserDoc().defaultAclPrivate;
+ }
+ public static set defaultAclPrivate(val) {
+ Doc.UserDoc().defaultAclPrivate = val;
+ }
+ public static CurrentUserEmail: string = '';
+ public static get CurrentUserEmailNormalized() {
+ return normalizeEmail(Doc.CurrentUserEmail);
+ }
public async [HandleUpdate](diff: any) {
const set = diff.$set;
const sameAuthor = this.author === Doc.CurrentUserEmail;
if (set) {
for (const key in set) {
- const fprefix = "fields.";
+ const fprefix = 'fields.';
if (!key.startsWith(fprefix)) {
continue;
}
@@ -255,7 +278,7 @@ export class Doc extends RefField {
this[UpdatingFromServer] = true;
this[fKey] = value;
this[UpdatingFromServer] = false;
- if (fKey.startsWith("acl")) {
+ if (fKey.startsWith('acl')) {
updateCachedAcls(this);
}
if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
@@ -263,7 +286,7 @@ export class Doc extends RefField {
}
};
const writeMode = DocServer.getFieldWriteMode(fKey);
- if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) {
+ if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -274,7 +297,7 @@ export class Doc extends RefField {
const unset = diff.$unset;
if (unset) {
for (const key in unset) {
- if (!key.startsWith("fields.")) {
+ if (!key.startsWith('fields.')) {
continue;
}
const fKey = key.substring(7);
@@ -326,7 +349,7 @@ export namespace Doc {
return {
end() {
makeEditable();
- }
+ },
};
}
@@ -341,16 +364,16 @@ export namespace Doc {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
}
export function IsPrototype(doc: Doc) {
- return GetT(doc, "isPrototype", "boolean", true);
+ return GetT(doc, 'isPrototype', 'boolean', true);
}
export function IsBaseProto(doc: Doc) {
- return GetT(doc, "baseProto", "boolean", true);
+ return GetT(doc, 'baseProto', 'boolean', true);
}
export function IsSystem(doc: Doc) {
- return GetT(doc, "system", "boolean", true);
+ return GetT(doc, 'system', 'boolean', true);
}
export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
- if (key.startsWith("_")) key = key.substring(1);
+ if (key.startsWith('_')) key = key.substring(1);
const hasProto = doc.proto instanceof Doc;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
@@ -359,7 +382,7 @@ export namespace Doc {
} else doc.proto![key] = value;
}
export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
- const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
+ const proto = Object.getOwnPropertyNames(doc).indexOf('isPrototype') === -1 ? doc.proto : doc;
if (proto) {
proto[key] = value;
@@ -391,7 +414,8 @@ export namespace Doc {
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const value = fields[key];
- if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds?
+ if (!skipUndefineds || value !== undefined) {
+ // Do we want to filter out undefineds?
doc[key] = value;
}
}
@@ -403,10 +427,10 @@ export namespace Doc {
// compare whether documents or their protos match
export function AreProtosEqual(doc?: Doc, other?: Doc) {
if (!doc || !other) return false;
- const r = (doc === other);
- const r2 = (Doc.GetProto(doc) === other);
- const r3 = (Doc.GetProto(other) === doc);
- const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined);
+ const r = doc === other;
+ const r2 = Doc.GetProto(doc) === other;
+ const r3 = Doc.GetProto(other) === doc;
+ const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined;
return r || r2 || r3 || r4;
}
@@ -417,7 +441,7 @@ export namespace Doc {
if (doc instanceof Promise) {
// console.log("GetProto: warning: got Promise insead of Doc");
}
- const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
+ const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc);
return proto === doc ? proto : Doc.GetProto(proto);
}
export function GetDataDoc(doc: Doc): Doc {
@@ -426,7 +450,7 @@ export namespace Doc {
}
export function allKeys(doc: Doc): string[] {
- const results: Set<string> = new Set;
+ const results: Set<string> = new Set();
let proto: Doc | undefined = doc;
while (proto) {
@@ -441,8 +465,8 @@ export namespace Doc {
* @returns the index of doc toFind in list of docs, -1 otherwise
*/
export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
- let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
- index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
+ let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1);
+ index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1);
return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
}
@@ -478,7 +502,7 @@ export namespace Doc {
const list = Cast(listDoc[key], listSpec(Doc));
if (list) {
if (allowDuplicates !== true) {
- const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1);
+ const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1);
if (pind !== -1) {
return true;
//list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
@@ -486,15 +510,13 @@ export namespace Doc {
}
if (first) {
list.splice(0, 0, doc);
- }
- else {
+ } else {
const ind = relativeTo ? list.indexOf(relativeTo) : -1;
if (ind === -1) {
if (reversed) list.splice(0, 0, doc);
else list.push(doc);
- }
- else {
- if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc);
+ } else {
+ if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc);
else list.splice(before ? ind : ind + 1, 0, doc);
}
}
@@ -507,19 +529,24 @@ export namespace Doc {
* Computes the bounds of the contents of a set of documents.
*/
export function ComputeContentBounds(docList: Doc[]) {
- const bounds = docList.reduce((bounds, doc) => {
- const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
- const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
- return {
- x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
- r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- };
- }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+ const bounds = docList.reduce(
+ (bounds, doc) => {
+ const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+ const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+ return {
+ x: Math.min(sptX, bounds.x),
+ y: Math.min(sptY, bounds.y),
+ r: Math.max(bptX, bounds.r),
+ b: Math.max(bptY, bounds.b),
+ };
+ },
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
+ );
return bounds;
}
export function MakeAlias(doc: Doc, id?: string) {
- const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
+ const alias = !GetT(doc, 'isPrototype', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
const layout = Doc.LayoutField(alias);
if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) {
Doc.SetLayout(alias, Doc.MakeAlias(layout));
@@ -529,71 +556,73 @@ export namespace Doc {
alias.title = ComputedField.MakeFunction(`renameAlias(this)`);
alias.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias);
+ Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'aliases', alias);
return alias;
}
- export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
+ export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<Doc, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise<Doc> {
if (Doc.IsBaseProto(doc)) return doc;
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
- const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true);
+ const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true);
cloneMap.set(doc[Id], copy);
- const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions;
- const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])];
- await Promise.all(Object.keys(doc).map(async key => {
- if (filter.includes(key)) return;
- const assignKey = (val: any) => !dontCreate && (copy[key] = val);
- const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (field: ObjectField) => {
- const list = await Cast(doc[key], listSpec(Doc));
- const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
- if (docs !== undefined && docs.length) {
- const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
- !dontCreate && assignKey(new List<Doc>(clones));
- } else if (doc[key] instanceof Doc) {
- assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
- } else {
- !dontCreate && assignKey(ObjectField.MakeCopy(field));
- if (field instanceof RichTextField) {
- if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
- rtfs.push({ copy, key, field });
+ const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions;
+ const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])];
+ await Promise.all(
+ Object.keys(doc).map(async key => {
+ if (filter.includes(key)) return;
+ const assignKey = (val: any) => !dontCreate && (copy[key] = val);
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const field = ProxyField.WithoutProxy(() => doc[key]);
+ const copyObjectField = async (field: ObjectField) => {
+ const list = await Cast(doc[key], listSpec(Doc));
+ const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
+ if (docs !== undefined && docs.length) {
+ const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)));
+ !dontCreate && assignKey(new List<Doc>(clones));
+ } else if (doc[key] instanceof Doc) {
+ assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields
+ } else {
+ !dontCreate && assignKey(ObjectField.MakeCopy(field));
+ if (field instanceof RichTextField) {
+ if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) {
+ rtfs.push({ copy, key, field });
+ }
}
}
- }
- };
- if (key === "proto") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
- }
- } else if (key === "anchor1" || key === "anchor2") {
- if (doc[key] instanceof Doc) {
- assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
- }
- } else {
- if (field instanceof RefField) {
- assignKey(field);
- } else if (cfield instanceof ComputedField) {
- !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
- } else if (field instanceof ObjectField) {
- await copyObjectField(field);
- } else if (field instanceof Promise) {
- debugger; //This shouldn't happen...
+ };
+ if (key === 'proto') {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch));
+ }
+ } else if (key === 'anchor1' || key === 'anchor2') {
+ if (doc[key] instanceof Doc) {
+ assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch));
+ }
} else {
- assignKey(field);
+ if (field instanceof RefField) {
+ assignKey(field);
+ } else if (cfield instanceof ComputedField) {
+ !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript));
+ } else if (field instanceof ObjectField) {
+ await copyObjectField(field);
+ } else if (field instanceof Promise) {
+ debugger; //This shouldn't happen...
+ } else {
+ assignKey(field);
+ }
}
- }
- }));
+ })
+ );
for (const link of Array.from(doc[DirectLinksSym])) {
const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch);
linkMap.set(link, linkClone);
}
if (!dontCreate) {
- Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true);
+ Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true);
asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
if (!Doc.IsPrototype(copy)) {
- Doc.AddDocToList(doc, "branches", Doc.GetProto(copy));
+ Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy));
}
cloneMap.set(doc[Id], copy);
}
@@ -601,20 +630,20 @@ export namespace Doc {
}
export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
const linkMap = new Map<Doc, Doc>();
- const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
- const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch);
+ const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
+ const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch);
Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true));
rtfMap.map(({ copy, key, field }) => {
const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
- return attr + "\"" + (mapped ? mapped[Id] : id) + "\"";
+ return attr + '"' + (mapped ? mapped[Id] : id) + '"';
};
const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
const regex = `(${Doc.localServerPath()})([^"]*)`;
- const re = new RegExp(regex, "g");
+ const re = new RegExp(regex, 'g');
copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text);
});
return { clone: copy, map: cloneMap };
@@ -628,37 +657,36 @@ export namespace Doc {
// a.click();
const { clone, map } = await Doc.MakeClone(doc, true);
function replacer(key: any, value: any) {
- if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined;
+ if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined;
else if (value instanceof Doc) {
- if (key !== "field" && Number.isNaN(Number(key))) {
+ if (key !== 'field' && Number.isNaN(Number(key))) {
const __fields = value[FieldsSym]();
- return { id: value[Id], __type: "Doc", fields: __fields };
+ return { id: value[Id], __type: 'Doc', fields: __fields };
} else {
- return { fieldId: value[Id], __type: "proxy" };
+ return { fieldId: value[Id], __type: 'proxy' };
}
- }
- else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
- else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
- else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
- else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
- else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" };
- else if (value instanceof VideoField) return { url: value.url.href, __type: "video" };
- else if (value instanceof WebField) return { url: value.url.href, __type: "web" };
- else if (value instanceof MapField) return { url: value.url.href, __type: "map" };
- else if (value instanceof DateField) return { date: value.toString(), __type: "date" };
- else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" };
- else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" };
- else if (value instanceof ComputedField) return { script: value.script, __type: "computed" };
+ } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' };
+ else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' };
+ else if (value instanceof ImageField) return { url: value.url.href, __type: 'image' };
+ else if (value instanceof PdfField) return { url: value.url.href, __type: 'pdf' };
+ else if (value instanceof AudioField) return { url: value.url.href, __type: 'audio' };
+ else if (value instanceof VideoField) return { url: value.url.href, __type: 'video' };
+ else if (value instanceof WebField) return { url: value.url.href, __type: 'web' };
+ else if (value instanceof MapField) return { url: value.url.href, __type: 'map' };
+ else if (value instanceof DateField) return { date: value.toString(), __type: 'date' };
+ else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: 'proxy' };
+ else if (value instanceof Array && key !== 'fields') return { fields: value, __type: 'list' };
+ else if (value instanceof ComputedField) return { script: value.script, __type: 'computed' };
else return value;
}
const docs: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]);
+ Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
const docString = JSON.stringify({ id: doc[Id], docs }, replacer);
const zip = new JSZip();
- zip.file("doc.json", docString);
+ zip.file('doc.json', docString);
// // Generate a directory within the Zip file structure
// var img = zip.folder("images");
@@ -667,11 +695,10 @@ export namespace Doc {
// img.file("smile.gif", imgData, {base64: true});
// Generate the zip file asynchronously
- zip.generateAsync({ type: "blob" })
- .then((content: any) => {
- // Force down of the Zip file
- saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title?
- });
+ zip.generateAsync({ type: 'blob' }).then((content: any) => {
+ // Force down of the Zip file
+ saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title?
+ });
}
//
// Determines whether the layout needs to be expanded (as a template).
@@ -694,46 +721,47 @@ export namespace Doc {
// layout_mytemplate(somparam=somearg).
// then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument
export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) {
- const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS);
- if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc;
+ const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS);
+ if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc;
- const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
+ const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders
// First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
// using the template layout doc's id as the field key.
// If it doesn't find the expanded layout, then it makes a delegate of the template layout and
// saves it on the data doc indexed by the template layout's id.
//
- const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS";
+ const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS';
const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc);
- const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]";
+ const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']';
let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
expandedTemplateLayout = undefined;
_pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
- }
- else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
+ } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) {
if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) {
expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
} else {
templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true);
- setTimeout(action(() => {
- const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]");
- // the template's arguments are stored in params which is derefenced to find
- // the actual field key where the parameterized template data is stored.
- newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
- newLayoutDoc.rootDocument = targetDoc;
- const dataDoc = Doc.GetProto(targetDoc);
- newLayoutDoc.resolvedDataDoc = dataDoc;
- if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
- dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
- }
- targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
-
- _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
- }));
+ setTimeout(
+ action(() => {
+ const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']');
+ // the template's arguments are stored in params which is derefenced to find
+ // the actual field key where the parameterized template data is stored.
+ newLayoutDoc[params] = args !== '...' ? args : ''; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances
+ newLayoutDoc.rootDocument = targetDoc;
+ const dataDoc = Doc.GetProto(targetDoc);
+ newLayoutDoc.resolvedDataDoc = dataDoc;
+ if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
+ dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
+ }
+ targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
+
+ _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args);
+ })
+ );
}
}
}
@@ -744,17 +772,17 @@ export namespace Doc {
// otherwise, it just returns the childDoc
export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
- console.log("No, no, no!");
+ console.log('No, no, no!');
return { layout: childDoc, data: childDoc };
}
- const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc);
- return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc };
+ const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc;
+ return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc };
}
export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
Object.keys(doc).forEach(key => {
const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
+ if (key === 'proto' && copyProto) {
if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
}
@@ -776,12 +804,12 @@ export namespace Doc {
export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = new Doc(copyProtoId, true);
- const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
+ const exclude = Cast(doc.cloneFieldFilter, listSpec('string'), []);
Object.keys(doc).forEach(key => {
if (exclude.includes(key)) return;
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- if (key === "proto" && copyProto) {
+ if (key === 'proto' && copyProto) {
if (doc[key] instanceof Doc) {
copy[key] = Doc.MakeCopy(doc[key]!, false);
}
@@ -789,11 +817,14 @@ export namespace Doc {
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
- copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript);
+ copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript);
} else if (field instanceof ObjectField) {
- copy[key] = doc[key] instanceof Doc ?
- key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields
- ObjectField.MakeCopy(field);
+ copy[key] =
+ doc[key] instanceof Doc
+ ? key.includes('layout[')
+ ? undefined
+ : doc[key] // reference documents except remove documents that are expanded teplate fields
+ : ObjectField.MakeCopy(field);
} else if (field instanceof Promise) {
debugger; //This shouldn't happend...
} else {
@@ -806,17 +837,16 @@ export namespace Doc {
Doc.GetProto(copy).context = undefined;
Doc.GetProto(copy).aliases = new List<Doc>([copy]);
} else {
- Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy);
+ Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'aliases', copy);
}
copy.context = undefined;
- Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
+ Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared');
if (retitle) {
copy.title = incrementTitleCopy(StrCast(copy.title));
}
return copy;
}
-
export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
@@ -825,8 +855,10 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = doc;
delegate.author = Doc.CurrentUserEmail;
- Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]);
- if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate);
+ Object.keys(doc)
+ .filter(key => key.startsWith('acl'))
+ .forEach(key => (delegate[key] = doc[key]));
+ if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
return delegate;
@@ -849,7 +881,7 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = delegateProto;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate);
+ Doc.AddDocToList(delegateProto[DataSym], 'aliases', delegate);
delegate[Initializing] = false;
delegateProto[Initializing] = false;
return delegate;
@@ -861,11 +893,11 @@ export namespace Doc {
const proto = new Doc();
proto.author = Doc.CurrentUserEmail;
const target = Doc.MakeDelegate(proto);
- const targetKey = StrCast(templateDoc.layoutKey, "layout");
- const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
+ const targetKey = StrCast(templateDoc.layoutKey, 'layout');
+ const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
target.layoutKey = targetKey;
applied && (Doc.GetProto(applied).type = templateDoc.type);
- Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared");
+ Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared');
return applied;
}
return undefined;
@@ -888,9 +920,8 @@ export namespace Doc {
// metadata field indicated by the title of the template field (not the default field that it was rendering)
//
export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
-
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, "");
+ const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '');
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
@@ -904,7 +935,7 @@ export namespace Doc {
// note 2: this will not overwrite any field that already exists on the template doc at the field key
if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) {
Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
- (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue));
+ Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
}
// get the layout string that the template uses to specify its layout
const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
@@ -913,19 +944,18 @@ export namespace Doc {
Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
// assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
- Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
+ Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc);
return true;
}
-
// converts a document id to a url path on the server
- export function globalServerPath(doc: Doc | string = ""): string {
- return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc));
+ export function globalServerPath(doc: Doc | string = ''): string {
+ return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
}
// converts a document id to a url path on the server
export function localServerPath(doc?: Doc): string {
- return "/doc/" + (doc ? doc[Id] : "");
+ return '/doc/' + (doc ? doc[Id] : '');
}
export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
@@ -958,47 +988,70 @@ export namespace Doc {
export class DocData {
@observable _user_doc: Doc = undefined!;
@observable _sharing_doc: Doc = undefined!;
- @observable _searchQuery: string = "";
+ @observable _searchQuery: string = '';
}
// the document containing the view layout information - will be the Document itself unless the Document has
// a layout field or 'layout' is given.
export function Layout(doc: Doc, layout?: Doc): Doc {
- const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null);
+ const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null);
return overrideLayout || doc[LayoutSym] || doc;
}
- export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; }
- export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
- export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; }
+ export function SetLayout(doc: Doc, layout: Doc | string) {
+ doc[StrCast(doc.layoutKey, 'layout')] = layout;
+ }
+ export function LayoutField(doc: Doc) {
+ return doc[StrCast(doc.layoutKey, 'layout')];
+ }
+ export function LayoutFieldKey(doc: Doc): string {
+ return StrCast(Doc.Layout(doc).layout).split("'")[1];
+ }
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
}
- export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); }
+ export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
+ return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0));
+ }
export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
- const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0;
- const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0;
+ const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0;
+ const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0;
return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
}
- export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; }
- export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; }
-
+ export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
+ doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width;
+ }
+ export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
+ doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeHeight'] = height;
+ }
const manager = new DocData();
- export function SearchQuery(): string { return manager._searchQuery; }
- export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
- export function UserDoc(): Doc { return manager._user_doc; }
- export function SharingDoc(): Doc { return CurrentUserUtils.MySharedDocs; }
- export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); }
- export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); }
+ export function SearchQuery(): string {
+ return manager._searchQuery;
+ }
+ export function SetSearchQuery(query: string) {
+ runInAction(() => (manager._searchQuery = query));
+ }
+ export function UserDoc(): Doc {
+ return manager._user_doc;
+ }
+ export function SharingDoc(): Doc {
+ return CurrentUserUtils.MySharedDocs;
+ }
+ export function LinkDBDoc(): Doc {
+ return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
+ }
+ export function SetUserDoc(doc: Doc) {
+ return (manager._user_doc = doc);
+ }
const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
});
- export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); }
+ export function IsSearchMatch(doc: Doc) {
+ return isSearchMatchCache(doc);
+ }
export function IsSearchMatchUnmemoized(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
- brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
}
export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
@@ -1017,8 +1070,12 @@ export namespace Doc {
brushManager.SearchMatchDoc.clear();
}
- const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); });
- export function IsBrushed(doc: Doc) { return isBrushedCache(doc); }
+ const isBrushedCache = computedFn(function IsBrushed(doc: Doc) {
+ return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
+ });
+ export function IsBrushed(doc: Doc) {
+ return isBrushedCache(doc);
+ }
export enum DocBrushStatus {
unbrushed = 0,
@@ -1037,9 +1094,7 @@ export namespace Doc {
for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) {
const a1 = Cast(link.anchor1, Doc, null);
const a2 = Cast(link.anchor2, Doc, null);
- if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) ||
- (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) ||
- (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) {
+ if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) {
return DocBrushStatus.linkHighlighted;
}
}
@@ -1070,22 +1125,21 @@ export namespace Doc {
}
export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
- return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) ||
- Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2";
+ return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2';
}
export function linkFollowUnhighlight() {
Doc.UnhighlightAll();
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ document.removeEventListener('pointerdown', linkFollowUnhighlight);
}
let _lastDate = 0;
export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) {
linkFollowUnhighlight();
(destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs));
- document.removeEventListener("pointerdown", linkFollowUnhighlight);
- document.addEventListener("pointerdown", linkFollowUnhighlight);
- const lastDate = _lastDate = Date.now();
+ document.removeEventListener('pointerdown', linkFollowUnhighlight);
+ document.addEventListener('pointerdown', linkFollowUnhighlight);
+ const lastDate = (_lastDate = Date.now());
window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
}
@@ -1116,53 +1170,57 @@ export namespace Doc {
const targetDoc = docEntry.value;
targetDoc && Doc.UnHighlightDoc(targetDoc);
}
-
}
export function UnBrushAllDocs() {
brushManager.BrushedDoc.clear();
}
export function getDocTemplate(doc?: Doc) {
- return !doc ? undefined :
- doc.isTemplateDoc ? doc :
- Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
- Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
- (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
- undefined;
+ return !doc
+ ? undefined
+ : doc.isTemplateDoc
+ ? doc
+ : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
+ ? doc.dragFactory
+ : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
+ ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
+ ? Doc.Layout(doc).proto
+ : Doc.Layout(doc)
+ : undefined;
}
export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
if (Utils.HasTransparencyFilter(value)) {
- const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1);
+ const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1;
return isTransparent(StrCast(doc[key]));
}
- if (typeof value === "string") {
- value = value.replace(`,${Utils.noRecursionHack}`, "");
+ if (typeof value === 'string') {
+ value = value.replace(`,${Utils.noRecursionHack}`, '');
}
- const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
- if (Cast(fieldVal, listSpec("string"), []).length) {
- const vals = Cast(fieldVal, listSpec("string"), []);
+ const fieldVal = key === '#' ? (StrCast(doc.tags).includes(':#' + value + ':') ? StrCast(doc.tags) : undefined) : doc[key];
+ if (Cast(fieldVal, listSpec('string'), []).length) {
+ const vals = Cast(fieldVal, listSpec('string'), []);
const docs = vals.some(v => (v as any) instanceof Doc);
if (docs) return value === Field.toString(fieldVal as Field);
- return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
const fieldStr = Field.toString(fieldVal as Field);
return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
export function deiconifyView(doc: Doc) {
- StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+ StrCast(doc.layoutKey).split('_')[1] === 'icon' && setNativeView(doc);
}
export function setNativeView(doc: any) {
- const prevLayout = StrCast(doc.layoutKey).split("_")[1];
- const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : "";
- prevLayout === "icon" && (doc.deiconifyLayout = undefined);
- doc.layoutKey = deiconify || "layout";
+ const prevLayout = StrCast(doc.layoutKey).split('_')[1];
+ const deiconify = prevLayout === 'icon' && StrCast(doc.deiconifyLayout) ? 'layout_' + StrCast(doc.deiconifyLayout) : '';
+ prevLayout === 'icon' && (doc.deiconifyLayout = undefined);
+ doc.layoutKey = deiconify || 'layout';
}
export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: number[]) {
if (!container) return;
- const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
+ const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
docRangeFilters.splice(i, 3);
@@ -1180,16 +1238,16 @@ export namespace Doc {
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
if (!container) return;
- const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
- const docFilters = Cast(container[filterField], listSpec("string"), []);
+ const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters';
+ const docFilters = Cast(container[filterField], listSpec('string'), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i++) {
- const fields = docFilters[i].split(":"); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === "match")) {
+ const fields = docFilters[i].split(':'); // split key:value:modifier
+ if (fields[0] === key && (fields[1] === value || modifiers === 'match')) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
- if (toggle) modifiers = "remove";
+ if (toggle) modifiers = 'remove';
else return;
}
docFilters.splice(i, 1);
@@ -1197,17 +1255,17 @@ export namespace Doc {
break;
}
}
- if (!docFilters.length && modifiers === "match" && value === undefined) {
+ if (!docFilters.length && modifiers === 'match' && value === undefined) {
container[filterField] = undefined;
- } else if (modifiers !== "remove") {
+ } else if (modifiers !== 'remove') {
!append && (docFilters.length = 0);
- docFilters.push(key + ":" + value + ":" + modifiers);
+ docFilters.push(key + ':' + value + ':' + modifiers);
container[filterField] = new List<string>(docFilters);
}
});
}
export function readDocRangeFilter(doc: Doc, key: string) {
- const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
+ const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])];
@@ -1225,8 +1283,7 @@ export namespace Doc {
layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale;
layoutDoc._nativeWidth = undefined;
layoutDoc._nativeHeight = undefined;
- }
- else {
+ } else {
layoutDoc._autoHeight = false;
if (!Doc.NativeWidth(layoutDoc)) {
layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
@@ -1239,44 +1296,61 @@ export namespace Doc {
export function isDocPinned(doc: Doc) {
//add this new doc to props.Document
const curPres = CurrentUserUtils.ActivePresentation;
- return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1;
+ return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1;
}
export function toIcon(doc?: Doc, isOpen?: boolean) {
switch (StrCast(doc?.type)) {
- case DocumentType.IMG: return "image";
- case DocumentType.COMPARISON: return "columns";
- case DocumentType.RTF: return "sticky-note";
+ case DocumentType.IMG:
+ return 'image';
+ case DocumentType.COMPARISON:
+ return 'columns';
+ case DocumentType.RTF:
+ return 'sticky-note';
case DocumentType.COL:
- const folder: IconProp = isOpen ? "folder-open" : "folder";
- const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right";
+ const folder: IconProp = isOpen ? 'folder-open' : 'folder';
+ const chevron: IconProp = isOpen ? 'chevron-down' : 'chevron-right';
return !doc?.isFolder ? folder : chevron;
- case DocumentType.WEB: return "globe-asia";
- case DocumentType.SCREENSHOT: return "photo-video";
- case DocumentType.WEBCAM: return "video";
- case DocumentType.AUDIO: return "microphone";
- case DocumentType.BUTTON: return "bolt";
- case DocumentType.PRES: return "tv";
- case DocumentType.SCRIPTING: return "terminal";
- case DocumentType.IMPORT: return "cloud-upload-alt";
- case DocumentType.VID: return "video";
- case DocumentType.INK: return "pen-nib";
- case DocumentType.PDF: return "file-pdf";
- case DocumentType.LINK: return "link";
- case DocumentType.MAP: return "map-marker-alt";
- default: return "question";
+ case DocumentType.WEB:
+ return 'globe-asia';
+ case DocumentType.SCREENSHOT:
+ return 'photo-video';
+ case DocumentType.WEBCAM:
+ return 'video';
+ case DocumentType.AUDIO:
+ return 'microphone';
+ case DocumentType.BUTTON:
+ return 'bolt';
+ case DocumentType.PRES:
+ return 'tv';
+ case DocumentType.SCRIPTING:
+ return 'terminal';
+ case DocumentType.IMPORT:
+ return 'cloud-upload-alt';
+ case DocumentType.VID:
+ return 'video';
+ case DocumentType.INK:
+ return 'pen-nib';
+ case DocumentType.PDF:
+ return 'file-pdf';
+ case DocumentType.LINK:
+ return 'link';
+ case DocumentType.MAP:
+ return 'map-marker-alt';
+ default:
+ return 'question';
}
}
- export async function importDocument(file:File) {
- const upload = Utils.prepend("/uploadDoc");
+ export async function importDocument(file: File) {
+ const upload = Utils.prepend('/uploadDoc');
const formData = new FormData();
if (file) {
formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
+ formData.append('remap', 'true');
+ const response = await fetch(upload, { method: 'POST', body: formData });
const json = await response.json();
- if (json !== "error") {
+ if (json !== 'error') {
const doc = await DocServer.GetRefField(json);
return doc;
}
@@ -1285,17 +1359,16 @@ export namespace Doc {
}
export namespace Get {
-
- const primitives = ["string", "number", "boolean"];
+ const primitives = ['string', 'number', 'boolean'];
export interface JsonConversionOpts {
data: any;
title?: string;
- appendToExisting?: { targetDoc: Doc, fieldKey?: string };
+ appendToExisting?: { targetDoc: Doc; fieldKey?: string };
excludeEmptyObjects?: boolean;
}
- const defaultKey = "json";
+ const defaultKey = 'json';
/**
* This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
@@ -1340,17 +1413,17 @@ export namespace Doc {
if (excludeEmptyObjects === undefined) {
excludeEmptyObjects = true;
}
- if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
+ if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) {
return undefined;
}
let resolved: any;
try {
- resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
+ resolved = JSON.parse(typeof data === 'string' ? data : JSON.stringify(data));
} catch (e) {
return undefined;
}
let output: Opt<Doc>;
- if (typeof resolved === "object" && !(resolved instanceof Array)) {
+ if (typeof resolved === 'object' && !(resolved instanceof Array)) {
output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
} else {
// give the proper types to the data extracted from the JSON
@@ -1358,7 +1431,7 @@ export namespace Doc {
if (appendToExisting) {
(output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
} else {
- (output = new Doc).json = result;
+ (output = new Doc()).json = result;
}
}
title && output && (output.title = title);
@@ -1375,14 +1448,14 @@ export namespace Doc {
const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
const hasEntries = Object.keys(object).length;
if (hasEntries || !excludeEmptyObjects) {
- const resolved = target ?? new Doc;
+ const resolved = target ?? new Doc();
if (hasEntries) {
let result: Opt<Field>;
Object.keys(object).map(key => {
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- if (result = toField(object[key], excludeEmptyObjects, key)) {
+ if ((result = toField(object[key], excludeEmptyObjects, key))) {
resolved[key] = result;
}
});
@@ -1418,40 +1491,78 @@ export namespace Doc {
if (primitives.includes(typeof data)) {
return data;
}
- if (typeof data === "object") {
+ if (typeof data === 'object') {
return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
}
throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
};
}
-
}
-ScriptingGlobals.add(function idToDoc(id: string): any { return DocServer.GetCachedRefField(id); });
-ScriptingGlobals.add(function renameAlias(doc: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${doc.aliasNumber})`; });
-ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); });
-ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
-ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
-ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); });
-ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); });
-ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); });
-ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
-ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); });
-ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); });
-ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); });
-ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; });
-ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); });
-ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); });
+ScriptingGlobals.add(function idToDoc(id: string): any {
+ return DocServer.GetCachedRefField(id);
+});
+ScriptingGlobals.add(function renameAlias(doc: any) {
+ return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`;
+});
+ScriptingGlobals.add(function getProto(doc: any) {
+ return Doc.GetProto(doc);
+});
+ScriptingGlobals.add(function getDocTemplate(doc?: any) {
+ return Doc.getDocTemplate(doc);
+});
+ScriptingGlobals.add(function getAlias(doc: any) {
+ return Doc.MakeAlias(doc);
+});
+ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
+ return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto);
+});
+ScriptingGlobals.add(function copyField(field: any) {
+ return Field.Copy(field);
+});
+ScriptingGlobals.add(function docList(field: any) {
+ return DocListCast(field);
+});
+ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) {
+ return Doc.AddDocToList(doc, field, added);
+});
+ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
+ return Doc.SetInPlace(doc, field, value, false);
+});
+ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
+ return Doc.AreProtosEqual(doc1, doc2);
+});
+ScriptingGlobals.add(function undo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Undo();
+});
+ScriptingGlobals.add(function redo() {
+ SelectionManager.DeselectAll();
+ return UndoManager.Redo();
+});
+ScriptingGlobals.add(function DOC(id: string) {
+ console.log("Can't parse a document id in a script");
+ return 'invalid';
+});
+ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
+ return Doc.assignDocToField(doc, field, id);
+});
+ScriptingGlobals.add(function docCast(doc: FieldResult): any {
+ return DocCastAsync(doc);
+});
ScriptingGlobals.add(function activePresentationItem() {
const curPres = CurrentUserUtils.ActivePresentation;
return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
});
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views().map(dv => dv.props.Document).
- filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP &&
- (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
+ const docs = SelectionManager.Views()
+ .map(dv => dv.props.Document)
+ .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
return docs.length ? new List(docs) : prevValue;
});
-ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: "match" | "check" | "x" | "remove") { Doc.setDocFilter(container, key, value, modifiers); });
-ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); });
+ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
+ Doc.setDocFilter(container, key, value, modifiers);
+});
+ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
+ Doc.setDocRangeFilter(container, key, range);
+});
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a19be5df9..bf055cd8b 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,48 +1,46 @@
-import { AssertionError } from "assert";
-import { docs_v1 } from "googleapis";
-import { Fragment, Mark, Node } from "prosemirror-model";
-import { sinkListItem } from "prosemirror-schema-list";
-import { Utils, DashColor } from "../Utils";
-import { Docs, DocUtils } from "../client/documents/Documents";
-import { schema } from "../client/views/nodes/formattedText/schema_rts";
-import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
-import { DocServer } from "../client/DocServer";
-import { Networking } from "../client/Network";
-import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
-import { Doc, Opt } from "./Doc";
-import { Id } from "./FieldSymbols";
-import { RichTextField } from "./RichTextField";
-import { Cast, StrCast } from "./Types";
+import { AssertionError } from 'assert';
+import { docs_v1 } from 'googleapis';
+import { Fragment, Mark, Node } from 'prosemirror-model';
+import { sinkListItem } from 'prosemirror-schema-list';
+import { Utils, DashColor } from '../Utils';
+import { Docs, DocUtils } from '../client/documents/Documents';
+import { schema } from '../client/views/nodes/formattedText/schema_rts';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocServer } from '../client/DocServer';
+import { Networking } from '../client/Network';
+import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
+import { Doc, Opt } from './Doc';
+import { Id } from './FieldSymbols';
+import { RichTextField } from './RichTextField';
+import { Cast, StrCast } from './Types';
import Color = require('color');
-import { EditorState, TextSelection, Transaction } from "prosemirror-state";
-import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
+import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
export namespace RichTextUtils {
-
- const delimiter = "\n";
- const joiner = "";
-
+ const delimiter = '\n';
+ const joiner = '';
export const Initialize = (initial?: string) => {
const content: any[] = [];
const state = {
doc: {
- type: "doc",
+ type: 'doc',
content,
},
selection: {
- type: "text",
+ type: 'text',
anchor: 0,
- head: 0
- }
+ head: 0,
+ },
};
if (initial && initial.length) {
content.push({
- type: "paragraph",
+ type: 'paragraph',
content: {
- type: "text",
- text: initial
- }
+ type: 'text',
+ text: initial,
+ },
});
state.selection.anchor = state.selection.head = initial.length + 1;
}
@@ -56,8 +54,8 @@ export namespace RichTextUtils {
export const ToPlainText = (state: EditorState) => {
// Because we're working with plain text, just concatenate all paragraphs
const content = state.doc.content;
- const paragraphs: Node<any>[] = [];
- content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node));
+ const paragraphs: Node[] = [];
+ content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node));
// Functions to flatten ProseMirror paragraph objects (and their components) to plain text
// Concatentate paragraphs and string the result together
@@ -80,22 +78,21 @@ export namespace RichTextUtils {
// Preserve the current state, but re-write the content to be the blocks
const parsed = JSON.parse(oldState ? oldState.Data : Initialize());
parsed.doc.content = elements.map(text => {
- const paragraph: any = { type: "paragraph" };
- text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
+ const paragraph: any = { type: 'paragraph' };
+ text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break
return paragraph;
});
// If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it
- parsed.selection = { type: "text", anchor: 1, head: 1 };
+ parsed.selection = { type: 'text', anchor: 1, head: 1 };
// Export the ProseMirror-compatible state object we've just built
return JSON.stringify(parsed);
};
export namespace GoogleDocs {
-
export const Export = async (state: EditorState): Promise<GoogleApiClientUtils.Docs.Content> => {
- const nodes: (Node<any> | null)[] = [];
+ const nodes: (Node | null)[] = [];
const text = ToPlainText(state);
state.doc.content.forEach(node => {
if (!node.childCount) {
@@ -126,10 +123,10 @@ export namespace RichTextUtils {
return { baseUrl: embeddedObject.imageProperties!.contentUri! };
});
- const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems });
+ const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems });
if (uploads.length !== mediaItems.length) {
- throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" });
+ throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' });
}
for (let i = 0; i < objects.length; i++) {
@@ -144,14 +141,14 @@ export namespace RichTextUtils {
title: embeddedObject.title || `Imported Image from ${document.title}`,
width,
url: Utils.prepend(_m.client),
- agnostic: Utils.prepend(agnostic.client)
+ agnostic: Utils.prepend(agnostic.client),
});
}
}
return inlineObjectMap;
};
- type BulletPosition = { value: number, sinks: number };
+ type BulletPosition = { value: number; sinks: number };
interface MediaItem {
baseUrl: string;
@@ -172,7 +169,7 @@ export namespace RichTextUtils {
const lists: ListGroup[] = [];
const indentMap = new Map<ListGroup, BulletPosition[]>();
let globalOffset = 0;
- const nodes: Node<any>[] = [];
+ const nodes: Node[] = [];
for (const element of structured) {
if (Array.isArray(element)) {
lists.push(element);
@@ -182,7 +179,7 @@ export namespace RichTextUtils {
const sinks = paragraph.bullet!;
positions.push({
value: position + globalOffset,
- sinks
+ sinks,
});
position += item.nodeSize;
globalOffset += 2 * sinks;
@@ -191,13 +188,13 @@ export namespace RichTextUtils {
indentMap.set(element, positions);
nodes.push(list(state.schema, items));
} else {
- if (element.contents.some(child => "inlineObjectId" in child)) {
+ if (element.contents.some(child => 'inlineObjectId' in child)) {
const group = element.contents;
group.forEach((child, i) => {
- let node: Opt<Node<any>>;
- if ("inlineObjectId" in child) {
+ let node: Opt<Node>;
+ if ('inlineObjectId' in child) {
node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote);
- } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
+ } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) {
node = paragraphNode(state.schema, [child]);
}
if (node) {
@@ -215,7 +212,7 @@ export namespace RichTextUtils {
state = state.apply(state.tr.replaceWith(0, 2, nodes));
const sink = sinkListItem(state.schema.nodes.list_item);
- const dispatcher = (tr: Transaction) => state = state.apply(tr);
+ const dispatcher = (tr: Transaction) => (state = state.apply(tr));
for (const list of lists) {
for (const pos of indentMap.get(list)!) {
const resolved = state.doc.resolve(pos.value);
@@ -252,17 +249,17 @@ export namespace RichTextUtils {
};
const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
- return schema.node("list_item", null, paragraphNode(schema, runs));
+ return schema.node('list_item', null, paragraphNode(schema, runs));
};
const list = (schema: any, items: Node[]): Node => {
- return schema.node("ordered_list", { mapStyle: "bullet" }, items);
+ return schema.node('ordered_list', { mapStyle: 'bullet' }, items);
};
const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined);
const fragment = children.length ? Fragment.from(children) : undefined;
- return schema.node("paragraph", null, fragment);
+ return schema.node('paragraph', null, fragment);
};
const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => {
@@ -278,7 +275,7 @@ export namespace RichTextUtils {
} else {
docid = backingDocId;
}
- return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" });
+ return schema.node('image', { src, agnostic, width, docid, float: null, location: 'add:right' });
};
const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
@@ -287,10 +284,10 @@ export namespace RichTextUtils {
};
const StyleToMark = new Map<keyof docs_v1.Schema$TextStyle, keyof typeof schema.marks>([
- ["bold", "strong"],
- ["italic", "em"],
- ["foregroundColor", "pFontColor"],
- ["fontSize", "pFontSize"]
+ ['bold', 'strong'],
+ ['italic', 'em'],
+ ['foregroundColor', 'pFontColor'],
+ ['fontSize', 'pFontSize'],
]);
const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => {
@@ -301,21 +298,21 @@ export namespace RichTextUtils {
Object.keys(textStyle).forEach(key => {
let value: any;
const targeted = key as keyof docs_v1.Schema$TextStyle;
- if (value = textStyle[targeted]) {
+ if ((value = textStyle[targeted])) {
const attributes: any = {};
let converted = StyleToMark.get(targeted) || targeted;
value.url && (attributes.href = value.url);
if (value.color) {
const object = value.color.rgbColor;
- attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex();
+ attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex();
}
if (value.magnitude) {
attributes.fontSize = value.magnitude;
}
- if (converted === "weightedFontFamily") {
- converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman";
+ if (converted === 'weightedFontFamily') {
+ converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman';
}
const mapped = schema.marks[converted];
@@ -332,38 +329,38 @@ export namespace RichTextUtils {
};
const MarkToStyle = new Map<keyof typeof schema.marks, keyof docs_v1.Schema$TextStyle>([
- ["strong", "bold"],
- ["em", "italic"],
- ["pFontColor", "foregroundColor"],
- ["pFontSize", "fontSize"],
- ["timesNewRoman", "weightedFontFamily"],
- ["georgia", "weightedFontFamily"],
- ["comicSans", "weightedFontFamily"],
- ["tahoma", "weightedFontFamily"],
- ["impact", "weightedFontFamily"]
+ ['strong', 'bold'],
+ ['em', 'italic'],
+ ['pFontColor', 'foregroundColor'],
+ ['pFontSize', 'fontSize'],
+ ['timesNewRoman', 'weightedFontFamily'],
+ ['georgia', 'weightedFontFamily'],
+ ['comicSans', 'weightedFontFamily'],
+ ['tahoma', 'weightedFontFamily'],
+ ['impact', 'weightedFontFamily'],
]);
const ExportFontFamilyMapping = new Map<string, string>([
- ["timesNewRoman", "Times New Roman"],
- ["arial", "Arial"],
- ["georgia", "Georgia"],
- ["comicSans", "Comic Sans MS"],
- ["tahoma", "Tahoma"],
- ["impact", "Impact"]
+ ['timesNewRoman', 'Times New Roman'],
+ ['arial', 'Arial'],
+ ['georgia', 'Georgia'],
+ ['comicSans', 'Comic Sans MS'],
+ ['tahoma', 'Tahoma'],
+ ['impact', 'Impact'],
]);
const ImportFontFamilyMapping = new Map<string, string>([
- ["Times New Roman", "timesNewRoman"],
- ["Arial", "arial"],
- ["Georgia", "georgia"],
- ["Comic Sans MS", "comicSans"],
- ["Tahoma", "tahoma"],
- ["Impact", "impact"]
+ ['Times New Roman', 'timesNewRoman'],
+ ['Arial', 'arial'],
+ ['Georgia', 'georgia'],
+ ['Comic Sans MS', 'comicSans'],
+ ['Tahoma', 'tahoma'],
+ ['Impact', 'impact'],
]);
- const ignored = ["user_mark"];
+ const ignored = ['user_mark'];
- const marksToStyle = async (nodes: (Node<any> | null)[]): Promise<docs_v1.Schema$Request[]> => {
+ const marksToStyle = async (nodes: (Node | null)[]): Promise<docs_v1.Schema$Request[]> => {
const requests: docs_v1.Schema$Request[] = [];
let position = 1;
for (const node of nodes) {
@@ -376,25 +373,25 @@ export namespace RichTextUtils {
const information: LinkInformation = {
startIndex: position,
endIndex: position + nodeSize,
- textStyle
+ textStyle,
};
- let mark: Mark<any>;
+ let mark: Mark;
const markMap = BuildMarkMap(marks);
for (const markName of Object.keys(schema.marks)) {
if (ignored.includes(markName) || !(mark = markMap[markName])) {
continue;
}
- let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle;
+ let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle);
let value: any = true;
if (!converted) {
continue;
}
const { attrs } = mark;
switch (converted) {
- case "link":
- let url = attrs.allLinks.length ? attrs.allLinks[0].href : "";
- const delimiter = "/doc/";
- const alreadyShared = "?sharing=true";
+ case 'link':
+ let url = attrs.allLinks.length ? attrs.allLinks[0].href : '';
+ const delimiter = '/doc/';
+ const alreadyShared = '?sharing=true';
if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {
const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]);
if (linkDoc instanceof Doc) {
@@ -411,41 +408,43 @@ export namespace RichTextUtils {
textStyle.foregroundColor = fromRgb.blue;
textStyle.bold = true;
break;
- case "fontSize":
- value = { magnitude: attrs.fontSize, unit: "PT" };
+ case 'fontSize':
+ value = { magnitude: attrs.fontSize, unit: 'PT' };
break;
- case "foregroundColor":
+ case 'foregroundColor':
value = fromHex(attrs.color);
break;
- case "weightedFontFamily":
+ case 'weightedFontFamily':
value = { fontFamily: ExportFontFamilyMapping.get(markName) };
}
let matches: RegExpExecArray | null;
if ((matches = /p(\d+)/g.exec(markName)) !== null) {
- converted = "fontSize";
- value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" };
+ converted = 'fontSize';
+ value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' };
}
textStyle[converted] = value;
}
if (Object.keys(textStyle).length) {
requests.push(EncodeStyleUpdate(information));
}
- if (node.type.name === "image") {
+ if (node.type.name === 'image') {
const width = attrs.width;
- requests.push(await EncodeImage({
- startIndex: position + nodeSize - 1,
- uri: attrs.agnostic,
- width: Number(typeof width === "string" ? width.replace("px", "") : width)
- }));
+ requests.push(
+ await EncodeImage({
+ startIndex: position + nodeSize - 1,
+ uri: attrs.agnostic,
+ width: Number(typeof width === 'string' ? width.replace('px', '') : width),
+ })
+ );
}
position += nodeSize;
}
return requests;
};
- const BuildMarkMap = (marks: Mark<any>[]) => {
- const markMap: { [type: string]: Mark<any> } = {};
- marks.forEach(mark => markMap[mark.type.name] = mark);
+ const BuildMarkMap = (marks: readonly Mark[]) => {
+ const markMap: { [type: string]: Mark } = {};
+ marks.forEach(mark => (markMap[mark.type.name] = mark));
return markMap;
};
@@ -462,23 +461,21 @@ export namespace RichTextUtils {
}
namespace fromRgb {
-
export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => {
return {
color: {
rgbColor: {
red: red / 255,
green: green / 255,
- blue: blue / 255
- }
- }
+ blue: blue / 255,
+ },
+ },
};
};
export const red = convert(255, 0, 0);
export const green = convert(0, 255, 0);
export const blue = convert(0, 0, 255);
-
}
const fromHex = (color: string): docs_v1.Schema$OptionalColor => {
@@ -490,10 +487,10 @@ export namespace RichTextUtils {
const { startIndex, endIndex, textStyle } = information;
return {
updateTextStyle: {
- fields: "*",
+ fields: '*',
range: { startIndex, endIndex },
- textStyle
- } as docs_v1.Schema$UpdateTextStyleRequest
+ textStyle,
+ } as docs_v1.Schema$UpdateTextStyleRequest,
};
};
@@ -507,13 +504,12 @@ export namespace RichTextUtils {
return {
insertInlineImage: {
uri: baseUrls[0],
- objectSize: { width: { magnitude: width, unit: "PT" } },
- location: { index: startIndex }
- }
+ objectSize: { width: { magnitude: width, unit: 'PT' } },
+ location: { index: startIndex },
+ },
};
}
return {};
};
}
-
-} \ No newline at end of file
+}