From 6cd6e035fc67812afd7a40f8abd0f07f8874f04a Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 26 Nov 2019 14:34:41 -0500 Subject: fixes for tree view drag drop with images. --- src/client/views/EditableView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 8e86f58ee..f78b61892 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -21,7 +21,7 @@ export interface EditableProps { OnFillDown?(value: string): void; - OnTab?(): void; + OnTab?(shift?: boolean): void; /** * The contents to render when not editing @@ -79,7 +79,7 @@ export class EditableView extends React.Component { if (e.key === "Tab") { e.stopPropagation(); this.finalizeEdit(e.currentTarget.value, e.shiftKey); - this.props.OnTab && this.props.OnTab(); + this.props.OnTab && this.props.OnTab(e.shiftKey); } else if (e.key === "Enter") { e.stopPropagation(); if (!e.ctrlKey) { -- cgit v1.2.3-70-g09d2 From 710dda13398c01b2b0f63b033ccca0c8ea4f7e49 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 4 Dec 2019 14:28:24 -0500 Subject: fixed mainview flyoutbehavior a bit. fixed keyvaluebox editing of lists a little. --- src/client/views/EditableView.tsx | 6 +++--- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 16 ++++++++++++++-- src/client/views/nodes/KeyValueBox.tsx | 8 ++++---- src/new_fields/DateField.ts | 4 ++++ src/new_fields/List.ts | 3 +-- 6 files changed, 27 insertions(+), 12 deletions(-) (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index f78b61892..ea9d548a1 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -10,7 +10,7 @@ export interface EditableProps { /** * Called to get the initial value for editing * */ - GetValue(): string; + GetValue(): string | undefined; /** * Called to apply changes @@ -108,8 +108,8 @@ export class EditableView extends React.Component { @action private finalizeEdit(value: string, shiftDown: boolean) { + this._editing = false; if (this.props.SetValue(value, shiftDown)) { - this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false); } } @@ -124,7 +124,7 @@ export class EditableView extends React.Component { } render() { - if (this._editing) { + if (this._editing && this.props.GetValue() !== undefined) { return this.props.autosuggestProps ? { MainView.Instance._flyoutTranslate = true; - MainView.Instance.flyoutWidth = 250; + MainView.Instance.flyoutWidth = (MainView.Instance.flyoutWidth || 250); }); @computed get expandButton() { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 48ea35c6b..95503147a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -279,7 +279,7 @@ class TreeView extends React.Component { const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; - if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { + if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { const remDoc = (doc: Doc) => this.remove(doc, key); const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : @@ -294,7 +294,7 @@ class TreeView extends React.Component { height={13} fontSize={12} GetValue={() => Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value)} />; + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; } rows.push(
{key + ":"} @@ -302,6 +302,18 @@ class TreeView extends React.Component { {contentElement}
); } + rows.push(
+ ""} + SetValue={(value: string) => { + value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true); + return true; + }} /> +
); return rows; } diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index fba29e4cd..322d639dc 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -66,10 +66,10 @@ export class KeyValueBox extends React.Component { return { script, type: dubEq, onDelegate: eq }; } - public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean { + public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean { const { script, type, onDelegate } = kvpScript; //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates - const target = onDelegate ? doc : Doc.GetProto(doc); + const target = forceOnDelegate || onDelegate ? doc : Doc.GetProto(doc); let field: Field; if (type === "computed") { field = new ComputedField(script); @@ -88,10 +88,10 @@ export class KeyValueBox extends React.Component { } @undoBatch - public static SetField(doc: Doc, key: string, value: string) { + public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean) { const script = this.CompileKVPScript(value); if (!script) return false; - return this.ApplyKVPScript(doc, key, script); + return this.ApplyKVPScript(doc, key, script, forceOnDelegate); } onPointerDown = (e: React.PointerEvent): void => { diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts index abec91e06..4f999e5e8 100644 --- a/src/new_fields/DateField.ts +++ b/src/new_fields/DateField.ts @@ -19,6 +19,10 @@ export class DateField extends ObjectField { return new DateField(this.date); } + toString() { + return `${this.date.toISOString()}`; + } + [ToScriptString]() { return `new DateField(new Date(${this.date.toISOString()}))`; } diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index e9101158b..bb48b1bb3 100644 --- a/src/new_fields/List.ts +++ b/src/new_fields/List.ts @@ -290,8 +290,7 @@ class ListImpl extends ObjectField { private [SelfProxy]: any; [ToScriptString]() { - return "invalid"; - // return `new List([${(this as any).map((field => Field.toScriptString(field))}])`; + return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; -- cgit v1.2.3-70-g09d2 From fe06511da85a9483c10353f0cc5d54b70a265079 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Dec 2019 15:54:01 -0500 Subject: added alt-t for toggle between editing title and text --- src/client/util/RichTextRules.ts | 18 +++++++++--------- src/client/views/EditableView.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 22 ++++++++++++++++++++-- src/client/views/nodes/FormattedTextBox.tsx | 21 +++++++++++++++------ src/server/authentication/models/user_model.ts | 2 +- 5 files changed, 47 insertions(+), 18 deletions(-) (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 4e60976d5..5f2d67a3e 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -64,10 +64,10 @@ export const inpRules = { new RegExp(/^#([0-9]+)\s$/), (state, match, start, end) => { const size = Number(match[1]); - const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; + const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); if (ruleProvider && heading) { - (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = size; + (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size; return state.tr.deleteRange(start, end); } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); @@ -163,8 +163,8 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; + const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "center"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; @@ -178,8 +178,8 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; + const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "left"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; @@ -193,8 +193,8 @@ export const inpRules = { (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider; - const heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; + const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "right"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index ea9d548a1..d0cecf03d 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -120,7 +120,9 @@ export class EditableView extends React.Component { @action setIsFocused = (value: boolean) => { + let wasFocused = this._editing; this._editing = value; + return wasFocused !== this._editing; } render() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c6b134d6c..9219da80b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -92,6 +92,7 @@ export class DocumentView extends DocComponent(Docu private _hitTemplateDrag = false; private _mainCont = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; + private _titleRef = React.createRef(); public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } @@ -138,6 +139,23 @@ export class DocumentView extends DocComponent(Docu } } + onKeyDown = (e: React.KeyboardEvent) => { + if (e.altKey && e.key === "t" && !(e.nativeEvent as any).StopPropagationForReal) { + (e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work... + e.stopPropagation(); + if (!StrCast(this.Document.showTitle)) this.Document.showTitle = "title"; + if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0); + else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... + { + this._titleRef.current?.setIsFocused(false); + let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); + any.keeplocation = true; + any?.focus(); + } + } + } + } + onClick = async (e: React.MouseEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { @@ -622,7 +640,7 @@ export class DocumentView extends DocComponent(Docu position: showTextTitle ? "relative" : "absolute", pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", }}> - StrCast(this.Document[showTitle])} @@ -694,7 +712,7 @@ export class DocumentView extends DocComponent(Docu const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; const highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; - return
Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} style={{ diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 42723b44b..70fa4974d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -80,7 +80,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _proseRef?: HTMLDivElement; private _editorView: Opt; private _applyingChange: boolean = false; - private _nodeClicked: any; private _searchIndex = 0; private _sidebarMovement = 0; private _lastX = 0; @@ -101,6 +100,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @observable private _ruleFontFamily = "Arial"; @observable private _fontAlign = ""; @observable private _entered = false; + + public static FocusedBox: FormattedTextBox | undefined; public static SelectOnLoad = ""; public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; @@ -874,8 +875,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onPointerDown = (e: React.PointerEvent): void => { FormattedTextBox._downEvent = true; FormattedTextBoxComment.textBox = this; - const pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { e.preventDefault(); } @@ -901,11 +900,17 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } - static InputBoxOverlay: FormattedTextBox | undefined; @action onFocused = (e: React.FocusEvent): void => { - FormattedTextBox.InputBoxOverlay = this; + FormattedTextBox.FocusedBox = this; this.tryUpdateHeight(); + + // see if we need to preserve the insertion point + let prosediv = this._proseRef?.children?.[0] as any; + let keeplocation = prosediv?.keeplocation; + prosediv && (prosediv.keeplocation = undefined); + let pos = this._editorView?.state.selection.$from.pos || 1; + keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); } onPointerWheel = (e: React.WheelEvent): void => { // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time @@ -1048,10 +1053,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._undoTyping.end(); this._undoTyping = undefined; } - this.doLinkOnDeselect(); + this.doLinkOnDeselect(); 6 } onKeyPress = (e: React.KeyboardEvent) => { + if (e.altKey) { + e.preventDefault(); + return; + } if (!this._editorView!.state.selection.empty && e.key === "%") { (this._editorView!.state as any).EnteringStyle = true; e.preventDefault(); diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts index 48910b612..6b71397dc 100644 --- a/src/server/authentication/models/user_model.ts +++ b/src/server/authentication/models/user_model.ts @@ -73,8 +73,8 @@ userSchema.pre("save", function save(next) { }); const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) { - return cb(undefined, true); bcrypt.compare(candidatePassword, this.password, cb); + // return cb(undefined, true); // use this to bypass passwords }; userSchema.methods.comparePassword = comparePassword; -- cgit v1.2.3-70-g09d2 From 9d8845fb64c08729b446f12206aa5ed215228f4e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 17:00:58 -0500 Subject: cleaned up toottipmenu and richtextschema a bit. fixed some problems with text styles. fixed warnings. --- logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 ---------------- logs/server_pids.txt | 1 - src/client/util/ProsemirrorExampleTransfer.ts | 2 +- src/client/util/RichTextRules.ts | 162 +++++----- src/client/util/RichTextSchema.tsx | 68 ++-- src/client/util/TooltipTextMenu.tsx | 414 +++++------------------- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/EditableView.tsx | 2 +- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 32 +- 12 files changed, 228 insertions(+), 741 deletions(-) delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/server_pids.txt (limited to 'src/client/views/EditableView.tsx') diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log deleted file mode 100644 index 37e232d48..000000000 --- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log +++ /dev/null @@ -1,274 +0,0 @@ - -> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web -> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts - -Using ts-node version 7.0.1, typescript version 3.7.2 -objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. - -starting execution of preliminary functions... -(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. -completed preliminary functions -. -Starting type checking and linting service... -Using 1 worker with 2048MB memory limit - -running server in development mode - -registering server routes... -all server routes have been successfully registered: -/ -/activity -/buxton -/delete -/deleteAll -/deleteWithAux -/deleteWithGoogleCredentials -/doc/:docId -/downloadId/:docId -/environment/:key -/getCurrentUser -/getUserDocumentId -/getUsers -/googleDocs/:sector/:action -/googlePhotosMediaDownload -/googlePhotosMediaUpload -/home -/imageHierarchyExport/:docId -/inspectImage -/persist -/pull -/readGoogleAccessToken -/search -/serializeDoc/:docId -/serverHeartbeat -/shutdown -/solr/:action -/textsearch -/thumbnail/:filename -/upload -/uploadDoc -/uploadURI -/version -/writeGoogleAccessToken - -websocket listening on port 4321 -server listening on port 1050 - -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 47419ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 47419ms -Built at: 12/11/2019 3:45:31 AM - Asset Size Chunks  Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] -32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] -718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] - assets/downarrow.png 3.28 KiB  [emitted] - assets/env.json 360 bytes  [emitted] - assets/google_photos.png 114 KiB  [emitted] - assets/google_tags.png 7.9 KiB  [emitted] - assets/loading.gif 112 KiB  [emitted] - assets/pdf.worker.js 1.55 MiB  [emitted] - bundle.js 20.8 MiB bundle [emitted] bundle - bundle.js.map 23.3 MiB bundle [emitted] bundle - debug/repl.html 348 bytes  [emitted] - debug/test.html 245 bytes  [emitted] - debug/viewer.html 357 bytes  [emitted] -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] - imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload - imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload - index.html 593 bytes  [emitted] - inkControls.js 116 KiB inkControls [emitted] inkControls - inkControls.js.map 122 KiB inkControls [emitted] inkControls - mobile/image.html 333 bytes  [emitted] - mobile/ink.html 252 bytes  [emitted] - repl.js 9.31 MiB repl [emitted] repl - repl.js.map 10.5 MiB repl [emitted] repl - test.js 1.2 MiB test [emitted] test - test.js.map 1.42 MiB test [emitted] test - test.pdf 53.6 KiB  [emitted] - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - viewer.js 9.47 MiB viewer [emitted] viewer - viewer.js.map 10.7 MiB viewer [emitted] viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] - [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] - [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] - [./src/debug/Test.tsx] 1.02 KiB {test} [built] - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -ℹ 「wdm」: Compiling... -webpack building... -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 615ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 615ms -Built at: 12/11/2019 3:45:33 AM - Asset Size Chunks Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   -32f1593298e6e7bee5673bf647328d72.png 429 bytes   -718c914a99a2136c01c84e01f63e505a.png 829 bytes   -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   - bundle.js 20.8 MiB bundle bundle - bundle.js.map 23.3 MiB bundle bundle -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   - imageUpload.js 20.8 MiB imageUpload imageUpload - imageUpload.js.map 23.3 MiB imageUpload imageUpload - inkControls.js 116 KiB inkControls inkControls - inkControls.js.map 122 KiB inkControls inkControls - repl.js 9.31 MiB repl repl - repl.js.map 10.5 MiB repl repl - test.js 1.2 MiB test test - test.js.map 1.42 MiB test test - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker - viewer.js 9.47 MiB viewer viewer - viewer.js.map 10.7 MiB viewer viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} - [./src/client/views/Main.tsx] 4.03 KiB {bundle} - [./src/debug/Repl.tsx] 6.87 KiB {repl} - [./src/debug/Test.tsx] 1.02 KiB {test} - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): -prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): -prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): -triple-equals: == should be === -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): -prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): -prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): -prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): -prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): -prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): -prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): -prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): -prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): -prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): -prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): -prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): -prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): -prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): -prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): -prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): -prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. -No type errors found -Version: typescript 3.7.2, tslint 5.18.0 -Time: 5473ms diff --git a/logs/server_pids.txt b/logs/server_pids.txt deleted file mode 100644 index 2aa143f24..000000000 --- a/logs/server_pids.txt +++ /dev/null @@ -1 +0,0 @@ -9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index f1fa6f11d..3324d8abe 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -105,7 +105,7 @@ export default function buildKeymap>(schema: S, mapKeys?: return true; }); - bind("Mod-s", TooltipTextMenu.insertStar); + bind("Mod-s", TooltipTextMenu.insertSummarizer); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { const ref = state.selection; diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 22b2a8204..364c85165 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -62,8 +62,9 @@ export const inpRules = { } ), + // set the font size using # new InputRule( - new RegExp(/^#([0-9]+)\s$/), + new RegExp(/^%([0-9]+)\s$/), (state, match, start, end) => { const size = Number(match[1]); const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; @@ -74,131 +75,123 @@ export const inpRules = { } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), + + // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/@$/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null; + + const value = state.doc.textBetween(start, end); + if (value) { + DocServer.GetRefField(value).then(docx => { + const doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(doc, value, returnFalse, returnFalse); + }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value }); + return state.tr.addMark(start, end, link); + } + return state.tr; + }), + + // activate a style by name using prefix '%' new InputRule( new RegExp(/%[a-z]+$/), (state, match, start, end) => { const color = match[0].substring(1, match[0].length); - let marks = TooltipTextMenuManager.Instance._brushMap.get(color); + const marks = TooltipTextMenuManager.Instance._brushMap.get(color); if (marks) { - let tr = state.tr.deleteRange(start, end); + const tr = state.tr.deleteRange(start, end); return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; } - let isValidColor = (strColor: string) => { - var s = new Option().style; + const isValidColor = (strColor: string) => { + const s = new Option().style; s.color = strColor; - return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned - } + 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 })); } return null; }), + // stop using active style new InputRule( new RegExp(/%%$/), (state, match, start, end) => { - let tr = state.tr.deleteRange(start, end); - let marks = state.tr.selection.$anchor.nodeBefore?.marks; + 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; }), + + // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) new InputRule( - new RegExp(/t$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/i$/), + new RegExp(/[ti!x]$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; + if (state.selection.to === state.selection.from || !(schema as any).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; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + 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; }), + + // 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$/), + new RegExp(/(%d|d)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + 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 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } 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$/), + new RegExp(/(%h|h)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + 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 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } 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$/), + new RegExp(/(%q|q)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) { - let node = (state.selection as NodeSelection).node; + 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 }); } - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + 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 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } return null; }), - new InputRule( - new RegExp(/!$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/x$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/@$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; - const value = state.doc.textBetween(start, end); - if (value) { - DocServer.GetRefField(value).then(docx => { - let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); - DocUtils.Publish(doc, value, returnFalse, returnFalse); - }); - const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value }); - return state.tr.addMark(start, end, link); - } - return state.tr; - }), + + // center justify text new InputRule( - new RegExp(/^\^\^\s$/), + new RegExp(/%\^$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -212,8 +205,9 @@ export const inpRules = { state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), + // left justify text new InputRule( - new RegExp(/^\[\[\s$/), + new RegExp(/%\[$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -227,8 +221,9 @@ export const inpRules = { state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), + // right justify text new InputRule( - new RegExp(/^\]\]\s$/), + new RegExp(/%\]$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -243,7 +238,7 @@ export const inpRules = { return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), new InputRule( - new RegExp(/##\s$/), + new RegExp(/%#$/), (state, match, start, end) => { const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" }); const node = (state.doc.resolve(start) as any).nodeAfter; @@ -255,26 +250,25 @@ export const inpRules = { return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end))); }), new InputRule( - new RegExp(/\(\(/), + new RegExp(/%\(/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const mark = state.schema.marks.highlight.create(); + const mark = state.schema.marks.summarizeInclusive.create(); 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, start, - schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))); }), new InputRule( - new RegExp(/\)\)/), + new RegExp(/%\)/), (state, match, start, end) => { - const mark = state.schema.marks.highlight.create(); - return state.tr.removeStoredMark(mark); + return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create()); }), new InputRule( - new RegExp(/\^f\s$/), + new RegExp(/%f\$/), (state, match, start, end) => { const newNode = schema.nodes.footnote.create({}); const tr = state.tr; @@ -283,9 +277,5 @@ export const inpRules = { 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))); }), - // let newNode = schema.nodes.footnote.create({}); - // if (dispatch && state.selection.from === state.selection.to) { - // return true; - // } ] }; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f9251fb7e..543f45731 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -109,7 +109,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, - star: { + summary: { inline: true, attrs: { visibility: { default: false }, @@ -121,15 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; }, - // parseDOM: [{ - // tag: "star", getAttrs(dom: any) { - // return { - // visibility: dom.getAttribute("visibility"), - // oldtext: dom.getAttribute("oldtext"), - // oldtextlen: dom.getAttribute("oldtextlen"), - // } - // } - // }] }, // :: NodeSpec An inline image (``) node. Supports `src`, @@ -228,6 +219,7 @@ export const nodes: { [index: string]: NodeSpec } = { mapStyle: { default: "decimal" }, setFontSize: { default: undefined }, setFontFamily: { default: "inherit" }, + setFontColor: { default: "inherit" }, inheritedFontSize: { default: undefined }, visibility: { default: true }, indent: { default: undefined } @@ -237,8 +229,10 @@ export const nodes: { [index: string]: NodeSpec } = { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; const ffam = node.attrs.setFontFamily; - return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }]; + const color = node.attrs.setFontColor; + return node.attrs.visibility ? + ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] : + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, @@ -318,7 +312,7 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { highlight: { default: "transparent" } }, - inclusive: false, + inclusive: true, parseDOM: [{ tag: "span", getAttrs(dom: any) { return { highlight: dom.getAttribute("backgroundColor") }; @@ -401,7 +395,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight: { + summarizeInclusive: { parseDOM: [ { tag: "span", @@ -410,7 +404,7 @@ export const marks: { [index: string]: MarkSpec } = { 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) { + p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { return null; } } @@ -419,6 +413,31 @@ export const marks: { [index: string]: MarkSpec } = { }, ], inclusive: true, + toDOM() { + 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", + getAttrs: (p: any) => { + 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) { + return null; + } + } + return false; + } + }, + ], toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' @@ -637,7 +656,7 @@ export class DashDocCommentView { } const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" }); view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc)); - setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))) } catch (e) { } }, 0); + setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0); return undefined; }; this._collapsed.onpointerdown = (e: any) => { @@ -646,16 +665,16 @@ export class DashDocCommentView { this._collapsed.onpointerup = (e: any) => { const target = targetNode(); if (target) { - let expand = target.hidden; - let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + const expand = target.hidden; + const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))) } catch (e) { } + try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { } }, 0); } e.stopPropagation(); - } + }; this._collapsed.onpointerenter = (e: any) => { DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); e.preventDefault(); @@ -908,7 +927,7 @@ export class FootnoteView { ignoreMutation() { return true; } } -export class SummarizedView { +export class SummaryView { _collapsed: HTMLElement; _view: any; constructor(node: any, view: any, getPos: any) { @@ -946,7 +965,8 @@ export class SummarizedView { className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); updateSummarizedText(start?: any) { - const mark = this._view.state.schema.marks.highlight.create(); + const mtype = this._view.state.schema.marks.summarize; + const mtypeInc = this._view.state.schema.marks.summarizeInclusive; let endPos = start; const visited = new Set(); @@ -954,7 +974,7 @@ export class SummarizedView { let skip = false; this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { if (node.isLeaf && !visited.has(node) && !skip) { - if (node.marks.find((m: any) => m.type === mark.type)) { + if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); endPos = i + node.nodeSize - 1; } @@ -979,7 +999,7 @@ const fromJson = schema.nodeFromJSON; schema.nodeFromJSON = (json: any) => { const node = fromJson(json); - if (json.type === "star") { + if (json.type === schema.marks.summarize.name) { node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); } return node; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index f29dbf2e4..483ab40a7 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,4 +1,3 @@ -import { action } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; @@ -10,8 +9,6 @@ import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { FieldViewProps } from "../views/nodes/FieldView"; import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; -import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; @@ -20,13 +17,11 @@ import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; -import { Keys } from "../views/search/FilterBox"; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - public static Toolbar: HTMLDivElement | undefined; // editor state properties @@ -48,10 +43,9 @@ export class TooltipTextMenu { // editor button doms private colorDom?: Node; private colorDropdownDom?: Node; - private highlightDom?: Node; - private highlightDropdownDom?: Node; + private highighterDom?: Node; + private highlighterDropdownDom?: Node; private linkEditor?: HTMLDivElement; - private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; @@ -94,7 +88,6 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") }, { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, - // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; // add menu items @@ -123,20 +116,15 @@ export class TooltipTextMenu { if (dom.contains(e.target as Node)) { e.stopPropagation(); command(this.view.state, this.view.dispatch, this.view); - // if (this.view.state.selection.empty) { - // if (dom.style.color === "white") { dom.style.color = "greenyellow"; } - // else { dom.style.color = "white"; } - // } } }); - }); - // highlight menu - this.highlightDom = this.createHighlightTool().render(this.view).dom; - this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom; - this.tooltip.appendChild(this.highlightDom); - this.tooltip.appendChild(this.highlightDropdownDom); + // summarize menu + this.highighterDom = this.createHighlightTool().render(this.view).dom; + this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom; + this.tooltip.appendChild(this.highighterDom); + this.tooltip.appendChild(this.highlighterDropdownDom); // color menu this.colorDom = this.createColorTool().render(this.view).dom; @@ -166,7 +154,7 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushDropdownDom); // star - this.tooltip.appendChild(this.createStar().render(this.view).dom); + this.tooltip.appendChild(this.createSummarizer().render(this.view).dom); // list types dropdown this.updateListItemDropdown(":", this.listTypeBtnDom); @@ -289,8 +277,6 @@ export class TooltipTextMenu { // stop moving when mouse button is released: document.onpointerup = null; document.onpointermove = null; - //self.highlightSearchTerms(self.state, ["hello"]); - //FormattedTextBox.Instance.unhighlightSearchTerms(); } } @@ -302,11 +288,10 @@ export class TooltipTextMenu { fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize)); }); - const newfontSizeDom = (new Dropdown(fontSizeBtns, { - label: label, - css: "color:black; min-width: 60px;" - }) as MenuItem).render(this.view).dom; - if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } + const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom; + if (this.fontSizeDom) { + this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); + } else { this.tooltip.appendChild(newfontSizeDom); } @@ -321,11 +306,10 @@ export class TooltipTextMenu { fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily)); }); - const newfontStyleDom = (new Dropdown(fontBtns, { - label: label, - css: "color:black; width: 125px;" - }) as MenuItem).render(this.view).dom; - if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } + const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom; + if (this.fontStyleDom) { + this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); + } else { this.tooltip.appendChild(newfontStyleDom); } @@ -333,94 +317,16 @@ export class TooltipTextMenu { } updateLinkMenu() { - if (!this.linkEditor || !this.linkText) { - this.linkEditor = document.createElement("div"); - this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkText = document.createElement("div"); - this.linkText.setAttribute("contenteditable", "true"); - this.linkText.style.whiteSpace = "nowrap"; - this.linkText.style.width = "150px"; - this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "white"; - this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; - const linkBtn = document.createElement("div"); - linkBtn.textContent = ">>"; - linkBtn.style.width = "10px"; - linkBtn.style.height = "10px"; - linkBtn.style.color = "white"; - linkBtn.style.cssFloat = "left"; - linkBtn.onpointerdown = (e: PointerEvent) => { - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - const href: string = link.attrs.href; - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const docid = href.replace(Utils.prepend("/doc/"), ""); - DocServer.GetRefField(docid).then(action((f: Opt) => { - if (f instanceof Doc) { - if (DocumentManager.Instance.getDocumentView(f)) { - DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); - } - else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); - } - })); - } - // TODO This should have an else to handle external links - e.stopPropagation(); - e.preventDefault(); - } - }; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "15px"; - this.linkDrag.style.height = "15px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.id = "link-drag"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { - if (!this.editorProps) return; - const dragData = new DragManager.LinkDragData(this.editorProps.Document); - dragData.dontClearTextBox = true; - // hack to get source context -sy - const docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); - e.stopPropagation(); - const ctrlKey = e.ctrlKey; - DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, - { - handlers: { - dragComplete: action(() => { - if (dragData.linkDocument) { - const linkDoc = dragData.linkDocument; - const proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - const text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab"); - if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { - proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link - } - } - }), - }, - hideSource: false - }); - e.stopPropagation(); - e.preventDefault(); - }; - this.linkEditor.appendChild(this.linkDrag); - this.tooltip.appendChild(this.linkEditor); - } - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); - this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - this.linkText.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter") { - // this.makeLink(this.linkText!.textContent!); - e.stopPropagation(); - e.preventDefault(); - } - }; + this.linkEditor = document.createElement("div"); + this.linkEditor.className = "ProseMirror-icon menuicon"; + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Click to set link target"; + this.linkDrag.id = "link-btn"; + this.linkEditor.appendChild(this.linkDrag); + this.tooltip.appendChild(this.linkEditor); } async getTextLinkTargetTitle() { @@ -485,9 +391,7 @@ export class TooltipTextMenu { return div; }, enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); // menu item to update/apply the hyperlink to the selected text @@ -584,24 +488,6 @@ export class TooltipTextMenu { } } - deleteLinkItem() { - const icon = { - height: 16, width: 16, - path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" - }; - return new MenuItem({ - title: "Delete Link", - label: "X", - icon: icon, - css: "color: red", - class: "summarize", - execEvent: "", - run: (state, dispatch) => { - this.deleteLink(); - } - }); - } - createLink() { const markType = schema.marks.link; return new MenuItem({ @@ -655,22 +541,19 @@ export class TooltipTextMenu { //Make a dropdown of all list types const toAdd: MenuItem[] = []; this.listTypeToIcon.forEach((icon, type) => { - toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType)); + toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType)); }); //option to remove the list formatting - toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType)); + toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType)); - listTypeBtn = (new Dropdown(toAdd, { - label: label, - css: "color:black; width: 40px;" - }) as MenuItem).render(this.view).dom; + listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom; //add this new button and return it this.tooltip.appendChild(listTypeBtn); return listTypeBtn; } - createStar() { + createSummarizer() { return new MenuItem({ title: "Summarize", label: "Summarize", @@ -678,31 +561,17 @@ export class TooltipTextMenu { css: "color:white;", class: "menuicon", execEvent: "", - run: (state, dispatch) => { - TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); - } - + run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch) }); } - public static insertStar(state: EditorState, dispatch: any) { + public static insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; - const mark = state.schema.marks.highlight.create(); + const mark = state.schema.marks.summarize.create(); const tr = state.tr; tr.addMark(state.selection.from, state.selection.to, mark); const content = tr.selection.content(); - const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } - - public static insertComment(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - const mark = state.schema.marks.highlight.create(); - const tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - const content = tr.selection.content(); - const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); return true; } @@ -722,7 +591,7 @@ export class TooltipTextMenu { const color = document.createElement("div"); color.className = "buttonColor"; - color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString(); + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString(); const wrapper = document.createElement("div"); wrapper.id = "colorPicker"; @@ -730,17 +599,14 @@ export class TooltipTextMenu { wrapper.appendChild(color); return wrapper; }, - run: (state, dispatch) => { - TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch); - } + run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch) }); } public static insertHighlight(color: String, state: EditorState, dispatch: any) { if (state.selection.empty) return false; - const highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color }); - dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); + toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); } createHighlightDropdown() { @@ -773,22 +639,22 @@ export class TooltipTextMenu { colors.forEach(color => { const button = document.createElement("button"); - button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; + button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker"; if (color) { button.style.backgroundColor = color; button.textContent = color === "transparent" ? "X" : ""; button.onclick = e => { - TooltipTextMenuManager.Instance.highlight = color; + TooltipTextMenuManager.Instance.highlighter = color; - TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch); + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch); // update color menu const highlightDom = self.createHighlightTool().render(self.view).dom; const highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom; - self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom); - self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom); - self.highlightDom = highlightDom; - self.highlightDropdownDom = highlightDropdownDom; + self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom); + self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom); + self.highighterDom = highlightDom; + self.highlighterDropdownDom = highlightDropdownDom; }; } colorsWrapper.appendChild(button); @@ -832,19 +698,18 @@ export class TooltipTextMenu { wrapper.appendChild(color); return wrapper; }, - run: (state, dispatch) => { - TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); - } + run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch) }); } - public static insertColor(color: String, state: EditorState, dispatch: any) { + public insertColor(color: String, state: EditorState, dispatch: any) { const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color }); if (state.selection.empty) { dispatch(state.tr.addStoredMark(colorMark)); return false; } - dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark)); + this.setMark(colorMark, state, dispatch); + toggleMark(colorMark.type, { color: color })(state, dispatch); } createColorDropdown() { @@ -883,7 +748,7 @@ export class TooltipTextMenu { button.onclick = e => { TooltipTextMenuManager.Instance.color = color; - TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); + self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); // update color menu const colorDom = self.createColorTool().render(self.view).dom; @@ -903,13 +768,10 @@ export class TooltipTextMenu { return div; }, enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); - const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; - return colorDropdown; + return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; } createBrush(active: boolean = false) { @@ -933,9 +795,7 @@ export class TooltipTextMenu { self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); self._brushDropdownDom = newBrushDropdowndom; }, - active: (state) => { - return true; - } + active: (state) => true }); } @@ -959,8 +819,7 @@ export class TooltipTextMenu { if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - const markType = mark.type; - this.changeToMarkInGroup(markType, this.view, []); + this.setMark(mark, this.view.state, this.view.dispatch); }); } } @@ -995,9 +854,7 @@ export class TooltipTextMenu { class: "button-setting-disabled", css: "", enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); const self = this; @@ -1056,71 +913,26 @@ export class TooltipTextMenu { }); const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0; - const brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; - return brushDom; + return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; } - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { - changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const dispatch = view.dispatch; - - //remove all other active font marks - fontMarks.forEach((type) => { - if (dispatch) { - if ($cursor) { - if (type.isInSet(state.storedMarks || $cursor.marks())) { - dispatch(state.tr.removeStoredMark(type)); - } - } else { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, type); - } - for (const i of ranges) { - if (has) { - toggleMark(type)(view.state, view.dispatch, view); - } - } - } - } - }); - if (markType) { - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + setMark = (mark: Mark, state: EditorState, dispatch: any) => { + if (mark) { + const node = (state.selection as NodeSelection).node; + if (node?.type === schema.nodes.ordered_list) { + let attrs = node.attrs; + if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; + if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; + const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); + dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); + } else { + toggleMark(mark.type, mark.attrs)(state, dispatch); } - else toggleMark(markType)(view.state, view.dispatch, view); } } changeToFontFamily = (mark: Mark, view: EditorView) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const dispatch = view.dispatch; - - //remove all other active font marks - if ($cursor) { - if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) { - dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily)); - } - } else { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily); - } - for (const i of ranges) { - if (has) { - toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view); - } - } - } - const fontName = mark.attrs.family; if (fontName) { this.updateFontStyleDropdown(fontName); } if (this.editorProps) { @@ -1130,39 +942,10 @@ export class TooltipTextMenu { ruleProvider["ruleFont_" + heading] = fontName; } } - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); - } - else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName }))); - view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })]; + this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch); } changeToFontSize = (mark: Mark, view: EditorView) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const dispatch = view.dispatch; - - //remove all other active font marks - if ($cursor) { - if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) { - dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize)); - } - } else { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize); - } - for (const i of ranges) { - if (has) { - toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view); - } - } - } - const size = mark.attrs.fontSize; if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } if (this.editorProps) { @@ -1172,18 +955,11 @@ export class TooltipTextMenu { ruleProvider["ruleSize_" + heading] = size; } } - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); - } - else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size }))); - view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })]; + this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch); } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined) => { + changeBulletType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new const view = this.view; if (nodeType === schema.nodes.bullet_list) { @@ -1211,46 +987,40 @@ export class TooltipTextMenu { //css is the style you want applied to the button dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark, view: EditorView) => any) { return new MenuItem({ - title: "", + title: "Set Font Family", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeFontFamily(mark, view); - } + run() { changeFontFamily(mark, view); } }); } //makes a button for the drop down FOR MARKS //css is the style you want applied to the button dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark, view: EditorView) => any) { return new MenuItem({ - title: "", + title: "Set Font Size", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeFontSize(mark, view); - } + run() { changeFontSize(mark, view); } }); } //makes a button for the drop down FOR NODE TYPES //css is the style you want applied to the button - dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[]) => any) { + dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[]) => any) { return new MenuItem({ - title: "", + title: "Set Bullet Style", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeToNodeInGroup(nodeType, view, groupNodes); - } + run() { changeToNodeInGroup(nodeType, view, groupNodes); } }); } @@ -1325,14 +1095,7 @@ export class TooltipTextMenu { getMarksInSelection(state: EditorState) { const found = new Set(); const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => { - const marks = node.marks; - if (marks) { - marks.forEach(m => { - found.add(m); - }); - } - }); + state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m))); return found; } @@ -1362,12 +1125,6 @@ export class TooltipTextMenu { this.reset_mark_doms(); - // Hide the tooltip if the selection is empty - if (state.selection.empty) { - //this.tooltip.style.display = "none"; - //return; - } - // update link dropdown const linkDropdown = await this.createLinkDropdown(); const newLinkDropdowndom = linkDropdown.render(this.view).dom; @@ -1473,7 +1230,6 @@ export class TooltipTextMenu { } const mark = state.schema.mark(mark_type); return ref_node.marks.includes(mark); - return false; }); } else { @@ -1518,19 +1274,19 @@ export class TooltipTextMenu { export class TooltipTextMenuManager { private static _instance: TooltipTextMenuManager; + private _isPinned: boolean = false; public pinnedX: number = 0; public pinnedY: number = 0; public unpinnedX: number = 0; public unpinnedY: number = 0; - private _isPinned: boolean = false; public _brushMarks: Set | undefined; public _brushMap: Map> = new Map(); public _brushIsEmpty: boolean = true; public color: String = "#000"; - public highlight: String = "transparent"; + public highlighter: String = "transparent"; public activeMenu: TooltipTextMenu | undefined; @@ -1541,11 +1297,7 @@ export class TooltipTextMenuManager { return TooltipTextMenuManager._instance; } - public get isPinned() { - return this._isPinned; - } + public get isPinned() { return this._isPinned; } - public toggleIsPinned() { - this._isPinned = !this._isPinned; - } + public toggleIsPinned() { this._isPinned = !this._isPinned; } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dd3b740fb..f366a3677 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -85,14 +85,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) { this._titleControlString = this._accumulatedTitle; } else if (this._titleControlString.startsWith("#")) { - let selectionTitleFieldKey = this._titleControlString.substring(1); + const selectionTitleFieldKey = this._titleControlString.substring(1); selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-")); selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d => Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle, true) ); } } - })) + })); @action titleEntered = (e: any) => { const key = e.keyCode || e.which; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d0cecf03d..54def38b5 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -120,7 +120,7 @@ export class EditableView extends React.Component { @action setIsFocused = (value: boolean) => { - let wasFocused = this._editing; + const wasFocused = this._editing; this._editing = value; return wasFocused !== this._editing; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 01cd7957c..25b7dc5ec 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -286,7 +286,7 @@ export class MainView extends React.Component { ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} - /> + />; } @computed get dockingContent() { TraceMobx(); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 411040332..15eec37de 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -240,9 +240,9 @@ export class CollectionView extends Touchable { const mainPath = path.extname(images[this._curLightboxImg]); const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]); const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]); - let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath); - let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath); - let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath); + const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath); + const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath); + const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath); return !this._isLightboxOpen ? (null) : ((Docu else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... { this._titleRef.current?.setIsFocused(false); - let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); + const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); any.keeplocation = true; any?.focus(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9318142e2..ec9e0ce5b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -27,7 +27,7 @@ import { DictationManager } from '../../util/DictationManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; +import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; @@ -822,7 +822,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, - star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, + summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } }, @@ -907,10 +907,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.tryUpdateHeight(); // see if we need to preserve the insertion point - let prosediv = this._proseRef?.children?.[0] as any; - let keeplocation = prosediv?.keeplocation; + const prosediv = this._proseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; prosediv && (prosediv.keeplocation = undefined); - let pos = this._editorView?.state.selection.$from.pos || 1; + const pos = this._editorView?.state.selection.$from.pos || 1; keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); } onPointerWheel = (e: React.WheelEvent): void => { @@ -925,14 +925,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onClick = (e: React.MouseEvent): void => { if ((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. - let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - let 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) + 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) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); e.preventDefault(); } if (!node && this._proseRef) { - let 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 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 if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); } @@ -973,7 +973,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & // } // } - this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false) + this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); } @@ -984,7 +984,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (pos && this.props.isSelected(true)) { // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined; //const node = this._editorView!.state.doc.nodeAt(pos.pos); - let $pos = this._editorView!.state.doc.resolve(pos.pos); + const $pos = this._editorView!.state.doc.resolve(pos.pos); let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined; if ($pos.node().type === schema.nodes.ordered_list) { for (let off = 1; off < 100; off++) { @@ -998,7 +998,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { if (select) { - let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); + const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); if (!highlightOnly) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos))); } @@ -1064,14 +1064,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & return; } if (!this._editorView!.state.selection.empty && e.key === "%") { - (this._editorView!.state as any).EnteringStyle = true; + this._editorView!.state.schema.EnteringStyle = true; e.preventDefault(); e.stopPropagation(); return; } - if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) { - (this._editorView!.state as any).EnteringStyle = false; + if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) { + this._editorView!.state.schema.EnteringStyle = false; } if (e.key === "Escape") { SelectionManager.DeselectAll(); @@ -1080,7 +1080,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); this._lastTimedMark = mark; this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); @@ -1100,7 +1100,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); const dh = NumCast(this.layoutDoc.height, 0); - let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle this.layoutDoc.height = newHeight; this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; -- cgit v1.2.3-70-g09d2 From 6162c951e07874fbb12717d4bcd2cd649e41d0d2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 13 Jan 2020 00:00:56 -0500 Subject: deleted old Session folder and fixed linter errors --- src/client/util/ProseMirrorEditorView.tsx | 8 +- src/client/util/RichTextMenu.tsx | 13 +- src/client/views/EditableView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/linking/LinkMenuGroup.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 23 +- src/pen-gestures/ndollar.ts | 66 +- src/server/MemoryDatabase.ts | 7 +- src/server/RouteManager.ts | 3 +- src/server/Session/session.ts | 686 --------------------- 11 files changed, 67 insertions(+), 749 deletions(-) delete mode 100644 src/server/Session/session.ts (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx index 3e5fd0604..b42adfbb4 100644 --- a/src/client/util/ProseMirrorEditorView.tsx +++ b/src/client/util/ProseMirrorEditorView.tsx @@ -18,23 +18,23 @@ export class ProseMirrorEditorView extends React.Component { - if (element != null) { + if (element !== null) { this._editorView = new EditorView(element, { state: this.props.editorState, dispatchTransaction: this.dispatchTransaction, }); } - }; + } dispatchTransaction = (tx: any) => { // In case EditorView makes any modification to a state we funnel those // modifications up to the parent and apply to the EditorView itself. const editorState = this.props.editorState.apply(tx); - if (this._editorView != null) { + if (this._editorView) { this._editorView.updateState(editorState); } this.props.onEditorState(editorState); - }; + } focus() { if (this._editorView) { diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index 639772faa..419d7caf9 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -175,7 +175,7 @@ export default class RichTextMenu extends AntimodeMenu { setMark = (mark: Mark, state: EditorState, dispatch: any) => { if (mark) { const node = (state.selection as NodeSelection).node; - if (node ?.type === schema.nodes.ordered_list) { + if (node?.type === schema.nodes.ordered_list) { let attrs = node.attrs; if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; @@ -294,8 +294,8 @@ export default class RichTextMenu extends AntimodeMenu { e.preventDefault(); e.stopPropagation(); self.view && self.view.focus(); - self.view && command && command(self.view!.state, self.view!.dispatch, self.view); - self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view); + self.view && command && command(self.view.state, self.view.dispatch, self.view); + self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); self.setActiveMarkButtons(self.getActiveMarksOnSelection()); } @@ -602,7 +602,7 @@ export default class RichTextMenu extends AntimodeMenu { const link = this.currentLink ? this.currentLink : ""; - const button = + const button = ; const dropdownContent =
@@ -684,8 +684,9 @@ export default class RichTextMenu extends AntimodeMenu { } } else { if (node) { - const extension = this.linkExtend(this.view!.state.selection.$anchor, href); - this.view!.dispatch(this.view!.state.tr.removeMark(extension.from, extension.to, this.view!.state.schema.marks.link)); + const { tr, schema, selection } = this.view.state; + const extension = this.linkExtend(selection.$anchor, href); + this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link)); } } } diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 54def38b5..faf02b946 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -36,7 +36,7 @@ export interface EditableProps { resetValue: () => void; value: string, onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void, - autosuggestProps: Autosuggest.AutosuggestProps + autosuggestProps: Autosuggest.AutosuggestProps }; oneLine?: boolean; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 936c4413f..e3780261d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -988,8 +988,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ; } @computed get contentScaling() { - let hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; - let wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; + const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; + const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1; return wscale < hscale ? wscale : hscale; } render() { diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index abd17ec4d..ace9a9e4c 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -47,7 +47,7 @@ export class LinkMenuGroup extends React.Component { document.removeEventListener("pointerup", this.onLinkButtonUp); const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; - DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets); + DragManager.StartLinkTargetsDrag(this._drag.current, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 10d2e2b3e..ba35366ff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -196,7 +196,7 @@ export class DocumentView extends DocComponent(Docu ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script FormattedTextBoxComment.Hide(); - this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0]!, this.props.Document)])); + this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)])); } else if (this.Document.isButton) { SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered. this.buttonClick(e.altKey, e.ctrlKey); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8e28cf928..8b5c24878 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -906,15 +906,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.tryUpdateHeight(); // see if we need to preserve the insertion point - const prosediv = this.ProseRef ?.children ?.[0] as any; - const keeplocation = prosediv ?.keeplocation; + const prosediv = this.ProseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; prosediv && (prosediv.keeplocation = undefined); - const pos = this._editorView ?.state.selection.$from.pos || 1; - keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + const pos = this._editorView?.state.selection.$from.pos || 1; + keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); // jump rich text menu to this textbox - if (this._ref.current) { - const x = Math.min(Math.max(this._ref.current!.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); + const { current } = this._ref; + if (current) { + const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width); const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50; RichTextMenu.Instance.jumpTo(x, y); } @@ -933,7 +934,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if ((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) { + if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); e.preventDefault(); } @@ -996,7 +997,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & for (let off = 1; off < 100; off++) { const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); - if (node ?.type === schema.nodes.list_item) { + if (node?.type === schema.nodes.list_item) { list_node = node; break; } @@ -1087,7 +1088,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (e.key === "Escape") { this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur ?.(); + (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); } e.stopPropagation(); @@ -1109,7 +1110,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @action tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this._ref.current ?.scrollHeight; + let scrollHeight = this._ref.current?.scrollHeight; if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation if (limitHeight && scrollHeight > limitHeight) { @@ -1171,7 +1172,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
this.toggleSidebar()} /> :
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}> this.sidebarWidth} diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index 872c524d6..ef5ca38c6 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -257,16 +257,16 @@ export class NDollarRecognizer { { if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes { - for (var j = 0; j < this.Multistrokes[i].Unistrokes.length; j++) // for each unistroke within this multistroke + for (const unistroke of this.Multistrokes[i].Unistrokes) // for each unistroke within this multistroke { - if (AngleBetweenUnitVectors(candidate.StartUnitVector, this.Multistrokes[i].Unistrokes[j].StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction + if (AngleBetweenUnitVectors(candidate.StartUnitVector, unistroke.StartUnitVector) <= AngleSimilarityThreshold) // strokes start in the same direction { var d; if (useProtractor) { - d = OptimalCosineDistance(this.Multistrokes[i].Unistrokes[j].Vector, candidate.Vector); // Protractor + d = OptimalCosineDistance(unistroke.Vector, candidate.Vector); // Protractor } else { - d = DistanceAtBestAngle(candidate.Points, this.Multistrokes[i].Unistrokes[j], -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N) + d = DistanceAtBestAngle(candidate.Points, unistroke, -AngleRange, +AngleRange, AnglePrecision); // Golden Section Search (original $N) } if (d < b) { b = d; // best (least) distance @@ -283,8 +283,8 @@ export class NDollarRecognizer { AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => { this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes); var num = 0; - for (var i = 0; i < this.Multistrokes.length; i++) { - if (this.Multistrokes[i].Name === name) { + for (const multistroke of this.Multistrokes) { + if (multistroke.Name === name) { num++; } } @@ -322,20 +322,20 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) { function MakeUnistrokes(strokes: any, orders: any) { const unistrokes = new Array(); // array of point arrays - for (var r = 0; r < orders.length; r++) { - for (var b = 0; b < Math.pow(2, orders[r].length); b++) // use b's bits for directions + for (const order of orders) { + for (var b = 0; b < Math.pow(2, order.length); b++) // use b's bits for directions { const unistroke = new Array(); // array of points - for (var i = 0; i < orders[r].length; i++) { + for (var i = 0; i < order.length; i++) { var pts; if (((b >> i) & 1) === 1) {// is b's bit at index i on? - pts = strokes[orders[r][i]].slice().reverse(); // copy and reverse + pts = strokes[order[i]].slice().reverse(); // copy and reverse } else { - pts = strokes[orders[r][i]].slice(); // copy + pts = strokes[order[i]].slice(); // copy } - for (var p = 0; p < pts.length; p++) { - unistroke[unistroke.length] = pts[p]; // append points + for (const point of pts) { + unistroke[unistroke.length] = point; // append points } } unistrokes[unistrokes.length] = unistroke; // add one unistroke to set @@ -346,9 +346,9 @@ function MakeUnistrokes(strokes: any, orders: any) { function CombineStrokes(strokes: any) { const points = new Array(); - for (var s = 0; s < strokes.length; s++) { - for (var p = 0; p < strokes[s].length; p++) { - points[points.length] = new Point(strokes[s][p].X, strokes[s][p].Y); + for (const stroke of strokes) { + for (const { X, Y } of stroke) { + points[points.length] = new Point(X, Y); } } return points; @@ -384,9 +384,9 @@ function RotateBy(points: any, radians: any) // rotates points around centroid const cos = Math.cos(radians); const sin = Math.sin(radians); const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X; - const qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y; + for (const point of points) { + const qx = (point.X - c.X) * cos - (point.Y - c.Y) * sin + c.X; + const qy = (point.X - c.X) * sin + (point.Y - c.Y) * cos + c.Y; newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -396,9 +396,9 @@ function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniform const B = BoundingBox(points); const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width); - const qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height); + for (const { X, Y } of points) { + const qx = uniformly ? X * (size / Math.max(B.Width, B.Height)) : X * (size / B.Width); + const qy = uniformly ? Y * (size / Math.max(B.Width, B.Height)) : Y * (size / B.Height); newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -407,9 +407,9 @@ function TranslateTo(points: any, pt: any) // translates points' centroid { const c = Centroid(points); const newpoints = new Array(); - for (var i = 0; i < points.length; i++) { - const qx = points[i].X + pt.X - c.X; - const qy = points[i].Y + pt.Y - c.Y; + for (const { X, Y } of points) { + const qx = X + pt.X - c.X; + const qy = Y + pt.Y - c.Y; newpoints[newpoints.length] = new Point(qx, qy); } return newpoints; @@ -478,9 +478,9 @@ function DistanceAtAngle(points: any, T: any, radians: any) { } function Centroid(points: any) { var x = 0.0, y = 0.0; - for (var i = 0; i < points.length; i++) { - x += points[i].X; - y += points[i].Y; + for (const point of points) { + x += point.X; + y += point.Y; } x /= points.length; y /= points.length; @@ -488,11 +488,11 @@ function Centroid(points: any) { } function BoundingBox(points: any) { var minX = +Infinity, maxX = -Infinity, minY = +Infinity, maxY = -Infinity; - for (var i = 0; i < points.length; i++) { - minX = Math.min(minX, points[i].X); - minY = Math.min(minY, points[i].Y); - maxX = Math.max(maxX, points[i].X); - maxY = Math.max(maxY, points[i].Y); + for (const { X, Y } of points) { + minX = Math.min(minX, X); + minY = Math.min(minY, Y); + maxX = Math.max(maxX, X); + maxY = Math.max(maxY, Y); } return new Rectangle(minX, minY, maxX - minX, maxY - minY); } diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts index e7396babf..543f96e7f 100644 --- a/src/server/MemoryDatabase.ts +++ b/src/server/MemoryDatabase.ts @@ -7,7 +7,7 @@ export class MemoryDatabase implements IDatabase { private db: { [collectionName: string]: { [id: string]: any } } = {}; private getCollection(collectionName: string) { - let collection = this.db[collectionName]; + const collection = this.db[collectionName]; if (collection) { return collection; } else { @@ -17,9 +17,10 @@ export class MemoryDatabase implements IDatabase { public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise { const collection = this.getCollection(collectionName); - if ("$set" in value) { + const set = "$set"; + if (set in value) { let currentVal = collection[id] ?? (collection[id] = {}); - const val = value["$set"]; + const val = value[set]; for (const key in val) { const keys = key.split("."); for (let i = 0; i < keys.length - 1; i++) { diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index a7ee405a7..5afd607fd 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -86,7 +86,8 @@ export default class RouteManager { const { method, subscription, secureHandler: onValidation, publicHandler: onUnauthenticated, errorHandler: onError } = initializer; const isRelease = this._isRelease; const supervised = async (req: express.Request, res: express.Response) => { - let { user, originalUrl: target } = req; + let { user } = req; + const { originalUrl: target } = req; if (process.env.DB === "MEM" && !user) { user = { id: "guest", email: "", userDocumentId: "guestDocId" }; } diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts deleted file mode 100644 index ec3d46ac1..000000000 --- a/src/server/Session/session.ts +++ /dev/null @@ -1,686 +0,0 @@ -import { red, cyan, green, yellow, magenta, blue, white, Color, grey, gray, black } from "colors"; -import { on, fork, setupMaster, Worker, isMaster, isWorker } from "cluster"; -import { get } from "request-promise"; -import { Utils } from "../../Utils"; -import Repl, { ReplAction } from "../repl"; -import { readFileSync } from "fs"; -import { validate, ValidationError } from "jsonschema"; -import { configurationSchema } from "./session_config_schema"; -import { exec, ExecOptions } from "child_process"; - -/** - * This namespace relies on NodeJS's cluster module, which allows a parent (master) process to share - * code with its children (workers). A simple `isMaster` flag indicates who is trying to access - * the code, and thus determines the functionality that actually gets invoked (checked by the caller, not internally). - * - * Think of the master thread as a factory, and the workers as the helpers that actually run the server. - * - * So, when we run `npm start`, given the appropriate check, initializeMaster() is called in the parent process - * This will spawn off its own child process (by default, mirrors the execution path of its parent), - * in which initializeWorker() is invoked. - */ -export namespace Session { - - type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black"; - const colorMapping: Map = new Map([ - ["yellow", yellow], - ["red", red], - ["cyan", cyan], - ["green", green], - ["blue", blue], - ["magenta", magenta], - ["grey", grey], - ["gray", gray], - ["white", white], - ["black", black] - ]); - - export abstract class AppliedSessionAgent { - - // the following two methods allow the developer to create a custom - // session and use the built in customization options for each thread - protected abstract async launchMonitor(): Promise; - protected abstract async launchServerWorker(): Promise; - - private launched = false; - - public killSession = (reason: string, graceful = true, errorCode = 0) => { - const target = isMaster ? this.sessionMonitor : this.serverWorker; - target.killSession(reason, graceful, errorCode); - } - - private sessionMonitorRef: Session.Monitor | undefined; - public get sessionMonitor(): Session.Monitor { - if (!isMaster) { - this.serverWorker.sendMonitorAction("kill", { - graceful: false, - reason: "Cannot access the session monitor directly from the server worker thread.", - errorCode: 1 - }); - throw new Error(); - } - return this.sessionMonitorRef!; - } - - private serverWorkerRef: Session.ServerWorker | undefined; - public get serverWorker(): Session.ServerWorker { - if (isMaster) { - throw new Error("Cannot access the server worker directly from the session monitor thread"); - } - return this.serverWorkerRef!; - } - - public async launch(): Promise { - if (!this.launched) { - this.launched = true; - if (isMaster) { - this.sessionMonitorRef = await this.launchMonitor(); - } else { - this.serverWorkerRef = await this.launchServerWorker(); - } - } else { - throw new Error("Cannot launch a session thread more than once per process."); - } - } - - } - - interface Identifier { - text: string; - color: ColorLabel; - } - - interface Identifiers { - master: Identifier; - worker: Identifier; - exec: Identifier; - } - - interface Configuration { - showServerOutput: boolean; - identifiers: Identifiers; - ports: { [description: string]: number }; - polling: { - route: string; - intervalSeconds: number; - failureTolerance: number; - }; - } - - const defaultConfig: Configuration = { - showServerOutput: false, - identifiers: { - master: { - text: "__monitor__", - color: "yellow" - }, - worker: { - text: "__server__", - color: "magenta" - }, - exec: { - text: "__exec__", - color: "green" - } - }, - ports: { server: 3000 }, - polling: { - route: "/", - intervalSeconds: 30, - failureTolerance: 0 - } - }; - - export type ExitHandler = (reason: Error | boolean) => void | Promise; - - export namespace Monitor { - - export interface NotifierHooks { - key?: (key: string) => (boolean | Promise); - crash?: (error: Error) => (boolean | Promise); - } - - export interface Action { - message: string; - args: any; - } - - export type ServerMessageHandler = (action: Action) => void | Promise; - - } - - /** - * Validates and reads the configuration file, accordingly builds a child process factory - * and spawns off an initial process that will respawn as predecessors die. - */ - export class Monitor { - - private static count = 0; - private exitHandlers: ExitHandler[] = []; - private readonly notifiers: Monitor.NotifierHooks | undefined; - private readonly config: Configuration; - private onMessage: { [message: string]: Monitor.ServerMessageHandler[] | undefined } = {}; - private activeWorker: Worker | undefined; - private key: string | undefined; - private repl: Repl; - - public static Create(notifiers?: Monitor.NotifierHooks) { - if (isWorker) { - process.send?.({ - action: { - message: "kill", - args: { - reason: "cannot create a monitor on the worker process.", - graceful: false, - errorCode: 1 - } - } - }); - process.exit(1); - } else if (++Monitor.count > 1) { - console.error(red("cannot create more than one monitor.")); - process.exit(1); - } else { - return new Monitor(notifiers); - } - } - - /** - * Kill this session and its active child - * server process, either gracefully (may wait - * indefinitely, but at least allows active networking - * requests to complete) or immediately. - */ - public killSession = async (reason: string, graceful = true, errorCode = 0) => { - this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`)); - this.mainLog(`session exit reason: ${(red(reason))}`); - await this.executeExitHandlers(true); - this.killActiveWorker(graceful, true); - process.exit(errorCode); - } - - /** - * Execute the list of functions registered to be called - * whenever the process exits. - */ - public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler); - - /** - * Extend the default repl by adding in custom commands - * that can invoke application logic external to this module - */ - public addReplCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => { - this.repl.registerCommand(basename, argPatterns, action); - } - - public exec = (command: string, options?: ExecOptions) => { - return new Promise(resolve => { - exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => { - if (error) { - this.execLog(red(`unable to execute ${white(command)}`)); - error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`))); - } else { - let outLines: string[], errorLines: string[]; - if ((outLines = stdout.split("\n").filter(line => line.length)).length) { - outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`))); - } - if ((errorLines = stderr.split("\n").filter(line => line.length)).length) { - errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`))); - } - } - resolve(); - }); - }); - } - - /** - * Add a listener at this message. When the monitor process - * receives a message, it will invoke all registered functions. - */ - public addServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => { - const handlers = this.onMessage[message]; - if (handlers) { - handlers.push(handler); - } else { - this.onMessage[message] = [handler]; - } - } - - /** - * Unregister a given listener at this message. - */ - public removeServerMessageListener = (message: string, handler: Monitor.ServerMessageHandler) => { - const handlers = this.onMessage[message]; - if (handlers) { - const index = handlers.indexOf(handler); - if (index > -1) { - handlers.splice(index, 1); - } - } - } - - /** - * Unregister all listeners at this message. - */ - public clearServerMessageListeners = (message: string) => this.onMessage[message] = undefined; - - private constructor(notifiers?: Monitor.NotifierHooks) { - this.notifiers = notifiers; - - console.log(this.timestamp(), cyan("initializing session...")); - - this.config = this.loadAndValidateConfiguration(); - - this.initializeSessionKey(); - // determines whether or not we see the compilation / initialization / runtime output of each child server process - const output = this.config.showServerOutput ? "inherit" : "ignore"; - setupMaster({ stdio: ["ignore", output, output, "ipc"] }); - - // handle exceptions in the master thread - there shouldn't be many of these - // the IPC (inter process communication) channel closed exception can't seem - // to be caught in a try catch, and is inconsequential, so it is ignored - process.on("uncaughtException", ({ message, stack }): void => { - if (message !== "Channel closed") { - this.mainLog(red(message)); - if (stack) { - this.mainLog(`uncaught exception\n${red(stack)}`); - } - } - }); - - // a helpful cluster event called on the master thread each time a child process exits - on("exit", ({ process: { pid } }, code, signal) => { - const prompt = `server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`; - this.mainLog(cyan(prompt)); - // to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one - this.spawn(); - }); - - this.repl = this.initializeRepl(); - this.spawn(); - } - - /** - * Generates a blue UTC string associated with the time - * of invocation. - */ - private timestamp = () => blue(`[${new Date().toUTCString()}]`); - - /** - * A formatted, identified and timestamped log in color - */ - public mainLog = (...optionalParams: any[]) => { - console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams); - } - - /** - * A formatted, identified and timestamped log in color for non- - */ - private execLog = (...optionalParams: any[]) => { - console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams); - } - - /** - * If the caller has indicated an interest - * in being notified of this feature, creates - * a GUID for this session that can, for example, - * be used as authentication for killing the server - * (checked externally). - */ - private initializeSessionKey = async (): Promise => { - if (this.notifiers?.key) { - this.key = Utils.GenerateGuid(); - const success = await this.notifiers.key(this.key); - const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed"); - this.mainLog(statement); - } - } - - /** - * At any arbitrary layer of nesting within the configuration objects, any single value that - * is not specified by the configuration is given the default counterpart. If, within an object, - * one peer is given by configuration and two are not, the one is preserved while the two are given - * the default value. - * @returns the composition of all of the assigned objects, much like Object.assign(), but with more - * granularity in the overwriting of nested objects - */ - private preciseAssign = (target: any, ...sources: any[]): any => { - for (const source of sources) { - this.preciseAssignHelper(target, source); - } - return target; - } - - private preciseAssignHelper = (target: any, source: any) => { - Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => { - let targetValue: any, sourceValue: any; - if (sourceValue = source[property]) { - if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") { - this.preciseAssignHelper(targetValue, sourceValue); - } else { - target[property] = sourceValue; - } - } - }); - } - - /** - * Reads in configuration .json file only once, in the master thread - * and pass down any variables the pertinent to the child processes as environment variables. - */ - private loadAndValidateConfiguration = (): Configuration => { - let config: Configuration; - try { - console.log(this.timestamp(), cyan("validating configuration...")); - config = JSON.parse(readFileSync('./session.config.json', 'utf8')); - const options = { - throwError: true, - allowUnknownAttributes: false - }; - // ensure all necessary and no excess information is specified by the configuration file - validate(config, configurationSchema, options); - config = this.preciseAssign({}, defaultConfig, config); - } catch (error) { - if (error instanceof ValidationError) { - console.log(red("\nSession configuration failed.")); - console.log("The given session.config.json configuration file is invalid."); - console.log(`${error.instance}: ${error.stack}`); - process.exit(0); - } else if (error.code === "ENOENT" && error.path === "./session.config.json") { - console.log(cyan("Loading default session parameters...")); - console.log("Consider including a session.config.json configuration file in your project root for customization."); - config = this.preciseAssign({}, defaultConfig); - } else { - console.log(red("\nSession configuration failed.")); - console.log("The following unknown error occurred during configuration."); - console.log(error.stack); - process.exit(0); - } - } finally { - const { identifiers } = config!; - Object.keys(identifiers).forEach(key => { - const resolved = key as keyof Identifiers; - const { text, color } = identifiers[resolved]; - identifiers[resolved].text = (colorMapping.get(color) || white)(`${text}:`); - }); - return config!; - } - } - - /** - * Builds the repl that allows the following commands to be typed into stdin of the master thread. - */ - private initializeRepl = (): Repl => { - const repl = new Repl({ identifier: () => `${this.timestamp()} ${this.config.identifiers.master.text}` }); - const boolean = /true|false/; - const number = /\d+/; - const letters = /[a-zA-Z]+/; - repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0)); - repl.registerCommand("restart", [/clean|force/], args => this.killActiveWorker(args[0] === "clean")); - repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true")); - repl.registerCommand("set", [/polling/, number, boolean], args => { - const newPollingIntervalSeconds = Math.floor(Number(args[2])); - if (newPollingIntervalSeconds < 0) { - this.mainLog(red("the polling interval must be a non-negative integer")); - } else { - if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) { - this.config.polling.intervalSeconds = newPollingIntervalSeconds; - if (args[3] === "true") { - this.activeWorker?.send({ newPollingIntervalSeconds }); - } - } - } - }); - return repl; - } - - private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason))); - - /** - * Attempts to kill the active worker gracefully, unless otherwise specified. - */ - private killActiveWorker = (graceful = true, isSessionEnd = false): void => { - if (this.activeWorker && !this.activeWorker.isDead()) { - if (graceful) { - this.activeWorker.send({ manualExit: { isSessionEnd } }); - } else { - this.activeWorker.process.kill(); - } - } - } - - /** - * Allows the caller to set the port at which the target (be it the server, - * the websocket, some other custom port) is listening. If an immediate restart - * is specified, this monitor will kill the active child and re-launch the server - * at the port. Otherwise, the updated port won't be used until / unless the child - * dies on its own and triggers a restart. - */ - private setPort = (port: "server" | "socket" | string, value: number, immediateRestart: boolean): void => { - if (value > 1023 && value < 65536) { - this.config.ports[port] = value; - if (immediateRestart) { - this.killActiveWorker(); - } - } else { - this.mainLog(red(`${port} is an invalid port number`)); - } - } - - /** - * Kills the current active worker and proceeds to spawn a new worker, - * feeding in configuration information as environment variables. - */ - private spawn = (): void => { - const { - polling: { - route, - failureTolerance, - intervalSeconds - }, - ports - } = this.config; - this.killActiveWorker(); - this.activeWorker = fork({ - pollingRoute: route, - pollingFailureTolerance: failureTolerance, - serverPort: ports.server, - socketPort: ports.socket, - pollingIntervalSeconds: intervalSeconds, - session_key: this.key, - DB: process.env.DB - }); - this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`)); - // an IPC message handler that executes actions on the master thread when prompted by the active worker - this.activeWorker.on("message", async ({ lifecycle, action }) => { - if (action) { - const { message, args } = action as Monitor.Action; - console.log(this.timestamp(), `${this.config.identifiers.worker.text} action requested (${cyan(message)})`); - switch (message) { - case "kill": - const { reason, graceful, errorCode } = args; - this.killSession(reason, graceful, errorCode); - break; - case "notify_crash": - if (this.notifiers?.crash) { - const { error } = args; - const success = await this.notifiers.crash(error); - const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed"); - this.mainLog(statement); - } - break; - case "set_port": - const { port, value, immediateRestart } = args; - this.setPort(port, value, immediateRestart); - break; - } - const handlers = this.onMessage[message]; - if (handlers) { - handlers.forEach(handler => handler({ message, args })); - } - } - if (lifecycle) { - console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`); - } - }); - } - - } - - /** - * Effectively, each worker repairs the connection to the server by reintroducing a consistent state - * if its predecessor has died. It itself also polls the server heartbeat, and exits with a notification - * email if the server encounters an uncaught exception or if the server cannot be reached. - */ - export class ServerWorker { - - private static count = 0; - private shouldServerBeResponsive = false; - private exitHandlers: ExitHandler[] = []; - private pollingFailureCount = 0; - private pollingIntervalSeconds: number; - private pollingFailureTolerance: number; - private pollTarget: string; - private serverPort: number; - - public static Create(work: Function) { - if (isMaster) { - console.error(red("cannot create a worker on the monitor process.")); - process.exit(1); - } else if (++ServerWorker.count > 1) { - process.send?.({ - action: { - message: "kill", args: { - reason: "cannot create more than one worker on a given worker process.", - graceful: false, - errorCode: 1 - } - } - }); - process.exit(1); - } else { - return new ServerWorker(work); - } - } - - /** - * Allows developers to invoke application specific logic - * by hooking into the exiting of the server process. - */ - public addExitHandler = (handler: ExitHandler) => this.exitHandlers.push(handler); - - /** - * Kill the session monitor (parent process) from this - * server worker (child process). This will also kill - * this process (child process). - */ - public killSession = (reason: string, graceful = true, errorCode = 0) => this.sendMonitorAction("kill", { reason, graceful, errorCode }); - - /** - * A convenience wrapper to tell the session monitor (parent process) - * to carry out the action with the specified message and arguments. - */ - public sendMonitorAction = (message: string, args?: any) => process.send!({ action: { message, args } }); - - private constructor(work: Function) { - this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`)); - - const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env; - this.serverPort = Number(serverPort); - this.pollingIntervalSeconds = Number(pollingIntervalSeconds); - this.pollingFailureTolerance = Number(pollingFailureTolerance); - this.pollTarget = `http://localhost:${serverPort}${pollingRoute}`; - - this.configureProcess(); - work(); - this.pollServer(); - } - - /** - * Set up message and uncaught exception handlers for this - * server process. - */ - private configureProcess = () => { - // updates the local values of variables to the those sent from master - process.on("message", async ({ newPollingIntervalSeconds, manualExit }) => { - if (newPollingIntervalSeconds !== undefined) { - this.pollingIntervalSeconds = newPollingIntervalSeconds; - } - if (manualExit !== undefined) { - const { isSessionEnd } = manualExit; - await this.executeExitHandlers(isSessionEnd); - process.exit(0); - } - }); - - // one reason to exit, as the process might be in an inconsistent state after such an exception - process.on('uncaughtException', this.proactiveUnplannedExit); - process.on('unhandledRejection', reason => { - const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`); - this.proactiveUnplannedExit(appropriateError); - }); - } - - /** - * Execute the list of functions registered to be called - * whenever the process exits. - */ - private executeExitHandlers = async (reason: Error | boolean) => Promise.all(this.exitHandlers.map(handler => handler(reason))); - - /** - * Notify master thread (which will log update in the console) of initialization via IPC. - */ - public lifecycleNotification = (event: string) => process.send?.({ lifecycle: event }); - - /** - * Called whenever the process has a reason to terminate, either through an uncaught exception - * in the process (potentially inconsistent state) or the server cannot be reached. - */ - private proactiveUnplannedExit = async (error: Error): Promise => { - this.shouldServerBeResponsive = false; - // communicates via IPC to the master thread that it should dispatch a crash notification email - this.sendMonitorAction("notify_crash", { error }); - await this.executeExitHandlers(error); - // notify master thread (which will log update in the console) of crash event via IPC - this.lifecycleNotification(red(`crash event detected @ ${new Date().toUTCString()}`)); - this.lifecycleNotification(red(error.message)); - process.exit(1); - } - - /** - * This monitors the health of the server by submitting a get request to whatever port / route specified - * by the configuration every n seconds, where n is also given by the configuration. - */ - private pollServer = async (): Promise => { - await new Promise(resolve => { - setTimeout(async () => { - try { - await get(this.pollTarget); - if (!this.shouldServerBeResponsive) { - // notify monitor thread that the server is up and running - this.lifecycleNotification(green(`listening on ${this.serverPort}...`)); - } - this.shouldServerBeResponsive = true; - } catch (error) { - // if we expect the server to be unavailable, i.e. during compilation, - // the listening variable is false, activeExit will return early and the child - // process will continue - if (this.shouldServerBeResponsive) { - if (++this.pollingFailureCount > this.pollingFailureTolerance) { - this.proactiveUnplannedExit(error); - } else { - this.lifecycleNotification(yellow(`the server has encountered ${this.pollingFailureCount} of ${this.pollingFailureTolerance} tolerable failures`)); - } - } - } finally { - resolve(); - } - }, 1000 * this.pollingIntervalSeconds); - }); - // controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed - this.pollServer(); - } - - } - -} -- cgit v1.2.3-70-g09d2 From 00a44ed4b0011ca6967f5d1512aa9fd1f6a1bdfe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 21 Jan 2020 00:39:21 -0500 Subject: fix to avoid crash when rendering a non-string in an EditableView (eg when the document's title is displayed as a richtextBox and as an EditableView header on a DocumentView) --- src/client/views/EditableView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index faf02b946..0d677b8ce 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -5,6 +5,7 @@ import "./EditableView.scss"; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; +import { ObjectField } from '../../new_fields/ObjectField'; export interface EditableProps { /** @@ -152,7 +153,7 @@ export class EditableView extends React.Component { />; } else { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); - return ( + return (this.props.contents instanceof ObjectField ? (null) :
-- cgit v1.2.3-70-g09d2 From bfebed91e12abca324d7a638adeb1da9755a9057 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 22 Jan 2020 15:41:04 -0500 Subject: basic cleanup for warnings --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/util/ProseMirrorEditorView.tsx | 74 ---------------------- src/client/util/RichTextMenu.tsx | 68 ++++++++++---------- src/client/util/RichTextSchema.tsx | 2 +- src/client/views/EditableView.tsx | 2 +- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionPivotView.tsx | 11 ++-- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/collections/CollectionViewChromes.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 3 + src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 1 - src/new_fields/Doc.ts | 10 +-- src/new_fields/documentSchemas.ts | 1 + src/server/Websocket/Websocket.ts | 2 - 17 files changed, 59 insertions(+), 132 deletions(-) delete mode 100644 src/client/util/ProseMirrorEditorView.tsx (limited to 'src/client/views/EditableView.tsx') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index fd3d9e2f1..d654e2530 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -46,7 +46,7 @@ export class YoutubeBox extends React.Component { * When component mounts, last search's results are laoded in based on the back up stored * in the document of the props. */ - async componentWillMount() { + async componentDidMount() { //DocServer.getYoutubeChannels(); const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc); const awaitedBackUp = await castedSearchBackUp; diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx deleted file mode 100644 index b42adfbb4..000000000 --- a/src/client/util/ProseMirrorEditorView.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { EditorView } from "prosemirror-view"; -import { EditorState } from "prosemirror-state"; - -export interface ProseMirrorEditorViewProps { - /* EditorState instance to use. */ - editorState: EditorState; - /* Called when EditorView produces new EditorState. */ - onEditorState: (editorState: EditorState) => any; -} - -/** - * This wraps ProseMirror's EditorView into React component. - * This code was found on https://discuss.prosemirror.net/t/using-with-react/904 - */ -export class ProseMirrorEditorView extends React.Component { - - private _editorView?: EditorView; - - _createEditorView = (element: HTMLDivElement | null) => { - if (element !== null) { - this._editorView = new EditorView(element, { - state: this.props.editorState, - dispatchTransaction: this.dispatchTransaction, - }); - } - } - - dispatchTransaction = (tx: any) => { - // In case EditorView makes any modification to a state we funnel those - // modifications up to the parent and apply to the EditorView itself. - const editorState = this.props.editorState.apply(tx); - if (this._editorView) { - this._editorView.updateState(editorState); - } - this.props.onEditorState(editorState); - } - - focus() { - if (this._editorView) { - this._editorView.focus(); - } - } - - componentWillReceiveProps(nextProps: { editorState: EditorState; }) { - // In case we receive new EditorState through props — we apply it to the - // EditorView instance. - if (this._editorView) { - if (nextProps.editorState !== this.props.editorState) { - this._editorView.updateState(nextProps.editorState); - } - } - } - - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); - } - } - - shouldComponentUpdate() { - // Note that EditorView manages its DOM itself so we'd ratrher don't mess - // with it. - return false; - } - - render() { - // Render just an empty div which is then used as a container for an - // EditorView instance. - return ( -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index f070589ae..eb5c90654 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -73,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu { this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize }, @@ -312,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu { } return ( - ); } - createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - : - ; + : + ; } return label === activeOption ? - : - ; + : + ; }); const self = this; @@ -340,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return ; + return ; } - createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - : - ; + : + ; } return label === activeOption ? - : - ; + : + ; }); const self = this; @@ -363,7 +363,7 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return ; + return ; } changeFontSize = (mark: Mark, view: EditorView) => { @@ -472,7 +472,7 @@ export default class RichTextMenu extends AntimodeMenu {
; return ( - + ); } @@ -535,21 +535,21 @@ export default class RichTextMenu extends AntimodeMenu { ; const dropdownContent = -
+

Change font color:

{this.fontColors.map(color => { if (color) { return this.activeFontColor === color ? - : - ; + : + ; } })}
; return ( - + ); } @@ -582,7 +582,7 @@ export default class RichTextMenu extends AntimodeMenu { } const button = - ; @@ -594,15 +594,15 @@ export default class RichTextMenu extends AntimodeMenu { {this.highlightColors.map(color => { if (color) { return this.activeHighlightColor === color ? - : - ; + : + ; } })}
; return ( - + ); } @@ -635,7 +635,7 @@ export default class RichTextMenu extends AntimodeMenu {
; return ( - + ); } @@ -773,7 +773,7 @@ export default class RichTextMenu extends AntimodeMenu { render() { - const row1 =
{[ + const row1 =
{[ this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), @@ -787,14 +787,14 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("indent", "Summarize", undefined, this.insertSummarizer), ]}
; - const row2 =
-
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions), - this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions), - this.createNodesDropdown(this.activeListType, this.listTypeOptions)]} + const row2 =
+
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), + this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
-
- {this.getDragger()} @@ -864,12 +864,12 @@ class ButtonDropdown extends React.Component { : <> {this.props.button} - } - {this.showDropdown ? this.props.dropdownContent : <>} + {this.showDropdown ? this.props.dropdownContent : (null)}
); } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1f70cafc4..5751c8c7e 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -306,7 +306,7 @@ export const marks: { [index: string]: MarkSpec } = { } }], toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; } }, diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 0d677b8ce..394caba72 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -66,7 +66,7 @@ export class EditableView extends React.Component { } @action - componentWillReceiveProps(nextProps: EditableProps) { + componentDidUpdate(nextProps: EditableProps) { // this is done because when autosuggest is turned on, the suggestions are passed in as a prop, // so when the suggestions are passed in, and no editing prop is passed in, it used to set it // to false. this will no longer do so -syip diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0958c434a..79e0cfea5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -64,7 +64,7 @@ export class MainView extends React.Component { public isPointerDown = false; - componentWillMount() { + componentDidMount() { const tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index f23540529..aa68cc18f 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -28,6 +28,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { if (!this.props.Document.facetCollection) { const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); facetCollection.target = this.props.Document; + facetCollection.dontCopyOnAlias = true; const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)"; const script = CompileScript(scriptText, { @@ -122,8 +123,8 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null); const flyout = (
- {this._allFacets.map(facet =>