From 1c0e229f9c298281cdeab64532334e0b875cd2e4 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 27 Apr 2020 16:21:56 -0400 Subject: cleaned up masonry events a bit. changed buxton template to have 3 sections. --- src/server/authentication/models/current_user_utils.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b5a44a8bc..1d41c3570 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -20,6 +20,7 @@ import { PrefetchProxy } from "../../../new_fields/Proxy"; import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox"; import { MainView } from "../../../client/views/MainView"; import { DocumentType } from "../../../client/documents/DocumentTypes"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; export class CurrentUserUtils { private static curr_id: string; @@ -108,10 +109,15 @@ export class CurrentUserUtils { const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 }; const descriptionWrapperOpts = { title: "descriptions", _height: 300, columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" }; - const descriptionWrapper = MasonryDocument([short, long], { ...shared, ...descriptionWrapperOpts }); - const detailView = Docs.Create.StackingDocument([carousel, details, descriptionWrapper], { ...shared, ...detailViewOpts }); + const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); + descriptionWrapper.sectionHeaders = new List([ + new SchemaHeaderField("[Long Description]", "LemonChiffon", undefined, undefined, undefined, true), + new SchemaHeaderField("[Details]", "lightBlue", undefined, undefined, undefined, true), + ]); + const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts }); detailView.isTemplateDoc = makeTemplate(detailView); + details.title = "Details"; short.title = "A Short Description"; long.title = "Long Description"; -- cgit v1.2.3-70-g09d2 From 69de8c235a6580ac222ef3f5b31746f6bc144659 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 28 Apr 2020 10:48:16 -0400 Subject: rearranged text files --- .../apis/google_docs/GooglePhotosClientUtils.ts | 2 +- src/client/documents/Documents.ts | 2 +- src/client/util/DashDocCommentView.tsx | 95 -- src/client/util/DashDocView.tsx | 269 ---- src/client/util/DashFieldView.scss | 36 - src/client/util/DashFieldView.tsx | 211 ---- src/client/util/FootnoteView.tsx | 162 --- src/client/util/ImageResizeView.tsx | 138 --- src/client/util/ParagraphNodeSpec.ts | 143 --- src/client/util/ProsemirrorExampleTransfer.ts | 241 ---- src/client/util/RichTextMenu.scss | 121 -- src/client/util/RichTextMenu.tsx | 875 ------------- src/client/util/RichTextRules.ts | 4 +- src/client/util/RichTextSchema.tsx | 718 ----------- src/client/util/SummaryView.tsx | 81 -- src/client/util/TooltipTextMenu.scss | 372 ------ src/client/util/marks_rts.ts | 296 ----- src/client/util/nodes_rts.ts | 264 ---- src/client/util/prosemirrorPatches.js | 139 --- src/client/util/schema_rts.ts | 26 - src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.scss | 265 ---- src/client/views/nodes/FormattedTextBox.tsx | 1306 -------------------- .../views/nodes/FormattedTextBoxComment.scss | 33 - src/client/views/nodes/FormattedTextBoxComment.tsx | 236 ---- .../nodes/formattedText/DashDocCommentView.tsx | 95 ++ .../views/nodes/formattedText/DashDocView.tsx | 269 ++++ .../views/nodes/formattedText/DashFieldView.scss | 36 + .../views/nodes/formattedText/DashFieldView.tsx | 211 ++++ .../views/nodes/formattedText/FootnoteView.tsx | 162 +++ .../nodes/formattedText/FormattedTextBox.scss | 265 ++++ .../views/nodes/formattedText/FormattedTextBox.tsx | 1303 +++++++++++++++++++ .../formattedText/FormattedTextBoxComment.scss | 33 + .../formattedText/FormattedTextBoxComment.tsx | 236 ++++ .../views/nodes/formattedText/ImageResizeView.tsx | 138 +++ .../views/nodes/formattedText/ParagraphNodeSpec.ts | 143 +++ .../formattedText/ProsemirrorExampleTransfer.ts | 241 ++++ .../views/nodes/formattedText/RichTextMenu.scss | 121 ++ .../views/nodes/formattedText/RichTextMenu.tsx | 875 +++++++++++++ .../views/nodes/formattedText/RichTextRules.ts | 319 +++++ .../views/nodes/formattedText/RichTextSchema.tsx | 718 +++++++++++ .../views/nodes/formattedText/SummaryView.tsx | 81 ++ .../views/nodes/formattedText/TooltipTextMenu.scss | 372 ++++++ src/client/views/nodes/formattedText/marks_rts.ts | 296 +++++ src/client/views/nodes/formattedText/nodes_rts.ts | 264 ++++ .../nodes/formattedText/prosemirrorPatches.js | 139 +++ src/client/views/nodes/formattedText/schema_rts.ts | 26 + src/mobile/MobileInterface.tsx | 2 +- src/new_fields/RichTextUtils.ts | 4 +- .../authentication/models/current_user_utils.ts | 2 +- 57 files changed, 6359 insertions(+), 6043 deletions(-) delete mode 100644 src/client/util/DashDocCommentView.tsx delete mode 100644 src/client/util/DashDocView.tsx delete mode 100644 src/client/util/DashFieldView.scss delete mode 100644 src/client/util/DashFieldView.tsx delete mode 100644 src/client/util/FootnoteView.tsx delete mode 100644 src/client/util/ImageResizeView.tsx delete mode 100644 src/client/util/ParagraphNodeSpec.ts delete mode 100644 src/client/util/ProsemirrorExampleTransfer.ts delete mode 100644 src/client/util/RichTextMenu.scss delete mode 100644 src/client/util/RichTextMenu.tsx delete mode 100644 src/client/util/RichTextSchema.tsx delete mode 100644 src/client/util/SummaryView.tsx delete mode 100644 src/client/util/TooltipTextMenu.scss delete mode 100644 src/client/util/marks_rts.ts delete mode 100644 src/client/util/nodes_rts.ts delete mode 100644 src/client/util/prosemirrorPatches.js delete mode 100644 src/client/util/schema_rts.ts delete mode 100644 src/client/views/nodes/FormattedTextBox.scss delete mode 100644 src/client/views/nodes/FormattedTextBox.tsx delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx create mode 100644 src/client/views/nodes/formattedText/DashDocCommentView.tsx create mode 100644 src/client/views/nodes/formattedText/DashDocView.tsx create mode 100644 src/client/views/nodes/formattedText/DashFieldView.scss create mode 100644 src/client/views/nodes/formattedText/DashFieldView.tsx create mode 100644 src/client/views/nodes/formattedText/FootnoteView.tsx create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.scss create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.tsx create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.scss create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx create mode 100644 src/client/views/nodes/formattedText/ImageResizeView.tsx create mode 100644 src/client/views/nodes/formattedText/ParagraphNodeSpec.ts create mode 100644 src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.scss create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.tsx create mode 100644 src/client/views/nodes/formattedText/RichTextRules.ts create mode 100644 src/client/views/nodes/formattedText/RichTextSchema.tsx create mode 100644 src/client/views/nodes/formattedText/SummaryView.tsx create mode 100644 src/client/views/nodes/formattedText/TooltipTextMenu.scss create mode 100644 src/client/views/nodes/formattedText/marks_rts.ts create mode 100644 src/client/views/nodes/formattedText/nodes_rts.ts create mode 100644 src/client/views/nodes/formattedText/prosemirrorPatches.js create mode 100644 src/client/views/nodes/formattedText/schema_rts.ts (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 8c0149a89..e3f801c46 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -10,7 +10,7 @@ import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/Share import { Utils } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { Networking } from "../../Network"; -import { FormattedTextBox } from "../../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox"; import GoogleAuthenticationManager from "../GoogleAuthenticationManager"; import Photos = require('googlephotos'); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c64916897..f96e3bcd1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,7 @@ import { CollectionView } from "../views/collections/CollectionView"; import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { PDFBox } from "../views/nodes/PDFBox"; diff --git a/src/client/util/DashDocCommentView.tsx b/src/client/util/DashDocCommentView.tsx deleted file mode 100644 index e716fac53..000000000 --- a/src/client/util/DashDocCommentView.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { keymap } from "prosemirror-keymap"; -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; -import { StepMap } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { List } from "../../new_fields/List"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; - -import React = require("react"); - -import { schema } from "./schema_rts"; - -interface IDashDocCommentView { - node: any; - view: any; - getPos: any; -} - -export class DashDocCommentView extends React.Component{ - constructor(props: IDashDocCommentView) { - super(props); - } - - targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor - for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) { - const m = this.props.view.state.doc.nodeAt(i); - if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; - } - } - const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" }); - this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc)); - setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); - return undefined; - } - - onPointerDownCollapse = (e: any) => e.stopPropagation(); - - onPointerUpCollapse = (e: any) => { - const target = this.targetNode(); - if (target) { - const expand = target.hidden; - const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); - this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs - setTimeout(() => { - expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } - }, 0); - } - e.stopPropagation(); - } - - onPointerEnterCollapse = (e: any) => { - DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); - e.preventDefault(); - e.stopPropagation(); - } - - onPointerLeaveCollapse = (e: any) => { - DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); - e.preventDefault(); - e.stopPropagation(); - } - - render() { - - const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid; - - return ( - - - - ); - } -} \ No newline at end of file diff --git a/src/client/util/DashDocView.tsx b/src/client/util/DashDocView.tsx deleted file mode 100644 index 39809187f..000000000 --- a/src/client/util/DashDocView.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { IReactionDisposer, reaction } from "mobx"; -import { NodeSelection } from "prosemirror-state"; -import { Doc, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs } from "../documents/Documents"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { Transform } from "./Transform"; -import React = require("react"); - -interface IDashDocView { - node: any; - view: any; - getPos: any; - tbox?: FormattedTextBox; - self: any; -} - -export class DashDocView extends React.Component { - - _dashDoc: Doc | undefined; - _reactionDisposer: IReactionDisposer | undefined; - _renderDisposer: IReactionDisposer | undefined; - _textBox: FormattedTextBox; - _finalLayout: any; - _resolvedDataDoc: any; - - - // constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - - constructor(props: IDashDocView) { - super(props); - - const node = this.props.node; - this._textBox = this.props.tbox as FormattedTextBox; - - const alias = node.attrs.alias; - const docid = node.attrs.docid || this._textBox.props.Document[Id]; - - DocServer.GetRefField(docid + alias).then(async dashDoc => { - if (!(dashDoc instanceof Doc)) { - alias && DocServer.GetRefField(docid).then(async dashDocBase => { - if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); - aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); - this._dashDoc = aliasedDoc; - // self.doRender(aliasedDoc, removeDoc, node, view, getPos); - } - }); - } else { - this._dashDoc = dashDoc; - // self.doRender(dashDoc, removeDoc, node, view, getPos); - } - }); - - this.onPointerLeave = this.onPointerLeave.bind(this); - this.onPointerEnter = this.onPointerEnter.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.onKeyPress = this.onKeyPress.bind(this); - this.onKeyUp = this.onKeyUp.bind(this); - this.onWheel = this.onWheel.bind(this); - } - /* #region Internal functions */ - - removeDoc = () => { - const view = this.props.view; - const pos = this.props.getPos(); - const ns = new NodeSelection(view.state.doc.resolve(pos)); - view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); - return true; - } - - getDocTransform = () => { - const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement; - const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement); - return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); - } - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; - - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target - - onKeyPress = (e: any) => { - e.stopPropagation(); - } - onWheel = (e: any) => { - e.preventDefault(); - } - onKeyUp = (e: any) => { - e.stopPropagation(); - } - onKeyDown = (e: any) => { - e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { - e.preventDefault(); - } - } - onPointerLeave = () => { - const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = ""; - } - } - onPointerEnter = () => { - const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = "orange"; - } - } - /*endregion*/ - - componentWillMount = () => { - this._reactionDisposer?.(); - } - - componentDidUpdate = () => { - - this._renderDisposer?.(); - this._renderDisposer = reaction(() => { - - const dashDoc = this._dashDoc as Doc; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey); - - if (finalLayout) { - if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { - finalLayout.rootDocument = dashDoc.aliasOf; - } - const layoutKey = StrCast(finalLayout.layoutKey); - const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; - if (finalLayout !== dashDoc && finalKey) { - const finalLayoutField = finalLayout[finalKey]; - if (finalLayoutField instanceof ObjectField) { - finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); - } - } - this._finalLayout = finalLayout; - this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null); - return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; - } - }, - (res) => { - - if (res) { - this._finalLayout = res.finalLayout; - this._resolvedDataDoc = res.resolvedDataDoc; - - this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc), - } - }, - { fireImmediately: true }); - - } - - render() { - // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { - - const node = this.props.node; - const view = this.props.view; - const getPos = this.props.getPos; - - const spanStyle = { - width: this.props.node.props.width, - height: this.props.node.props.height, - position: 'absolute' as 'absolute', - display: 'inline-block' - }; - - - const outerStyle = { - position: "relative" as "relative", - textIndent: "0", - border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")), - width: this.props.node.props.width, - height: this.props.node.props.height, - display: this.props.node.props.hidden ? "none" : "inline-block", - float: this.props.node.props.float, - }; - - const dashDoc = this._dashDoc as Doc; - const self = this; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); - const resolvedDataDoc = this._resolvedDataDoc; //Added this - - if (!finalLayout) { - return
; - // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); - } else { - - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => - ({ - dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], - color: finalLayout.color - }), - ({ dim, color }) => { - spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px"; - spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px"; - outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - }, { fireImmediately: true }); - - if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); - } catch (e) { - console.log(e); - } - } - - - //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { - // ReactDOM.unmountComponentAtNode(this._dashSpan); - - return ( - -
- - -
-
- ); - - } - } - -} \ No newline at end of file diff --git a/src/client/util/DashFieldView.scss b/src/client/util/DashFieldView.scss deleted file mode 100644 index 35ff9c1e6..000000000 --- a/src/client/util/DashFieldView.scss +++ /dev/null @@ -1,36 +0,0 @@ -.dashFieldView { - position: relative; - display: inline-block; - - .dashFieldView-enumerables { - width: 10px; - height: 10px; - position: relative; - display: inline-block; - background: dimGray; - } - .dashFieldView-fieldCheck { - min-width: 12px; - position: relative; - display: inline-block; - background-color: rgba(155, 155, 155, 0.24); - } - .dashFieldView-labelSpan { - position: relative; - display: inline-block; - font-size: small; - } - .dashFieldView-fieldSpan { - min-width: 20px; - margin-left: 2px; - margin-right: 5px; - position: relative; - display: inline-block; - background-color: rgba(155, 155, 155, 0.24); - span { - min-width: 100%; - display: inline-block; - } - } -} - \ No newline at end of file diff --git a/src/client/util/DashFieldView.tsx b/src/client/util/DashFieldView.tsx deleted file mode 100644 index cf09e0132..000000000 --- a/src/client/util/DashFieldView.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { IReactionDisposer, observable, runInAction, computed, action } from "mobx"; -import { Doc, DocListCast, Field } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../new_fields/Types"; -import { DocServer } from "../DocServer"; -import { CollectionViewType } from "../views/collections/CollectionView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import React = require("react"); -import * as ReactDOM from 'react-dom'; -import "./DashFieldView.scss"; -import { observer } from "mobx-react"; - - -export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; - } - destroy() { - ReactDOM.unmountComponentAtNode(this._fieldWrapper); - } - selectNode() { } - -} -interface IDashFieldViewInternal { - fieldKey: string; - docid: string; - view: any; - getPos: any; - tbox: FormattedTextBox; - width: number; - height: number; -} - -@observer -export class DashFieldViewInternal extends React.Component { - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - _fieldKey: string; - _fieldStringRef = React.createRef(); - @observable _showEnumerables: boolean = false; - @observable _dashDoc: Doc | undefined; - - constructor(props: IDashFieldViewInternal) { - super(props); - this._fieldKey = this.props.fieldKey; - this._textBoxDoc = this.props.tbox.props.Document; - - if (this.props.docid) { - DocServer.GetRefField(this.props.docid). - then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); - } else { - this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc; - } - } - componentWillUnmount() { - this._reactionDisposer?.(); - } - - // set the display of the field's value (checkbox for booleans, span of text for strings) - @computed get fieldValueContent() { - if (this._dashDoc) { - const dashVal = this._dashDoc[this._fieldKey]; - const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal; - const boolVal = Cast(fval, "boolean", null); - const strVal = Field.toString(fval as Field) || ""; - - // field value is a boolean, so use a checkbox or similar widget to display it - if (boolVal === true || boolVal === false) { - return this._dashDoc![this._fieldKey] = e.target.checked} - />; - } - else // field value is a string, so display it as an editable span - { - // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't - // use React events. Essentially, React events occur after native events have been processed, so corresponding React events - // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return { - r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); - r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true)); - }}> - {strVal} - - } - } - } - - // we need to handle all key events on the input span or else they will propagate to prosemirror. - @action - fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { - if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. - e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); - this.updateText(span.textContent!, true); - e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view - } - if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(span); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected - } - e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. - } - - @action - updateText = (nodeText: string, forceMatch: boolean) => { - this._showEnumerables = false; - if (nodeText) { - const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; - - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); - this._dashDoc![this._fieldKey] = modText; - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(":=")) { - this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith("=:=")) { - Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); - } else { - this._dashDoc![this._fieldKey] = newText; - } - }); - } - } - - // display a collection of all the enumerable values for this field - onPointerDownEnumerables = async (e: any) => { - e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); - collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); - } - - - // clicking on the label creates a pivot view collection of all documents - // in the same collection. The pivot field is the fieldKey of this label - onPointerDownLabelSpan = (e: any) => { - e.stopPropagation(); - let container = this.props.tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } - if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; - let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); - if (!list) { - alias.schemaColumns = list = new List(); - } - list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = this._fieldKey; - this.props.tbox.props.addDocTab(alias, "onRight"); - } - } - - render() { - return
- - {this._fieldKey} - - -
- {this.fieldValueContent} -
- - {!this._showEnumerables ? (null) :
} - -
; - } -} \ No newline at end of file diff --git a/src/client/util/FootnoteView.tsx b/src/client/util/FootnoteView.tsx deleted file mode 100644 index ee21fb765..000000000 --- a/src/client/util/FootnoteView.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { EditorView } from "prosemirror-view"; -import { EditorState } from "prosemirror-state"; -import { keymap } from "prosemirror-keymap"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { schema } from "./schema_rts"; -import { redo, undo } from "prosemirror-history"; -import { StepMap } from "prosemirror-transform"; - -import React = require("react"); - -interface IFootnoteView { - innerView: any; - outerView: any; - node: any; - dom: any; - getPos: any; -} - -export class FootnoteView extends React.Component { - _innerView: any; - _node: any; - - constructor(props: IFootnoteView) { - super(props); - const node = this.props.node; - const outerView = this.props.outerView; - const _innerView = this.props.innerView; - const getPos = this.props.getPos; - } - - selectNode() { - const attrs = { ...this.props.node.attrs }; - attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode"); - if (!this.props.innerView) this.open(); - } - - deselectNode() { - const attrs = { ...this.props.node.attrs }; - attrs.visibility = false; - this.dom.classList.remove("ProseMirror-selectednode"); - if (this.props.innerView) this.close(); - } - open() { - // Append a tooltip to the outer node - const tooltip = this.dom.appendChild(document.createElement("div")); - tooltip.className = "footnote-tooltip"; - // And put a sub-ProseMirror into that - this.props.innerView.defineProperty(new EditorView(tooltip, { - // You can use any node as an editor document - state: EditorState.create({ - doc: this.props.node, - plugins: [keymap(baseKeymap), - keymap({ - "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch), - "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch), - "Mod-b": toggleMark(schema.marks.strong) - }), - // new Plugin({ - // view(newView) { - // // TODO -- make this work with RichTextMenu - // // return FormattedTextBox.getToolTip(newView); - // } - // }) - ], - - }), - // This is the magic part - dispatchTransaction: this.dispatchInner.bind(this), - handleDOMEvents: { - pointerdown: ((view: any, e: PointerEvent) => { - // Kludge to prevent issues due to the fact that the whole - // footnote is node-selected (and thus DOM-selected) when - // the parent editor is focused. - e.stopPropagation(); - document.addEventListener("pointerup", this.ignore, true); - if (this.props.outerView.hasFocus()) this.props.innerView.focus(); - }) as any - } - })); - setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0); - } - - ignore = (e: PointerEvent) => { - e.stopPropagation(); - document.removeEventListener("pointerup", this.ignore, true); - } - - dispatchInner(tr: any) { - const { state, transactions } = this.props.innerView.state.applyTransaction(tr); - this.props.innerView.updateState(state); - - if (!tr.getMeta("fromOutside")) { - const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1); - for (const transaction of transactions) { - const steps = transaction.steps; - for (const step of steps) { - outerTr.step(step.map(offsetMap)); - } - } - if (outerTr.docChanged) this.props.outerView.dispatch(outerTr); - } - } - update(node: any) { - if (!node.sameMarkup(this.props.node)) return false; - this._node = node; //not sure - if (this.props.innerView) { - const state = this.props.innerView.state; - const start = node.content.findDiffStart(state.doc.content); - if (start !== null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); - const overlap = start - Math.min(endA, endB); - if (overlap > 0) { endA += overlap; endB += overlap; } - this.props.innerView.dispatch( - state.tr - .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)); - } - } - return true; - } - onPointerUp = (e: any) => { - this.toggle(e); - } - - toggle = (e: any) => { - e.preventDefault(); - if (this.props.innerView) this.close(); - else { - this.open(); - } - } - - close() { - this.props.innerView && this.props.innerView.destroy(); - this._innerView = null; - this.dom.textContent = ""; - } - - destroy() { - if (this.props.innerView) this.close(); - } - - stopEvent(event: any) { - return this.props.innerView && this.props.innerView.dom.contains(event.target); - } - - ignoreMutation() { return true; } - - - render() { - return ( -
-
- -
-
- ); - } -} diff --git a/src/client/util/ImageResizeView.tsx b/src/client/util/ImageResizeView.tsx deleted file mode 100644 index 4f66475fb..000000000 --- a/src/client/util/ImageResizeView.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { NodeSelection } from "prosemirror-state"; -import { Doc } from "../../new_fields/Doc"; -import { DocServer } from "../DocServer"; -import { DocumentManager } from "./DocumentManager"; -import React = require("react"); - -import { schema } from "./schema_rts"; - -interface IImageResizeView { - node: any; - view: any; - getPos: any; - addDocTab: any; -} - -export class ImageResizeView extends React.Component { - constructor(props: IImageResizeView) { - super(props); - } - - onClickImg = (e: any) => { - e.stopPropagation(); - e.preventDefault(); - if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) { - this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2)))); - } - } - - onPointerDownImg = (e: any) => { - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc => - (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document, - document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false)); - } - } - - onPointerDownHandle = (e: any) => { - e.preventDefault(); - e.stopPropagation(); - const elementImage = document.getElementById("imageId") as HTMLElement; - const wid = Number(getComputedStyle(elementImage).width.replace(/px/, "")); - const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, "")); - const startX = e.pageX; - const startWidth = parseFloat(this.props.node.attrs.width); - - const onpointermove = (e: any) => { - const elementOuter = document.getElementById("outerId") as HTMLElement; - - const currentX = e.pageX; - const diffInPx = currentX - startX; - elementOuter.style.width = `${startWidth + diffInPx}`; - elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`; - }; - - const onpointerup = () => { - document.removeEventListener("pointermove", onpointermove); - document.removeEventListener("pointerup", onpointerup); - const pos = this.props.view.state.selection.from; - const elementOuter = document.getElementById("outerId") as HTMLElement; - this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height })); - this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos)))); - }; - - document.addEventListener("pointermove", onpointermove); - document.addEventListener("pointerup", onpointerup); - } - - selectNode() { - const elementImage = document.getElementById("imageId") as HTMLElement; - const elementHandle = document.getElementById("handleId") as HTMLElement; - - elementImage.classList.add("ProseMirror-selectednode"); - elementHandle.style.display = ""; - } - - deselectNode() { - const elementImage = document.getElementById("imageId") as HTMLElement; - const elementHandle = document.getElementById("handleId") as HTMLElement; - - elementImage.classList.remove("ProseMirror-selectednode"); - elementHandle.style.display = "none"; - } - - - render() { - - const outerStyle = { - width: this.props.node.attrs.width, - height: this.props.node.attrs.height, - display: "inline-block", - overflow: "hidden", - float: this.props.node.attrs.float - }; - - const imageStyle = { - width: "100%", - }; - - const handleStyle = { - position: "absolute", - width: "20px", - heiht: "20px", - backgroundColor: "blue", - borderRadius: "15px", - display: "none", - bottom: "-10px", - right: "-10px" - - }; - - - - return ( -
- - - - - -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts deleted file mode 100644 index 0a3b68217..000000000 --- a/src/client/util/ParagraphNodeSpec.ts +++ /dev/null @@ -1,143 +0,0 @@ -import clamp from './clamp'; -import convertToCSSPTValue from './convertToCSSPTValue'; -import toCSSLineSpacing from './toCSSLineSpacing'; -import { Node, DOMOutputSpec } from 'prosemirror-model'; - -//import type { NodeSpec } from './Types'; -type NodeSpec = { - attrs?: { [key: string]: any }, - content?: string, - draggable?: boolean, - group?: string, - inline?: boolean, - name?: string, - parseDOM?: Array, - toDOM?: (node: any) => DOMOutputSpec, -}; - -// This assumes that every 36pt maps to one indent level. -export const INDENT_MARGIN_PT_SIZE = 36; -export const MIN_INDENT_LEVEL = 0; -export const MAX_INDENT_LEVEL = 7; -export const ATTRIBUTE_INDENT = 'data-indent'; - -export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']); - -const ALIGN_PATTERN = /(left|right|center|justify)/; - -// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js -// :: NodeSpec A plain paragraph textblock. Represented in the DOM -// as a `

` element. -const ParagraphNodeSpec: NodeSpec = { - attrs: { - align: { default: null }, - color: { default: null }, - id: { default: null }, - indent: { default: null }, - inset: { default: null }, - lineSpacing: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingBottom: { default: null }, - // TODO: Add UI to let user edit / clear padding. - paddingTop: { default: null }, - }, - content: 'inline*', - group: 'block', - parseDOM: [{ tag: 'p', getAttrs }], - toDOM, -}; - -function getAttrs(dom: HTMLElement): Object { - const { - lineHeight, - textAlign, - marginLeft, - paddingTop, - paddingBottom, - } = dom.style; - - let align = dom.getAttribute('align') || textAlign || ''; - align = ALIGN_PATTERN.test(align) ? align : ""; - - let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10); - - if (!indent && marginLeft) { - indent = convertMarginLeftToIndentValue(marginLeft); - } - - indent = indent || MIN_INDENT_LEVEL; - - const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null; - - const id = dom.getAttribute('id') || ''; - return { align, indent, lineSpacing, paddingTop, paddingBottom, id }; -} - -function toDOM(node: Node): DOMOutputSpec { - const { - align, - indent, - inset, - lineSpacing, - paddingTop, - paddingBottom, - id, - } = node.attrs; - const attrs: { [key: string]: any } | null = {}; - - let style = ''; - if (align && align !== 'left') { - style += `text-align: ${align};`; - } - - if (lineSpacing) { - const cssLineSpacing = toCSSLineSpacing(lineSpacing); - style += - `line-height: ${cssLineSpacing};` + - // This creates the local css variable `--czi-content-line-height` - // that its children may apply. - `--czi-content-line-height: ${cssLineSpacing}`; - } - - if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) { - style += `padding-top: ${paddingTop};`; - } - - if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) { - style += `padding-bottom: ${paddingBottom};`; - } - - if (indent) { - style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`; - } - - if (inset) { - style += `margin-left: ${inset}; margin-right: ${inset};`; - } - - style && (attrs.style = style); - - if (indent) { - attrs[ATTRIBUTE_INDENT] = String(indent); - } - - if (id) { - attrs.id = id; - } - - return ['p', attrs, 0]; -} - -export const toParagraphDOM = toDOM; -export const getParagraphNodeAttrs = getAttrs; - -export function convertMarginLeftToIndentValue(marginLeft: string): number { - const ptValue = convertToCSSPTValue(marginLeft); - return clamp( - MIN_INDENT_LEVEL, - Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), - MAX_INDENT_LEVEL - ); -} - -export default ParagraphNodeSpec; \ No newline at end of file diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts deleted file mode 100644 index 356f20ce6..000000000 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { undoInputRule } from "prosemirror-inputrules"; -import { Schema } from "prosemirror-model"; -import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; -import { splitListItem, wrapInList, } from "prosemirror-schema-list"; -import { EditorState, Transaction, TextSelection } from "prosemirror-state"; -import { SelectionManager } from "./SelectionManager"; -import { Docs } from "../documents/Documents"; -import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types"; -import { Doc } from "../../new_fields/Doc"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { Id } from "../../new_fields/FieldSymbols"; - -const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; - -export type KeyMap = { [key: string]: any }; - -export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { - let fontSize: number | undefined = undefined; - tx2.doc.descendants((node: any, offset: any, index: any) => { - if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { - const path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); - if (node.type === schema.nodes.ordered_list) depth++; - fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; - const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); - } - }); - return tx2; -}; -export default function buildKeymap>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { - const keys: { [key: string]: any } = {}; - - function bind(key: string, cmd: any) { - if (mapKeys) { - const mapped = mapKeys[key]; - if (mapped === false) return; - if (mapped) key = mapped; - } - keys[key] = cmd; - } - - bind("Mod-z", undo); - bind("Shift-Mod-z", redo); - bind("Backspace", undoInputRule); - - !mac && bind("Mod-y", redo); - - bind("Alt-ArrowUp", joinUp); - bind("Alt-ArrowDown", joinDown); - bind("Mod-BracketLeft", lift); - bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); - (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); - }); - - bind("Mod-b", toggleMark(schema.marks.strong)); - bind("Mod-B", toggleMark(schema.marks.strong)); - - bind("Mod-e", toggleMark(schema.marks.em)); - bind("Mod-E", toggleMark(schema.marks.em)); - - bind("Mod-u", toggleMark(schema.marks.underline)); - bind("Mod-U", toggleMark(schema.marks.underline)); - - bind("Mod-`", toggleMark(schema.marks.code)); - - bind("Ctrl-.", wrapInList(schema.nodes.bullet_list)); - - bind("Ctrl-n", wrapInList(schema.nodes.ordered_list)); - - bind("Ctrl->", wrapIn(schema.nodes.blockquote)); - - // bind("^", (state: EditorState, dispatch: (tx: Transaction) => void) => { - // let newNode = schema.nodes.footnote.create({}); - // if (dispatch && state.selection.from === state.selection.to) { - // let tr = state.tr; - // tr.replaceSelectionWith(newNode); // replace insertion with a footnote. - // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display - // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)))); - // return true; - // } - // return false; - // }); - - - const cmd = chainCommands(exitCode, (state, dispatch) => { - if (dispatch) { - dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView()); - return true; - } - return false; - }); - bind("Mod-Enter", cmd); - bind("Shift-Enter", cmd); - mac && bind("Ctrl-Enter", cmd); - - - bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph)); - - bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); - - for (let i = 1; i <= 6; i++) { - bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); - } - - const hr = schema.nodes.horizontal_rule; - bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => { - dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); - return true; - }); - - bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const ref = state.selection; - const range = ref.$from.blockRange(ref.$to); - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { // couldn't sink into an existing list, so wrap in a new one - const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); - if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - // when promoting to a list, assume list will format things so don't copy the stored marks. - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet promote fail"); - } - } - }); - - bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - - if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet demote fail"); - } - }); - bind("Ctrl-Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const layoutDoc = props.Document; - const originalDoc = layoutDoc.rootDocument || layoutDoc; - if (originalDoc instanceof Doc) { - const layoutKey = StrCast(originalDoc.layoutKey); - const newDoc = Docs.Create.TextDocument("", { - layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, - layoutKey, - _singleLine: BoolCast(originalDoc._singleLine), - x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) - }); - if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { - newDoc[layoutKey] = originalDoc[layoutKey]; - } - FormattedTextBox.SelectOnLoad = newDoc[Id]; - props.addDocument(newDoc); - } - }); - - const splitMetadata = (marks: any, tx: Transaction) => { - marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - return tx; - }; - const addTextOnRight = (force: boolean) => { - const layoutDoc = props.Document; - const originalDoc = layoutDoc.rootDocument || layoutDoc; - if (force || props.Document._singleLine) { - const layoutKey = StrCast(originalDoc.layoutKey); - const newDoc = Docs.Create.TextDocument("", { - layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, - layoutKey, - _singleLine: BoolCast(originalDoc._singleLine), - x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) - }); - if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { - newDoc[layoutKey] = originalDoc[layoutKey]; - } - FormattedTextBox.SelectOnLoad = newDoc[Id]; - props.addDocument(newDoc); - return true; - } - return false; - }; - bind("Alt-Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - return addTextOnRight(true); - }); - bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - if (addTextOnRight(false)) return true; - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!splitListItem(schema.nodes.list_item)(state, dispatch)) { - if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - splitMetadata(marks, tx3); - if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { - dispatch(tx3); - } - })) { - return false; - } - } - return true; - }); - bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - dispatch(splitMetadata(marks, state.tr)); - return false; - }); - bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { - return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); - }); - const path = (state.doc.resolve(state.selection.from - 1) as any).path; - const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; - const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; - if (anchor >= 0) { - const textsel = TextSelection.create(state.doc, anchor, range!.end); - const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; - let whitespace = text.length - 1; - for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } - if (text.endsWith(":")) { - dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). - addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); - } - } - return false; - }); - - - return keys; -} diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss deleted file mode 100644 index 43cc23ecd..000000000 --- a/src/client/util/RichTextMenu.scss +++ /dev/null @@ -1,121 +0,0 @@ -@import "../views/globalCssVariables"; - -.button-dropdown-wrapper { - position: relative; - - .dropdown-button { - width: 15px; - padding-left: 5px; - padding-right: 5px; - } - - .dropdown-button-combined { - width: 50px; - display: flex; - justify-content: space-between; - - svg:nth-child(2) { - margin-top: 2px; - } - } - - .dropdown { - position: absolute; - top: 35px; - left: 0; - background-color: #323232; - color: $light-color-secondary; - border: 1px solid #4d4d4d; - border-radius: 0 6px 6px 6px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - min-width: 150px; - padding: 5px; - font-size: 12px; - z-index: 10001; - - button { - background-color: #323232; - border: 1px solid black; - border-radius: 1px; - padding: 6px; - margin: 5px 0; - font-size: 10px; - - &:hover { - background-color: black; - } - - &:last-child { - margin-bottom: 0; - } - } - } - - input { - color: black; - } -} - -.link-menu { - .divider { - background-color: white; - height: 1px; - width: 100%; - } -} - -.color-preview-button { - .color-preview { - width: 100%; - height: 3px; - margin-top: 3px; - } -} - -.color-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - - button.color-button { - width: 20px; - height: 20px; - border-radius: 15px !important; - margin: 3px; - border: 2px solid transparent !important; - padding: 3px; - - &.active { - border: 2px solid white !important; - } - } -} - -select { - background-color: #323232; - color: white; - border: 1px solid black; - // border-top: none; - // border-bottom: none; - font-size: 12px; - height: 100%; - margin-right: 3px; - - &:focus, - &:hover { - background-color: black; - } - - &::-ms-expand { - color: white; - } -} - -.row-2 { - display: flex; - justify-content: space-between; - - >div { - display: flex; - } -} \ No newline at end of file diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx deleted file mode 100644 index 140635a1d..000000000 --- a/src/client/util/RichTextMenu.tsx +++ /dev/null @@ -1,875 +0,0 @@ -import React = require("react"); -import AntimodeMenu from "../views/AntimodeMenu"; -import { observable, action, } from "mobx"; -import { observer } from "mobx-react"; -import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; -import { schema } from "./schema_rts"; -import { EditorView } from "prosemirror-view"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import { FieldViewProps } from "../views/nodes/FieldView"; -import { Cast, StrCast } from "../../new_fields/Types"; -import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; -import { unimplementedFunction, Utils } from "../../Utils"; -import { wrapInList } from "prosemirror-schema-list"; -import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; -import "./RichTextMenu.scss"; -import { DocServer } from "../DocServer"; -import { Doc } from "../../new_fields/Doc"; -import { SelectionManager } from "./SelectionManager"; -import { LinkManager } from "./LinkManager"; -const { toggleMark, setBlockType } = require("prosemirror-commands"); - -library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); - -@observer -export default class RichTextMenu extends AntimodeMenu { - static Instance: RichTextMenu; - public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable - - private view?: EditorView; - public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - - public _brushMap: Map> = new Map(); - private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; - private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; - private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[]; - private fontColors: (string | undefined)[]; - private highlightColors: (string | undefined)[]; - - @observable private collapsed: boolean = false; - @observable private boldActive: boolean = false; - @observable private italicsActive: boolean = false; - @observable private underlineActive: boolean = false; - @observable private strikethroughActive: boolean = false; - @observable private subscriptActive: boolean = false; - @observable private superscriptActive: boolean = false; - - @observable private activeFontSize: string = ""; - @observable private activeFontFamily: string = ""; - @observable private activeListType: string = ""; - - @observable private brushIsEmpty: boolean = true; - @observable private brushMarks: Set = new Set(); - @observable private showBrushDropdown: boolean = false; - - @observable private activeFontColor: string = "black"; - @observable private showColorDropdown: boolean = false; - - @observable private activeHighlightColor: string = "transparent"; - @observable private showHighlightDropdown: boolean = false; - - @observable private currentLink: string | undefined = ""; - @observable private showLinkDropdown: boolean = false; - - constructor(props: Readonly<{}>) { - super(props); - RichTextMenu.Instance = this; - this._canFade = false; - - 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: "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 }, - { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize }, - { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, - { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option - ]; - - this.fontFamilyOptions = [ - { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } }, - { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } }, - { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } }, - { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } }, - { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } }, - { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } }, - { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } }, - { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, - // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true }, - ]; - - this.listTypeOptions = [ - { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, - { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, - { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, - { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, - ]; - - this.fontColors = [ - DarkPastelSchemaPalette.get("pink2"), - DarkPastelSchemaPalette.get("purple4"), - DarkPastelSchemaPalette.get("bluegreen1"), - DarkPastelSchemaPalette.get("yellow4"), - DarkPastelSchemaPalette.get("red2"), - DarkPastelSchemaPalette.get("bluegreen7"), - DarkPastelSchemaPalette.get("bluegreen5"), - DarkPastelSchemaPalette.get("orange1"), - "#757472", - "#000" - ]; - - this.highlightColors = [ - PastelSchemaPalette.get("pink2"), - PastelSchemaPalette.get("purple4"), - PastelSchemaPalette.get("bluegreen1"), - PastelSchemaPalette.get("yellow4"), - PastelSchemaPalette.get("red2"), - PastelSchemaPalette.get("bluegreen7"), - PastelSchemaPalette.get("bluegreen5"), - PastelSchemaPalette.get("orange1"), - "white", - "transparent" - ]; - } - - @action - changeView(view: EditorView) { - this.view = view; - } - - update(view: EditorView, lastState: EditorState | undefined) { - this.updateFromDash(view, lastState, this.editorProps); - } - - - public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { - if (this.view) { - const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). - addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - return this.view.state.selection.$from.nodeAfter?.text || ""; - } - return ""; - } - - @action - public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { - if (!view) { - console.log("no editor? why?"); - return; - } - this.view = view; - const state = view.state; - props && (this.editorProps = props); - - // Don't do anything if the document/selection didn't change - if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; - - // update active marks - const activeMarks = this.getActiveMarksOnSelection(); - this.setActiveMarkButtons(activeMarks); - - // update active font family and size - const active = this.getActiveFontStylesOnSelection(); - const activeFamilies = active && active.get("families"); - const activeSizes = active && active.get("sizes"); - - this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various"; - - // update link in current selection - const targetTitle = await this.getTextLinkTargetTitle(); - this.setCurrentLink(targetTitle); - } - - 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, (tx: any) => { - const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { - toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); - } else dispatch(tx); - }); - } - } - } - - // finds font sizes and families in selection - getActiveFontStylesOnSelection() { - if (!this.view) return; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const state = this.view.state; - const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - ref_node.marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); - }); - } - - const styles = new Map(); - styles.set("families", activeFamilies); - styles.set("sizes", activeSizes); - return styles; - } - - getMarksInSelection(state: EditorState) { - const found = new Set(); - const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); - return found; - } - - //finds all active marks on selection in given group - getActiveMarksOnSelection() { - if (!this.view) return; - - const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; - if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); - //current selection - const { empty, ranges, $to } = this.view.state.selection as TextSelection; - const state = this.view.state; - let activeMarks: MarkType[] = []; - if (!empty) { - activeMarks = markGroup.filter(mark => { - const has = false; - for (let i = 0; !has && i < ranges.length; i++) { - return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark); - } - return false; - }); - } - else { - const pos = this.view.state.selection.$from; - const ref_node: ProsNode | null = this.reference_node(pos); - if (ref_node !== null && ref_node !== this.view.state.doc) { - if (ref_node.isText) { - } - else { - return []; - } - activeMarks = markGroup.filter(mark_type => { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } - const mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); - }); - } - } - return activeMarks; - } - - destroy() { - this.fadeOut(true); - } - - @action - setActiveMarkButtons(activeMarks: MarkType[] | undefined) { - if (!activeMarks) return; - - this.boldActive = false; - this.italicsActive = false; - this.underlineActive = false; - this.strikethroughActive = false; - this.subscriptActive = false; - this.superscriptActive = false; - - activeMarks.forEach(mark => { - switch (mark.name) { - case "strong": this.boldActive = true; break; - case "em": this.italicsActive = true; break; - case "underline": this.underlineActive = true; break; - case "strikethrough": this.strikethroughActive = true; break; - case "subscript": this.subscriptActive = true; break; - case "superscript": this.superscriptActive = true; break; - } - }); - } - - createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) { - const self = this; - function onClick(e: React.PointerEvent) { - 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.setActiveMarkButtons(self.getActiveMarksOnSelection()); - } - - return ( - - ); - } - - 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; - function onChange(e: React.ChangeEvent) { - e.stopPropagation(); - e.preventDefault(); - options.forEach(({ label, mark, command }) => { - if (e.target.value === label) { - self.view && mark && command(mark, self.view); - } - }); - } - return ; - } - - 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; - function onChange(val: string) { - options.forEach(({ label, node, command }) => { - if (val === label) { - self.view && node && command(node); - } - }); - } - return ; - } - - changeFontSize = (mark: Mark, view: EditorView) => { - this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch); - } - - changeFontFamily = (mark: Mark, view: EditorView) => { - this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch); - } - - // TODO: remove doesn't work - //remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: NodeType | undefined) => { - if (!this.view) return; - - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(this.view.state, this.view.dispatch); - } else { - const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - this.view!.dispatch(tx2); - })) { - const tx2 = this.view.state.tr; - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - this.view.dispatch(tx3); - } - } - } - - insertSummarizer(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - const mark = state.schema.marks.summarize.create(); - const tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - const content = tr.selection.content(); - const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } - - @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } - - // todo: add brushes to brushMap to save with a style name - onBrushNameKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); - this._brushNameRef.current!.style.background = "lightGray"; - } - } - _brushNameRef = React.createRef(); - - createBrushButton() { - const self = this; - function onBrushClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.fillBrush(self.view.state, self.view.dispatch); - } - - let label = "Stored marks: "; - if (this.brushMarks && this.brushMarks.size > 0) { - this.brushMarks.forEach((mark: Mark) => { - const markType = mark.type; - label += markType.name; - label += ", "; - }); - label = label.substring(0, label.length - 2); - } else { - label = "No marks are currently stored"; - } - - const button = - ; - - const dropdownContent = -

-

{label}

- - -
; - - return ( - - ); - } - - @action - clearBrush() { - RichTextMenu.Instance.brushIsEmpty = true; - RichTextMenu.Instance.brushMarks = new Set(); - } - - @action - fillBrush(state: EditorState, dispatch: any) { - if (!this.view) return; - - if (this.brushIsEmpty) { - const selected_marks = this.getMarksInSelection(this.view.state); - if (selected_marks.size >= 0) { - this.brushMarks = selected_marks; - this.brushIsEmpty = !this.brushIsEmpty; - } - } - else { - const { from, to, $from } = this.view.state.selection; - if (!this.view.state.selection.empty && $from && $from.nodeAfter) { - if (this.brushMarks && to - from > 0) { - this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - this.setMark(mark, this.view!.state, this.view!.dispatch); - }); - } - } - else { - this.brushIsEmpty = !this.brushIsEmpty; - } - } - } - - @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } - @action setActiveColor(color: string) { this.activeFontColor = color; } - - createColorButton() { - const self = this; - function onColorClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); - } - function changeColor(e: React.PointerEvent, color: string) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.setActiveColor(color); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); - } - - const button = - ; - - const dropdownContent = -
-

Change font color:

-
- {this.fontColors.map(color => { - if (color) { - return this.activeFontColor === color ? - : - ; - } - })} -
-
; - - return ( - - ); - } - - 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; - } - this.setMark(colorMark, state, dispatch); - } - - @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; } - @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } - - createHighlighterButton() { - const self = this; - function onHighlightClick(e: React.PointerEvent) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); - } - function changeHighlight(e: React.PointerEvent, color: string) { - e.preventDefault(); - e.stopPropagation(); - self.view && self.view.focus(); - self.setActiveHighlight(color); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); - } - - const button = - ; - - const dropdownContent = -
-

Change highlight color:

-
- {this.highlightColors.map(color => { - if (color) { - return this.activeHighlightColor === color ? - : - ; - } - })} -
-
; - - return ( - - ); - } - - insertHighlight(color: String, state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); - } - - @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; } - @action setCurrentLink(link: string) { this.currentLink = link; } - - createLinkButton() { - const self = this; - - function onLinkChange(e: React.ChangeEvent) { - self.setCurrentLink(e.target.value); - } - - const link = this.currentLink ? this.currentLink : ""; - - const button = ; - - const dropdownContent = -
-

Linked to:

- - -
- -
; - - return ( - - ); - } - - async getTextLinkTargetTitle() { - if (!this.view) return; - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - const href = link.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - const linkDoc = await DocServer.GetRefField(linkclicked); - if (linkDoc instanceof Doc) { - const anchor1 = await Cast(linkDoc.anchor1, Doc); - const anchor2 = await Cast(linkDoc.anchor2, Doc); - const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) { - return StrCast(anchor2.title); - } - if (Doc.AreProtosEqual(currentDoc, anchor2)) { - return StrCast(anchor1.title); - } - } - } - } - } else { - return href; - } - } else { - return link.attrs.title; - } - } - } - - // TODO: should check for valid URL - makeLinkToURL = (target: String, lcoation: string) => { - if (!this.view) return; - - let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); - } - - deleteLink = () => { - if (!this.view) return; - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link); - const href = link!.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - DocServer.GetRefField(linkclicked).then(async linkDoc => { - if (linkDoc instanceof Doc) { - LinkManager.Instance.deleteLink(linkDoc); - this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link)); - } - }); - } - } else { - if (node) { - 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)); - } - } - } - } - - linkExtend($start: ResolvedPos, href: string) { - const mark = this.view!.state.schema.marks.link; - - let startIndex = $start.index(); - let endIndex = $start.indexAfter(); - - while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--; - while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++; - - let startPos = $start.start(); - let endPos = startPos; - for (let i = 0; i < endIndex; i++) { - const size = $start.parent.child(i).nodeSize; - if (i < startIndex) startPos += size; - endPos += size; - } - return { from: startPos, to: endPos }; - } - - reference_node(pos: ResolvedPos): ProsNode | null { - if (!this.view) return null; - - let ref_node: ProsNode = this.view.state.doc; - if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { - ref_node = pos.nodeBefore; - } - else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { - ref_node = pos.nodeAfter; - } - else if (pos.pos > 0) { - let skip = false; - for (let i: number = pos.pos - 1; i > 0; i--) { - this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { - if (node.isLeaf && !skip) { - ref_node = node; - skip = true; - } - - }); - } - } - if (!ref_node.isLeaf && ref_node.childCount > 0) { - ref_node = ref_node.child(0); - } - return ref_node; - } - - @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } - @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } - - @action - toggleMenuPin = (e: React.MouseEvent) => { - this.Pinned = !this.Pinned; - if (!this.Pinned) { - this.fadeOut(true); - } - } - - @action - protected toggleCollapse = (e: React.MouseEvent) => { - this.collapsed = !this.collapsed; - setTimeout(() => { - const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width); - RichTextMenu.Instance.jumpTo(x, this._top); - }, 0); - } - - render() { - - 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)), - this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), - this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), - this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), - this.createColorButton(), - this.createHighlighterButton(), - this.createLinkButton(), - this.createBrushButton(), - this.createButton("indent", "Summarize", undefined, this.insertSummarizer), - ]}
; - - 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()} -
-
; - - return ( -
- {this.getElementWithRows([row1, row2], 2, false)} -
- ); - } -} - -interface ButtonDropdownProps { - view?: EditorView; - button: JSX.Element; - dropdownContent: JSX.Element; - openDropdownOnButton?: boolean; -} - -@observer -class ButtonDropdown extends React.Component { - - @observable private showDropdown: boolean = false; - private ref: HTMLDivElement | null = null; - - componentDidMount() { - document.addEventListener("pointerdown", this.onBlur); - } - - componentWillUnmount() { - document.removeEventListener("pointerdown", this.onBlur); - } - - @action - setShowDropdown(show: boolean) { - this.showDropdown = show; - } - @action - toggleDropdown() { - this.showDropdown = !this.showDropdown; - } - - onDropdownClick = (e: React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - this.props.view && this.props.view.focus(); - this.toggleDropdown(); - } - - onBlur = (e: PointerEvent) => { - setTimeout(() => { - if (this.ref !== null && !this.ref.contains(e.target as Node)) { - this.setShowDropdown(false); - } - }, 0); - } - - render() { - return ( -
this.ref = node}> - {this.props.openDropdownOnButton ? - : - <> - {this.props.button} - - } - - {this.showDropdown ? this.props.dropdownContent : (null)} -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 63a3815ea..7ce6bbf85 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -7,9 +7,9 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { returnFalse, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocUtils } from "../documents/Documents"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; -import RichTextMenu from "./RichTextMenu"; +import RichTextMenu from "../views/nodes/formattedText/RichTextMenu"; import { schema } from "./schema_rts"; export class RichTextRules { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx deleted file mode 100644 index 3e45d5de5..000000000 --- a/src/client/util/RichTextSchema.tsx +++ /dev/null @@ -1,718 +0,0 @@ -import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { baseKeymap, toggleMark } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { keymap } from "prosemirror-keymap"; -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; -import { StepMap } from "prosemirror-transform"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc"; -import { Id } from "../../new_fields/FieldSymbols"; -import { List } from "../../new_fields/List"; -import { ObjectField } from "../../new_fields/ObjectField"; -import { listSpec } from "../../new_fields/Schema"; -import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; -import { ComputedField } from "../../new_fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionView"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { DocumentManager } from "./DocumentManager"; -import { Transform } from "./Transform"; -import React = require("react"); - -import { schema } from "./schema_rts"; - -export class OrderedListView { - update(node: any) { - return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update - } -} - -export class ImageResizeView { - _handle: HTMLElement; - _img: HTMLElement; - _outer: HTMLElement; - constructor(node: any, view: any, getPos: any, addDocTab: any) { - //moved - this._handle = document.createElement("span"); - this._img = document.createElement("img"); - this._outer = document.createElement("span"); - this._outer.style.position = "relative"; - this._outer.style.width = node.attrs.width; - this._outer.style.height = node.attrs.height; - this._outer.style.display = "inline-block"; - this._outer.style.overflow = "hidden"; - (this._outer.style as any).float = node.attrs.float; - //moved - this._img.setAttribute("src", node.attrs.src); - this._img.style.width = "100%"; - this._handle.style.position = "absolute"; - this._handle.style.width = "20px"; - this._handle.style.height = "20px"; - this._handle.style.backgroundColor = "blue"; - this._handle.style.borderRadius = "15px"; - this._handle.style.display = "none"; - this._handle.style.bottom = "-10px"; - this._handle.style.right = "-10px"; - const self = this; - //moved - this._img.onclick = function (e: any) { - e.stopPropagation(); - e.preventDefault(); - if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { - view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); - } - }; - //moved - this._img.onpointerdown = function (e: any) { - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); - DocServer.GetRefField(node.attrs.docid).then(async linkDoc => - (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, - document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false)); - } - }; - //moved - this._handle.onpointerdown = function (e: any) { - e.preventDefault(); - e.stopPropagation(); - const wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); - const hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); - const startX = e.pageX; - const startWidth = parseFloat(node.attrs.width); - const onpointermove = (e: any) => { - const currentX = e.pageX; - const diffInPx = currentX - startX; - self._outer.style.width = `${startWidth + diffInPx}`; - self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`; - }; - - const onpointerup = () => { - document.removeEventListener("pointermove", onpointermove); - document.removeEventListener("pointerup", onpointerup); - const pos = view.state.selection.from; - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); - view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos)))); - }; - - document.addEventListener("pointermove", onpointermove); - document.addEventListener("pointerup", onpointerup); - }; - //Moved - this._outer.appendChild(this._img); - this._outer.appendChild(this._handle); - (this as any).dom = this._outer; - } - - selectNode() { - this._img.classList.add("ProseMirror-selectednode"); - - this._handle.style.display = ""; - } - - deselectNode() { - this._img.classList.remove("ProseMirror-selectednode"); - - this._handle.style.display = "none"; - } -} - -export class DashDocCommentView { - _collapsed: HTMLElement; - _view: any; - constructor(node: any, view: any, getPos: any) { - //moved - this._collapsed = document.createElement("span"); - this._collapsed.className = "formattedTextBox-inlineComment"; - this._collapsed.id = "DashDocCommentView-" + node.attrs.docid; - this._view = view; - //moved - const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor - for (let i = getPos() + 1; i < view.state.doc.content.size; i++) { - const m = view.state.doc.nodeAt(i); - if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; - } - } - 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); - return undefined; - }; - //moved - this._collapsed.onpointerdown = (e: any) => { - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerup = (e: any) => { - const target = targetNode(); - if (target) { - 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) { } - }, 0); - } - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerenter = (e: any) => { - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); - e.preventDefault(); - e.stopPropagation(); - }; - //moved - this._collapsed.onpointerleave = (e: any) => { - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); - e.preventDefault(); - e.stopPropagation(); - }; - - (this as any).dom = this._collapsed; - } - //moved - selectNode() { } -} - -export class DashDocView { - _dashSpan: HTMLDivElement; - _outer: HTMLElement; - _dashDoc: Doc | undefined; - _reactionDisposer: IReactionDisposer | undefined; - _renderDisposer: IReactionDisposer | undefined; - _textBox: FormattedTextBox; - - getDocTransform = () => { - const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); - return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); - } - contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; - - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._textBox = tbox; - this._dashSpan = document.createElement("div"); - this._outer = document.createElement("span"); - this._outer.style.position = "relative"; - this._outer.style.textIndent = "0"; - this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - this._outer.style.width = node.attrs.width; - this._outer.style.height = node.attrs.height; - this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; - // this._outer.style.overflow = "hidden"; // bcz: not sure if this is needed. if it's used, then the doc doesn't highlight when you hover over a docComment - (this._outer.style as any).float = node.attrs.float; - - this._dashSpan.style.width = node.attrs.width; - this._dashSpan.style.height = node.attrs.height; - this._dashSpan.style.position = "absolute"; - this._dashSpan.style.display = "inline-block"; - this._dashSpan.onpointerleave = () => { - const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = ""; - } - }; - this._dashSpan.onpointerenter = () => { - const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); - if (ele) { - (ele as HTMLDivElement).style.backgroundColor = "orange"; - } - }; - const removeDoc = () => { - const pos = getPos(); - const ns = new NodeSelection(view.state.doc.resolve(pos)); - view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); - return true; - }; - const alias = node.attrs.alias; - - const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; - DocServer.GetRefField(docid + alias).then(async dashDoc => { - if (!(dashDoc instanceof Doc)) { - alias && DocServer.GetRefField(docid).then(async dashDocBase => { - if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); - aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); - self.doRender(aliasedDoc, removeDoc, node, view, getPos); - } - }); - } else { - self.doRender(dashDoc, removeDoc, node, view, getPos); - } - }); - const self = this; - this._dashSpan.onkeydown = function (e: any) { - e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { - e.preventDefault(); - } - }; - this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; - this._dashSpan.onwheel = function (e: any) { e.preventDefault(); }; - this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._outer.appendChild(this._dashSpan); - (this as any).dom = this._outer; - } - - doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { - this._dashDoc = dashDoc; - const self = this; - const dashLayoutDoc = Doc.Layout(dashDoc); - const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); - - if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); - else { - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { - this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; - this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; - this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); - }, { fireImmediately: true }); - const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { - ReactDOM.unmountComponentAtNode(this._dashSpan); - - ReactDOM.render(, this._dashSpan); - if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); - } catch (e) { - console.log(e); - } - } - }; - this._renderDisposer?.(); - this._renderDisposer = reaction(() => { - // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { - // finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here? - // } - const layoutKey = StrCast(finalLayout.layoutKey); - const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; - if (finalLayout !== dashDoc && finalKey) { - const finalLayoutField = finalLayout[finalKey]; - if (finalLayoutField instanceof ObjectField) { - finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); - } - } - return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; - }, - (res) => doReactRender(res.finalLayout, res.resolvedDataDoc), - { fireImmediately: true }); - } - } - destroy() { - ReactDOM.unmountComponentAtNode(this._dashSpan); - this._reactionDisposer?.(); - } -} - -export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value - _labelSpan: HTMLSpanElement; // field label - _fieldSpan: HTMLSpanElement; // field value - _fieldCheck: HTMLInputElement; - _enumerables: HTMLDivElement; // field value - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - @observable _dashDoc: Doc | undefined; - _fieldKey: string; - _options: Doc[] = []; - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldKey = node.attrs.fieldKey; - this._textBoxDoc = tbox.props.Document; - this._fieldWrapper = document.createElement("p"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - - const self = this; - - this._enumerables = document.createElement("div"); - this._enumerables.style.width = "10px"; - this._enumerables.style.height = "10px"; - this._enumerables.style.position = "relative"; - this._enumerables.style.display = "none"; - - //Moved - this._enumerables.onpointerdown = async (e) => { - e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); - }; - //Moved - const updateText = (forceMatch: boolean) => { - self._enumerables.style.display = "none"; - const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; - - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(self._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; - Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (self._fieldSpan.innerText.startsWith(":=")) { - self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); - } else if (self._fieldSpan.innerText.startsWith("=:=")) { - Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); - } else { - self._dashDoc![self._fieldKey] = newText; - } - }); - }; - - //Moved - this._fieldCheck = document.createElement("input"); - this._fieldCheck.id = Utils.GenerateGuid(); - this._fieldCheck.type = "checkbox"; - this._fieldCheck.style.position = "relative"; - this._fieldCheck.style.display = "none"; - this._fieldCheck.style.minWidth = "12px"; - this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; - this._fieldCheck.onchange = function (e: any) { - self._dashDoc![self._fieldKey] = e.target.checked; - }; - - this._fieldSpan = document.createElement("span"); - this._fieldSpan.id = Utils.GenerateGuid(); - this._fieldSpan.contentEditable = "true"; - this._fieldSpan.style.position = "relative"; - this._fieldSpan.style.display = "none"; - this._fieldSpan.style.minWidth = "12px"; - this._fieldSpan.style.fontSize = "large"; - this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; - this._fieldSpan.onblur = function (e: any) { updateText(false); }; - - // MOVED - const setDashDoc = (doc: Doc) => { - self._dashDoc = doc; - if (self._options?.length && !self._dashDoc[self._fieldKey]) { - self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); - } - this._labelSpan.innerHTML = `${self._fieldKey}: `; - const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); - this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none"; - }; - - //Moved - this._fieldSpan.onkeydown = function (e: any) { - e.stopPropagation(); - if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(self._fieldSpan); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); - } - if (e.key === "Enter") { - e.preventDefault(); - e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - updateText(true); - } - }; - - this._labelSpan = document.createElement("span"); - this._labelSpan.style.position = "relative"; - this._labelSpan.style.fontSize = "small"; - this._labelSpan.title = "click to see related tags"; - this._labelSpan.style.fontSize = "x-small"; - this._labelSpan.onpointerdown = function (e: any) { - e.stopPropagation(); - let container = tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } - if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; - let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); - if (!list) { - alias.schemaColumns = list = new List(); - } - list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = self._fieldKey; - tbox.props.addDocTab(alias, "onRight"); - } - }; - this._labelSpan.innerHTML = `${self._fieldKey}: `; - //MOVED - if (node.attrs.docid) { - DocServer.GetRefField(node.attrs.docid). - then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); - } else { - setDashDoc(tbox.props.DataDoc || tbox.dataDoc); - } - - //Moved - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes - const dashVal = this._dashDoc?.[self._fieldKey]; - return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; - }, fval => { - const boolVal = Cast(fval, "boolean", null); - if (boolVal === true || boolVal === false) { - this._fieldCheck.checked = boolVal; - } else { - this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; - } - this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none"; - }, { fireImmediately: true }); - - //MOVED IN ORDER - this._fieldWrapper.appendChild(this._labelSpan); - this._fieldWrapper.appendChild(this._fieldCheck); - this._fieldWrapper.appendChild(this._fieldSpan); - this._fieldWrapper.appendChild(this._enumerables); - (this as any).dom = this._fieldWrapper; - //updateText(false); - } - //MOVED - destroy() { - this._reactionDisposer?.(); - } - //moved - selectNode() { } -} - -export class FootnoteView { - innerView: any; - outerView: any; - node: any; - dom: any; - getPos: any; - - constructor(node: any, view: any, getPos: any) { - // We'll need these later - this.node = node; - this.outerView = view; - this.getPos = getPos; - - // The node's representation in the editor (empty, for now) - this.dom = document.createElement("footnote"); - this.dom.addEventListener("pointerup", this.toggle, true); - // These are used when the footnote is selected - this.innerView = null; - } - selectNode() { - const attrs = { ...this.node.attrs }; - attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode"); - if (!this.innerView) this.open(); - } - - deselectNode() { - const attrs = { ...this.node.attrs }; - attrs.visibility = false; - this.dom.classList.remove("ProseMirror-selectednode"); - if (this.innerView) this.close(); - } - open() { - // Append a tooltip to the outer node - const tooltip = this.dom.appendChild(document.createElement("div")); - tooltip.className = "footnote-tooltip"; - // And put a sub-ProseMirror into that - this.innerView = new EditorView(tooltip, { - // You can use any node as an editor document - state: EditorState.create({ - doc: this.node, - plugins: [keymap(baseKeymap), - keymap({ - "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), - "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), - "Mod-b": toggleMark(schema.marks.strong) - }), - // new Plugin({ - // view(newView) { - // // TODO -- make this work with RichTextMenu - // // return FormattedTextBox.getToolTip(newView); - // } - // }) - ], - - }), - // This is the magic part - dispatchTransaction: this.dispatchInner.bind(this), - handleDOMEvents: { - pointerdown: ((view: any, e: PointerEvent) => { - // Kludge to prevent issues due to the fact that the whole - // footnote is node-selected (and thus DOM-selected) when - // the parent editor is focused. - e.stopPropagation(); - document.addEventListener("pointerup", this.ignore, true); - if (this.outerView.hasFocus()) this.innerView.focus(); - }) as any - } - - }); - setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0); - } - - ignore = (e: PointerEvent) => { - e.stopPropagation(); - document.removeEventListener("pointerup", this.ignore, true); - } - - toggle = () => { - if (this.innerView) this.close(); - else { - this.open(); - } - } - close() { - this.innerView && this.innerView.destroy(); - this.innerView = null; - this.dom.textContent = ""; - } - - dispatchInner(tr: any) { - const { state, transactions } = this.innerView.state.applyTransaction(tr); - this.innerView.updateState(state); - - if (!tr.getMeta("fromOutside")) { - const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); - for (const transaction of transactions) { - const steps = transaction.steps; - for (const step of steps) { - outerTr.step(step.map(offsetMap)); - } - } - if (outerTr.docChanged) this.outerView.dispatch(outerTr); - } - } - update(node: any) { - if (!node.sameMarkup(this.node)) return false; - this.node = node; - if (this.innerView) { - const state = this.innerView.state; - const start = node.content.findDiffStart(state.doc.content); - if (start !== null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); - const overlap = start - Math.min(endA, endB); - if (overlap > 0) { endA += overlap; endB += overlap; } - this.innerView.dispatch( - state.tr - .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)); - } - } - return true; - } - - destroy() { - if (this.innerView) this.close(); - } - - stopEvent(event: any) { - return this.innerView && this.innerView.dom.contains(event.target); - } - - ignoreMutation() { return true; } -} - -export class SummaryView { - _collapsed: HTMLElement; - _view: any; - constructor(node: any, view: any, getPos: any) { - this._collapsed = document.createElement("span"); - this._collapsed.className = this.className(node.attrs.visibility); - this._view = view; - const js = node.toJSON; - node.toJSON = function () { - return js.apply(this, arguments); - }; - - this._collapsed.onpointerdown = (e: any) => { - const visible = !node.attrs.visibility; - const attrs = { ...node.attrs, visibility: visible }; - let textSelection = TextSelection.create(view.state.doc, getPos() + 1); - if (!visible) { // update summarized text and save in attrs - textSelection = this.updateSummarizedText(getPos() + 1); - attrs.text = textSelection.content(); - attrs.textslice = attrs.text.toJSON(); - } - view.dispatch(view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it - setNodeMarkup(getPos(), undefined, attrs)); // update the attrs - e.preventDefault(); - e.stopPropagation(); - this._collapsed.className = this.className(visible); - }; - (this as any).dom = this._collapsed; - } - selectNode() { } - - deselectNode() { } - - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - - updateSummarizedText(start?: any) { - const mtype = this._view.state.schema.marks.summarize; - const mtypeInc = this._view.state.schema.marks.summarizeInclusive; - let endPos = start; - - const visited = new Set(); - for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) { - 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 === mtype || m.type === mtypeInc)) { - visited.add(node); - endPos = i + node.nodeSize - 1; - } - else skip = true; - } - }); - } - return TextSelection.create(this._view.state.doc, start, endPos); - } -} \ No newline at end of file diff --git a/src/client/util/SummaryView.tsx b/src/client/util/SummaryView.tsx deleted file mode 100644 index 89908d8ee..000000000 --- a/src/client/util/SummaryView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { TextSelection } from "prosemirror-state"; -import { Fragment, Node, Slice } from "prosemirror-model"; - -import React = require("react"); - -interface ISummaryView { - node: any; - view: any; - getPos: any; - self: any; -} -export class SummaryView extends React.Component { - - onPointerDown = (e: any) => { - const visible = !this.props.node.attrs.visibility; - const attrs = { ...this.props.node.attrs, visibility: visible }; - let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1); - if (!visible) { // update summarized text and save in attrs - textSelection = this.updateSummarizedText(this.props.getPos() + 1); - attrs.text = textSelection.content(); - attrs.textslice = attrs.text.toJSON(); - } - this.props.view.dispatch(this.props.view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it - setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs - e.preventDefault(); - e.stopPropagation(); - const _collapsed = document.getElementById('collapse') as HTMLElement; - _collapsed.className = this.className(visible); - } - - updateSummarizedText(start?: any) { - const mtype = this.props.view.state.schema.marks.summarize; - const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive; - let endPos = start; - - const visited = new Set(); - for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) { - let skip = false; - this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { - if (this.props.node.isLeaf && !visited.has(node) && !skip) { - if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { - visited.add(node); - endPos = i + this.props.node.nodeSize - 1; - } - else skip = true; - } - }); - } - return TextSelection.create(this.props.view.state.doc, start, endPos); - } - - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - - selectNode() { } - - deselectNode() { } - - render() { - const _view = this.props.node.view; - const js = this.props.node.toJSon; - - this.props.node.toJSON = function () { - return js.apply(this, arguments); - }; - - const spanCollapsedClassName = this.className(this.props.node.attrs.visibility); - - return ( - - - - ); - - } -} \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss deleted file mode 100644 index e2149e9c1..000000000 --- a/src/client/util/TooltipTextMenu.scss +++ /dev/null @@ -1,372 +0,0 @@ -@import "../views/globalCssVariables"; -.ProseMirror-menu-dropdown-wrap { - display: inline-block; - position: relative; -} - -.ProseMirror-menu-dropdown { - vertical-align: 1px; - cursor: pointer; - position: relative; - padding: 0 15px 0 4px; - background: white; - border-radius: 2px; - text-align: left; - font-size: 12px; - white-space: nowrap; - margin-right: 4px; - - &:after { - content: ""; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 2px); - } -} - -.ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; -} - -.ProseMirror-menu-dropdown-menu, -.ProseMirror-menu-submenu { - font-size: 12px; - background: white; - border: 1px solid rgb(223, 223, 223); - min-width: 40px; - z-index: 50000; - position: absolute; - box-sizing: content-box; - - .ProseMirror-menu-dropdown-item { - cursor: pointer; - z-index: 100000; - text-align: left; - padding: 3px; - - &:hover { - background-color: $light-color-secondary; - } - } -} - - -.ProseMirror-menu-submenu-label:after { - content: ""; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 4px); -} - - .ProseMirror-icon { - display: inline-block; - // line-height: .8; - // vertical-align: -2px; /* Compensate for padding */ - // padding: 2px 8px; - cursor: pointer; - - &.ProseMirror-menu-disabled { - cursor: default; - } - - svg { - fill:white; - height: 1em; - } - - span { - vertical-align: text-top; - } - } - -.wrapper { - position: absolute; - pointer-events: all; - display: flex; - align-items: center; - transform: translateY(-85px); - - height: 35px; - background: #323232; - border-radius: 6px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - -} - -.tooltipMenu, .basic-tools { - z-index: 20000; - pointer-events: all; - padding: 3px; - padding-bottom: 5px; - display: flex; - align-items: center; - - .ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; - } - - .ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; - } -} - -.menuicon { - width: 25px; - height: 25px; - cursor: pointer; - text-align: center; - line-height: 25px; - margin: 0 2px; - border-radius: 3px; - - &:hover { - background-color: black; - - #link-drag { - background-color: black; - } - } - - &> * { - margin-top: 50%; - margin-left: 50%; - transform: translate(-50%, -50%); - } - - svg { - fill: white; - width: 18px; - height: 18px; - } -} - -.menuicon-active { - width: 25px; - height: 25px; - cursor: pointer; - text-align: center; - line-height: 25px; - margin: 0 2px; - border-radius: 3px; - - &:hover { - background-color: black; - } - - &> * { - margin-top: 50%; - margin-left: 50%; - transform: translate(-50%, -50%); - } - - svg { - fill: greenyellow; - width: 18px; - height: 18px; - } -} - -#colorPicker { - position: relative; - - svg { - width: 18px; - height: 18px; - // margin-top: 11px; - } - - .buttonColor { - position: absolute; - top: 24px; - left: 1px; - width: 24px; - height: 4px; - margin-top: 0; - } -} - -#link-drag { - background-color: #323232; -} - -.underline svg { - margin-top: 13px; -} - - .font-size-indicator { - font-size: 12px; - padding-right: 0px; - } - .summarize{ - color: white; - height: 20px; - text-align: center; - } - - -.brush{ - display: inline-block; - width: 1em; - height: 1em; - stroke-width: 0; - stroke: currentColor; - fill: currentColor; - margin-right: 15px; -} - -.brush-active{ - display: inline-block; - width: 1em; - height: 1em; - stroke-width: 3; - fill: greenyellow; - margin-right: 15px; -} - -.dragger-wrapper { - color: #eee; - height: 22px; - padding: 0 5px; - box-sizing: content-box; - cursor: grab; - - .dragger { - width: 18px; - height: 100%; - display: flex; - justify-content: space-evenly; - } - - .dragger-line { - width: 2px; - height: 100%; - background-color: black; - } -} - -.button-dropdown-wrapper { - display: flex; - align-content: center; - - &:hover { - background-color: black; - } -} - -.buttonSettings-dropdown { - - &.ProseMirror-menu-dropdown { - width: 10px; - height: 25px; - margin: 0; - padding: 0 2px; - background-color: #323232; - text-align: center; - - &:after { - border-top: 4px solid white; - right: 2px; - } - - &:hover { - background-color: black; - } - } - - &.ProseMirror-menu-dropdown-menu { - min-width: 150px; - left: -27px; - top: 31px; - background-color: #323232; - border: 1px solid #4d4d4d; - color: $light-color-secondary; - // border: none; - // border: 1px solid $light-color-secondary; - border-radius: 0 6px 6px 6px; - padding: 3px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - - .ProseMirror-menu-dropdown-item{ - cursor: default; - - &:last-child { - border-bottom: none; - } - - &:hover { - background-color: #323232; - } - - .button-setting, .button-setting-disabled { - padding: 2px; - border-radius: 2px; - } - - .button-setting:hover { - cursor: pointer; - background-color: black; - } - - .separated-button { - border-top: 1px solid $light-color-secondary; - padding-top: 6px; - } - - input { - color: black; - border: none; - border-radius: 1px; - padding: 3px; - } - - button { - padding: 6px; - background-color: #323232; - border: 1px solid black; - border-radius: 1px; - - &:hover { - background-color: black; - } - } - } - - - } -} - -.colorPicker-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - margin-top: 3px; - margin-left: -3px; - width: calc(100% + 6px); -} - -button.colorPicker { - width: 20px; - height: 20px; - border-radius: 15px !important; - margin: 3px; - border: none !important; - - &.active { - border: 2px solid white !important; - } -} diff --git a/src/client/util/marks_rts.ts b/src/client/util/marks_rts.ts deleted file mode 100644 index 75d7109e4..000000000 --- a/src/client/util/marks_rts.ts +++ /dev/null @@ -1,296 +0,0 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { Doc } from "../../new_fields/Doc"; - - -const emDOM: DOMOutputSpecArray = ["em", 0]; -const strongDOM: DOMOutputSpecArray = ["strong", 0]; -const codeDOM: DOMOutputSpecArray = ["code", 0]; - -// :: Object [Specs](#model.MarkSpec) for the marks in the schema. -export const marks: { [index: string]: MarkSpec } = { - // :: MarkSpec A link. Has `href` and `title` attributes. `title` - // defaults to the empty string. Rendered and parsed as an `` - // element. - link: { - attrs: { - href: {}, - targetId: { default: "" }, - linkId: { default: "" }, - showPreview: { default: true }, - location: { default: null }, - title: { default: null }, - docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text - }, - inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") }; - } - }], - toDOM(node: any) { - return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0]; - } - }, - - - // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. - pFontColor: { - attrs: { - color: { default: "#000" } - }, - inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { color: dom.getAttribute("color") }; - } - }], - toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; - } - }, - - marker: { - attrs: { - highlight: { default: "transparent" } - }, - inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { highlight: dom.getAttribute("backgroundColor") }; - } - }], - toDOM(node: any) { - return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; - } - }, - - // :: MarkSpec An emphasis mark. Rendered as an `` element. - // Has parse rules that also match `` and `font-style: italic`. - em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], - toDOM() { return emDOM; } - }, - - // :: MarkSpec A strong mark. Rendered as ``, parse rules - // also match `` and `font-weight: bold`. - strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM; } - }, - - strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } - ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] - }, - - subscript: { - excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] - }, - - superscript: { - excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] - }, - - mbulletType: { - attrs: { - bulletType: { default: "decimal" } - }, - toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` - }]; - } - }, - - metadata: { - toDOM() { - return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; - } - }, - metadataKey: { - toDOM() { - return ['span', { style: 'font-style:italic; ' }]; - } - }, - metadataVal: { - toDOM() { - return ['span']; - } - }, - - summarizeInclusive: { - parseDOM: [ - { - tag: "span", - 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: solid") !== -1) { - return null; - } - } - return false; - } - }, - ], - inclusive: true, - toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } - }, - - 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)' - }]; - } - }, - - underline: { - parseDOM: [ - { - tag: "span", - getAttrs: (p: any) => { - if (typeof (p) !== "string") { - const style = getComputedStyle(p); - if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { - return null; - } - } - return false; - } - } - // { style: "text-decoration=underline" } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline;text-decoration-style:line' - }] - }, - - search_highlight: { - attrs: { - selected: { default: false } - }, - parseDOM: [{ style: 'background: yellow' }], - toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.selected ? "orange" : "yellow"}` - }]; - } - }, - - // the id of the user who entered the text - user_mark: { - attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - }, - group: "inline", - toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - const min = Math.round(node.attrs.modified / 12); - const hr = Math.round(min / 60); - const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; - } - }, - // the id of the user who entered the text - user_tag: { - attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - tag: { default: "" } - }, - group: "inline", - inclusive: false, - toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; - } - }, - - - // :: MarkSpec Code font mark. Represented as a `` element. - code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM; } - }, - - /* FONTS */ - pFontFamily: { - attrs: { - family: { default: "Crimson Text" }, - }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - const cstyle = getComputedStyle(dom); - if (cstyle.font) { - if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; - if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; - if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; - if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; - if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; - if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; - } - } - }], - toDOM: (node) => ['span', { - style: `font-family: "${node.attrs.family}";` - }] - }, - - /** FONT SIZES */ - pFontSize: { - attrs: { - fontSize: { default: 10 } - }, - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: (node) => ['span', { - style: `font-size: ${node.attrs.fontSize}px;` - }] - }, -}; diff --git a/src/client/util/nodes_rts.ts b/src/client/util/nodes_rts.ts deleted file mode 100644 index e7bcf444a..000000000 --- a/src/client/util/nodes_rts.ts +++ /dev/null @@ -1,264 +0,0 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import ParagraphNodeSpec from "./ParagraphNodeSpec"; - -const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; - -// :: Object -// [Specs](#model.NodeSpec) for the nodes defined in this schema. -export const nodes: { [index: string]: NodeSpec } = { - // :: NodeSpec The top level document node. - doc: { - content: "block+" - }, - - footnote: { - group: "inline", - content: "inline*", - inline: true, - attrs: { - visibility: { default: false } - }, - // This makes the view treat the node as a leaf, even though it - // technically has content - atom: true, - toDOM: () => ["footnote", 0], - parseDOM: [{ tag: "footnote" }] - }, - - paragraph: ParagraphNodeSpec, - - // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. - blockquote: { - content: "block+", - group: "block", - defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM; } - }, - - // :: NodeSpec A horizontal rule (`
`). - horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM; } - }, - - // :: NodeSpec A heading textblock, with a `level` attribute that - // should hold the number 1 to 6. Parsed and serialized as `

` to - // `

` elements. - heading: { - attrs: { level: { default: 1 } }, - content: "inline*", - group: "block", - defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], - toDOM(node: any) { return ["h" + node.attrs.level, 0]; } - }, - - // :: NodeSpec A code listing. Disallows marks or non-text inline - // nodes by default. Represented as a `
` element with a
-    // `` element inside of it.
-    code_block: {
-        content: "text*",
-        marks: "",
-        group: "block",
-        code: true,
-        defining: true,
-        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-        toDOM() { return preDOM; }
-    },
-
-    // :: NodeSpec The text node.
-    text: {
-        group: "inline"
-    },
-
-    dashComment: {
-        attrs: {
-            docid: { default: "" },
-        },
-        inline: true,
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }, "←"];
-        },
-    },
-
-    summary: {
-        inline: true,
-        attrs: {
-            visibility: { default: false },
-            text: { default: undefined },
-            textslice: { default: undefined },
-        },
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }];
-        },
-    },
-
-    // :: NodeSpec An inline image (``) node. Supports `src`,
-    // `alt`, and `href` attributes. The latter two default to the empty
-    // string.
-    image: {
-        inline: true,
-        attrs: {
-            src: {},
-            agnostic: { default: null },
-            width: { default: 100 },
-            alt: { default: null },
-            title: { default: null },
-            float: { default: "left" },
-            location: { default: "onRight" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "img[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        // TODO if we don't define toDom, dragging the image crashes. Why?
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashDoc: {
-        inline: true,
-        attrs: {
-            width: { default: 200 },
-            height: { default: 100 },
-            title: { default: null },
-            float: { default: "right" },
-            location: { default: "onRight" },
-            hidden: { default: false },
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            alias: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashField: {
-        inline: true,
-        attrs: {
-            fieldKey: { default: "" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    video: {
-        inline: true,
-        attrs: {
-            src: {},
-            width: { default: "100px" },
-            alt: { default: null },
-            title: { default: null }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "video[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["video", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    // :: NodeSpec A hard line break, represented in the DOM as `
`. - hard_break: { - inline: true, - group: "inline", - selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM; } - }, - - ordered_list: { - ...orderedList, - content: 'list_item+', - group: 'block', - attrs: { - bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, - setFontSize: { default: undefined }, - setFontFamily: { default: "inherit" }, - setFontColor: { default: "inherit" }, - inheritedFontSize: { default: undefined }, - visibility: { default: true }, - indent: { default: undefined } - }, - toDOM(node: Node) { - if (node.attrs.mapStyle === "bullet") return ['ul', 0]; - 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; - 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;` }]; - } - }, - - bullet_list: { - ...bulletList, - content: 'list_item+', - group: 'block', - // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], - toDOM(node: Node) { - return ['ul', 0]; - } - }, - - list_item: { - attrs: { - bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, - visibility: { default: true } - }, - ...listItem, - content: 'paragraph block*', - toDOM(node: any) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; - //return ["li", { class: `${map}` }, 0]; - } - }, -}; \ No newline at end of file diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js deleted file mode 100644 index 269423482..000000000 --- a/src/client/util/prosemirrorPatches.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -var prosemirrorInputRules = require('prosemirror-inputrules'); -var prosemirrorTransform = require('prosemirror-transform'); -var prosemirrorModel = require('prosemirror-model'); - -exports.liftListItem = liftListItem; -exports.sinkListItem = sinkListItem; -exports.wrappingInputRule = wrappingInputRule; -// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool -// Create a command to lift the list item around the selection up into -// a wrapping list. -function liftListItem(itemType) { - return function (tx, dispatch) { - var ref = tx.selection; - var $from = ref.$from; - var $to = ref.$to; - var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); - if (!range) { return false } - if (!dispatch) { return true } - if ($from.node(range.depth - 1).type == itemType) // Inside a parent list - { return liftToOuterList(tx, dispatch, itemType, range) } - else // Outer list node - { return liftOutOfList(tx, dispatch, range) } - } -} - -function liftToOuterList(tr, dispatch, itemType, range) { - var end = range.end, endOfList = range.$to.end(range.depth); - if (end < endOfList) { - // There are siblings after the lifted items, which must become - // children of the last item - tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList, - new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true)); - range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth); - } - dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView()); - return true -} - -function liftOutOfList(tr, dispatch, range) { - var list = range.parent; - // Merge the list items into a single big item - for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) { - pos -= list.child(i).nodeSize; - tr.delete(pos - 1, pos + 1); - } - var $start = tr.doc.resolve(range.start), item = $start.nodeAfter; - var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount; - var parent = $start.node(-1), indexBefore = $start.index(-1); - if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, - item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false } - var start = $start.pos, end = start + item.nodeSize; - // Strip off the surrounding list. At the sides where we're not at - // the end of the list, the existing list is closed. At sides where - // this is the end, it is overwritten to its end. - tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, - new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))) - .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))), - atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1)); - dispatch(tr.scrollIntoView()); - return true -} - -// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool -// Create a command to sink the list item around the selection down -// into an inner list. -function sinkListItem(itemType) { - return function (state, dispatch) { - var ref = state.selection; - var $from = ref.$from; - var $to = ref.$to; - var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); - if (!range) { return false } - var startIndex = range.startIndex; - if (startIndex == 0) { return false } - var parent = range.parent, nodeBefore = parent.child(startIndex - 1); - if (nodeBefore.type != itemType) { return false; } - - if (dispatch) { - var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type; - var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null); - let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))), - nestedBefore ? 3 : 1, 0); - var before = range.start, after = range.end; - dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, - before, after, slice, 1, true)) - .scrollIntoView()); - } - return true - } -} - -function findWrappingOutside(range, type) { - var parent = range.parent; - var startIndex = range.startIndex; - var endIndex = range.endIndex; - var around = parent.contentMatchAt(startIndex).findWrapping(type); - if (!around) { return null } - var outer = around.length ? around[0] : type; - return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null -} - -function findWrappingInside(range, type) { - var parent = range.parent; - var startIndex = range.startIndex; - var endIndex = range.endIndex; - var inner = parent.child(startIndex); - var inside = type.contentMatch.findWrapping(inner.type); - if (!inside) { return null } - var lastType = inside.length ? inside[inside.length - 1] : type; - var innerMatch = lastType.contentMatch; - for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } - if (!innerMatch || !innerMatch.validEnd) { return null } - return inside -} -function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { - if (innerRange === void 0) innerRange = range; - let withAttrs = (type) => ({ type: type, attrs: null }); - var around = findWrappingOutside(range, nodeType); - var inner = around && findWrappingInside(innerRange, nodeType); - if (!inner) { return null } - return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) -} -function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { - return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { - var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; - var tr = state.tr.delete(start, end); - var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); - if (!wrapping) { return null } - tr.wrap(range, wrapping); - var before = tr.doc.resolve(start - 1).nodeBefore; - if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && - (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } - return tr - }) -} \ No newline at end of file diff --git a/src/client/util/schema_rts.ts b/src/client/util/schema_rts.ts deleted file mode 100644 index 83561073c..000000000 --- a/src/client/util/schema_rts.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Schema, Slice } from "prosemirror-model"; - -import { nodes } from "./nodes_rts"; -import { marks } from "./marks_rts"; - - -// :: Schema -// This schema rougly corresponds to the document schema used by -// [CommonMark](http://commonmark.org/), minus the list elements, -// which are defined in the [`prosemirror-schema-list`](#schema-list) -// module. -// -// To reuse elements from this schema, extend or read from its -// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). - -export const schema = new Schema({ nodes, marks }); - -const fromJson = schema.nodeFromJSON; - -schema.nodeFromJSON = (json: any) => { - const node = fromJson(json); - if (json.type === schema.nodes.summary.name) { - node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); - } - return node; -}; \ No newline at end of file diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c02f79187..3624cdb6d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -15,7 +15,7 @@ import './collections/ParentDocumentSelector.scss'; import './DocumentButtonBar.scss'; import { LinkMenu } from "./linking/LinkMenu"; import { DocumentView } from './nodes/DocumentView'; -import { GoogleRef } from "./nodes/FormattedTextBox"; +import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 172c1864a..70ea955e1 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -8,7 +8,7 @@ import { Scripting } from "../util/Scripting"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import GestureOverlay from "./GestureOverlay"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; +import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; export class InkingControl { @observable static Instance: InkingControl; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8fb67c435..20238985d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -19,7 +19,7 @@ import { DocServer } from '../DocServer'; import { Docs, DocumentOptions } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; -import RichTextMenu from '../util/RichTextMenu'; +import RichTextMenu from './nodes/formattedText/RichTextMenu'; import { Scripting } from '../util/Scripting'; import SettingsManager from '../util/SettingsManager'; import SharingManager from '../util/SharingManager'; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f4250e96d..eda8e5684 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -11,7 +11,7 @@ import "./CollectionCarouselView.scss"; import { CollectionSubView } from './CollectionSubView'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { Doc } from '../../../new_fields/Doc'; -import { FormattedTextBox } from '../nodes/FormattedTextBox'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { ContextMenu } from '../ContextMenu'; import { ObjectField } from '../../../new_fields/ObjectField'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 49abc6ee6..fb7535d9f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -15,7 +15,7 @@ import { DragManager, dropActionType } from "../../util/DragManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox"; +import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; import { CollectionView } from "./CollectionView"; import React = require("react"); import { basename } from 'path'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 28b461313..92b27a0c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingControl } from "../../InkingControl"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; import { CollectionDockingView } from "../CollectionDockingView"; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index cd8166309..2d3bb6f3c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,7 +19,7 @@ import React = require("react"); import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { RichTextField } from "../../../../new_fields/RichTextField"; import { CollectionView } from "../CollectionView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { ScriptField } from "../../../../new_fields/ScriptField"; interface MarqueeViewProps { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index cd78ac7b3..4d20d3e2c 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -19,7 +19,7 @@ import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; import { FieldView, FieldViewProps } from "./FieldView"; -import { FormattedTextBox } from "./FormattedTextBox"; +import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss deleted file mode 100644 index 3bedb7127..000000000 --- a/src/client/views/nodes/FormattedTextBox.scss +++ /dev/null @@ -1,265 +0,0 @@ -@import "../globalCssVariables"; - -.ProseMirror { - width: 100%; - height: 100%; - min-height: 100%; -} - -.ProseMirror:focus { - outline: none !important; -} - -.formattedTextBox-cont { - touch-action: none; - cursor: text; - background: inherit; - padding: 0; - border-width: 0px; - border-radius: inherit; - border-color: $intermediate-color; - box-sizing: border-box; - background-color: inherit; - border-style: solid; - overflow-y: auto; - overflow-x: hidden; - color: initial; - max-height: 100%; - display: flex; - flex-direction: row; - transition: opacity 1s; - - .formattedTextBox-dictation { - height: 12px; - width: 10px; - top: 0px; - left: 0px; - position: absolute; - } -} -.formattedTextBox-outer { - position: relative; - overflow: auto; - display: inline-block; - width: 100%; - height: 100%; -} - -.formattedTextBox-sidebar-handle { - position: absolute; - top: calc(50% - 17.5px); - width: 10px; - height: 35px; - background: lightgray; - border-radius: 20px; - cursor:grabbing; -} - -.formattedTextBox-cont>.formattedTextBox-sidebar-handle { - right: 0; - left: unset; -} - -.formattedTextBox-sidebar, -.formattedTextBox-sidebar-inking { - border-left: dashed 1px black; - height: 100%; - display: inline-block; - position: absolute; - right: 0; - - .collectionfreeformview-container { - position: relative; - } - - >.formattedTextBox-sidebar-handle { - right: unset; - left: -5; - } -} - -.formattedTextBox-sidebar-inking { - pointer-events: all; -} - -.formattedTextBox-inner-rounded { - height: 70%; - width: 85%; - position: absolute; - overflow: auto; - top: 15%; - left: 10%; -} - -.formattedTextBox-inner-rounded, -.formattedTextBox-inner { - height: 100%; - white-space: pre-wrap; -} - -// .menuicon { -// display: inline-block; -// border-right: 1px solid rgba(0, 0, 0, 0.2); -// color: #888; -// line-height: 1; -// padding: 0 7px; -// margin: 1px; -// cursor: pointer; -// text-align: center; -// min-width: 1.4em; -// } - -.strong, -.heading { - font-weight: bold; -} - -.em { - font-style: italic; -} - -.userMarkOpen { - background: rgba(255, 255, 0, 0.267); - display: inline; -} - -.userMark { - background: rgba(255, 255, 0, 0.267); - font-size: 2px; - display: inline-grid; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 10px; - min-height: 10px; - text-align: center; - align-content: center; -} - -footnote { - display: inline-block; - position: relative; - cursor: pointer; - - div { - padding: 0 !important; - } -} - -footnote::after { - content: counter(prosemirror-footnote); - vertical-align: super; - font-size: 75%; - counter-increment: prosemirror-footnote; -} - -.ProseMirror { - counter-reset: prosemirror-footnote; -} - -.footnote-tooltip { - cursor: auto; - font-size: 75%; - position: absolute; - left: -30px; - top: calc(100% + 10px); - background: silver; - padding: 3px; - border-radius: 2px; - max-width: 100px; - min-width: 50px; - width: max-content; -} - -.prosemirror-attribution { - font-size: 8px; -} - -.footnote-tooltip::before { - border: 5px solid silver; - border-top-width: 0px; - border-left-color: transparent; - border-right-color: transparent; - position: absolute; - top: -5px; - left: 27px; - content: " "; - height: 0; - width: 0; -} - - -.formattedTextBox-inlineComment { - position: relative; - width: 40px; - height: 20px; - &::before { - content: "→"; - } - &:hover { - background: orange; - } -} - -.formattedTextBox-summarizer { - opacity: 0.5; - position: relative; - width: 40px; - height: 20px; - &::after { - content: "←"; - } -} - -.formattedTextBox-summarizer-collapsed { - opacity: 0.5; - position: relative; - width: 40px; - height: 20px; - &::after { - content: "..."; - } -} - -.ProseMirror { - touch-action: none; - span { - font-family: inherit; - } - - ol, ul { - counter-reset: deci1 0 multi1 0; - padding-left: 1em; - font-family: inherit; - } - ol { - margin-left: 1em; - font-family: inherit; - } - - .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } - .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} - .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} - .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } - - .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } - .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} - .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} - - .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } - .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } - .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } - .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } - .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - - .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } - .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } - .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } - .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } -} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx deleted file mode 100644 index e65453aa0..000000000 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ /dev/null @@ -1,1306 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { isEqual } from "lodash"; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { baseKeymap } from "prosemirror-commands"; -import { history } from "prosemirror-history"; -import { inputRules } from 'prosemirror-inputrules'; -import { keymap } from "prosemirror-keymap"; -import { Fragment, Mark, Node, Slice } from "prosemirror-model"; -import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; -import { ReplaceStep } from 'prosemirror-transform'; -import { EditorView } from "prosemirror-view"; -import { DateField } from '../../../new_fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; -import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; -import { InkTool } from '../../../new_fields/InkField'; -import { PrefetchProxy } from '../../../new_fields/Proxy'; -import { RichTextField } from "../../../new_fields/RichTextField"; -import { RichTextUtils } from '../../../new_fields/RichTextUtils'; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { TraceMobx } from '../../../new_fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils'; -import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; -import { DocServer } from "../../DocServer"; -import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { DictationManager } from '../../util/DictationManager'; -import { DragManager } from "../../util/DragManager"; -import { makeTemplate } from '../../util/DropConverter'; -import buildKeymap from "../../util/ProsemirrorExampleTransfer"; -import RichTextMenu from '../../util/RichTextMenu'; -import { RichTextRules } from "../../util/RichTextRules"; -// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "../../util/RichTextSchema"; -// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "../../util/RichTextSchema"; -import { OrderedListView } from "../../util/RichTextSchema"; -import { ImageResizeView } from "../../util/ImageResizeView"; - -import { DashDocCommentView } from "../../util/DashDocCommentView"; -import { DashFieldView } from "../../util/DashFieldView"; -import { FootnoteView } from "../../util/FootnoteView"; -import { SummaryView } from "../../util/SummaryView"; -import { DashDocView } from "../../util/DashDocView"; - -import { schema } from "../../util/schema_rts"; -import { SelectionManager } from "../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; -import { DocumentButtonBar } from '../DocumentButtonBar'; -import { InkingControl } from "../InkingControl"; -import { AudioBox } from './AudioBox'; -import { FieldView, FieldViewProps } from "./FieldView"; -import "./FormattedTextBox.scss"; -import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; -import React = require("react"); - -library.add(faEdit); -library.add(faSmile, faTextHeight, faUpload); - -export interface FormattedTextBoxProps { - hideOnLeave?: boolean; - makeLink?: () => Opt; - xMargin?: number; - yMargin?: number; -} - -const richTextSchema = createSchema({ - documentText: "string" -}); - -export const GoogleRef = "googleDocId"; - -type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; -const RichTextDocument = makeInterface(richTextSchema, documentSchema); - -type PullHandler = (exportState: Opt, dataDoc: Doc) => void; - -@observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } - public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); - public static Instance: FormattedTextBox; - public ProseRef?: HTMLDivElement; - private _ref: React.RefObject = React.createRef(); - private _scrollRef: React.RefObject = React.createRef(); - private _editorView: Opt; - private _applyingChange: boolean = false; - private _searchIndex = 0; - private _sidebarMovement = 0; - private _lastX = 0; - private _lastY = 0; - private _undoTyping?: UndoManager.Batch; - private _disposers: { [name: string]: IReactionDisposer } = {}; - private dropDisposer?: DragManager.DragDropDisposer; - - @computed get _recording() { return this.dataDoc.audioState === "recording"; } - set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } - - @observable private _entered = false; - - public static FocusedBox: FormattedTextBox | undefined; - public static SelectOnLoad = ""; - public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { - return html.indexOf("data-pm-slice") !== -1; - } - public static GetHref(html: string): string { - const parser = new DOMParser(); - const parsedHtml = parser.parseFromString(html, 'text/html'); - if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && - (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { - return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; - } - return ""; - } - public static GetDocFromUrl(url: string) { - if (url.startsWith(document.location.origin)) { - const split = new URL(url).pathname.split("doc/"); - const docid = split[split.length - 1]; - return docid; - } - return ""; - } - - @undoBatch - public setFontColor(color: string) { - const view = this._editorView!; - if (view.state.selection.from === view.state.selection.to) return false; - if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.layoutDoc.color = color; - } - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); - view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); - return true; - } - - constructor(props: any) { - super(props); - FormattedTextBox.Instance = this; - this.updateHighlights(); - } - - public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - - linkOnDeselect: Map = new Map(); - - doLinkOnDeselect() { - Array.from(this.linkOnDeselect.entries()).map(entry => { - const key = entry[0]; - const value = entry[1]; - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); - DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); - if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); - }); - }); - }); - this.linkOnDeselect.clear(); - } - - dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); - let text = range ? tx.doc.textBetween(range.start, range.end) : ""; - let textEndSelection = tx.selection.to; - for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } - text = text.substr(0, textEndSelection - range!.start); - text = text.split(" ")[text.split(" ").length - 1]; - const split = text.split("::"); - if (split.length > 1 && split[1]) { - const key = split[0]; - const value = split[split.length - 1]; - this.linkOnDeselect.set(key, value); - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value }); - const mval = this._editorView.state.schema.marks.metadataVal.create(); - const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); - tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); - this.dataDoc[key] = value; - } - } - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); - (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); - - const tsel = this._editorView.state.selection.$from; - tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); - if (!this._applyingChange) { - this._applyingChange = true; - this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited - } else { // if we've deleted all the text in a note driven by a template, then restore the template data - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data))); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have - } - this._applyingChange = false; - } - this.updateTitle(); - this.tryUpdateHeight(); - } - } - - updateTitle = () => { - if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { - const str = this._editorView.state.doc.textContent; - const titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } - } - - // needs a better API for taking in a set of words with target documents instead of just one target - public hyperlinkTerms = (terms: string[], target: Doc) => { - if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - const tr = this._editorView.state.tr; - const flattened: TextSelection[] = []; - res.map(r => r.map(h => flattened.push(h))); - const lastSel = Math.min(flattened.length - 1, this._searchIndex); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!; - const link = this._editorView.state.schema.marks.link.create({ - href: Utils.prepend("/doc/" + alink[Id]), - title: "a link", location: location, linkId: alink[Id], targetId: target[Id] - }); - this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); - } - } - public highlightSearchTerms = (terms: string[]) => { - if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - let tr = this._editorView.state.tr; - const flattened: TextSelection[] = []; - res.map(r => r.map(h => flattened.push(h))); - const lastSel = Math.min(flattened.length - 1, this._searchIndex); - flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); - } - } - - public unhighlightSearchTerms = () => { - if (this._editorView && (this._editorView as any).docView) { - const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - const end = this._editorView.state.doc.nodeSize - 2; - this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); - } - } - adoptAnnotation = (start: number, end: number, mark: Mark) => { - const view = this._editorView!; - const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); - view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); - } - protected createDropTarget = (ele: HTMLDivElement) => { - this.ProseRef = ele; - this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); - } - - @undoBatch - @action - drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0]; - // replace text contents whend dragging with Alt - if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); - e.stopPropagation(); - } - // embed document when dragging with a userDropAction or an embedDoc flag set - } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { - const target = de.complete.docDragData.droppedDocuments[0]; - // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - // if (link) { - target._fitToBox = true; - const node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - const view = this._editorView!; - view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - // } - } // otherwise, fall through to outer collection to handle drop - } else if (de.complete.linkDragData) { - de.complete.linkDragData.linkDropCallback = this.linkDrop; - } - } - linkDrop = (data: DragManager.LinkDragData) => { - const linkDoc = data.linkDocument!; - const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; - const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); - } - - getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { - let offset = 0; - - if (context === node) return { from: offset, to: offset + node.nodeSize }; - - if (node.isBlock) { - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < (context.content as any).content.length; i++) { - const result = this.getNodeEndpoints((context.content as any).content[i], node); - if (result) { - return { - from: result.from + offset + (context.type.name === "doc" ? 0 : 1), - to: result.to + offset + (context.type.name === "doc" ? 0 : 1) - }; - } - offset += (context.content as any).content[i].nodeSize; - } - return null; - } else { - return null; - } - } - - - //Recursively finds matches within a given node - findInNode(pm: EditorView, node: Node, find: string) { - let ret: TextSelection[] = []; - - if (node.isTextblock) { - let index = 0, foundAt; - const ep = this.getNodeEndpoints(pm.state.doc, node); - while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { - const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); - ret.push(sel); - index = index + foundAt + find.length; - } - } else { - node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); - } - return ret; - } - static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; - - updateHighlights = () => { - clearStyleSheetRules(FormattedTextBox._userStyleSheet); - if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); - } - if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); - } - if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); - } - if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); - } - if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); - } - if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); - } - if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - const min = Math.round(Date.now() / 1000 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); - setTimeout(() => this.updateHighlights()); - } - if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - const hr = Math.round(Date.now() / 1000 / 60 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); - } - } - - sidebarDown = (e: React.PointerEvent) => { - this._lastX = e.clientX; - this._lastY = e.clientY; - this._sidebarMovement = 0; - document.addEventListener("pointermove", this.sidebarMove); - document.addEventListener("pointerup", this.sidebarUp); - e.stopPropagation(); - e.preventDefault(); // prevents text from being selected during drag - } - sidebarMove = (e: PointerEvent) => { - const bounds = this.CurrentDiv.getBoundingClientRect(); - this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)); - this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; - } - sidebarUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.sidebarMove); - document.removeEventListener("pointerup", this.sidebarUp); - } - - toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); - - public static get DefaultLayout(): Doc | string | undefined { - return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); - } - specificContextMenu = (e: React.MouseEvent): void => { - const cm = ContextMenu.Instance; - - const funcs: ContextMenuProps[] = []; - this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); - funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - !this.props.Document.rootDocument && funcs.push({ - description: "Make Template", event: () => { - this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); - }, icon: "eye" - }); - funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); - - const highlighting: ContextMenuProps[] = []; - ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => - highlighting.push({ - description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { - e.stopPropagation(); - if (FormattedTextBox._highlights.indexOf(option) === -1) { - FormattedTextBox._highlights.push(option); - } else { - FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); - } - this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); - funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); - - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - - const change = cm.findByDescription("Change Perspective..."); - const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : []; - - const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); - DocListCast(noteTypesDoc?.data).forEach(note => { - changeItems.push({ - description: StrCast(note.title), event: undoBatch(() => { - Doc.setNativeView(this.props.Document); - Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); - }), icon: "eye" - }); - }); - changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" }); - !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); - - const open = cm.findByDescription("Add a Perspective..."); - const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; - - openItems.push({ - description: "FreeForm", event: undoBatch(() => { - const alias = Doc.MakeAlias(this.rootDoc); - Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform"); - this.props.addDocTab(alias, "onRight"); - }), icon: "eye" - }); - !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" }); - - } - - recordDictation = () => { - DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - }).then(results => { - if (results && [DictationManager.Controls.Infringed].includes(results)) { - DictationManager.Controls.stop(); - } - //this._editorView!.focus(); - }); - } - stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; - - @action - toggleMenubar = () => { - this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; - } - - recordBullet = async () => { - const completedCue = "end session"; - const results = await DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - terminators: [completedCue, "bullet", "next"] - }); - if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { - DictationManager.Controls.stop(); - return; - } - this.nextBullet(this._editorView!.state.selection.to); - setTimeout(this.recordBullet, 2000); - } - - setCurrentBulletContent = (value: string) => { - if (this._editorView) { - const state = this._editorView.state; - const now = Date.now(); - let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); - if (!this._break && state.selection.to !== state.selection.from) { - for (let i = state.selection.from; i <= state.selection.to; i++) { - const pos = state.doc.resolve(i); - const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); - if (um) { - mark = um; - break; - } - } - } - const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); - this._break = false; - value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; - const from = state.selection.from; - const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); - this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); - } - } - - nextBullet = (pos: number) => { - if (this._editorView) { - const frag = Fragment.fromArray(this.newListItems(2)); - if (this._editorView.state.doc.resolve(pos).depth >= 2) { - const slice = new Slice(frag, 2, 2); - let state = this._editorView.state; - this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); - pos += 4; - state = this._editorView.state; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); - } - } - } - - private newListItems = (count: number) => { - return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); - } - - _keymap: any = undefined; - _rules: RichTextRules | undefined; - @computed get config() { - this._keymap = buildKeymap(schema, this.props); - this._rules = new RichTextRules(this.props.Document, this); - return { - schema, - plugins: [ - inputRules(this._rules.inpRules), - this.richTextMenuPlugin(), - history(), - keymap(this._keymap), - keymap(baseKeymap), - new Plugin({ - props: { - attributes: { class: "ProseMirror-example-setup-style" } - } - }), - formattedTextBoxCommentPlugin - ] - }; - } - - makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) { - if (this._editorView) { - const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link). - addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link)); - } - } - componentDidMount() { - this._disposers.buttonBar = reaction( - () => DocumentButtonBar.Instance, - instance => { - if (instance) { - this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); - } - } - ); - this._disposers.linkMaker = reaction( - () => this.props.makeLink?.(), - (linkDoc: Opt) => { - if (linkDoc) { - const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; - const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); - } - }, - { fireImmediately: true } - ); - this._disposers.editorState = reaction( - () => { - if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { - return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; - } - return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; - }, - incomingValue => { - if (incomingValue !== undefined && this._editorView && !this._applyingChange) { - const updatedState = JSON.parse(incomingValue); - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); - } - } - ); - this._disposers.pullDoc = reaction( - () => this.props.Document[Pulls], - () => { - if (!DocumentButtonBar.hasPulledHack) { - DocumentButtonBar.hasPulledHack = true; - const unchanged = this.dataDoc.unchanged; - this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); - } - } - ); - this._disposers.pushDoc = reaction( - () => this.props.Document[Pushes], - () => { - if (!DocumentButtonBar.hasPushedHack) { - DocumentButtonBar.hasPushedHack = true; - this.pushToGoogleDoc(); - } - } - ); - this._disposers.height = reaction( - () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], - () => this.tryUpdateHeight() - ); - - this.setupEditor(this.config, this.props.fieldKey); - - this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), - { fireImmediately: true }); - - this._disposers.record = reaction(() => this._recording, - () => { - if (this._recording) { - setTimeout(action(() => { - this.stopDictation(true); - setTimeout(() => this.recordDictation(), 500); - }), 500); - } else setTimeout(() => this.stopDictation(true), 0); - } - ); - this._disposers.scrollToRegion = reaction( - () => StrCast(this.layoutDoc.scrollToLinkID), - async (scrollToLinkID) => { - const findLinkFrag = (frag: Fragment, editor: EditorView) => { - const nodes: Node[] = []; - frag.forEach((node, index) => { - const examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { - nodes.push(examinedNode); - start += index; - } - }); - return { frag: Fragment.fromArray(nodes), start: start }; - }; - const findLinkNode = (node: Node, editor: EditorView) => { - if (!node.isText) { - const content = findLinkFrag(node.content, editor); - return node.copy(content.frag); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); - return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; - }; - - let start = -1; - if (this._editorView && scrollToLinkID) { - const editor = this._editorView; - const ret = findLinkFrag(editor.state.doc.content, editor); - - if (ret.frag.size > 2 && ret.start >= 0) { - let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start - if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected - } - editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); - } - Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); - } - - }, - { fireImmediately: true } - ); - this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), - pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } - ); - - setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0))); - } - - pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { - const modes = GoogleApiClientUtils.Docs.WriteMode; - let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); - if (!reference) { - mode = modes.Insert; - reference = { title: StrCast(this.dataDoc.title) }; - } - const redo = async () => { - if (this._editorView && reference) { - const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); - const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); - response && (this.dataDoc[GoogleRef] = response.documentId); - const pushSuccess = response !== undefined && !("errors" in response); - dataDoc.unchanged = pushSuccess; - DocumentButtonBar.Instance.startPushOutcome(pushSuccess); - } - }; - const undo = () => { - if (!exportState) { - return; - } - const content: GoogleApiClientUtils.Docs.Content = { - text: exportState.text, - requests: [] - }; - if (reference && content) { - GoogleApiClientUtils.Docs.write({ reference, content, mode }); - } - }; - UndoManager.AddEvent({ undo, redo }); - redo(); - }); - } - - pullFromGoogleDoc = async (handler: PullHandler) => { - const dataDoc = this.dataDoc; - const documentId = StrCast(dataDoc[GoogleRef]); - let exportState: Opt; - if (documentId) { - exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); - } - UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); - } - - updateState = (exportState: Opt, dataDoc: Doc) => { - let pullSuccess = false; - if (exportState !== undefined) { - pullSuccess = true; - dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); - setTimeout(() => { - if (this._editorView) { - const state = this._editorView.state; - const end = state.doc.content.size - 1; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); - } - }, 0); - dataDoc.title = exportState.title; - this.rootDoc.customTitle = true; - dataDoc.unchanged = true; - } else { - delete dataDoc[GoogleRef]; - } - DocumentButtonBar.Instance.startPullOutcome(pullSuccess); - } - - checkState = (exportState: Opt, dataDoc: Doc) => { - if (exportState && this._editorView) { - const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); - const equalTitles = dataDoc.title === exportState.title; - const unchanged = equalContent && equalTitles; - dataDoc.unchanged = unchanged; - DocumentButtonBar.Instance.setPullState(unchanged); - } - } - - clipboardTextSerializer = (slice: Slice): string => { - let text = "", separated = true; - const from = 0, to = slice.content.size; - slice.content.nodesBetween(from, to, (node, pos) => { - if (node.isText) { - text += node.text!.slice(Math.max(from, pos) - pos, to - pos); - separated = false; - } else if (!separated && node.isBlock) { - text += "\n"; - separated = true; - } else if (node.type.name === "hard_break") { - text += "\n"; - } - }, 0); - return text; - } - - sliceSingleNode(slice: Slice) { - return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; - } - - handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { - const cbe = event as ClipboardEvent; - const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); - const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); - if (pdfDocId && pdfRegionId) { - DocServer.GetRefField(pdfDocId).then(pdfDoc => { - DocServer.GetRefField(pdfRegionId).then(pdfRegion => { - if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { - setTimeout(async () => { - const targetField = Doc.LayoutFieldKey(pdfDoc); - const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations - targetAnnotations?.push(pdfRegion); - }); - - const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); - if (link) { - cbe.clipboardData!.setData("dash/linkDoc", link[Id]); - const linkId = link[Id]; - const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); - slice = new Slice(frag, slice.openStart, slice.openEnd); - const tr = view.state.tr.replaceSelection(slice); - view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); - } - } - }); - }); - return true; - } - return false; - - - function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { - const nodes: Node[] = []; - frag.forEach(node => nodes.push(marker(node))); - return Fragment.fromArray(nodes); - } - function addLinkMark(node: Node, title: string, linkId: string) { - if (!node.isText) { - const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); - return node.copy(content); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); - marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); - return node.mark(marks); - } - } - - private setupEditor(config: any, fieldKey: string) { - const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); - const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"]; - const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); - if (this.ProseRef) { - const self = this; - this._editorView?.destroy(); - this._editorView = new EditorView(this.ProseRef, { - state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), - handleScrollToSelection: (editorView) => { - const docPos = editorView.coordsAtPos(editorView.state.selection.from); - const viewRect = self._ref.current!.getBoundingClientRect(); - if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) { - docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale); - } - return true; - }, - dispatchTransaction: this.dispatchTransaction, - nodeViews: { - //dashComment(node, view, getPos) { return new DashDocCommentView({ node, view, getPos }); }, - dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, - //dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, - // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); }, - - // image(node, view, getPos) { - // //const addDocTab = this.props.addDocTab; - // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab }); - // }, - - - // // WAS : - // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); }, - - // 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, outerView, getPos }); } - }, - clipboardTextSerializer: this.clipboardTextSerializer, - handlePaste: this.handlePaste, - }); - const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); - if (startupText) { - const { state: { tr }, dispatch } = this._editorView; - dispatch(tr.insertText(startupText)); - } - } - - const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; - if (selectOnLoad && !this.props.dontRegisterView) { - FormattedTextBox.SelectOnLoad = ""; - this.props.select(false); - FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar)); - FormattedTextBox.SelectOnLoadChar = ""; - - } - (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); - // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; - } - getFont(font: string) { - switch (font) { - case "Arial": return schema.marks.arial.create(); - case "Times New Roman": return schema.marks.timesNewRoman.create(); - case "Georgia": return schema.marks.georgia.create(); - case "Comic Sans MS": return schema.marks.comicSans.create(); - case "Tahoma": return schema.marks.tahoma.create(); - case "Impact": return schema.marks.impact.create(); - case "ACrimson Textrial": return schema.marks.crimson.create(); - } - return schema.marks.arial.create(); - } - - componentWillUnmount() { - Object.values(this._disposers).forEach(disposer => disposer?.()); - this._editorView?.destroy(); - } - - static _downEvent: any; - _downX = 0; - _downY = 0; - _break = false; - onPointerDown = (e: React.PointerEvent): void => { - if (this._recording && !e.ctrlKey && e.button === 0) { - this.stopDictation(true); - this._break = true; - const state = this._editorView!.state; - const to = state.selection.to; - const updated = TextSelection.create(state.doc, to, to); - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to)); - e.preventDefault(); - e.stopPropagation(); - if (this._recording) setTimeout(() => this.recordDictation(), 500); - } - this._downX = e.clientX; - this._downY = e.clientY; - this.doLinkOnDeselect(); - FormattedTextBox._downEvent = true; - FormattedTextBoxComment.textBox = this; - if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { - e.preventDefault(); - } - if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar - e.stopPropagation(); - } - } - if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - e.preventDefault(); - } - } - - onPointerUp = (e: React.PointerEvent): void => { - if (!FormattedTextBox._downEvent) return; - FormattedTextBox._downEvent = false; - if (!(e.nativeEvent as any).formattedHandled) { - FormattedTextBoxComment.textBox = this; - FormattedTextBoxComment.update(this._editorView!); - } - (e.nativeEvent as any).formattedHandled = true; - - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { - e.stopPropagation(); - } - this._downX = this._downY = Number.NaN; - } - - @action - onFocused = (e: React.FocusEvent): void => { - FormattedTextBox.FocusedBox = this; - this.tryUpdateHeight(); - - // see if we need to preserve the insertion point - 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 coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos); - - // jump rich text menu to this textbox - const bounds = this._ref.current?.getBoundingClientRect(); - if (bounds && this.props.Document._chromeStatus !== "disabled") { - const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); - let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); - if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { - y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); - } - RichTextMenu.Instance.jumpTo(x, y); - } - } - 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 - if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } - - static _bulletStyleSheet: any = addStyleSheet(); - static _userStyleSheet: any = addStyleSheet(); - - 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. - 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) { - 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))); - } - } - } - if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } - (e.nativeEvent as any).formattedHandled = true; - // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { - // let href = (e.target as any).href; - // let location: string; - // if ((e.target as any).attributes.location) { - // location = (e.target as any).attributes.location.value; - // } - // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); - // if (node) { - // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). - // href = link && link.attrs.href; - // location = link && link.attrs.location; - // } - // } - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkClicked) { - // DocServer.GetRefField(linkClicked).then(async linkDoc => { - // (linkDoc instanceof Doc) && - // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false); - // }); - // } - // } else { - // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); - // this.props.addDocument && this.props.addDocument(webDoc); - // } - // e.stopPropagation(); - // e.preventDefault(); - // } - // } - - if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { - this.props.select(e.ctrlKey); - this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); - } - } - - // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. - hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) { - clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); - const pos = this._editorView!.posAtCoords({ left: x, top: y }); - 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); - 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++) { - 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) { - list_node = node; - break; - } - } - } - if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { - if (select) { - 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))); - } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); - } else if (Math.abs(pos.pos - pos.inside) < 2) { - if (!highlightOnly) { - const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0; - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility })); - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset))); - } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); - } - } - } - } - onMouseUp = (e: React.MouseEvent): void => { - e.stopPropagation(); - - const view = this._editorView as any; - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there - // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. - if (view.mouseDown) { - const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); - view.mouseDown.up = (e: MouseEvent) => { - !(e as any).formattedHandled && originalUpHandler(e); - // e.stopPropagation(); - (e as any).formattedHandled = true; - }; - view.root.addEventListener("mouseup", view.mouseDown.up); - } - } - - richTextMenuPlugin() { - return new Plugin({ - view(newView) { - RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); - return RichTextMenu.Instance; - } - }); - } - - public static HadSelection: boolean = false; - onBlur = (e: any) => { - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; - //DictationManager.Controls.stop(false); - if (this._undoTyping) { - this._undoTyping.end(); - this._undoTyping = undefined; - } - this.doLinkOnDeselect(); - - // move the richtextmenu offscreen - if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); - } - - _lastTimedMark: Mark | undefined = undefined; - onKeyPress = (e: React.KeyboardEvent) => { - if (e.altKey) { - e.preventDefault(); - return; - } - const state = this._editorView!.state; - if (!state.selection.empty && e.key === "%") { - this._rules!.EnteringStyle = true; - e.preventDefault(); - e.stopPropagation(); - return; - } - - if (state.selection.empty || !this._rules!.EnteringStyle) { - this._rules!.EnteringStyle = false; - } - 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?.(); - SelectionManager.DeselectAll(); - } - e.stopPropagation(); - 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.floor(Date.now() / 1000) }); - this._lastTimedMark = mark; - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); - - if (!this._undoTyping) { - this._undoTyping = UndoManager.StartBatch("undoTyping"); - } - } - - onscrolled = (ev: React.UIEvent) => { - this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; - } - @action - tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this._ref.current?.scrollHeight; - if (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) { - scrollHeight = limitHeight; - this.layoutDoc.limitHeight = undefined; - this.layoutDoc._autoHeight = false; - } - const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); - const dh = NumCast(this.layoutDoc._height, 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; - } - } - } - - @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); - @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } - render() { - TraceMobx(); - const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; - if (this.props.isSelected()) { - this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); - } else if (FormattedTextBoxComment.textBox === this) { - FormattedTextBoxComment.Hide(); - } - return ( - -
this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} - onBlur={this.onBlur} - onPointerUp={this.onPointerUp} - onPointerDown={this.onPointerDown} - onMouseUp={this.onMouseUp} - onWheel={this.onPointerWheel} - onPointerEnter={action(() => this._entered = true)} - onPointerLeave={action((e: React.PointerEvent) => { - this._entered = false; - const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); - for (let child: any = target; child; child = child?.parentElement) { - if (child === this._ref.current!) { - this._entered = true; - } - } - })} - > -
-
-
- {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? -
this.toggleSidebar()} /> : -
- - -
this.toggleSidebar()} /> -
} - {!this.props.Document._showAudio ? (null) : -
{ - runInAction(() => this._recording = !this._recording); - setTimeout(() => this._editorView!.focus(), 500); - e.stopPropagation(); - }} > - -
} -
- ); - } -} diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss deleted file mode 100644 index 2dd63ec21..000000000 --- a/src/client/views/nodes/FormattedTextBoxComment.scss +++ /dev/null @@ -1,33 +0,0 @@ -.FormattedTextBox-tooltip { - position: absolute; - pointer-events: none; - z-index: 20; - background: white; - border: 1px solid silver; - border-radius: 2px; - margin-bottom: 7px; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); - } - .FormattedTextBox-tooltip:before { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -6px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: silver; - } - .FormattedTextBox-tooltip:after { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -4.5px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: white; - } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx deleted file mode 100644 index dfea0f6bb..000000000 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import { Doc, DocCastAsync } from "../../../new_fields/Doc"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils"; -import { DocServer } from "../../DocServer"; -import { DocumentManager } from "../../util/DocumentManager"; -import { schema } from "../../util/schema_rts"; -import { Transform } from "../../util/Transform"; -import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; -import { FormattedTextBox } from "./FormattedTextBox"; -import './FormattedTextBoxComment.scss'; -import React = require("react"); -import { Docs } from "../../documents/Documents"; -import wiki from "wikijs"; -import { DocumentType } from "../../documents/DocumentTypes"; - -export let formattedTextBoxCommentPlugin = new Plugin({ - view(editorView) { return new FormattedTextBoxComment(editorView); } -}); -export function findOtherUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); -} -export function findUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid); -} -export function findLinkMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.type === schema.marks.link); -} -export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0; - let nbef = rpos.nodeBefore; - while (nbef && finder(nbef.marks)) { - before += nbef.nodeSize; - rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); - rpos && (nbef = rpos.nodeBefore); - } - return before; -} -export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0; - let naft = rpos.nodeAfter; - while (naft && finder(naft.marks)) { - after += naft.nodeSize; - rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); - rpos && (naft = rpos.nodeAfter); - } - return after; -} - - -export class FormattedTextBoxComment { - static tooltip: HTMLElement; - static tooltipText: HTMLElement; - static tooltipInput: HTMLInputElement; - static start: number; - static end: number; - static mark: Mark; - static textBox: FormattedTextBox | undefined; - static linkDoc: Doc | undefined; - constructor(view: any) { - if (!FormattedTextBoxComment.tooltip) { - const root = document.getElementById("root"); - FormattedTextBoxComment.tooltipInput = document.createElement("input"); - FormattedTextBoxComment.tooltipInput.type = "checkbox"; - FormattedTextBoxComment.tooltip = document.createElement("div"); - FormattedTextBoxComment.tooltipText = document.createElement("div"); - FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; - FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; - FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; - FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; - FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - FormattedTextBoxComment.tooltip.style.overflow = "hidden"; - FormattedTextBoxComment.tooltip.style.display = "none"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); - FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { - const keep = e.target && (e.target as any).type === "checkbox" ? true : false; - const textBox = FormattedTextBoxComment.textBox; - if (FormattedTextBoxComment.linkDoc && !keep && textBox) { - if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); - } else { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, - (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); - } - } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); - } - keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( - FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); - e.stopPropagation(); - e.preventDefault(); - }; - root && root.appendChild(FormattedTextBoxComment.tooltip); - } - } - - public static Hide() { - FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } - public static SetState(textBox: any, start: number, end: number, mark: Mark) { - FormattedTextBoxComment.textBox = textBox; - FormattedTextBoxComment.start = start; - FormattedTextBoxComment.end = end; - FormattedTextBoxComment.mark = mark; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); - } - - static update(view: EditorView, lastState?: EditorState) { - const state = view.state; - // Don't do anything if the document/selection didn't change - if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) { - return; - } - FormattedTextBoxComment.linkDoc = undefined; - - const textBox = FormattedTextBoxComment.textBox; - if (!textBox || !textBox.props) { - return; - } - let set = "none"; - let nbef = 0; - FormattedTextBoxComment.tooltipInput.style.display = "none"; - FormattedTextBoxComment.tooltip.style.width = ""; - FormattedTextBoxComment.tooltip.style.height = ""; - (FormattedTextBoxComment.tooltipText as any).href = ""; - FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; - FormattedTextBoxComment.tooltipText.style.overflow = ""; - // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date - if (state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); - const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const noselection = view.state.selection.$from === view.state.selection.$to; - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - const mark = child && findOtherUserMark(child.marks); - if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); - } - if (mark && child && ((nbef && naft) || !noselection)) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); - set = ""; - FormattedTextBoxComment.tooltipInput.style.display = ""; - } - } - // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. - if (set === "none" && state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findLinkMark); - const naft = findEndOfMark(state.selection.$from, view, findLinkMark); - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - const mark = child && findLinkMark(child.marks); - if (mark && child && nbef && naft && mark.attrs.showPreview) { - FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; - (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; - if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); - } else { - FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; - FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; - } - if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { - FormattedTextBoxComment.tooltipText.textContent = "target not found..."; - (FormattedTextBoxComment.tooltipText as any).href = ""; - const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { } - docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { - if (linkDoc instanceof Doc) { - (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; - FormattedTextBoxComment.linkDoc = linkDoc; - const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; - if (anchor !== target && anchor && target) { - target.scrollY = NumCast(anchor?.y); - } - if (target) { - ReactDOM.render( Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => Math.min(250, NumCast(target._height, 250))} - focus={emptyFunction} - whenActiveChanged={returnFalse} - />, FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; - FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; - } - // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... - // let text = ext && StrCast(ext.text); - // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); - } - }); - } - set = ""; - } - } - if (set !== "none") { - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - } - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); - } - - destroy() { } -} diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx new file mode 100644 index 000000000..d94fe7fc6 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -0,0 +1,95 @@ +import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; +import { StepMap } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { List } from "../../../../new_fields/List"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; + +import React = require("react"); + +import { schema } from "./schema_rts"; + +interface IDashDocCommentView { + node: any; + view: any; + getPos: any; +} + +export class DashDocCommentView extends React.Component{ + constructor(props: IDashDocCommentView) { + super(props); + } + + targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) { + const m = this.props.view.state.doc.nodeAt(i); + if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) { + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + } + } + const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" }); + this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc)); + setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); + return undefined; + } + + onPointerDownCollapse = (e: any) => e.stopPropagation(); + + onPointerUpCollapse = (e: any) => { + const target = this.targetNode(); + if (target) { + const expand = target.hidden; + const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs + setTimeout(() => { + expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } + }, 0); + } + e.stopPropagation(); + } + + onPointerEnterCollapse = (e: any) => { + DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + e.preventDefault(); + e.stopPropagation(); + } + + onPointerLeaveCollapse = (e: any) => { + DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + e.preventDefault(); + e.stopPropagation(); + } + + render() { + + const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid; + + return ( + + + + ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx new file mode 100644 index 000000000..9fe8fa320 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -0,0 +1,269 @@ +import { IReactionDisposer, reaction } from "mobx"; +import { NodeSelection } from "prosemirror-state"; +import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { DocumentView } from "../DocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { Transform } from "../../../util/Transform"; +import React = require("react"); + +interface IDashDocView { + node: any; + view: any; + getPos: any; + tbox?: FormattedTextBox; + self: any; +} + +export class DashDocView extends React.Component { + + _dashDoc: Doc | undefined; + _reactionDisposer: IReactionDisposer | undefined; + _renderDisposer: IReactionDisposer | undefined; + _textBox: FormattedTextBox; + _finalLayout: any; + _resolvedDataDoc: any; + + + // constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + + constructor(props: IDashDocView) { + super(props); + + const node = this.props.node; + this._textBox = this.props.tbox as FormattedTextBox; + + const alias = node.attrs.alias; + const docid = node.attrs.docid || this._textBox.props.Document[Id]; + + DocServer.GetRefField(docid + alias).then(async dashDoc => { + if (!(dashDoc instanceof Doc)) { + alias && DocServer.GetRefField(docid).then(async dashDocBase => { + if (dashDocBase instanceof Doc) { + const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout"; + node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + this._dashDoc = aliasedDoc; + // self.doRender(aliasedDoc, removeDoc, node, view, getPos); + } + }); + } else { + this._dashDoc = dashDoc; + // self.doRender(dashDoc, removeDoc, node, view, getPos); + } + }); + + this.onPointerLeave = this.onPointerLeave.bind(this); + this.onPointerEnter = this.onPointerEnter.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onKeyPress = this.onKeyPress.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + this.onWheel = this.onWheel.bind(this); + } + /* #region Internal functions */ + + removeDoc = () => { + const view = this.props.view; + const pos = this.props.getPos(); + const ns = new NodeSelection(view.state.doc.resolve(pos)); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + } + + getDocTransform = () => { + const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement; + const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement); + return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); + } + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + + onKeyPress = (e: any) => { + e.stopPropagation(); + } + onWheel = (e: any) => { + e.preventDefault(); + } + onKeyUp = (e: any) => { + e.stopPropagation(); + } + onKeyDown = (e: any) => { + e.stopPropagation(); + if (e.key === "Tab" || e.key === "Enter") { + e.preventDefault(); + } + } + onPointerLeave = () => { + const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = ""; + } + } + onPointerEnter = () => { + const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = "orange"; + } + } + /*endregion*/ + + componentWillMount = () => { + this._reactionDisposer?.(); + } + + componentDidUpdate = () => { + + this._renderDisposer?.(); + this._renderDisposer = reaction(() => { + + const dashDoc = this._dashDoc as Doc; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey); + + if (finalLayout) { + if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { + finalLayout.rootDocument = dashDoc.aliasOf; + } + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + this._finalLayout = finalLayout; + this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null); + return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; + } + }, + (res) => { + + if (res) { + this._finalLayout = res.finalLayout; + this._resolvedDataDoc = res.resolvedDataDoc; + + this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc), + } + }, + { fireImmediately: true }); + + } + + render() { + // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { + + const node = this.props.node; + const view = this.props.view; + const getPos = this.props.getPos; + + const spanStyle = { + width: this.props.node.props.width, + height: this.props.node.props.height, + position: 'absolute' as 'absolute', + display: 'inline-block' + }; + + + const outerStyle = { + position: "relative" as "relative", + textIndent: "0", + border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")), + width: this.props.node.props.width, + height: this.props.node.props.height, + display: this.props.node.props.hidden ? "none" : "inline-block", + float: this.props.node.props.float, + }; + + const dashDoc = this._dashDoc as Doc; + const self = this; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); + const resolvedDataDoc = this._resolvedDataDoc; //Added this + + if (!finalLayout) { + return
; + // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + } else { + + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => + ({ + dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], + color: finalLayout.color + }), + ({ dim, color }) => { + spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px"; + spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px"; + outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + }, { fireImmediately: true }); + + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + + + //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { + // ReactDOM.unmountComponentAtNode(this._dashSpan); + + return ( + +
+ + +
+
+ ); + + } + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss new file mode 100644 index 000000000..35ff9c1e6 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -0,0 +1,36 @@ +.dashFieldView { + position: relative; + display: inline-block; + + .dashFieldView-enumerables { + width: 10px; + height: 10px; + position: relative; + display: inline-block; + background: dimGray; + } + .dashFieldView-fieldCheck { + min-width: 12px; + position: relative; + display: inline-block; + background-color: rgba(155, 155, 155, 0.24); + } + .dashFieldView-labelSpan { + position: relative; + display: inline-block; + font-size: small; + } + .dashFieldView-fieldSpan { + min-width: 20px; + margin-left: 2px; + margin-right: 5px; + position: relative; + display: inline-block; + background-color: rgba(155, 155, 155, 0.24); + span { + min-width: 100%; + display: inline-block; + } + } +} + \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx new file mode 100644 index 000000000..82c3185e7 --- /dev/null +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -0,0 +1,211 @@ +import { IReactionDisposer, observable, runInAction, computed, action } from "mobx"; +import { Doc, DocListCast, Field } from "../../../../new_fields/Doc"; +import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../../new_fields/Types"; +import { DocServer } from "../../../DocServer"; +import { CollectionViewType } from "../../collections/CollectionView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import "./DashFieldView.scss"; +import { observer } from "mobx-react"; + + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; // container for label and value + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + + ReactDOM.render(, this._fieldWrapper); + (this as any).dom = this._fieldWrapper; + } + destroy() { + ReactDOM.unmountComponentAtNode(this._fieldWrapper); + } + selectNode() { } + +} +interface IDashFieldViewInternal { + fieldKey: string; + docid: string; + view: any; + getPos: any; + tbox: FormattedTextBox; + width: number; + height: number; +} + +@observer +export class DashFieldViewInternal extends React.Component { + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + _fieldKey: string; + _fieldStringRef = React.createRef(); + @observable _showEnumerables: boolean = false; + @observable _dashDoc: Doc | undefined; + + constructor(props: IDashFieldViewInternal) { + super(props); + this._fieldKey = this.props.fieldKey; + this._textBoxDoc = this.props.tbox.props.Document; + + if (this.props.docid) { + DocServer.GetRefField(this.props.docid). + then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + } else { + this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc; + } + } + componentWillUnmount() { + this._reactionDisposer?.(); + } + + // set the display of the field's value (checkbox for booleans, span of text for strings) + @computed get fieldValueContent() { + if (this._dashDoc) { + const dashVal = this._dashDoc[this._fieldKey]; + const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal; + const boolVal = Cast(fval, "boolean", null); + const strVal = Field.toString(fval as Field) || ""; + + // field value is a boolean, so use a checkbox or similar widget to display it + if (boolVal === true || boolVal === false) { + return this._dashDoc![this._fieldKey] = e.target.checked} + />; + } + else // field value is a string, so display it as an editable span + { + // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't + // use React events. Essentially, React events occur after native events have been processed, so corresponding React events + // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. + return { + r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); + r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); + r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true)); + }}> + {strVal} + ; + } + } + } + + // we need to handle all key events on the input span or else they will propagate to prosemirror. + @action + fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { + if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. + e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); + this.updateText(span.textContent!, true); + e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view + } + if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span + if (window.getSelection) { + const range = document.createRange(); + range.selectNodeContents(span); + window.getSelection()!.removeAllRanges(); + window.getSelection()!.addRange(range); + } + e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected + } + e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. + } + + @action + updateText = (nodeText: string, forceMatch: boolean) => { + this._showEnumerables = false; + if (nodeText) { + const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; + + // look for a document whose id === the fieldKey being displayed. If there's a match, then that document + // holds the different enumerated values for the field in the titles of its collected documents. + // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. + DocServer.GetRefField(this._fieldKey).then(options => { + let modText = ""; + (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + if (modText) { + // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; + Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); + this._dashDoc![this._fieldKey] = modText; + } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key + else if (nodeText.startsWith(":=")) { + this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); + } else if (nodeText.startsWith("=:=")) { + Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); + } else { + this._dashDoc![this._fieldKey] = newText; + } + }); + } + } + + // display a collection of all the enumerable values for this field + onPointerDownEnumerables = async (e: any) => { + e.stopPropagation(); + const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); + collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); + } + + + // clicking on the label creates a pivot view collection of all documents + // in the same collection. The pivot field is the fieldKey of this label + onPointerDownLabelSpan = (e: any) => { + e.stopPropagation(); + let container = this.props.tbox.props.ContainingCollectionView; + while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { + container = container.props.ContainingCollectionView; + } + if (container) { + const alias = Doc.MakeAlias(container.props.Document); + alias.viewType = CollectionViewType.Time; + let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); + if (!list) { + alias.schemaColumns = list = new List(); + } + list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); + list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); + alias._pivotField = this._fieldKey; + this.props.tbox.props.addDocTab(alias, "onRight"); + } + } + + render() { + return
+ + {this._fieldKey} + + +
+ {this.fieldValueContent} +
+ + {!this._showEnumerables ? (null) :
} + +
; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx new file mode 100644 index 000000000..ee21fb765 --- /dev/null +++ b/src/client/views/nodes/formattedText/FootnoteView.tsx @@ -0,0 +1,162 @@ +import { EditorView } from "prosemirror-view"; +import { EditorState } from "prosemirror-state"; +import { keymap } from "prosemirror-keymap"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { schema } from "./schema_rts"; +import { redo, undo } from "prosemirror-history"; +import { StepMap } from "prosemirror-transform"; + +import React = require("react"); + +interface IFootnoteView { + innerView: any; + outerView: any; + node: any; + dom: any; + getPos: any; +} + +export class FootnoteView extends React.Component { + _innerView: any; + _node: any; + + constructor(props: IFootnoteView) { + super(props); + const node = this.props.node; + const outerView = this.props.outerView; + const _innerView = this.props.innerView; + const getPos = this.props.getPos; + } + + selectNode() { + const attrs = { ...this.props.node.attrs }; + attrs.visibility = true; + this.dom.classList.add("ProseMirror-selectednode"); + if (!this.props.innerView) this.open(); + } + + deselectNode() { + const attrs = { ...this.props.node.attrs }; + attrs.visibility = false; + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.props.innerView) this.close(); + } + open() { + // Append a tooltip to the outer node + const tooltip = this.dom.appendChild(document.createElement("div")); + tooltip.className = "footnote-tooltip"; + // And put a sub-ProseMirror into that + this.props.innerView.defineProperty(new EditorView(tooltip, { + // You can use any node as an editor document + state: EditorState.create({ + doc: this.props.node, + plugins: [keymap(baseKeymap), + keymap({ + "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch), + "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch), + "Mod-b": toggleMark(schema.marks.strong) + }), + // new Plugin({ + // view(newView) { + // // TODO -- make this work with RichTextMenu + // // return FormattedTextBox.getToolTip(newView); + // } + // }) + ], + + }), + // This is the magic part + dispatchTransaction: this.dispatchInner.bind(this), + handleDOMEvents: { + pointerdown: ((view: any, e: PointerEvent) => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + e.stopPropagation(); + document.addEventListener("pointerup", this.ignore, true); + if (this.props.outerView.hasFocus()) this.props.innerView.focus(); + }) as any + } + })); + setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0); + } + + ignore = (e: PointerEvent) => { + e.stopPropagation(); + document.removeEventListener("pointerup", this.ignore, true); + } + + dispatchInner(tr: any) { + const { state, transactions } = this.props.innerView.state.applyTransaction(tr); + this.props.innerView.updateState(state); + + if (!tr.getMeta("fromOutside")) { + const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1); + for (const transaction of transactions) { + const steps = transaction.steps; + for (const step of steps) { + outerTr.step(step.map(offsetMap)); + } + } + if (outerTr.docChanged) this.props.outerView.dispatch(outerTr); + } + } + update(node: any) { + if (!node.sameMarkup(this.props.node)) return false; + this._node = node; //not sure + if (this.props.innerView) { + const state = this.props.innerView.state; + const start = node.content.findDiffStart(state.doc.content); + if (start !== null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + const overlap = start - Math.min(endA, endB); + if (overlap > 0) { endA += overlap; endB += overlap; } + this.props.innerView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta("fromOutside", true)); + } + } + return true; + } + onPointerUp = (e: any) => { + this.toggle(e); + } + + toggle = (e: any) => { + e.preventDefault(); + if (this.props.innerView) this.close(); + else { + this.open(); + } + } + + close() { + this.props.innerView && this.props.innerView.destroy(); + this._innerView = null; + this.dom.textContent = ""; + } + + destroy() { + if (this.props.innerView) this.close(); + } + + stopEvent(event: any) { + return this.props.innerView && this.props.innerView.dom.contains(event.target); + } + + ignoreMutation() { return true; } + + + render() { + return ( +
+
+ +
+
+ ); + } +} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss new file mode 100644 index 000000000..477a2ca08 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -0,0 +1,265 @@ +@import "../../globalCssVariables"; + +.ProseMirror { + width: 100%; + height: 100%; + min-height: 100%; +} + +.ProseMirror:focus { + outline: none !important; +} + +.formattedTextBox-cont { + touch-action: none; + cursor: text; + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $intermediate-color; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: initial; + max-height: 100%; + display: flex; + flex-direction: row; + transition: opacity 1s; + + .formattedTextBox-dictation { + height: 12px; + width: 10px; + top: 0px; + left: 0px; + position: absolute; + } +} +.formattedTextBox-outer { + position: relative; + overflow: auto; + display: inline-block; + width: 100%; + height: 100%; +} + +.formattedTextBox-sidebar-handle { + position: absolute; + top: calc(50% - 17.5px); + width: 10px; + height: 35px; + background: lightgray; + border-radius: 20px; + cursor:grabbing; +} + +.formattedTextBox-cont>.formattedTextBox-sidebar-handle { + right: 0; + left: unset; +} + +.formattedTextBox-sidebar, +.formattedTextBox-sidebar-inking { + border-left: dashed 1px black; + height: 100%; + display: inline-block; + position: absolute; + right: 0; + + .collectionfreeformview-container { + position: relative; + } + + >.formattedTextBox-sidebar-handle { + right: unset; + left: -5; + } +} + +.formattedTextBox-sidebar-inking { + pointer-events: all; +} + +.formattedTextBox-inner-rounded { + height: 70%; + width: 85%; + position: absolute; + overflow: auto; + top: 15%; + left: 10%; +} + +.formattedTextBox-inner-rounded, +.formattedTextBox-inner { + height: 100%; + white-space: pre-wrap; +} + +// .menuicon { +// display: inline-block; +// border-right: 1px solid rgba(0, 0, 0, 0.2); +// color: #888; +// line-height: 1; +// padding: 0 7px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 1.4em; +// } + +.strong, +.heading { + font-weight: bold; +} + +.em { + font-style: italic; +} + +.userMarkOpen { + background: rgba(255, 255, 0, 0.267); + display: inline; +} + +.userMark { + background: rgba(255, 255, 0, 0.267); + font-size: 2px; + display: inline-grid; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 10px; + min-height: 10px; + text-align: center; + align-content: center; +} + +footnote { + display: inline-block; + position: relative; + cursor: pointer; + + div { + padding: 0 !important; + } +} + +footnote::after { + content: counter(prosemirror-footnote); + vertical-align: super; + font-size: 75%; + counter-increment: prosemirror-footnote; +} + +.ProseMirror { + counter-reset: prosemirror-footnote; +} + +.footnote-tooltip { + cursor: auto; + font-size: 75%; + position: absolute; + left: -30px; + top: calc(100% + 10px); + background: silver; + padding: 3px; + border-radius: 2px; + max-width: 100px; + min-width: 50px; + width: max-content; +} + +.prosemirror-attribution { + font-size: 8px; +} + +.footnote-tooltip::before { + border: 5px solid silver; + border-top-width: 0px; + border-left-color: transparent; + border-right-color: transparent; + position: absolute; + top: -5px; + left: 27px; + content: " "; + height: 0; + width: 0; +} + + +.formattedTextBox-inlineComment { + position: relative; + width: 40px; + height: 20px; + &::before { + content: "→"; + } + &:hover { + background: orange; + } +} + +.formattedTextBox-summarizer { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "←"; + } +} + +.formattedTextBox-summarizer-collapsed { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "..."; + } +} + +.ProseMirror { + touch-action: none; + span { + font-family: inherit; + } + + ol, ul { + counter-reset: deci1 0 multi1 0; + padding-left: 1em; + font-family: inherit; + } + ol { + margin-left: 1em; + font-family: inherit; + } + + .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } + .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} + .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} + .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } + + .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } + .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} + .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} + + .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } + .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } + .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } + .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } + .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } + .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } + .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } + .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } + .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } + .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx new file mode 100644 index 000000000..248b4f467 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -0,0 +1,1303 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isEqual } from "lodash"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { baseKeymap } from "prosemirror-commands"; +import { history } from "prosemirror-history"; +import { inputRules } from 'prosemirror-inputrules'; +import { keymap } from "prosemirror-keymap"; +import { Fragment, Mark, Node, Slice } from "prosemirror-model"; +import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; +import { ReplaceStep } from 'prosemirror-transform'; +import { EditorView } from "prosemirror-view"; +import { DateField } from '../../../../new_fields/DateField'; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; +import { documentSchema } from '../../../../new_fields/documentSchemas'; +import { Id } from '../../../../new_fields/FieldSymbols'; +import { InkTool } from '../../../../new_fields/InkField'; +import { PrefetchProxy } from '../../../../new_fields/Proxy'; +import { RichTextField } from "../../../../new_fields/RichTextField"; +import { RichTextUtils } from '../../../../new_fields/RichTextUtils'; +import { createSchema, makeInterface } from "../../../../new_fields/Schema"; +import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { TraceMobx } from '../../../../new_fields/util'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../../Utils'; +import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; +import { DocServer } from "../../../DocServer"; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { DictationManager } from '../../../util/DictationManager'; +import { DragManager } from "../../../util/DragManager"; +import { makeTemplate } from '../../../util/DropConverter'; +import buildKeymap from "./ProsemirrorExampleTransfer"; +import RichTextMenu from './RichTextMenu'; +import { RichTextRules } from "./RichTextRules"; +import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema"; +// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema"; +// import { OrderedListView } from "./RichTextSchema"; +// import { ImageResizeView } from "./ImageResizeView"; +// import { DashDocCommentView } from "./DashDocCommentView"; +// import { FootnoteView } from "./FootnoteView"; +// import { SummaryView } from "./SummaryView"; +// import { DashDocView } from "./DashDocView"; +import { DashFieldView } from "./DashFieldView"; + +import { schema } from "./schema_rts"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; +import { ContextMenu } from '../../ContextMenu'; +import { ContextMenuProps } from '../../ContextMenuItem'; +import { ViewBoxAnnotatableComponent } from "../../DocComponent"; +import { DocumentButtonBar } from '../../DocumentButtonBar'; +import { InkingControl } from "../../InkingControl"; +import { AudioBox } from '../AudioBox'; +import { FieldView, FieldViewProps } from "../FieldView"; +import "./FormattedTextBox.scss"; +import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; +import React = require("react"); + +library.add(faEdit); +library.add(faSmile, faTextHeight, faUpload); + +export interface FormattedTextBoxProps { + hideOnLeave?: boolean; + makeLink?: () => Opt; + xMargin?: number; + yMargin?: number; +} + +const richTextSchema = createSchema({ + documentText: "string" +}); + +export const GoogleRef = "googleDocId"; + +type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; +const RichTextDocument = makeInterface(richTextSchema, documentSchema); + +type PullHandler = (exportState: Opt, dataDoc: Doc) => void; + +@observer +export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } + public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); + public static Instance: FormattedTextBox; + public ProseRef?: HTMLDivElement; + private _ref: React.RefObject = React.createRef(); + private _scrollRef: React.RefObject = React.createRef(); + private _editorView: Opt; + private _applyingChange: boolean = false; + private _searchIndex = 0; + private _sidebarMovement = 0; + private _lastX = 0; + private _lastY = 0; + private _undoTyping?: UndoManager.Batch; + private _disposers: { [name: string]: IReactionDisposer } = {}; + private dropDisposer?: DragManager.DragDropDisposer; + + @computed get _recording() { return this.dataDoc.audioState === "recording"; } + set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + + @observable private _entered = false; + + public static FocusedBox: FormattedTextBox | undefined; + public static SelectOnLoad = ""; + public static SelectOnLoadChar = ""; + public static IsFragment(html: string) { + return html.indexOf("data-pm-slice") !== -1; + } + public static GetHref(html: string): string { + const parser = new DOMParser(); + const parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && + (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ""; + } + public static GetDocFromUrl(url: string) { + if (url.startsWith(document.location.origin)) { + const split = new URL(url).pathname.split("doc/"); + const docid = split[split.length - 1]; + return docid; + } + return ""; + } + + @undoBatch + public setFontColor(color: string) { + const view = this._editorView!; + if (view.state.selection.from === view.state.selection.to) return false; + if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { + this.layoutDoc.color = color; + } + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); + return true; + } + + constructor(props: any) { + super(props); + FormattedTextBox.Instance = this; + this.updateHighlights(); + } + + public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + + linkOnDeselect: Map = new Map(); + + doLinkOnDeselect() { + Array.from(this.linkOnDeselect.entries()).map(entry => { + const key = entry[0]; + const value = entry[1]; + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + DocServer.GetRefField(value).then(doc => { + DocServer.GetRefField(id).then(linkDoc => { + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); + DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); + }); + }); + }); + this.linkOnDeselect.clear(); + } + + dispatchTransaction = (tx: Transaction) => { + if (this._editorView) { + const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + const range = tx.selection.$from.blockRange(tx.selection.$to); + let text = range ? tx.doc.textBetween(range.start, range.end) : ""; + let textEndSelection = tx.selection.to; + for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } + text = text.substr(0, textEndSelection - range!.start); + text = text.split(" ")[text.split(" ").length - 1]; + const split = text.split("::"); + if (split.length > 1 && split[1]) { + const key = split[0]; + const value = split[split.length - 1]; + this.linkOnDeselect.set(key, value); + + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value }); + const mval = this._editorView.state.schema.marks.metadataVal.create(); + const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); + tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); + this.dataDoc[key] = value; + } + } + const state = this._editorView.state.apply(tx); + this._editorView.updateState(state); + (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); + + const tsel = this._editorView.state.selection.$from; + tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); + const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); + const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField); + if (!this._applyingChange) { + this._applyingChange = true; + this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + } else { // if we've deleted all the text in a note driven by a template, then restore the template data + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data))); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + } + this._applyingChange = false; + } + this.updateTitle(); + this.tryUpdateHeight(); + } + } + + updateTitle = () => { + if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { + const str = this._editorView.state.doc.textContent; + const titlestr = str.substr(0, Math.min(40, str.length)); + this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); + } + } + + // needs a better API for taking in a set of words with target documents instead of just one target + public hyperlinkTerms = (terms: string[], target: Doc) => { + if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const tr = this._editorView.state.tr; + const flattened: TextSelection[] = []; + res.map(r => r.map(h => flattened.push(h))); + const lastSel = Math.min(flattened.length - 1, this._searchIndex); + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!; + const link = this._editorView.state.schema.marks.link.create({ + href: Utils.prepend("/doc/" + alink[Id]), + title: "a link", location: location, linkId: alink[Id], targetId: target[Id] + }); + this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); + } + } + public highlightSearchTerms = (terms: string[]) => { + if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + let tr = this._editorView.state.tr; + const flattened: TextSelection[] = []; + res.map(r => r.map(h => flattened.push(h))); + const lastSel = Math.min(flattened.length - 1, this._searchIndex); + flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + } + } + + public unhighlightSearchTerms = () => { + if (this._editorView && (this._editorView as any).docView) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); + const end = this._editorView.state.doc.nodeSize - 2; + this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + } + } + adoptAnnotation = (start: number, end: number, mark: Mark) => { + const view = this._editorView!; + const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); + view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); + } + protected createDropTarget = (ele: HTMLDivElement) => { + this.ProseRef = ele; + this.dropDisposer?.(); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + } + + @undoBatch + @action + drop = async (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0]; + // replace text contents whend dragging with Alt + if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); + e.stopPropagation(); + } + // embed document when dragging with a userDropAction or an embedDoc flag set + } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { + const target = de.complete.docDragData.droppedDocuments[0]; + // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + // if (link) { + target._fitToBox = true; + const node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + const view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + // } + } // otherwise, fall through to outer collection to handle drop + } else if (de.complete.linkDragData) { + de.complete.linkDragData.linkDropCallback = this.linkDrop; + } + } + linkDrop = (data: DragManager.LinkDragData) => { + const linkDoc = data.linkDocument!; + const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; + const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); + } + + getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { + let offset = 0; + + if (context === node) return { from: offset, to: offset + node.nodeSize }; + + if (node.isBlock) { + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < (context.content as any).content.length; i++) { + const result = this.getNodeEndpoints((context.content as any).content[i], node); + if (result) { + return { + from: result.from + offset + (context.type.name === "doc" ? 0 : 1), + to: result.to + offset + (context.type.name === "doc" ? 0 : 1) + }; + } + offset += (context.content as any).content[i].nodeSize; + } + return null; + } else { + return null; + } + } + + + //Recursively finds matches within a given node + findInNode(pm: EditorView, node: Node, find: string) { + let ret: TextSelection[] = []; + + if (node.isTextblock) { + let index = 0, foundAt; + const ep = this.getNodeEndpoints(pm.state.doc, node); + while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { + const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); + ret.push(sel); + index = index + foundAt + find.length; + } + } else { + node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); + } + return ret; + } + static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + + updateHighlights = () => { + clearStyleSheetRules(FormattedTextBox._userStyleSheet); + if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); + } + if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); + } + if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); + } + if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); + } + if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); + } + if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" }); + } + if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + const min = Math.round(Date.now() / 1000 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); + setTimeout(() => this.updateHighlights()); + } + if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + const hr = Math.round(Date.now() / 1000 / 60 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); + } + } + + sidebarDown = (e: React.PointerEvent) => { + this._lastX = e.clientX; + this._lastY = e.clientY; + this._sidebarMovement = 0; + document.addEventListener("pointermove", this.sidebarMove); + document.addEventListener("pointerup", this.sidebarUp); + e.stopPropagation(); + e.preventDefault(); // prevents text from being selected during drag + } + sidebarMove = (e: PointerEvent) => { + const bounds = this.CurrentDiv.getBoundingClientRect(); + this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)); + this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; + } + sidebarUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.sidebarMove); + document.removeEventListener("pointerup", this.sidebarUp); + } + + toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); + + public static get DefaultLayout(): Doc | string | undefined { + return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); + } + specificContextMenu = (e: React.MouseEvent): void => { + const cm = ContextMenu.Instance; + + const funcs: ContextMenuProps[] = []; + this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); + funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + !this.props.Document.rootDocument && funcs.push({ + description: "Make Template", event: () => { + this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); + Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); + }, icon: "eye" + }); + funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); + + const highlighting: ContextMenuProps[] = []; + ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + highlighting.push({ + description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + e.stopPropagation(); + if (FormattedTextBox._highlights.indexOf(option) === -1) { + FormattedTextBox._highlights.push(option); + } else { + FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); + } + this.updateHighlights(); + }, icon: "expand-arrows-alt" + })); + funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); + + ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + + const change = cm.findByDescription("Change Perspective..."); + const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : []; + + const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); + DocListCast(noteTypesDoc?.data).forEach(note => { + changeItems.push({ + description: StrCast(note.title), event: undoBatch(() => { + Doc.setNativeView(this.props.Document); + Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); + }), icon: "eye" + }); + }); + changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" }); + !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + + const open = cm.findByDescription("Add a Perspective..."); + const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; + + openItems.push({ + description: "FreeForm", event: undoBatch(() => { + const alias = Doc.MakeAlias(this.rootDoc); + Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform"); + this.props.addDocTab(alias, "onRight"); + }), icon: "eye" + }); + !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" }); + + } + + recordDictation = () => { + DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + //this._editorView!.focus(); + }); + } + stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; + + @action + toggleMenubar = () => { + this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; + } + + recordBullet = async () => { + const completedCue = "end session"; + const results = await DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + terminators: [completedCue, "bullet", "next"] + }); + if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { + DictationManager.Controls.stop(); + return; + } + this.nextBullet(this._editorView!.state.selection.to); + setTimeout(this.recordBullet, 2000); + } + + setCurrentBulletContent = (value: string) => { + if (this._editorView) { + const state = this._editorView.state; + const now = Date.now(); + let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); + if (!this._break && state.selection.to !== state.selection.from) { + for (let i = state.selection.from; i <= state.selection.to; i++) { + const pos = state.doc.resolve(i); + const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark); + if (um) { + mark = um; + break; + } + } + } + const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); + this._break = false; + value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; + const from = state.selection.from; + const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); + this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); + } + } + + nextBullet = (pos: number) => { + if (this._editorView) { + const frag = Fragment.fromArray(this.newListItems(2)); + if (this._editorView.state.doc.resolve(pos).depth >= 2) { + const slice = new Slice(frag, 2, 2); + let state = this._editorView.state; + this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); + pos += 4; + state = this._editorView.state; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + } + } + } + + private newListItems = (count: number) => { + return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); + } + + _keymap: any = undefined; + _rules: RichTextRules | undefined; + @computed get config() { + this._keymap = buildKeymap(schema, this.props); + this._rules = new RichTextRules(this.props.Document, this); + return { + schema, + plugins: [ + inputRules(this._rules.inpRules), + this.richTextMenuPlugin(), + history(), + keymap(this._keymap), + keymap(baseKeymap), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }), + formattedTextBoxCommentPlugin + ] + }; + } + + makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) { + if (this._editorView) { + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); + this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link). + addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link)); + } + } + componentDidMount() { + this._disposers.buttonBar = reaction( + () => DocumentButtonBar.Instance, + instance => { + if (instance) { + this.pullFromGoogleDoc(this.checkState); + this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); + } + } + ); + this._disposers.linkMaker = reaction( + () => this.props.makeLink?.(), + (linkDoc: Opt) => { + if (linkDoc) { + const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; + const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; + this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); + } + }, + { fireImmediately: true } + ); + this._disposers.editorState = reaction( + () => { + if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { + return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; + } + return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; + }, + incomingValue => { + if (incomingValue !== undefined && this._editorView && !this._applyingChange) { + const updatedState = JSON.parse(incomingValue); + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } + } + ); + this._disposers.pullDoc = reaction( + () => this.props.Document[Pulls], + () => { + if (!DocumentButtonBar.hasPulledHack) { + DocumentButtonBar.hasPulledHack = true; + const unchanged = this.dataDoc.unchanged; + this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); + } + } + ); + this._disposers.pushDoc = reaction( + () => this.props.Document[Pushes], + () => { + if (!DocumentButtonBar.hasPushedHack) { + DocumentButtonBar.hasPushedHack = true; + this.pushToGoogleDoc(); + } + } + ); + this._disposers.height = reaction( + () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], + () => this.tryUpdateHeight() + ); + + this.setupEditor(this.config, this.props.fieldKey); + + this._disposers.search = reaction(() => this.rootDoc.searchMatch, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + { fireImmediately: true }); + + this._disposers.record = reaction(() => this._recording, + () => { + if (this._recording) { + setTimeout(action(() => { + this.stopDictation(true); + setTimeout(() => this.recordDictation(), 500); + }), 500); + } else setTimeout(() => this.stopDictation(true), 0); + } + ); + this._disposers.scrollToRegion = reaction( + () => StrCast(this.layoutDoc.scrollToLinkID), + async (scrollToLinkID) => { + const findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + frag.forEach((node, index) => { + const examinedNode = findLinkNode(node, editor); + if (examinedNode && examinedNode.textContent) { + nodes.push(examinedNode); + start += index; + } + }); + return { frag: Fragment.fromArray(nodes), start: start }; + }; + const findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return node.copy(content.frag); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); + return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; + }; + + let start = -1; + if (this._editorView && scrollToLinkID) { + const editor = this._editorView; + const ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ret.start >= 0) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); + } + + }, + { fireImmediately: true } + ); + this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), + pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } + ); + + setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0))); + } + + pushToGoogleDoc = async () => { + this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { + const modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + if (!reference) { + mode = modes.Insert; + reference = { title: StrCast(this.dataDoc.title) }; + } + const redo = async () => { + if (this._editorView && reference) { + const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); + const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + response && (this.dataDoc[GoogleRef] = response.documentId); + const pushSuccess = response !== undefined && !("errors" in response); + dataDoc.unchanged = pushSuccess; + DocumentButtonBar.Instance.startPushOutcome(pushSuccess); + } + }; + const undo = () => { + if (!exportState) { + return; + } + const content: GoogleApiClientUtils.Docs.Content = { + text: exportState.text, + requests: [] + }; + if (reference && content) { + GoogleApiClientUtils.Docs.write({ reference, content, mode }); + } + }; + UndoManager.AddEvent({ undo, redo }); + redo(); + }); + } + + pullFromGoogleDoc = async (handler: PullHandler) => { + const dataDoc = this.dataDoc; + const documentId = StrCast(dataDoc[GoogleRef]); + let exportState: Opt; + if (documentId) { + exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); + } + UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); + } + + updateState = (exportState: Opt, dataDoc: Doc) => { + let pullSuccess = false; + if (exportState !== undefined) { + pullSuccess = true; + dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); + setTimeout(() => { + if (this._editorView) { + const state = this._editorView.state; + const end = state.doc.content.size - 1; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); + } + }, 0); + dataDoc.title = exportState.title; + this.rootDoc.customTitle = true; + dataDoc.unchanged = true; + } else { + delete dataDoc[GoogleRef]; + } + DocumentButtonBar.Instance.startPullOutcome(pullSuccess); + } + + checkState = (exportState: Opt, dataDoc: Doc) => { + if (exportState && this._editorView) { + const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); + const equalTitles = dataDoc.title === exportState.title; + const unchanged = equalContent && equalTitles; + dataDoc.unchanged = unchanged; + DocumentButtonBar.Instance.setPullState(unchanged); + } + } + + clipboardTextSerializer = (slice: Slice): string => { + let text = "", separated = true; + const from = 0, to = slice.content.size; + slice.content.nodesBetween(from, to, (node, pos) => { + if (node.isText) { + text += node.text!.slice(Math.max(from, pos) - pos, to - pos); + separated = false; + } else if (!separated && node.isBlock) { + text += "\n"; + separated = true; + } else if (node.type.name === "hard_break") { + text += "\n"; + } + }, 0); + return text; + } + + sliceSingleNode(slice: Slice) { + return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; + } + + handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { + const cbe = event as ClipboardEvent; + const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); + const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); + if (pdfDocId && pdfRegionId) { + DocServer.GetRefField(pdfDocId).then(pdfDoc => { + DocServer.GetRefField(pdfRegionId).then(pdfRegion => { + if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + setTimeout(async () => { + const targetField = Doc.LayoutFieldKey(pdfDoc); + const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations + targetAnnotations?.push(pdfRegion); + }); + + const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); + if (link) { + cbe.clipboardData!.setData("dash/linkDoc", link[Id]); + const linkId = link[Id]; + const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + slice = new Slice(frag, slice.openStart, slice.openEnd); + const tr = view.state.tr.replaceSelection(slice); + view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + } + } + }); + }); + return true; + } + return false; + + + function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { + const nodes: Node[] = []; + frag.forEach(node => nodes.push(marker(node))); + return Fragment.fromArray(nodes); + } + function addLinkMark(node: Node, title: string, linkId: string) { + if (!node.isText) { + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); + return node.copy(content); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type.name === "link"); + const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); + marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); + return node.mark(marks); + } + } + + private setupEditor(config: any, fieldKey: string) { + const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); + const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"]; + const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); + if (this.ProseRef) { + const self = this; + this._editorView?.destroy(); + this._editorView = new EditorView(this.ProseRef, { + state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), + handleScrollToSelection: (editorView) => { + const docPos = editorView.coordsAtPos(editorView.state.selection.from); + const viewRect = self._ref.current!.getBoundingClientRect(); + if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) { + docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale); + } + return true; + }, + dispatchTransaction: this.dispatchTransaction, + nodeViews: { + dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, + dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, + dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, + // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); }, + + // image(node, view, getPos) { + // //const addDocTab = this.props.addDocTab; + // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab }); + // }, + // // WAS : + // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); }, + + 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); } + }, + clipboardTextSerializer: this.clipboardTextSerializer, + handlePaste: this.handlePaste, + }); + const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); + if (startupText) { + const { state: { tr }, dispatch } = this._editorView; + dispatch(tr.insertText(startupText)); + } + } + + const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad && !this.props.dontRegisterView) { + FormattedTextBox.SelectOnLoad = ""; + this.props.select(false); + FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar)); + FormattedTextBox.SelectOnLoadChar = ""; + + } + (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); + // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; + } + getFont(font: string) { + switch (font) { + case "Arial": return schema.marks.arial.create(); + case "Times New Roman": return schema.marks.timesNewRoman.create(); + case "Georgia": return schema.marks.georgia.create(); + case "Comic Sans MS": return schema.marks.comicSans.create(); + case "Tahoma": return schema.marks.tahoma.create(); + case "Impact": return schema.marks.impact.create(); + case "ACrimson Textrial": return schema.marks.crimson.create(); + } + return schema.marks.arial.create(); + } + + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + this._editorView?.destroy(); + } + + static _downEvent: any; + _downX = 0; + _downY = 0; + _break = false; + onPointerDown = (e: React.PointerEvent): void => { + if (this._recording && !e.ctrlKey && e.button === 0) { + this.stopDictation(true); + this._break = true; + const state = this._editorView!.state; + const to = state.selection.to; + const updated = TextSelection.create(state.doc, to, to); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to)); + e.preventDefault(); + e.stopPropagation(); + if (this._recording) setTimeout(() => this.recordDictation(), 500); + } + this._downX = e.clientX; + this._downY = e.clientY; + this.doLinkOnDeselect(); + FormattedTextBox._downEvent = true; + FormattedTextBoxComment.textBox = this; + if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { + e.preventDefault(); + } + if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar + e.stopPropagation(); + } + } + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + e.preventDefault(); + } + } + + onPointerUp = (e: React.PointerEvent): void => { + if (!FormattedTextBox._downEvent) return; + FormattedTextBox._downEvent = false; + if (!(e.nativeEvent as any).formattedHandled) { + FormattedTextBoxComment.textBox = this; + FormattedTextBoxComment.update(this._editorView!); + } + (e.nativeEvent as any).formattedHandled = true; + + if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + e.stopPropagation(); + } + this._downX = this._downY = Number.NaN; + } + + @action + onFocused = (e: React.FocusEvent): void => { + FormattedTextBox.FocusedBox = this; + this.tryUpdateHeight(); + + // see if we need to preserve the insertion point + 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 coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos); + + // jump rich text menu to this textbox + const bounds = this._ref.current?.getBoundingClientRect(); + if (bounds && this.props.Document._chromeStatus !== "disabled") { + const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); + let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); + if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { + y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); + } + RichTextMenu.Instance.jumpTo(x, y); + } + } + 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 + if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + e.stopPropagation(); + } + } + + static _bulletStyleSheet: any = addStyleSheet(); + static _userStyleSheet: any = addStyleSheet(); + + 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. + 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) { + 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))); + } + } + } + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } + (e.nativeEvent as any).formattedHandled = true; + // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) { + // let href = (e.target as any).href; + // let location: string; + // if ((e.target as any).attributes.location) { + // location = (e.target as any).attributes.location.value; + // } + // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); + // if (node) { + // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); + // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + // href = link && link.attrs.href; + // location = link && link.attrs.location; + // } + // } + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkClicked) { + // DocServer.GetRefField(linkClicked).then(async linkDoc => { + // (linkDoc instanceof Doc) && + // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false); + // }); + // } + // } else { + // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); + // this.props.addDocument && this.props.addDocument(webDoc); + // } + // e.stopPropagation(); + // e.preventDefault(); + // } + // } + + if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { + this.props.select(e.ctrlKey); + this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); + } + } + + // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. + hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) { + clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); + const pos = this._editorView!.posAtCoords({ left: x, top: y }); + 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); + 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++) { + 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) { + list_node = node; + break; + } + } + } + if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { + if (select) { + 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))); + } + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + } else if (Math.abs(pos.pos - pos.inside) < 2) { + if (!highlightOnly) { + const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0; + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility })); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset))); + } + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + } + } + } + } + onMouseUp = (e: React.MouseEvent): void => { + e.stopPropagation(); + + const view = this._editorView as any; + // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there + // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. + if (view.mouseDown) { + const originalUpHandler = view.mouseDown.up; + view.root.removeEventListener("mouseup", originalUpHandler); + view.mouseDown.up = (e: MouseEvent) => { + !(e as any).formattedHandled && originalUpHandler(e); + // e.stopPropagation(); + (e as any).formattedHandled = true; + }; + view.root.addEventListener("mouseup", view.mouseDown.up); + } + } + + richTextMenuPlugin() { + return new Plugin({ + view(newView) { + RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView); + return RichTextMenu.Instance; + } + }); + } + + public static HadSelection: boolean = false; + onBlur = (e: any) => { + FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + //DictationManager.Controls.stop(false); + if (this._undoTyping) { + this._undoTyping.end(); + this._undoTyping = undefined; + } + this.doLinkOnDeselect(); + + // move the richtextmenu offscreen + if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); + } + + _lastTimedMark: Mark | undefined = undefined; + onKeyPress = (e: React.KeyboardEvent) => { + if (e.altKey) { + e.preventDefault(); + return; + } + const state = this._editorView!.state; + if (!state.selection.empty && e.key === "%") { + this._rules!.EnteringStyle = true; + e.preventDefault(); + e.stopPropagation(); + return; + } + + if (state.selection.empty || !this._rules!.EnteringStyle) { + this._rules!.EnteringStyle = false; + } + 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?.(); + SelectionManager.DeselectAll(); + } + e.stopPropagation(); + 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.floor(Date.now() / 1000) }); + this._lastTimedMark = mark; + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); + + if (!this._undoTyping) { + this._undoTyping = UndoManager.StartBatch("undoTyping"); + } + } + + onscrolled = (ev: React.UIEvent) => { + this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; + } + @action + tryUpdateHeight(limitHeight?: number) { + let scrollHeight = this._ref.current?.scrollHeight; + if (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) { + scrollHeight = limitHeight; + this.layoutDoc.limitHeight = undefined; + this.layoutDoc._autoHeight = false; + } + const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); + const dh = NumCast(this.layoutDoc._height, 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; + } + } + } + + @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } + sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); + @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } + render() { + TraceMobx(); + const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; + if (this.props.isSelected()) { + this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); + } else if (FormattedTextBoxComment.textBox === this) { + FormattedTextBoxComment.Hide(); + } + return ( + +
this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)} + onBlur={this.onBlur} + onPointerUp={this.onPointerUp} + onPointerDown={this.onPointerDown} + onMouseUp={this.onMouseUp} + onWheel={this.onPointerWheel} + onPointerEnter={action(() => this._entered = true)} + onPointerLeave={action((e: React.PointerEvent) => { + this._entered = false; + const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); + for (let child: any = target; child; child = child?.parentElement) { + if (child === this._ref.current!) { + this._entered = true; + } + } + })} + > +
+
+
+ {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? +
this.toggleSidebar()} /> : +
+ + +
this.toggleSidebar()} /> +
} + {!this.props.Document._showAudio ? (null) : +
{ + runInAction(() => this._recording = !this._recording); + setTimeout(() => this._editorView!.focus(), 500); + e.stopPropagation(); + }} > + +
} +
+ ); + } +} diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss new file mode 100644 index 000000000..2dd63ec21 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -0,0 +1,33 @@ +.FormattedTextBox-tooltip { + position: absolute; + pointer-events: none; + z-index: 20; + background: white; + border: 1px solid silver; + border-radius: 2px; + margin-bottom: 7px; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + } + .FormattedTextBox-tooltip:before { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -6px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: silver; + } + .FormattedTextBox-tooltip:after { + content: ""; + height: 0; width: 0; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: -4.5px; + border: 5px solid transparent; + border-bottom-width: 0; + border-top-color: white; + } \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx new file mode 100644 index 000000000..f9e4c5210 --- /dev/null +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -0,0 +1,236 @@ +import { Mark, ResolvedPos } from "prosemirror-model"; +import { EditorState, Plugin } from "prosemirror-state"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocCastAsync } from "../../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { schema } from "./schema_rts"; +import { Transform } from "../../../util/Transform"; +import { ContentFittingDocumentView } from "../ContentFittingDocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import './FormattedTextBoxComment.scss'; +import React = require("react"); +import { Docs } from "../../../documents/Documents"; +import wiki from "wikijs"; +import { DocumentType } from "../../../documents/DocumentTypes"; + +export let formattedTextBoxCommentPlugin = new Plugin({ + view(editorView) { return new FormattedTextBoxComment(editorView); } +}); +export function findOtherUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); +} +export function findUserMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid); +} +export function findLinkMark(marks: Mark[]): Mark | undefined { + return marks.find(m => m.type === schema.marks.link); +} +export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let before = 0; + let nbef = rpos.nodeBefore; + while (nbef && finder(nbef.marks)) { + before += nbef.nodeSize; + rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); + rpos && (nbef = rpos.nodeBefore); + } + return before; +} +export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { + let after = 0; + let naft = rpos.nodeAfter; + while (naft && finder(naft.marks)) { + after += naft.nodeSize; + rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); + rpos && (naft = rpos.nodeAfter); + } + return after; +} + + +export class FormattedTextBoxComment { + static tooltip: HTMLElement; + static tooltipText: HTMLElement; + static tooltipInput: HTMLInputElement; + static start: number; + static end: number; + static mark: Mark; + static textBox: FormattedTextBox | undefined; + static linkDoc: Doc | undefined; + constructor(view: any) { + if (!FormattedTextBoxComment.tooltip) { + const root = document.getElementById("root"); + FormattedTextBoxComment.tooltipInput = document.createElement("input"); + FormattedTextBoxComment.tooltipInput.type = "checkbox"; + FormattedTextBoxComment.tooltip = document.createElement("div"); + FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; + FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; + FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; + FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; + FormattedTextBoxComment.tooltip.style.overflow = "hidden"; + FormattedTextBoxComment.tooltip.style.display = "none"; + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); + FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { + const keep = e.target && (e.target as any).type === "checkbox" ? true : false; + const textBox = FormattedTextBoxComment.textBox; + if (FormattedTextBoxComment.linkDoc && !keep && textBox) { + if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + } else { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } + } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight"); + } + keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( + FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); + e.stopPropagation(); + e.preventDefault(); + }; + root && root.appendChild(FormattedTextBoxComment.tooltip); + } + } + + public static Hide() { + FormattedTextBoxComment.textBox = undefined; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } + public static SetState(textBox: any, start: number, end: number, mark: Mark) { + FormattedTextBoxComment.textBox = textBox; + FormattedTextBoxComment.start = start; + FormattedTextBoxComment.end = end; + FormattedTextBoxComment.mark = mark; + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); + } + + static update(view: EditorView, lastState?: EditorState) { + const state = view.state; + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && + lastState.selection.eq(state.selection)) { + return; + } + FormattedTextBoxComment.linkDoc = undefined; + + const textBox = FormattedTextBoxComment.textBox; + if (!textBox || !textBox.props) { + return; + } + let set = "none"; + let nbef = 0; + FormattedTextBoxComment.tooltipInput.style.display = "none"; + FormattedTextBoxComment.tooltip.style.width = ""; + FormattedTextBoxComment.tooltip.style.height = ""; + (FormattedTextBoxComment.tooltipText as any).href = ""; + FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; + FormattedTextBoxComment.tooltipText.style.overflow = ""; + // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date + if (state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); + const noselection = view.state.selection.$from === view.state.selection.$to; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + const mark = child && findOtherUserMark(child.marks); + if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); + } + if (mark && child && ((nbef && naft) || !noselection)) { + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); + set = ""; + FormattedTextBoxComment.tooltipInput.style.display = ""; + } + } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. + if (set === "none" && state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + const naft = findEndOfMark(state.selection.$from, view, findLinkMark); + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + const mark = child && findLinkMark(child.marks); + if (mark && child && nbef && naft && mark.attrs.showPreview) { + FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; + if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { + wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); + } else { + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + } + if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { + FormattedTextBoxComment.tooltipText.textContent = "target not found..."; + (FormattedTextBoxComment.tooltipText as any).href = ""; + const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { } + docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { + if (linkDoc instanceof Doc) { + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; + FormattedTextBoxComment.linkDoc = linkDoc; + const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); + const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + if (anchor !== target && anchor && target) { + target.scrollY = NumCast(anchor?.y); + } + if (target) { + ReactDOM.render( Math.min(350, NumCast(target._width, 350))} + PanelHeight={() => Math.min(250, NumCast(target._height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; + FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; + } + // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + // let text = ext && StrCast(ext.text); + // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); + } + }); + } + set = ""; + } + } + if (set !== "none") { + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + const left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); + } + + destroy() { } +} diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx new file mode 100644 index 000000000..8f98da0fd --- /dev/null +++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx @@ -0,0 +1,138 @@ +import { NodeSelection } from "prosemirror-state"; +import { Doc } from "../../../../new_fields/Doc"; +import { DocServer } from "../../../DocServer"; +import { DocumentManager } from "../../../util/DocumentManager"; +import React = require("react"); + +import { schema } from "./schema_rts"; + +interface IImageResizeView { + node: any; + view: any; + getPos: any; + addDocTab: any; +} + +export class ImageResizeView extends React.Component { + constructor(props: IImageResizeView) { + super(props); + } + + onClickImg = (e: any) => { + e.stopPropagation(); + e.preventDefault(); + if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) { + this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2)))); + } + } + + onPointerDownImg = (e: any) => { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document, + document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false)); + } + } + + onPointerDownHandle = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + const elementImage = document.getElementById("imageId") as HTMLElement; + const wid = Number(getComputedStyle(elementImage).width.replace(/px/, "")); + const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, "")); + const startX = e.pageX; + const startWidth = parseFloat(this.props.node.attrs.width); + + const onpointermove = (e: any) => { + const elementOuter = document.getElementById("outerId") as HTMLElement; + + const currentX = e.pageX; + const diffInPx = currentX - startX; + elementOuter.style.width = `${startWidth + diffInPx}`; + elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`; + }; + + const onpointerup = () => { + document.removeEventListener("pointermove", onpointermove); + document.removeEventListener("pointerup", onpointerup); + const pos = this.props.view.state.selection.from; + const elementOuter = document.getElementById("outerId") as HTMLElement; + this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height })); + this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos)))); + }; + + document.addEventListener("pointermove", onpointermove); + document.addEventListener("pointerup", onpointerup); + } + + selectNode() { + const elementImage = document.getElementById("imageId") as HTMLElement; + const elementHandle = document.getElementById("handleId") as HTMLElement; + + elementImage.classList.add("ProseMirror-selectednode"); + elementHandle.style.display = ""; + } + + deselectNode() { + const elementImage = document.getElementById("imageId") as HTMLElement; + const elementHandle = document.getElementById("handleId") as HTMLElement; + + elementImage.classList.remove("ProseMirror-selectednode"); + elementHandle.style.display = "none"; + } + + + render() { + + const outerStyle = { + width: this.props.node.attrs.width, + height: this.props.node.attrs.height, + display: "inline-block", + overflow: "hidden", + float: this.props.node.attrs.float + }; + + const imageStyle = { + width: "100%", + }; + + const handleStyle = { + position: "absolute", + width: "20px", + heiht: "20px", + backgroundColor: "blue", + borderRadius: "15px", + display: "none", + bottom: "-10px", + right: "-10px" + + }; + + + + return ( +
+ + + + + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts new file mode 100644 index 000000000..d80e64634 --- /dev/null +++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts @@ -0,0 +1,143 @@ +import clamp from '../../../util/clamp'; +import convertToCSSPTValue from '../../../util/convertToCSSPTValue'; +import toCSSLineSpacing from '../../../util/toCSSLineSpacing'; +import { Node, DOMOutputSpec } from 'prosemirror-model'; + +//import type { NodeSpec } from './Types'; +type NodeSpec = { + attrs?: { [key: string]: any }, + content?: string, + draggable?: boolean, + group?: string, + inline?: boolean, + name?: string, + parseDOM?: Array, + toDOM?: (node: any) => DOMOutputSpec, +}; + +// This assumes that every 36pt maps to one indent level. +export const INDENT_MARGIN_PT_SIZE = 36; +export const MIN_INDENT_LEVEL = 0; +export const MAX_INDENT_LEVEL = 7; +export const ATTRIBUTE_INDENT = 'data-indent'; + +export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']); + +const ALIGN_PATTERN = /(left|right|center|justify)/; + +// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js +// :: NodeSpec A plain paragraph textblock. Represented in the DOM +// as a `

` element. +const ParagraphNodeSpec: NodeSpec = { + attrs: { + align: { default: null }, + color: { default: null }, + id: { default: null }, + indent: { default: null }, + inset: { default: null }, + lineSpacing: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingBottom: { default: null }, + // TODO: Add UI to let user edit / clear padding. + paddingTop: { default: null }, + }, + content: 'inline*', + group: 'block', + parseDOM: [{ tag: 'p', getAttrs }], + toDOM, +}; + +function getAttrs(dom: HTMLElement): Object { + const { + lineHeight, + textAlign, + marginLeft, + paddingTop, + paddingBottom, + } = dom.style; + + let align = dom.getAttribute('align') || textAlign || ''; + align = ALIGN_PATTERN.test(align) ? align : ""; + + let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10); + + if (!indent && marginLeft) { + indent = convertMarginLeftToIndentValue(marginLeft); + } + + indent = indent || MIN_INDENT_LEVEL; + + const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null; + + const id = dom.getAttribute('id') || ''; + return { align, indent, lineSpacing, paddingTop, paddingBottom, id }; +} + +function toDOM(node: Node): DOMOutputSpec { + const { + align, + indent, + inset, + lineSpacing, + paddingTop, + paddingBottom, + id, + } = node.attrs; + const attrs: { [key: string]: any } | null = {}; + + let style = ''; + if (align && align !== 'left') { + style += `text-align: ${align};`; + } + + if (lineSpacing) { + const cssLineSpacing = toCSSLineSpacing(lineSpacing); + style += + `line-height: ${cssLineSpacing};` + + // This creates the local css variable `--czi-content-line-height` + // that its children may apply. + `--czi-content-line-height: ${cssLineSpacing}`; + } + + if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) { + style += `padding-top: ${paddingTop};`; + } + + if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) { + style += `padding-bottom: ${paddingBottom};`; + } + + if (indent) { + style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`; + } + + if (inset) { + style += `margin-left: ${inset}; margin-right: ${inset};`; + } + + style && (attrs.style = style); + + if (indent) { + attrs[ATTRIBUTE_INDENT] = String(indent); + } + + if (id) { + attrs.id = id; + } + + return ['p', attrs, 0]; +} + +export const toParagraphDOM = toDOM; +export const getParagraphNodeAttrs = getAttrs; + +export function convertMarginLeftToIndentValue(marginLeft: string): number { + const ptValue = convertToCSSPTValue(marginLeft); + return clamp( + MIN_INDENT_LEVEL, + Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), + MAX_INDENT_LEVEL + ); +} + +export default ParagraphNodeSpec; \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts new file mode 100644 index 000000000..a0b02880e --- /dev/null +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -0,0 +1,241 @@ +import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { undoInputRule } from "prosemirror-inputrules"; +import { Schema } from "prosemirror-model"; +import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; +import { splitListItem, wrapInList, } from "prosemirror-schema-list"; +import { EditorState, Transaction, TextSelection } from "prosemirror-state"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { Docs } from "../../../documents/Documents"; +import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types"; +import { Doc } from "../../../../new_fields/Doc"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { Id } from "../../../../new_fields/FieldSymbols"; + +const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; + +export type KeyMap = { [key: string]: any }; + +export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { + let fontSize: number | undefined = undefined; + tx2.doc.descendants((node: any, offset: any, index: any) => { + if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { + const path = (tx2.doc.resolve(offset) as any).path; + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); + if (node.type === schema.nodes.ordered_list) depth++; + fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; + const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); + } + }); + return tx2; +}; +export default function buildKeymap>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { + const keys: { [key: string]: any } = {}; + + function bind(key: string, cmd: any) { + if (mapKeys) { + const mapped = mapKeys[key]; + if (mapped === false) return; + if (mapped) key = mapped; + } + keys[key] = cmd; + } + + bind("Mod-z", undo); + bind("Shift-Mod-z", redo); + bind("Backspace", undoInputRule); + + !mac && bind("Mod-y", redo); + + bind("Alt-ArrowUp", joinUp); + bind("Alt-ArrowDown", joinDown); + bind("Mod-BracketLeft", lift); + bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { + dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); + (document.activeElement as any).blur?.(); + SelectionManager.DeselectAll(); + }); + + bind("Mod-b", toggleMark(schema.marks.strong)); + bind("Mod-B", toggleMark(schema.marks.strong)); + + bind("Mod-e", toggleMark(schema.marks.em)); + bind("Mod-E", toggleMark(schema.marks.em)); + + bind("Mod-u", toggleMark(schema.marks.underline)); + bind("Mod-U", toggleMark(schema.marks.underline)); + + bind("Mod-`", toggleMark(schema.marks.code)); + + bind("Ctrl-.", wrapInList(schema.nodes.bullet_list)); + + bind("Ctrl-n", wrapInList(schema.nodes.ordered_list)); + + bind("Ctrl->", wrapIn(schema.nodes.blockquote)); + + // bind("^", (state: EditorState, dispatch: (tx: Transaction) => void) => { + // let newNode = schema.nodes.footnote.create({}); + // if (dispatch && state.selection.from === state.selection.to) { + // let tr = state.tr; + // tr.replaceSelectionWith(newNode); // replace insertion with a footnote. + // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display + // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)))); + // return true; + // } + // return false; + // }); + + + const cmd = chainCommands(exitCode, (state, dispatch) => { + if (dispatch) { + dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView()); + return true; + } + return false; + }); + bind("Mod-Enter", cmd); + bind("Shift-Enter", cmd); + mac && bind("Ctrl-Enter", cmd); + + + bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph)); + + bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); + + for (let i = 1; i <= 6; i++) { + bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); + } + + const hr = schema.nodes.horizontal_rule; + bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => { + dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); + return true; + }); + + bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const ref = state.selection; + const range = ref.$from.blockRange(ref.$to); + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { // couldn't sink into an existing list, so wrap in a new one + const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); + if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + // when promoting to a list, assume list will format things so don't copy the stored marks. + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { + console.log("bullet promote fail"); + } + } + }); + + bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + + if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + })) { + console.log("bullet demote fail"); + } + }); + bind("Ctrl-Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const layoutDoc = props.Document; + const originalDoc = layoutDoc.rootDocument || layoutDoc; + if (originalDoc instanceof Doc) { + const layoutKey = StrCast(originalDoc.layoutKey); + const newDoc = Docs.Create.TextDocument("", { + layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, + layoutKey, + _singleLine: BoolCast(originalDoc._singleLine), + x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) + }); + if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = originalDoc[layoutKey]; + } + FormattedTextBox.SelectOnLoad = newDoc[Id]; + props.addDocument(newDoc); + } + }); + + const splitMetadata = (marks: any, tx: Transaction) => { + marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); + marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); + return tx; + }; + const addTextOnRight = (force: boolean) => { + const layoutDoc = props.Document; + const originalDoc = layoutDoc.rootDocument || layoutDoc; + if (force || props.Document._singleLine) { + const layoutKey = StrCast(originalDoc.layoutKey); + const newDoc = Docs.Create.TextDocument("", { + layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, + layoutKey, + _singleLine: BoolCast(originalDoc._singleLine), + x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height) + }); + if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = originalDoc[layoutKey]; + } + FormattedTextBox.SelectOnLoad = newDoc[Id]; + props.addDocument(newDoc); + return true; + } + return false; + }; + bind("Alt-Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + return addTextOnRight(true); + }); + bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + if (addTextOnRight(false)) return true; + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + if (!splitListItem(schema.nodes.list_item)(state, dispatch)) { + if (!splitBlockKeepMarks(state, (tx3: Transaction) => { + splitMetadata(marks, tx3); + if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { + dispatch(tx3); + } + })) { + return false; + } + } + return true; + }); + bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + dispatch(splitMetadata(marks, state.tr)); + return false; + }); + bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { + const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { + return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); + }); + const path = (state.doc.resolve(state.selection.from - 1) as any).path; + const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; + const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; + if (anchor >= 0) { + const textsel = TextSelection.create(state.doc, anchor, range!.end); + const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; + let whitespace = text.length - 1; + for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } + if (text.endsWith(":")) { + dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). + addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); + } + } + return false; + }); + + + return keys; +} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss new file mode 100644 index 000000000..36da769c3 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -0,0 +1,121 @@ +@import "../../globalCssVariables"; + +.button-dropdown-wrapper { + position: relative; + + .dropdown-button { + width: 15px; + padding-left: 5px; + padding-right: 5px; + } + + .dropdown-button-combined { + width: 50px; + display: flex; + justify-content: space-between; + + svg:nth-child(2) { + margin-top: 2px; + } + } + + .dropdown { + position: absolute; + top: 35px; + left: 0; + background-color: #323232; + color: $light-color-secondary; + border: 1px solid #4d4d4d; + border-radius: 0 6px 6px 6px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + min-width: 150px; + padding: 5px; + font-size: 12px; + z-index: 10001; + + button { + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + padding: 6px; + margin: 5px 0; + font-size: 10px; + + &:hover { + background-color: black; + } + + &:last-child { + margin-bottom: 0; + } + } + } + + input { + color: black; + } +} + +.link-menu { + .divider { + background-color: white; + height: 1px; + width: 100%; + } +} + +.color-preview-button { + .color-preview { + width: 100%; + height: 3px; + margin-top: 3px; + } +} + +.color-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + button.color-button { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: 2px solid transparent !important; + padding: 3px; + + &.active { + border: 2px solid white !important; + } + } +} + +select { + background-color: #323232; + color: white; + border: 1px solid black; + // border-top: none; + // border-bottom: none; + font-size: 12px; + height: 100%; + margin-right: 3px; + + &:focus, + &:hover { + background-color: black; + } + + &::-ms-expand { + color: white; + } +} + +.row-2 { + display: flex; + justify-content: space-between; + + >div { + display: flex; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx new file mode 100644 index 000000000..cc04e0d6d --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -0,0 +1,875 @@ +import React = require("react"); +import AntimodeMenu from "../../AntimodeMenu"; +import { observable, action, } from "mobx"; +import { observer } from "mobx-react"; +import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; +import { schema } from "./schema_rts"; +import { EditorView } from "prosemirror-view"; +import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; +import { updateBullets } from "./ProsemirrorExampleTransfer"; +import { FieldViewProps } from "../FieldView"; +import { Cast, StrCast } from "../../../../new_fields/Types"; +import { FormattedTextBoxProps } from "./FormattedTextBox"; +import { unimplementedFunction, Utils } from "../../../../Utils"; +import { wrapInList } from "prosemirror-schema-list"; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField'; +import "./RichTextMenu.scss"; +import { DocServer } from "../../../DocServer"; +import { Doc } from "../../../../new_fields/Doc"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { LinkManager } from "../../../util/LinkManager"; +const { toggleMark, setBlockType } = require("prosemirror-commands"); + +library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); + +@observer +export default class RichTextMenu extends AntimodeMenu { + static Instance: RichTextMenu; + public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable + + private view?: EditorView; + public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + + public _brushMap: Map> = new Map(); + private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; + private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[]; + private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[]; + private fontColors: (string | undefined)[]; + private highlightColors: (string | undefined)[]; + + @observable private collapsed: boolean = false; + @observable private boldActive: boolean = false; + @observable private italicsActive: boolean = false; + @observable private underlineActive: boolean = false; + @observable private strikethroughActive: boolean = false; + @observable private subscriptActive: boolean = false; + @observable private superscriptActive: boolean = false; + + @observable private activeFontSize: string = ""; + @observable private activeFontFamily: string = ""; + @observable private activeListType: string = ""; + + @observable private brushIsEmpty: boolean = true; + @observable private brushMarks: Set = new Set(); + @observable private showBrushDropdown: boolean = false; + + @observable private activeFontColor: string = "black"; + @observable private showColorDropdown: boolean = false; + + @observable private activeHighlightColor: string = "transparent"; + @observable private showHighlightDropdown: boolean = false; + + @observable private currentLink: string | undefined = ""; + @observable private showLinkDropdown: boolean = false; + + constructor(props: Readonly<{}>) { + super(props); + RichTextMenu.Instance = this; + this._canFade = false; + + 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: "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 }, + { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize }, + { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, + { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option + ]; + + this.fontFamilyOptions = [ + { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } }, + { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } }, + { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } }, + { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } }, + { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } }, + { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } }, + { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } }, + { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true }, + // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true }, + ]; + + this.listTypeOptions = [ + { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, + { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, + ]; + + this.fontColors = [ + DarkPastelSchemaPalette.get("pink2"), + DarkPastelSchemaPalette.get("purple4"), + DarkPastelSchemaPalette.get("bluegreen1"), + DarkPastelSchemaPalette.get("yellow4"), + DarkPastelSchemaPalette.get("red2"), + DarkPastelSchemaPalette.get("bluegreen7"), + DarkPastelSchemaPalette.get("bluegreen5"), + DarkPastelSchemaPalette.get("orange1"), + "#757472", + "#000" + ]; + + this.highlightColors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + "white", + "transparent" + ]; + } + + @action + changeView(view: EditorView) { + this.view = view; + } + + update(view: EditorView, lastState: EditorState | undefined) { + this.updateFromDash(view, lastState, this.editorProps); + } + + + public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { + if (this.view) { + const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). + addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + return this.view.state.selection.$from.nodeAfter?.text || ""; + } + return ""; + } + + @action + public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + if (!view) { + console.log("no editor? why?"); + return; + } + this.view = view; + const state = view.state; + props && (this.editorProps = props); + + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; + + // update active marks + const activeMarks = this.getActiveMarksOnSelection(); + this.setActiveMarkButtons(activeMarks); + + // update active font family and size + const active = this.getActiveFontStylesOnSelection(); + const activeFamilies = active && active.get("families"); + const activeSizes = active && active.get("sizes"); + + this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; + this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various"; + + // update link in current selection + const targetTitle = await this.getTextLinkTargetTitle(); + this.setCurrentLink(targetTitle); + } + + 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, (tx: any) => { + const { from, $from, to, empty } = tx.selection; + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); + } else dispatch(tx); + }); + } + } + } + + // finds font sizes and families in selection + getActiveFontStylesOnSelection() { + if (!this.view) return; + + const activeFamilies: string[] = []; + const activeSizes: string[] = []; + const state = this.view.state; + const pos = this.view.state.selection.$from; + const ref_node = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); + }); + } + + const styles = new Map(); + styles.set("families", activeFamilies); + styles.set("sizes", activeSizes); + return styles; + } + + getMarksInSelection(state: EditorState) { + const found = new Set(); + const { from, to } = state.selection as TextSelection; + state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); + return found; + } + + //finds all active marks on selection in given group + getActiveMarksOnSelection() { + if (!this.view) return; + + const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; + if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); + //current selection + const { empty, ranges, $to } = this.view.state.selection as TextSelection; + const state = this.view.state; + let activeMarks: MarkType[] = []; + if (!empty) { + activeMarks = markGroup.filter(mark => { + const has = false; + for (let i = 0; !has && i < ranges.length; i++) { + return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark); + } + return false; + }); + } + else { + const pos = this.view.state.selection.$from; + const ref_node: ProsNode | null = this.reference_node(pos); + if (ref_node !== null && ref_node !== this.view.state.doc) { + if (ref_node.isText) { + } + else { + return []; + } + activeMarks = markGroup.filter(mark_type => { + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + } + const mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); + }); + } + } + return activeMarks; + } + + destroy() { + this.fadeOut(true); + } + + @action + setActiveMarkButtons(activeMarks: MarkType[] | undefined) { + if (!activeMarks) return; + + this.boldActive = false; + this.italicsActive = false; + this.underlineActive = false; + this.strikethroughActive = false; + this.subscriptActive = false; + this.superscriptActive = false; + + activeMarks.forEach(mark => { + switch (mark.name) { + case "strong": this.boldActive = true; break; + case "em": this.italicsActive = true; break; + case "underline": this.underlineActive = true; break; + case "strikethrough": this.strikethroughActive = true; break; + case "subscript": this.subscriptActive = true; break; + case "superscript": this.superscriptActive = true; break; + } + }); + } + + createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) { + const self = this; + function onClick(e: React.PointerEvent) { + 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.setActiveMarkButtons(self.getActiveMarksOnSelection()); + } + + return ( + + ); + } + + 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; + function onChange(e: React.ChangeEvent) { + e.stopPropagation(); + e.preventDefault(); + options.forEach(({ label, mark, command }) => { + if (e.target.value === label) { + self.view && mark && command(mark, self.view); + } + }); + } + return ; + } + + 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; + function onChange(val: string) { + options.forEach(({ label, node, command }) => { + if (val === label) { + self.view && node && command(node); + } + }); + } + return ; + } + + changeFontSize = (mark: Mark, view: EditorView) => { + this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch); + } + + changeFontFamily = (mark: Mark, view: EditorView) => { + this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch); + } + + // TODO: remove doesn't work + //remove all node type and apply the passed-in one to the selected text + changeListType = (nodeType: NodeType | undefined) => { + if (!this.view) return; + + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(this.view.state, this.view.dispatch); + } else { + const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view!.dispatch(tx2); + })) { + const tx2 = this.view.state.tr; + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view.dispatch(tx3); + } + } + } + + insertSummarizer(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + const mark = state.schema.marks.summarize.create(); + const tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + const content = tr.selection.content(); + const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + + @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } + + // todo: add brushes to brushMap to save with a style name + onBrushNameKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); + this._brushNameRef.current!.style.background = "lightGray"; + } + } + _brushNameRef = React.createRef(); + + createBrushButton() { + const self = this; + function onBrushClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.fillBrush(self.view.state, self.view.dispatch); + } + + let label = "Stored marks: "; + if (this.brushMarks && this.brushMarks.size > 0) { + this.brushMarks.forEach((mark: Mark) => { + const markType = mark.type; + label += markType.name; + label += ", "; + }); + label = label.substring(0, label.length - 2); + } else { + label = "No marks are currently stored"; + } + + const button = + ; + + const dropdownContent = +

+

{label}

+ + +
; + + return ( + + ); + } + + @action + clearBrush() { + RichTextMenu.Instance.brushIsEmpty = true; + RichTextMenu.Instance.brushMarks = new Set(); + } + + @action + fillBrush(state: EditorState, dispatch: any) { + if (!this.view) return; + + if (this.brushIsEmpty) { + const selected_marks = this.getMarksInSelection(this.view.state); + if (selected_marks.size >= 0) { + this.brushMarks = selected_marks; + this.brushIsEmpty = !this.brushIsEmpty; + } + } + else { + const { from, to, $from } = this.view.state.selection; + if (!this.view.state.selection.empty && $from && $from.nodeAfter) { + if (this.brushMarks && to - from > 0) { + this.view.dispatch(this.view.state.tr.removeMark(from, to)); + Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { + this.setMark(mark, this.view!.state, this.view!.dispatch); + }); + } + } + else { + this.brushIsEmpty = !this.brushIsEmpty; + } + } + } + + @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } + @action setActiveColor(color: string) { this.activeFontColor = color; } + + createColorButton() { + const self = this; + function onColorClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + } + function changeColor(e: React.PointerEvent, color: string) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.setActiveColor(color); + self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + } + + const button = + ; + + const dropdownContent = +
+

Change font color:

+
+ {this.fontColors.map(color => { + if (color) { + return this.activeFontColor === color ? + : + ; + } + })} +
+
; + + return ( + + ); + } + + 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; + } + this.setMark(colorMark, state, dispatch); + } + + @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; } + @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } + + createHighlighterButton() { + const self = this; + function onHighlightClick(e: React.PointerEvent) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + } + function changeHighlight(e: React.PointerEvent, color: string) { + e.preventDefault(); + e.stopPropagation(); + self.view && self.view.focus(); + self.setActiveHighlight(color); + self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + } + + const button = + ; + + const dropdownContent = +
+

Change highlight color:

+
+ {this.highlightColors.map(color => { + if (color) { + return this.activeHighlightColor === color ? + : + ; + } + })} +
+
; + + return ( + + ); + } + + insertHighlight(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); + } + + @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; } + @action setCurrentLink(link: string) { this.currentLink = link; } + + createLinkButton() { + const self = this; + + function onLinkChange(e: React.ChangeEvent) { + self.setCurrentLink(e.target.value); + } + + const link = this.currentLink ? this.currentLink : ""; + + const button = ; + + const dropdownContent = +
+

Linked to:

+ + +
+ +
; + + return ( + + ); + } + + async getTextLinkTargetTitle() { + if (!this.view) return; + + const node = this.view.state.selection.$from.nodeAfter; + const link = node && node.marks.find(m => m.type.name === "link"); + if (link) { + const href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + const linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + const anchor1 = await Cast(linkDoc.anchor1, Doc); + const anchor2 = await Cast(linkDoc.anchor2, Doc); + const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) { + return StrCast(anchor2.title); + } + if (Doc.AreProtosEqual(currentDoc, anchor2)) { + return StrCast(anchor1.title); + } + } + } + } + } else { + return href; + } + } else { + return link.attrs.title; + } + } + } + + // TODO: should check for valid URL + makeLinkToURL = (target: String, lcoation: string) => { + if (!this.view) return; + + let node = this.view.state.selection.$from.nodeAfter; + let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); + this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + node = this.view.state.selection.$from.nodeAfter; + link = node && node.marks.find(m => m.type.name === "link"); + } + + deleteLink = () => { + if (!this.view) return; + + const node = this.view.state.selection.$from.nodeAfter; + const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link); + const href = link!.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + DocServer.GetRefField(linkclicked).then(async linkDoc => { + if (linkDoc instanceof Doc) { + LinkManager.Instance.deleteLink(linkDoc); + this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link)); + } + }); + } + } else { + if (node) { + 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)); + } + } + } + } + + linkExtend($start: ResolvedPos, href: string) { + const mark = this.view!.state.schema.marks.link; + + let startIndex = $start.index(); + let endIndex = $start.indexAfter(); + + while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--; + while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++; + + let startPos = $start.start(); + let endPos = startPos; + for (let i = 0; i < endIndex; i++) { + const size = $start.parent.child(i).nodeSize; + if (i < startIndex) startPos += size; + endPos += size; + } + return { from: startPos, to: endPos }; + } + + reference_node(pos: ResolvedPos): ProsNode | null { + if (!this.view) return null; + + let ref_node: ProsNode = this.view.state.doc; + if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { + ref_node = pos.nodeBefore; + } + else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { + ref_node = pos.nodeAfter; + } + else if (pos.pos > 0) { + let skip = false; + for (let i: number = pos.pos - 1; i > 0; i--) { + this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { + if (node.isLeaf && !skip) { + ref_node = node; + skip = true; + } + + }); + } + } + if (!ref_node.isLeaf && ref_node.childCount > 0) { + ref_node = ref_node.child(0); + } + return ref_node; + } + + @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } + @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } + + @action + toggleMenuPin = (e: React.MouseEvent) => { + this.Pinned = !this.Pinned; + if (!this.Pinned) { + this.fadeOut(true); + } + } + + @action + protected toggleCollapse = (e: React.MouseEvent) => { + this.collapsed = !this.collapsed; + setTimeout(() => { + const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width); + RichTextMenu.Instance.jumpTo(x, this._top); + }, 0); + } + + render() { + + 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)), + this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), + this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), + this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), + this.createColorButton(), + this.createHighlighterButton(), + this.createLinkButton(), + this.createBrushButton(), + this.createButton("indent", "Summarize", undefined, this.insertSummarizer), + ]}
; + + 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()} +
+
; + + return ( +
+ {this.getElementWithRows([row1, row2], 2, false)} +
+ ); + } +} + +interface ButtonDropdownProps { + view?: EditorView; + button: JSX.Element; + dropdownContent: JSX.Element; + openDropdownOnButton?: boolean; +} + +@observer +class ButtonDropdown extends React.Component { + + @observable private showDropdown: boolean = false; + private ref: HTMLDivElement | null = null; + + componentDidMount() { + document.addEventListener("pointerdown", this.onBlur); + } + + componentWillUnmount() { + document.removeEventListener("pointerdown", this.onBlur); + } + + @action + setShowDropdown(show: boolean) { + this.showDropdown = show; + } + @action + toggleDropdown() { + this.showDropdown = !this.showDropdown; + } + + onDropdownClick = (e: React.PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.props.view && this.props.view.focus(); + this.toggleDropdown(); + } + + onBlur = (e: PointerEvent) => { + setTimeout(() => { + if (this.ref !== null && !this.ref.contains(e.target as Node)) { + this.setShowDropdown(false); + } + }, 0); + } + + render() { + return ( +
this.ref = node}> + {this.props.openDropdownOnButton ? + : + <> + {this.props.button} + + } + + {this.showDropdown ? this.props.dropdownContent : (null)} +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts new file mode 100644 index 000000000..335094e23 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -0,0 +1,319 @@ +import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules"; +import { NodeSelection, TextSelection } from "prosemirror-state"; +import { DataSym, Doc } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { Cast, NumCast } from "../../../../new_fields/Types"; +import { returnFalse, Utils } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs, DocUtils } from "../../../documents/Documents"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { wrappingInputRule } from "./prosemirrorPatches"; +import RichTextMenu from "./RichTextMenu"; +import { schema } from "./schema_rts"; + +export class RichTextRules { + public Document: Doc; + public TextBox: FormattedTextBox; + public EnteringStyle: boolean = false; + constructor(doc: Doc, textBox: FormattedTextBox) { + this.Document = doc; + this.TextBox = textBox; + } + public inpRules = { + rules: [ + ...smartQuotes, + ellipsis, + emDash, + + // > blockquote + wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote), + + // 1. ordered list + wrappingInputRule( + /^1\.\s$/, + schema.nodes.ordered_list, + () => { + return ({ mapStyle: "decimal", bulletStyle: 1 }); + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } }) + ), + // a. alphabbetical list + wrappingInputRule( + /^a\.\s$/, + schema.nodes.ordered_list, + // match => { + () => { + return ({ mapStyle: "alpha", bulletStyle: 1 }); + // return ({ order: +match[1] }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } }) + ), + + // * bullet list + wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list), + + // ``` code block + textblockTypeInputRule(/^```$/, schema.nodes.code_block), + + // create an inline view of a tag stored under the '#' field + new InputRule( + new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), + (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]["#"] = tag; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + + // # heading + textblockTypeInputRule( + new RegExp(/^(#{1,6})\s$/), + schema.nodes.heading, + match => { + return ({ level: match[1].length }); + } + ), + + // set the font size using # + new InputRule( + new RegExp(/%([0-9]+)\s$/), + (state, match, start, end) => { + const size = Number(match[1]); + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); + }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ : ]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc + new InputRule( + new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/), + (state, match, start, end) => { + const fieldKey = match[1]; + const docid = match[3]?.substring(1); + const value = match[2]?.substring(1); + if (!fieldKey) { + if (docid) { + DocServer.GetRefField(docid).then(docx => { + const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid); + DocUtils.Publish(target, docid, returnFalse, returnFalse); + DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to"); + }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); + } + return state.tr; + } + if (value !== "" && value !== undefined) { + const num = value.match(/^[0-9.]/); + this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value); + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), + // create an inline view of a document {{ : }} // {{:Doc}} => show default view of document {{}} => show layout for this doc {{ : Doc}} => show layout for another doc + new InputRule( + new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/), + (state, match, start, end) => { + const fieldKey = match[1] || ""; + const fieldParam = match[2]?.replace("…", "...") || ""; + const docid = match[3]?.substring(1); + if (!fieldKey && !docid) return state.tr; + docid && DocServer.GetRefField(docid).then(docx => { + if (!(docx instanceof Doc && docx)) { + const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid); + DocUtils.Publish(docx, docid, returnFalse, returnFalse); + } + }); + const node = (state.doc.resolve(start) as any).nodeAfter; + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() }); + const sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + }), + new InputRule( + new RegExp(/>>$/), + (state, match, start, end) => { + const textDoc = this.Document[DataSym]; + const numInlines = NumCast(textDoc.inlineTextCount); + textDoc.inlineTextCount = numInlines + 1; + const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to + const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation + const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" }); + textDocInline.title = inlineFieldKey; // give the annotation its own title + textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point + textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] + textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); + textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text + textDoc[inlineFieldKey] = ""; // set a default value for the annotation + const node = (state.doc.resolve(start) as any).nodeAfter; + const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" }); + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced; + }), + // stop using active style + new InputRule( + new RegExp(/%%$/), + (state, match, start, end) => { + const tr = state.tr.deleteRange(start, end); + const marks = state.tr.selection.$anchor.nodeBefore?.marks; + return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr; + }), + + // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/[ti!x]$/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; + const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??"; + const node = (state.doc.resolve(start) as any).nodeAfter; + 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: 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|d)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + + // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/(%h|h)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/(%q|q)$/), + (state, match, start, end) => { + if (!match[0].startsWith("%") && !this.EnteringStyle) return null; + const pos = (state.doc.resolve(start) as any); + if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { + const node = state.selection.node; + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); + } + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; + } + } + return null; + }), + + + // center justify text + new InputRule( + new RegExp(/%\^$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + // left justify text + new InputRule( + new RegExp(/%\[$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + // right justify text + new InputRule( + new RegExp(/%\]$/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + new InputRule( + new RegExp(/%\(/), + (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks || []; + const mark = state.schema.marks.summarizeInclusive.create(); + sm.push(mark); + const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); + const content = selected.selection.content(); + const replaced = node ? selected.replaceRangeWith(start, end, + schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); + }), + new InputRule( + new RegExp(/%\)/), + (state, match, start, end) => { + return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); + }), + new InputRule( + new RegExp(/%f$/), + (state, match, start, end) => { + const newNode = schema.nodes.footnote.create({}); + const tr = state.tr; + tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. + return tr.setSelection(new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))); + }), + + // 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); + const marks = RichTextMenu.Instance._brushMap.get(color); + if (marks) { + const tr = state.tr.deleteRange(start, end); + return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + } + const isValidColor = (strColor: string) => { + const s = new Option().style; + s.color = strColor; + return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned + }; + if (isValidColor(color)) { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); + } + return null; + }), + ] + }; +} diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx new file mode 100644 index 000000000..33caf5751 --- /dev/null +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -0,0 +1,718 @@ +import { IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { baseKeymap, toggleMark } from "prosemirror-commands"; +import { redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state"; +import { StepMap } from "prosemirror-transform"; +import { EditorView } from "prosemirror-view"; +import * as ReactDOM from 'react-dom'; +import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Id } from "../../../../new_fields/FieldSymbols"; +import { List } from "../../../../new_fields/List"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils"; +import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { CollectionViewType } from "../../collections/CollectionView"; +import { DocumentView } from "../DocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { Transform } from "../../../util/Transform"; +import React = require("react"); + +import { schema } from "./schema_rts"; + +export class OrderedListView { + update(node: any) { + return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update + } +} + +export class ImageResizeView { + _handle: HTMLElement; + _img: HTMLElement; + _outer: HTMLElement; + constructor(node: any, view: any, getPos: any, addDocTab: any) { + //moved + this._handle = document.createElement("span"); + this._img = document.createElement("img"); + this._outer = document.createElement("span"); + this._outer.style.position = "relative"; + this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; + this._outer.style.display = "inline-block"; + this._outer.style.overflow = "hidden"; + (this._outer.style as any).float = node.attrs.float; + //moved + this._img.setAttribute("src", node.attrs.src); + this._img.style.width = "100%"; + this._handle.style.position = "absolute"; + this._handle.style.width = "20px"; + this._handle.style.height = "20px"; + this._handle.style.backgroundColor = "blue"; + this._handle.style.borderRadius = "15px"; + this._handle.style.display = "none"; + this._handle.style.bottom = "-10px"; + this._handle.style.right = "-10px"; + const self = this; + //moved + this._img.onclick = function (e: any) { + e.stopPropagation(); + e.preventDefault(); + if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); + } + }; + //moved + this._img.onpointerdown = function (e: any) { + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, + document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false)); + } + }; + //moved + this._handle.onpointerdown = function (e: any) { + e.preventDefault(); + e.stopPropagation(); + const wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); + const hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); + const startX = e.pageX; + const startWidth = parseFloat(node.attrs.width); + const onpointermove = (e: any) => { + const currentX = e.pageX; + const diffInPx = currentX - startX; + self._outer.style.width = `${startWidth + diffInPx}`; + self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`; + }; + + const onpointerup = () => { + document.removeEventListener("pointermove", onpointermove); + document.removeEventListener("pointerup", onpointerup); + const pos = view.state.selection.from; + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos)))); + }; + + document.addEventListener("pointermove", onpointermove); + document.addEventListener("pointerup", onpointerup); + }; + //Moved + this._outer.appendChild(this._img); + this._outer.appendChild(this._handle); + (this as any).dom = this._outer; + } + + selectNode() { + this._img.classList.add("ProseMirror-selectednode"); + + this._handle.style.display = ""; + } + + deselectNode() { + this._img.classList.remove("ProseMirror-selectednode"); + + this._handle.style.display = "none"; + } +} + +export class DashDocCommentView { + _collapsed: HTMLElement; + _view: any; + constructor(node: any, view: any, getPos: any) { + //moved + this._collapsed = document.createElement("span"); + this._collapsed.className = "formattedTextBox-inlineComment"; + this._collapsed.id = "DashDocCommentView-" + node.attrs.docid; + this._view = view; + //moved + const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + for (let i = getPos() + 1; i < view.state.doc.content.size; i++) { + const m = view.state.doc.nodeAt(i); + if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) { + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + } + } + 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); + return undefined; + }; + //moved + this._collapsed.onpointerdown = (e: any) => { + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerup = (e: any) => { + const target = targetNode(); + if (target) { + 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) { } + }, 0); + } + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerenter = (e: any) => { + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + e.preventDefault(); + e.stopPropagation(); + }; + //moved + this._collapsed.onpointerleave = (e: any) => { + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + e.preventDefault(); + e.stopPropagation(); + }; + + (this as any).dom = this._collapsed; + } + //moved + selectNode() { } +} + +export class DashDocView { + _dashSpan: HTMLDivElement; + _outer: HTMLElement; + _dashDoc: Doc | undefined; + _reactionDisposer: IReactionDisposer | undefined; + _renderDisposer: IReactionDisposer | undefined; + _textBox: FormattedTextBox; + + getDocTransform = () => { + const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); + return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); + } + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1; + + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._textBox = tbox; + this._dashSpan = document.createElement("div"); + this._outer = document.createElement("span"); + this._outer.style.position = "relative"; + this._outer.style.textIndent = "0"; + this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; + this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; + // this._outer.style.overflow = "hidden"; // bcz: not sure if this is needed. if it's used, then the doc doesn't highlight when you hover over a docComment + (this._outer.style as any).float = node.attrs.float; + + this._dashSpan.style.width = node.attrs.width; + this._dashSpan.style.height = node.attrs.height; + this._dashSpan.style.position = "absolute"; + this._dashSpan.style.display = "inline-block"; + this._dashSpan.onpointerleave = () => { + const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = ""; + } + }; + this._dashSpan.onpointerenter = () => { + const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid); + if (ele) { + (ele as HTMLDivElement).style.backgroundColor = "orange"; + } + }; + const removeDoc = () => { + const pos = getPos(); + const ns = new NodeSelection(view.state.doc.resolve(pos)); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + }; + const alias = node.attrs.alias; + + const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id]; + DocServer.GetRefField(docid + alias).then(async dashDoc => { + if (!(dashDoc instanceof Doc)) { + alias && DocServer.GetRefField(docid).then(async dashDocBase => { + if (dashDocBase instanceof Doc) { + const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout"; + node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + self.doRender(aliasedDoc, removeDoc, node, view, getPos); + } + }); + } else { + self.doRender(dashDoc, removeDoc, node, view, getPos); + } + }); + const self = this; + this._dashSpan.onkeydown = function (e: any) { + e.stopPropagation(); + if (e.key === "Tab" || e.key === "Enter") { + e.preventDefault(); + } + }; + this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onwheel = function (e: any) { e.preventDefault(); }; + this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; + this._outer.appendChild(this._dashSpan); + (this as any).dom = this._outer; + } + + doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) { + this._dashDoc = dashDoc; + const self = this; + const dashLayoutDoc = Doc.Layout(dashDoc); + const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey); + + if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + else { + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { + this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; + this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; + this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + }, { fireImmediately: true }); + const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { + ReactDOM.unmountComponentAtNode(this._dashSpan); + + ReactDOM.render(, this._dashSpan); + if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" })); + } catch (e) { + console.log(e); + } + } + }; + this._renderDisposer?.(); + this._renderDisposer = reaction(() => { + // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) { + // finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here? + // } + const layoutKey = StrCast(finalLayout.layoutKey); + const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1]; + if (finalLayout !== dashDoc && finalKey) { + const finalLayoutField = finalLayout[finalKey]; + if (finalLayoutField instanceof ObjectField) { + finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) }; + }, + (res) => doReactRender(res.finalLayout, res.resolvedDataDoc), + { fireImmediately: true }); + } + } + destroy() { + ReactDOM.unmountComponentAtNode(this._dashSpan); + this._reactionDisposer?.(); + } +} + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; // container for label and value + _labelSpan: HTMLSpanElement; // field label + _fieldSpan: HTMLSpanElement; // field value + _fieldCheck: HTMLInputElement; + _enumerables: HTMLDivElement; // field value + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + @observable _dashDoc: Doc | undefined; + _fieldKey: string; + _options: Doc[] = []; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldKey = node.attrs.fieldKey; + this._textBoxDoc = tbox.props.Document; + this._fieldWrapper = document.createElement("p"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + + const self = this; + + this._enumerables = document.createElement("div"); + this._enumerables.style.width = "10px"; + this._enumerables.style.height = "10px"; + this._enumerables.style.position = "relative"; + this._enumerables.style.display = "none"; + + //Moved + this._enumerables.onpointerdown = async (e) => { + e.stopPropagation(); + const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); + }; + //Moved + const updateText = (forceMatch: boolean) => { + self._enumerables.style.display = "none"; + const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; + + // look for a document whose id === the fieldKey being displayed. If there's a match, then that document + // holds the different enumerated values for the field in the titles of its collected documents. + // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. + DocServer.GetRefField(self._fieldKey).then(options => { + let modText = ""; + (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + if (modText) { + self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; + Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); + } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key + else if (self._fieldSpan.innerText.startsWith(":=")) { + self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); + } else if (self._fieldSpan.innerText.startsWith("=:=")) { + Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); + } else { + self._dashDoc![self._fieldKey] = newText; + } + }); + }; + + //Moved + this._fieldCheck = document.createElement("input"); + this._fieldCheck.id = Utils.GenerateGuid(); + this._fieldCheck.type = "checkbox"; + this._fieldCheck.style.position = "relative"; + this._fieldCheck.style.display = "none"; + this._fieldCheck.style.minWidth = "12px"; + this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; + this._fieldCheck.onchange = function (e: any) { + self._dashDoc![self._fieldKey] = e.target.checked; + }; + + this._fieldSpan = document.createElement("span"); + this._fieldSpan.id = Utils.GenerateGuid(); + this._fieldSpan.contentEditable = "true"; + this._fieldSpan.style.position = "relative"; + this._fieldSpan.style.display = "none"; + this._fieldSpan.style.minWidth = "12px"; + this._fieldSpan.style.fontSize = "large"; + this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; + this._fieldSpan.onblur = function (e: any) { updateText(false); }; + + // MOVED + const setDashDoc = (doc: Doc) => { + self._dashDoc = doc; + if (self._options?.length && !self._dashDoc[self._fieldKey]) { + self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); + } + this._labelSpan.innerHTML = `${self._fieldKey}: `; + const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); + this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none"; + }; + + //Moved + this._fieldSpan.onkeydown = function (e: any) { + e.stopPropagation(); + if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { + if (window.getSelection) { + const range = document.createRange(); + range.selectNodeContents(self._fieldSpan); + window.getSelection()!.removeAllRanges(); + window.getSelection()!.addRange(range); + } + e.preventDefault(); + } + if (e.key === "Enter") { + e.preventDefault(); + e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); + updateText(true); + } + }; + + this._labelSpan = document.createElement("span"); + this._labelSpan.style.position = "relative"; + this._labelSpan.style.fontSize = "small"; + this._labelSpan.title = "click to see related tags"; + this._labelSpan.style.fontSize = "x-small"; + this._labelSpan.onpointerdown = function (e: any) { + e.stopPropagation(); + let container = tbox.props.ContainingCollectionView; + while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { + container = container.props.ContainingCollectionView; + } + if (container) { + const alias = Doc.MakeAlias(container.props.Document); + alias.viewType = CollectionViewType.Time; + let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); + if (!list) { + alias.schemaColumns = list = new List(); + } + list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); + list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); + alias._pivotField = self._fieldKey; + tbox.props.addDocTab(alias, "onRight"); + } + }; + this._labelSpan.innerHTML = `${self._fieldKey}: `; + //MOVED + if (node.attrs.docid) { + DocServer.GetRefField(node.attrs.docid). + then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); + } else { + setDashDoc(tbox.props.DataDoc || tbox.dataDoc); + } + + //Moved + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes + const dashVal = this._dashDoc?.[self._fieldKey]; + return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; + }, fval => { + const boolVal = Cast(fval, "boolean", null); + if (boolVal === true || boolVal === false) { + this._fieldCheck.checked = boolVal; + } else { + this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; + } + this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; + this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none"; + }, { fireImmediately: true }); + + //MOVED IN ORDER + this._fieldWrapper.appendChild(this._labelSpan); + this._fieldWrapper.appendChild(this._fieldCheck); + this._fieldWrapper.appendChild(this._fieldSpan); + this._fieldWrapper.appendChild(this._enumerables); + (this as any).dom = this._fieldWrapper; + //updateText(false); + } + //MOVED + destroy() { + this._reactionDisposer?.(); + } + //moved + selectNode() { } +} + +export class FootnoteView { + innerView: any; + outerView: any; + node: any; + dom: any; + getPos: any; + + constructor(node: any, view: any, getPos: any) { + // We'll need these later + this.node = node; + this.outerView = view; + this.getPos = getPos; + + // The node's representation in the editor (empty, for now) + this.dom = document.createElement("footnote"); + this.dom.addEventListener("pointerup", this.toggle, true); + // These are used when the footnote is selected + this.innerView = null; + } + selectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = true; + this.dom.classList.add("ProseMirror-selectednode"); + if (!this.innerView) this.open(); + } + + deselectNode() { + const attrs = { ...this.node.attrs }; + attrs.visibility = false; + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.innerView) this.close(); + } + open() { + // Append a tooltip to the outer node + const tooltip = this.dom.appendChild(document.createElement("div")); + tooltip.className = "footnote-tooltip"; + // And put a sub-ProseMirror into that + this.innerView = new EditorView(tooltip, { + // You can use any node as an editor document + state: EditorState.create({ + doc: this.node, + plugins: [keymap(baseKeymap), + keymap({ + "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), + "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), + "Mod-b": toggleMark(schema.marks.strong) + }), + // new Plugin({ + // view(newView) { + // // TODO -- make this work with RichTextMenu + // // return FormattedTextBox.getToolTip(newView); + // } + // }) + ], + + }), + // This is the magic part + dispatchTransaction: this.dispatchInner.bind(this), + handleDOMEvents: { + pointerdown: ((view: any, e: PointerEvent) => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + e.stopPropagation(); + document.addEventListener("pointerup", this.ignore, true); + if (this.outerView.hasFocus()) this.innerView.focus(); + }) as any + } + + }); + setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0); + } + + ignore = (e: PointerEvent) => { + e.stopPropagation(); + document.removeEventListener("pointerup", this.ignore, true); + } + + toggle = () => { + if (this.innerView) this.close(); + else { + this.open(); + } + } + close() { + this.innerView && this.innerView.destroy(); + this.innerView = null; + this.dom.textContent = ""; + } + + dispatchInner(tr: any) { + const { state, transactions } = this.innerView.state.applyTransaction(tr); + this.innerView.updateState(state); + + if (!tr.getMeta("fromOutside")) { + const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); + for (const transaction of transactions) { + const steps = transaction.steps; + for (const step of steps) { + outerTr.step(step.map(offsetMap)); + } + } + if (outerTr.docChanged) this.outerView.dispatch(outerTr); + } + } + update(node: any) { + if (!node.sameMarkup(this.node)) return false; + this.node = node; + if (this.innerView) { + const state = this.innerView.state; + const start = node.content.findDiffStart(state.doc.content); + if (start !== null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + const overlap = start - Math.min(endA, endB); + if (overlap > 0) { endA += overlap; endB += overlap; } + this.innerView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta("fromOutside", true)); + } + } + return true; + } + + destroy() { + if (this.innerView) this.close(); + } + + stopEvent(event: any) { + return this.innerView && this.innerView.dom.contains(event.target); + } + + ignoreMutation() { return true; } +} + +export class SummaryView { + _collapsed: HTMLElement; + _view: any; + constructor(node: any, view: any, getPos: any) { + this._collapsed = document.createElement("span"); + this._collapsed.className = this.className(node.attrs.visibility); + this._view = view; + const js = node.toJSON; + node.toJSON = function () { + return js.apply(this, arguments); + }; + + this._collapsed.onpointerdown = (e: any) => { + const visible = !node.attrs.visibility; + const attrs = { ...node.attrs, visibility: visible }; + let textSelection = TextSelection.create(view.state.doc, getPos() + 1); + if (!visible) { // update summarized text and save in attrs + textSelection = this.updateSummarizedText(getPos() + 1); + attrs.text = textSelection.content(); + attrs.textslice = attrs.text.toJSON(); + } + view.dispatch(view.state.tr. + setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) + replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it + setNodeMarkup(getPos(), undefined, attrs)); // update the attrs + e.preventDefault(); + e.stopPropagation(); + this._collapsed.className = this.className(visible); + }; + (this as any).dom = this._collapsed; + } + selectNode() { } + + deselectNode() { } + + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + updateSummarizedText(start?: any) { + const mtype = this._view.state.schema.marks.summarize; + const mtypeInc = this._view.state.schema.marks.summarizeInclusive; + let endPos = start; + + const visited = new Set(); + for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) { + 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 === mtype || m.type === mtypeInc)) { + visited.add(node); + endPos = i + node.nodeSize - 1; + } + else skip = true; + } + }); + } + return TextSelection.create(this._view.state.doc, start, endPos); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx new file mode 100644 index 000000000..89908d8ee --- /dev/null +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -0,0 +1,81 @@ +import { TextSelection } from "prosemirror-state"; +import { Fragment, Node, Slice } from "prosemirror-model"; + +import React = require("react"); + +interface ISummaryView { + node: any; + view: any; + getPos: any; + self: any; +} +export class SummaryView extends React.Component { + + onPointerDown = (e: any) => { + const visible = !this.props.node.attrs.visibility; + const attrs = { ...this.props.node.attrs, visibility: visible }; + let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1); + if (!visible) { // update summarized text and save in attrs + textSelection = this.updateSummarizedText(this.props.getPos() + 1); + attrs.text = textSelection.content(); + attrs.textslice = attrs.text.toJSON(); + } + this.props.view.dispatch(this.props.view.state.tr. + setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) + replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it + setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs + e.preventDefault(); + e.stopPropagation(); + const _collapsed = document.getElementById('collapse') as HTMLElement; + _collapsed.className = this.className(visible); + } + + updateSummarizedText(start?: any) { + const mtype = this.props.view.state.schema.marks.summarize; + const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive; + let endPos = start; + + const visited = new Set(); + for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) { + let skip = false; + this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { + if (this.props.node.isLeaf && !visited.has(node) && !skip) { + if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { + visited.add(node); + endPos = i + this.props.node.nodeSize - 1; + } + else skip = true; + } + }); + } + return TextSelection.create(this.props.view.state.doc, start, endPos); + } + + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + selectNode() { } + + deselectNode() { } + + render() { + const _view = this.props.node.view; + const js = this.props.node.toJSon; + + this.props.node.toJSON = function () { + return js.apply(this, arguments); + }; + + const spanCollapsedClassName = this.className(this.props.node.attrs.visibility); + + return ( + + + + ); + + } +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss new file mode 100644 index 000000000..e2149e9c1 --- /dev/null +++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss @@ -0,0 +1,372 @@ +@import "../views/globalCssVariables"; +.ProseMirror-menu-dropdown-wrap { + display: inline-block; + position: relative; +} + +.ProseMirror-menu-dropdown { + vertical-align: 1px; + cursor: pointer; + position: relative; + padding: 0 15px 0 4px; + background: white; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } +} + +.ProseMirror-menu-submenu-wrap { + position: relative; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; + border: 1px solid rgb(223, 223, 223); + min-width: 40px; + z-index: 50000; + position: absolute; + box-sizing: content-box; + + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + + +.ProseMirror-menu-submenu-label:after { + content: ""; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 4px); +} + + .ProseMirror-icon { + display: inline-block; + // line-height: .8; + // vertical-align: -2px; /* Compensate for padding */ + // padding: 2px 8px; + cursor: pointer; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +.wrapper { + position: absolute; + pointer-events: all; + display: flex; + align-items: center; + transform: translateY(-85px); + + height: 35px; + background: #323232; + border-radius: 6px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; + pointer-events: all; + padding: 3px; + padding-bottom: 5px; + display: flex; + align-items: center; + + .ProseMirror-example-setup-style hr { + padding: 2px 10px; + border: none; + margin: 1em 0; + } + + .ProseMirror-example-setup-style hr:after { + content: ""; + display: block; + height: 1px; + background-color: silver; + line-height: 2px; + } +} + +.menuicon { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: white; + width: 18px; + height: 18px; + } +} + +.menuicon-active { + width: 25px; + height: 25px; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + // margin-top: 11px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 13px; +} + + .font-size-indicator { + font-size: 12px; + padding-right: 0px; + } + .summarize{ + color: white; + height: 20px; + text-align: center; + } + + +.brush{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; + margin-right: 15px; +} + +.brush-active{ + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 3; + fill: greenyellow; + margin-right: 15px; +} + +.dragger-wrapper { + color: #eee; + height: 22px; + padding: 0 5px; + box-sizing: content-box; + cursor: grab; + + .dragger { + width: 18px; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + border: 1px solid #4d4d4d; + color: $light-color-secondary; + // border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts new file mode 100644 index 000000000..46bf481fb --- /dev/null +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -0,0 +1,296 @@ +import React = require("react"); +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { Doc } from "../../../../new_fields/Doc"; + + +const emDOM: DOMOutputSpecArray = ["em", 0]; +const strongDOM: DOMOutputSpecArray = ["strong", 0]; +const codeDOM: DOMOutputSpecArray = ["code", 0]; + +// :: Object [Specs](#model.MarkSpec) for the marks in the schema. +export const marks: { [index: string]: MarkSpec } = { + // :: MarkSpec A link. Has `href` and `title` attributes. `title` + // defaults to the empty string. Rendered and parsed as an `` + // element. + link: { + attrs: { + href: {}, + targetId: { default: "" }, + linkId: { default: "" }, + showPreview: { default: true }, + location: { default: null }, + title: { default: null }, + docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text + }, + inclusive: false, + parseDOM: [{ + tag: "a[href]", getAttrs(dom: any) { + return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") }; + } + }], + toDOM(node: any) { + return node.attrs.docref && node.attrs.title ? + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0]; + } + }, + + + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. + pFontColor: { + attrs: { + color: { default: "#000" } + }, + inclusive: true, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { color: dom.getAttribute("color") }; + } + }], + toDOM(node: any) { + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; + } + }, + + marker: { + attrs: { + highlight: { default: "transparent" } + }, + inclusive: true, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { highlight: dom.getAttribute("backgroundColor") }; + } + }], + toDOM(node: any) { + return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; + } + }, + + // :: MarkSpec An emphasis mark. Rendered as an `` element. + // Has parse rules that also match `` and `font-style: italic`. + em: { + parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], + toDOM() { return emDOM; } + }, + + // :: MarkSpec A strong mark. Rendered as ``, parse rules + // also match `` and `font-weight: bold`. + strong: { + parseDOM: [{ tag: "strong" }, + { tag: "b" }, + { style: "font-weight" }], + toDOM() { return strongDOM; } + }, + + strikethrough: { + parseDOM: [ + { tag: 'strike' }, + { style: 'text-decoration=line-through' }, + { style: 'text-decoration-line=line-through' } + ], + toDOM: () => ['span', { + style: 'text-decoration-line:line-through' + }] + }, + + subscript: { + excludes: 'superscript', + parseDOM: [ + { tag: 'sub' }, + { style: 'vertical-align=sub' } + ], + toDOM: () => ['sub'] + }, + + superscript: { + excludes: 'subscript', + parseDOM: [ + { tag: 'sup' }, + { style: 'vertical-align=super' } + ], + toDOM: () => ['sup'] + }, + + mbulletType: { + attrs: { + bulletType: { default: "decimal" } + }, + toDOM(node: any) { + return ['span', { + style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` + }]; + } + }, + + metadata: { + toDOM() { + return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; + } + }, + metadataKey: { + toDOM() { + return ['span', { style: 'font-style:italic; ' }]; + } + }, + metadataVal: { + toDOM() { + return ['span']; + } + }, + + summarizeInclusive: { + parseDOM: [ + { + tag: "span", + 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: solid") !== -1) { + return null; + } + } + return false; + } + }, + ], + inclusive: true, + toDOM() { + return ['span', { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' + }]; + } + }, + + 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)' + }]; + } + }, + + underline: { + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { + return null; + } + } + return false; + } + } + // { style: "text-decoration=underline" } + ], + toDOM: () => ['span', { + style: 'text-decoration:underline;text-decoration-style:line' + }] + }, + + search_highlight: { + attrs: { + selected: { default: false } + }, + parseDOM: [{ style: 'background: yellow' }], + toDOM(node: any) { + return ['span', { + style: `background: ${node.attrs.selected ? "orange" : "yellow"}` + }]; + } + }, + + // the id of the user who entered the text + user_mark: { + attrs: { + userid: { default: "" }, + modified: { default: "when?" }, // 1 second intervals since 1970 + }, + group: "inline", + toDOM(node: any) { + const uid = node.attrs.userid.replace(".", "").replace("@", ""); + const min = Math.round(node.attrs.modified / 12); + const hr = Math.round(min / 60); + const day = Math.round(hr / 60 / 24); + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; + return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; + } + }, + // the id of the user who entered the text + user_tag: { + attrs: { + userid: { default: "" }, + modified: { default: "when?" }, // 1 second intervals since 1970 + tag: { default: "" } + }, + group: "inline", + inclusive: false, + toDOM(node: any) { + const uid = node.attrs.userid.replace(".", "").replace("@", ""); + return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; + } + }, + + + // :: MarkSpec Code font mark. Represented as a `` element. + code: { + parseDOM: [{ tag: "code" }], + toDOM() { return codeDOM; } + }, + + /* FONTS */ + pFontFamily: { + attrs: { + family: { default: "Crimson Text" }, + }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + const cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => ['span', { + style: `font-family: "${node.attrs.family}";` + }] + }, + + /** FONT SIZES */ + pFontSize: { + attrs: { + fontSize: { default: 10 } + }, + parseDOM: [{ style: 'font-size: 10px;' }], + toDOM: (node) => ['span', { + style: `font-size: ${node.attrs.fontSize}px;` + }] + }, +}; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts new file mode 100644 index 000000000..e7bcf444a --- /dev/null +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -0,0 +1,264 @@ +import React = require("react"); +import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; +import ParagraphNodeSpec from "./ParagraphNodeSpec"; + +const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], + preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; + +// :: Object +// [Specs](#model.NodeSpec) for the nodes defined in this schema. +export const nodes: { [index: string]: NodeSpec } = { + // :: NodeSpec The top level document node. + doc: { + content: "block+" + }, + + footnote: { + group: "inline", + content: "inline*", + inline: true, + attrs: { + visibility: { default: false } + }, + // This makes the view treat the node as a leaf, even though it + // technically has content + atom: true, + toDOM: () => ["footnote", 0], + parseDOM: [{ tag: "footnote" }] + }, + + paragraph: ParagraphNodeSpec, + + // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. + blockquote: { + content: "block+", + group: "block", + defining: true, + parseDOM: [{ tag: "blockquote" }], + toDOM() { return blockquoteDOM; } + }, + + // :: NodeSpec A horizontal rule (`
`). + horizontal_rule: { + group: "block", + parseDOM: [{ tag: "hr" }], + toDOM() { return hrDOM; } + }, + + // :: NodeSpec A heading textblock, with a `level` attribute that + // should hold the number 1 to 6. Parsed and serialized as `

` to + // `

` elements. + heading: { + attrs: { level: { default: 1 } }, + content: "inline*", + group: "block", + defining: true, + parseDOM: [{ tag: "h1", attrs: { level: 1 } }, + { tag: "h2", attrs: { level: 2 } }, + { tag: "h3", attrs: { level: 3 } }, + { tag: "h4", attrs: { level: 4 } }, + { tag: "h5", attrs: { level: 5 } }, + { tag: "h6", attrs: { level: 6 } }], + toDOM(node: any) { return ["h" + node.attrs.level, 0]; } + }, + + // :: NodeSpec A code listing. Disallows marks or non-text inline + // nodes by default. Represented as a `
` element with a
+    // `` element inside of it.
+    code_block: {
+        content: "text*",
+        marks: "",
+        group: "block",
+        code: true,
+        defining: true,
+        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+        toDOM() { return preDOM; }
+    },
+
+    // :: NodeSpec The text node.
+    text: {
+        group: "inline"
+    },
+
+    dashComment: {
+        attrs: {
+            docid: { default: "" },
+        },
+        inline: true,
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }, "←"];
+        },
+    },
+
+    summary: {
+        inline: true,
+        attrs: {
+            visibility: { default: false },
+            text: { default: undefined },
+            textslice: { default: undefined },
+        },
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }];
+        },
+    },
+
+    // :: NodeSpec An inline image (``) node. Supports `src`,
+    // `alt`, and `href` attributes. The latter two default to the empty
+    // string.
+    image: {
+        inline: true,
+        attrs: {
+            src: {},
+            agnostic: { default: null },
+            width: { default: 100 },
+            alt: { default: null },
+            title: { default: null },
+            float: { default: "left" },
+            location: { default: "onRight" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "img[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        // TODO if we don't define toDom, dragging the image crashes. Why?
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashDoc: {
+        inline: true,
+        attrs: {
+            width: { default: 200 },
+            height: { default: 100 },
+            title: { default: null },
+            float: { default: "right" },
+            location: { default: "onRight" },
+            hidden: { default: false },
+            fieldKey: { default: "" },
+            docid: { default: "" },
+            alias: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashField: {
+        inline: true,
+        attrs: {
+            fieldKey: { default: "" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    video: {
+        inline: true,
+        attrs: {
+            src: {},
+            width: { default: "100px" },
+            alt: { default: null },
+            title: { default: null }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "video[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["video", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    // :: NodeSpec A hard line break, represented in the DOM as `
`. + hard_break: { + inline: true, + group: "inline", + selectable: false, + parseDOM: [{ tag: "br" }], + toDOM() { return brDOM; } + }, + + ordered_list: { + ...orderedList, + content: 'list_item+', + group: 'block', + attrs: { + bulletStyle: { default: 0 }, + mapStyle: { default: "decimal" }, + setFontSize: { default: undefined }, + setFontFamily: { default: "inherit" }, + setFontColor: { default: "inherit" }, + inheritedFontSize: { default: undefined }, + visibility: { default: true }, + indent: { default: undefined } + }, + toDOM(node: Node) { + if (node.attrs.mapStyle === "bullet") return ['ul', 0]; + 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; + 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;` }]; + } + }, + + bullet_list: { + ...bulletList, + content: 'list_item+', + group: 'block', + // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], + toDOM(node: Node) { + return ['ul', 0]; + } + }, + + list_item: { + attrs: { + bulletStyle: { default: 0 }, + mapStyle: { default: "decimal" }, + visibility: { default: true } + }, + ...listItem, + content: 'paragraph block*', + toDOM(node: any) { + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; + return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; + //return ["li", { class: `${map}` }, 0]; + } + }, +}; \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js new file mode 100644 index 000000000..269423482 --- /dev/null +++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js @@ -0,0 +1,139 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var prosemirrorInputRules = require('prosemirror-inputrules'); +var prosemirrorTransform = require('prosemirror-transform'); +var prosemirrorModel = require('prosemirror-model'); + +exports.liftListItem = liftListItem; +exports.sinkListItem = sinkListItem; +exports.wrappingInputRule = wrappingInputRule; +// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool +// Create a command to lift the list item around the selection up into +// a wrapping list. +function liftListItem(itemType) { + return function (tx, dispatch) { + var ref = tx.selection; + var $from = ref.$from; + var $to = ref.$to; + var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); + if (!range) { return false } + if (!dispatch) { return true } + if ($from.node(range.depth - 1).type == itemType) // Inside a parent list + { return liftToOuterList(tx, dispatch, itemType, range) } + else // Outer list node + { return liftOutOfList(tx, dispatch, range) } + } +} + +function liftToOuterList(tr, dispatch, itemType, range) { + var end = range.end, endOfList = range.$to.end(range.depth); + if (end < endOfList) { + // There are siblings after the lifted items, which must become + // children of the last item + tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList, + new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true)); + range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth); + } + dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView()); + return true +} + +function liftOutOfList(tr, dispatch, range) { + var list = range.parent; + // Merge the list items into a single big item + for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) { + pos -= list.child(i).nodeSize; + tr.delete(pos - 1, pos + 1); + } + var $start = tr.doc.resolve(range.start), item = $start.nodeAfter; + var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount; + var parent = $start.node(-1), indexBefore = $start.index(-1); + if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1, + item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false } + var start = $start.pos, end = start + item.nodeSize; + // Strip off the surrounding list. At the sides where we're not at + // the end of the list, the existing list is closed. At sides where + // this is the end, it is overwritten to its end. + tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1, + new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))) + .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))), + atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1)); + dispatch(tr.scrollIntoView()); + return true +} + +// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool +// Create a command to sink the list item around the selection down +// into an inner list. +function sinkListItem(itemType) { + return function (state, dispatch) { + var ref = state.selection; + var $from = ref.$from; + var $to = ref.$to; + var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; }); + if (!range) { return false } + var startIndex = range.startIndex; + if (startIndex == 0) { return false } + var parent = range.parent, nodeBefore = parent.child(startIndex - 1); + if (nodeBefore.type != itemType) { return false; } + + if (dispatch) { + var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type; + var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null); + let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))), + nestedBefore ? 3 : 1, 0); + var before = range.start, after = range.end; + dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, + before, after, slice, 1, true)) + .scrollIntoView()); + } + return true + } +} + +function findWrappingOutside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var around = parent.contentMatchAt(startIndex).findWrapping(type); + if (!around) { return null } + var outer = around.length ? around[0] : type; + return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null +} + +function findWrappingInside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var inner = parent.child(startIndex); + var inside = type.contentMatch.findWrapping(inner.type); + if (!inside) { return null } + var lastType = inside.length ? inside[inside.length - 1] : type; + var innerMatch = lastType.contentMatch; + for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } + if (!innerMatch || !innerMatch.validEnd) { return null } + return inside +} +function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { + if (innerRange === void 0) innerRange = range; + let withAttrs = (type) => ({ type: type, attrs: null }); + var around = findWrappingOutside(range, nodeType); + var inner = around && findWrappingInside(innerRange, nodeType); + if (!inner) { return null } + return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) +} +function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { + return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { + var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; + var tr = state.tr.delete(start, end); + var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); + if (!wrapping) { return null } + tr.wrap(range, wrapping); + var before = tr.doc.resolve(start - 1).nodeBefore; + if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && + (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } + return tr + }) +} \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts new file mode 100644 index 000000000..83561073c --- /dev/null +++ b/src/client/views/nodes/formattedText/schema_rts.ts @@ -0,0 +1,26 @@ +import { Schema, Slice } from "prosemirror-model"; + +import { nodes } from "./nodes_rts"; +import { marks } from "./marks_rts"; + + +// :: Schema +// This schema rougly corresponds to the document schema used by +// [CommonMark](http://commonmark.org/), minus the list elements, +// which are defined in the [`prosemirror-schema-list`](#schema-list) +// module. +// +// To reuse elements from this schema, extend or read from its +// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). + +export const schema = new Schema({ nodes, marks }); + +const fromJson = schema.nodeFromJSON; + +schema.nodeFromJSON = (json: any) => { + const node = fromJson(json); + if (json.type === schema.nodes.summary.name) { + node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); + } + return node; +}; \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 73ebbb303..69a80e1b4 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -7,7 +7,7 @@ import { observer } from 'mobx-react'; import { DocServer } from '../client/DocServer'; import { Docs } from '../client/documents/Documents'; import { DocumentManager } from '../client/util/DocumentManager'; -import RichTextMenu from '../client/util/RichTextMenu'; +import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu'; import { Scripting } from '../client/util/Scripting'; import { Transform } from '../client/util/Transform'; import { CollectionView } from '../client/views/collections/CollectionView'; diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index c9960e783..c475d0d73 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -4,11 +4,11 @@ import { Fragment, Mark, Node } from "prosemirror-model"; import { sinkListItem } from "prosemirror-schema-list"; import { Utils } from "../Utils"; import { Docs } from "../client/documents/Documents"; -import { schema } from "../client/util/schema_rts"; +import { schema } from "../client/views/nodes/formattedText/schema_rts"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; import { DocServer } from "../client/DocServer"; import { Networking } from "../client/Network"; -import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; import { Doc, Opt } from "./Doc"; import { Id } from "./FieldSymbols"; import { RichTextField } from "./RichTextField"; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 1d41c3570..08dc21460 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -17,7 +17,7 @@ import { CollectionViewType } from "../../../client/views/collections/Collection import { makeTemplate } from "../../../client/util/DropConverter"; import { RichTextField } from "../../../new_fields/RichTextField"; import { PrefetchProxy } from "../../../new_fields/Proxy"; -import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox"; import { MainView } from "../../../client/views/MainView"; import { DocumentType } from "../../../client/documents/DocumentTypes"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -- cgit v1.2.3-70-g09d2 From d66aaffc27405f4231a29cd6edda3477077ae946 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 29 Apr 2020 13:48:13 -0400 Subject: fixes for text layout strings. --- src/client/views/animationtimeline/Keyframe.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentBox.tsx | 27 ++- .../views/nodes/formattedText/DashDocView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- .../views/nodes/formattedText/RichTextSchema.tsx | 181 --------------------- .../authentication/models/current_user_utils.ts | 14 +- 7 files changed, 28 insertions(+), 207 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index a3407f653..bbd7b2676 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -225,7 +225,7 @@ export class Keyframe extends React.Component { this._mouseToggled = true; } const left = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions)!; - const right = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions!); + const right = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions)!; const prevX = this.regiondata.position; const futureX = this.regiondata.position + KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement); if (futureX <= 0) { @@ -495,7 +495,7 @@ export class Keyframe extends React.Component { } else { return
-
+
; } }); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 2d3bb6f3c..c70301b2f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -113,7 +113,7 @@ export class MarqueeView extends React.Component; @@ -28,7 +27,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent this.contentDoc[this.props.fieldKey], (data) => { + this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => { if (data instanceof Doc && !this.isSelectionLocked()) { this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1); this._selections.push(data); @@ -42,22 +41,20 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" }); - funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => Doc.GetProto(this.props.Document).excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" }); - funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); + funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" }); + funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" }); + funcs.push({ description: `Show ${this.layoutDoc.childTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childTemplateName = this.layoutDoc.childTemplateName ? undefined : "keyValue", icon: "project-diagram" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } - @computed get contentDoc() { - return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document)); - } lockSelection = () => { - this.contentDoc[this.props.fieldKey] = this.props.Document[this.props.fieldKey]; + this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey]; } showSelection = () => { - this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); + this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); } isSelectionLocked = () => { - const kvpstring = Field.toKeyValueString(this.contentDoc, this.props.fieldKey); + const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey); return !kvpstring || kvpstring.includes("DOC"); } toggleLockSelection = () => { @@ -67,13 +64,13 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { this.lockSelection(); if (this._curSelection > 0) { - this.contentDoc[this.props.fieldKey] = this._selections[--this._curSelection]; + this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection]; return true; } } nextSelection = () => { if (this._curSelection < this._selections.length - 1 && this._selections.length) { - this.contentDoc[this.props.fieldKey] = this._selections[++this._curSelection]; + this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection]; return true; } } @@ -107,8 +104,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent this.props.PanelHeight() - 2 * this.yPad; getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); get renderContents() { - const containedDoc = Cast(this.contentDoc[this.props.fieldKey], Doc, null); - const childTemplateName = StrCast(this.props.Document.childTemplateName); + const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); + const childTemplateName = StrCast(this.layoutDoc.childTemplateName); if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) { setTimeout(() => { Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName); @@ -145,7 +142,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { if (dashDocBase instanceof Doc) { const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); aliasedDoc.layoutKey = "layout"; - node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); + node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined); this._dashDoc = aliasedDoc; // self.doRender(aliasedDoc, removeDoc, node, view, getPos); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c4e387e5a..782a91547 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -418,9 +418,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; - this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); + this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); + !this.rootDoc.isTemplateDoc && funcs.push({ description: "Show Template", event: async () => this.props.addDocTab(Doc.GetProto(this.layoutDoc), "onRight"), icon: "eye" }); funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - !this.props.Document.rootDocument && funcs.push({ + !this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 33caf5751..cdb7374f8 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -342,187 +342,6 @@ export class DashDocView { } } -export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value - _labelSpan: HTMLSpanElement; // field label - _fieldSpan: HTMLSpanElement; // field value - _fieldCheck: HTMLInputElement; - _enumerables: HTMLDivElement; // field value - _reactionDisposer: IReactionDisposer | undefined; - _textBoxDoc: Doc; - @observable _dashDoc: Doc | undefined; - _fieldKey: string; - _options: Doc[] = []; - - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldKey = node.attrs.fieldKey; - this._textBoxDoc = tbox.props.Document; - this._fieldWrapper = document.createElement("p"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - - const self = this; - - this._enumerables = document.createElement("div"); - this._enumerables.style.width = "10px"; - this._enumerables.style.height = "10px"; - this._enumerables.style.position = "relative"; - this._enumerables.style.display = "none"; - - //Moved - this._enumerables.onpointerdown = async (e) => { - e.stopPropagation(); - const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - collview instanceof Doc && tbox.props.addDocTab(collview, "onRight"); - }; - //Moved - const updateText = (forceMatch: boolean) => { - self._enumerables.style.display = "none"; - const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText; - - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(self._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText; - Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []); - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (self._fieldSpan.innerText.startsWith(":=")) { - self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2)); - } else if (self._fieldSpan.innerText.startsWith("=:=")) { - Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3)); - } else { - self._dashDoc![self._fieldKey] = newText; - } - }); - }; - - //Moved - this._fieldCheck = document.createElement("input"); - this._fieldCheck.id = Utils.GenerateGuid(); - this._fieldCheck.type = "checkbox"; - this._fieldCheck.style.position = "relative"; - this._fieldCheck.style.display = "none"; - this._fieldCheck.style.minWidth = "12px"; - this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; - this._fieldCheck.onchange = function (e: any) { - self._dashDoc![self._fieldKey] = e.target.checked; - }; - - this._fieldSpan = document.createElement("span"); - this._fieldSpan.id = Utils.GenerateGuid(); - this._fieldSpan.contentEditable = "true"; - this._fieldSpan.style.position = "relative"; - this._fieldSpan.style.display = "none"; - this._fieldSpan.style.minWidth = "12px"; - this._fieldSpan.style.fontSize = "large"; - this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; }; - this._fieldSpan.onblur = function (e: any) { updateText(false); }; - - // MOVED - const setDashDoc = (doc: Doc) => { - self._dashDoc = doc; - if (self._options?.length && !self._dashDoc[self._fieldKey]) { - self._dashDoc[self._fieldKey] = StrCast(self._options[0].title); - } - this._labelSpan.innerHTML = `${self._fieldKey}: `; - const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null); - this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none"; - }; - - //Moved - this._fieldSpan.onkeydown = function (e: any) { - e.stopPropagation(); - if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(self._fieldSpan); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); - } - if (e.key === "Enter") { - e.preventDefault(); - e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]); - updateText(true); - } - }; - - this._labelSpan = document.createElement("span"); - this._labelSpan.style.position = "relative"; - this._labelSpan.style.fontSize = "small"; - this._labelSpan.title = "click to see related tags"; - this._labelSpan.style.fontSize = "x-small"; - this._labelSpan.onpointerdown = function (e: any) { - e.stopPropagation(); - let container = tbox.props.ContainingCollectionView; - while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { - container = container.props.ContainingCollectionView; - } - if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; - let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField)); - if (!list) { - alias.schemaColumns = list = new List(); - } - list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = self._fieldKey; - tbox.props.addDocTab(alias, "onRight"); - } - }; - this._labelSpan.innerHTML = `${self._fieldKey}: `; - //MOVED - if (node.attrs.docid) { - DocServer.GetRefField(node.attrs.docid). - then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc))); - } else { - setDashDoc(tbox.props.DataDoc || tbox.dataDoc); - } - - //Moved - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes - const dashVal = this._dashDoc?.[self._fieldKey]; - return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal; - }, fval => { - const boolVal = Cast(fval, "boolean", null); - if (boolVal === true || boolVal === false) { - this._fieldCheck.checked = boolVal; - } else { - this._fieldSpan.innerHTML = Field.toString(fval as Field) || ""; - } - this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none"; - this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none"; - }, { fireImmediately: true }); - - //MOVED IN ORDER - this._fieldWrapper.appendChild(this._labelSpan); - this._fieldWrapper.appendChild(this._fieldCheck); - this._fieldWrapper.appendChild(this._fieldSpan); - this._fieldWrapper.appendChild(this._enumerables); - (this as any).dom = this._fieldWrapper; - //updateText(false); - } - //MOVED - destroy() { - this._reactionDisposer?.(); - } - //moved - selectNode() { } -} - export class FootnoteView { innerView: any; outerView: any; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 08dc21460..4b2aafac1 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -72,8 +72,8 @@ export class CurrentUserUtils { } if (doc["template-button-description"] === undefined) { - const descriptionTemplate = Docs.Create.TextDocument("", { title: "text", _height: 100, _showTitle: "title" }); - Doc.GetProto(descriptionTemplate).layout = FormattedTextBox.LayoutString("description"); + const descriptionTemplate = Docs.Create.TextDocument("", { title: "header", _height: 100 }); + Doc.GetProto(descriptionTemplate).layout = "
"; descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView"); doc["template-button-description"] = CurrentUserUtils.ficon({ @@ -181,9 +181,13 @@ export class CurrentUserUtils { doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc], { title: "Note Layouts", _height: 75 })); } else { - const noteTypes = Cast(doc["template-notes"], Doc, null); - DocListCastAsync(noteTypes).then(list => noteTypes.data = new List([doc["template-note-Note"] as any as Doc, - doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc])); + const curNoteTypes = Cast(doc["template-notes"], Doc, null); + const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, + doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc]; + DocListCastAsync(curNoteTypes.data).then(async curNotes => { + await Promise.all(curNotes!); + requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); + }); } return doc["template-notes"] as Doc; -- cgit v1.2.3-70-g09d2 From 43e573ea0cf4634b65b513c633f90be84846f8df Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 29 Apr 2020 21:08:37 -0400 Subject: changed detailedView template to be a layoutstring. fixed text boxes to allow new templates from templated text boxes. fixed __LAYOUT__ to work with comound layout strings --- src/client/documents/Documents.ts | 19 +++++++++++++++++-- src/client/views/animationtimeline/Timeline.tsx | 4 +--- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 +++++++++++++++++--- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 2 +- 5 files changed, 37 insertions(+), 10 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 811bb5fb2..0d1d73ca3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -541,8 +541,23 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), "", options); } - export function TextDocument(text: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.RTF), text, options, undefined, "text"); + export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = "text") { + const rtf = { + doc: { + type: "doc", content: [{ + type: "paragraph", + content: [{ + type: "text", + text + }] + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + + const field = text ? new RichTextField(JSON.stringify(rtf), text) : undefined; + return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 677267ca0..fe1e40778 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -119,9 +119,7 @@ export class Timeline extends React.Component { } componentWillUnmount() { - runInAction(() => { - this.props.Document.AnimationLength = this._time; //save animation length - }); + this.props.Document.AnimationLength = this._time; //save animation length } ///////////////////////////////////////////////// diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 782a91547..2038efbc6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -419,14 +419,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const funcs: ContextMenuProps[] = []; this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" }); - !this.rootDoc.isTemplateDoc && funcs.push({ description: "Show Template", event: async () => this.props.addDocTab(Doc.GetProto(this.layoutDoc), "onRight"), icon: "eye" }); funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - !this.rootDoc.isTemplateDoc && funcs.push({ + !this.layoutDoc.isTemplateDoc && funcs.push({ description: "Make Template", event: () => { - this.props.Document.isTemplateDoc = makeTemplate(this.props.Document); + this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc); Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); }, icon: "eye" }); + this.layoutDoc.isTemplateDoc && funcs.push({ + description: "Make New Template", event: () => { + const title = this.rootDoc.title as string; + this.rootDoc.layout = (this.layoutDoc as Doc).layout as string; + this.rootDoc.title = this.layoutDoc.isTemplateForField as string; + this.rootDoc.isTemplateDoc = false; + this.rootDoc.isTemplateForField = ""; + this.rootDoc.layoutKey = "layout"; + this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + this.rootDoc._width = this.layoutDoc._width || 300; // the width and height are stored on the template, since we're getting rid of the old template + this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields + this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); + Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); + }, icon: "eye" + }); funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" }); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 153af933a..77eee03ce 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -183,7 +183,7 @@ export class Doc extends RefField { let renderFieldKey: any; const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")]; if (typeof layoutField === "string") { - renderFieldKey = layoutField.split("'")[1]; + renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1]; } else { return Cast(layoutField, Doc, null); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 4b2aafac1..eedd3ee67 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -72,7 +72,7 @@ export class CurrentUserUtils { } if (doc["template-button-description"] === undefined) { - const descriptionTemplate = Docs.Create.TextDocument("", { title: "header", _height: 100 }); + const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created Doc.GetProto(descriptionTemplate).layout = "
"; descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView"); -- cgit v1.2.3-70-g09d2 From 0f4b9e541e4e1332bbed95f4a99113162ffef806 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 11:10:27 -0400 Subject: added double click script support --- src/client/documents/Documents.ts | 2 ++ src/client/views/ScriptBox.tsx | 4 ++-- .../views/collections/CollectionStackingView.tsx | 7 ++++--- src/client/views/collections/CollectionSubView.tsx | 2 ++ src/client/views/collections/CollectionView.tsx | 7 ++++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 ++- .../CollectionMulticolumnView.tsx | 2 ++ .../CollectionMultirowView.tsx | 3 ++- .../views/nodes/ContentFittingDocumentView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 23 ++++++++++++++++------ src/new_fields/Types.ts | 4 ++-- src/new_fields/documentSchemas.ts | 1 + .../authentication/models/current_user_utils.ts | 8 ++++++-- 13 files changed, 47 insertions(+), 20 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0d1d73ca3..436e59daf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -95,6 +95,7 @@ export interface DocumentOptions { hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; + scriptKey?: string; // the script key that a child click func script template document will write into templates?: List; backgroundColor?: string | ScriptField; // background color for data doc _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) @@ -132,6 +133,7 @@ export interface DocumentOptions { activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked + onChildDoubleClick?: ScriptField; // script given to children of a collection to execute when they are double clicked onPointerDown?: ScriptField; onPointerUp?: ScriptField; dropConverter?: ScriptField; // script to run when documents are dropped on this Document. diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 153b81876..66d3b937e 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -81,9 +81,9 @@ export class ScriptBox extends React.Component { ); } //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);} - public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }) { + public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, contextParams?: { [name: string]: string }, defaultScript?: ScriptField) { let overlayDisposer: () => void = emptyFunction; - const script = ScriptCast(doc[fieldKey]); + const script = ScriptCast(doc[fieldKey]) || defaultScript; let originalText: string | undefined = undefined; if (script) { originalText = script.script.originalScript; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e3720bf01..556d7df5c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -150,8 +150,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { this.createDashEventsTarget(ele!); //so the whole grid is the drop target? } - @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } - @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); } + @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); } + @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } addDocTab = (doc: Doc, where: string) => { if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { @@ -178,7 +178,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} rootSelected={this.rootSelected} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} - onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler} + onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} getTransform={dxf} focus={this.props.focus} CollectionDoc={this.props.CollectionView?.props.Document} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 1bfd408f8..8cc1af55b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -43,6 +43,8 @@ export interface CollectionViewProps extends FieldViewProps { export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: Opt; children?: never | (() => JSX.Element[]) | React.ReactNode; + childClickScript?: ScriptField; + childDoubleClickScript?: ScriptField; freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 801704673..2c52097aa 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -247,13 +247,14 @@ export class CollectionView extends Touchable { const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }]; + const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }, + { key: "onChildDoubleClick", name: "On Child Double Clicked", script: undefined as any as ScriptField }]; DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick => - funcs.push({ key: "onChildClick", name: StrCast(childClick.title), script: ScriptCast(childClick.script) })); + funcs.push({ key: StrCast(childClick.scriptKey), name: StrCast(childClick.title), script: ScriptCast(childClick.data) })); funcs.map(func => onClicks.push({ description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script)); - ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }); + ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }, func.script); } })); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 77de486d9..11d0f298d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -70,7 +70,6 @@ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchem const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); export type collectionFreeformViewProps = { forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) - childClickScript?: ScriptField; viewDefDivClick?: ScriptField; }; @@ -855,6 +854,7 @@ export class CollectionFreeFormView extends CollectionSubView BoolCast(this.Document.useClusters); @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); } parentActive = () => this.props.active() || this.backgroundActive ? true : false; @@ -873,6 +873,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -229,6 +230,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu rootSelected={this.rootSelected} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} getTransform={dxf} focus={this.props.focus} CollectionDoc={this.props.CollectionView?.props.Document} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index af0cc3b5c..615efdb39 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -203,7 +203,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } - + @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); } addDocTab = (doc: Doc, where: string) => { if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { @@ -229,6 +229,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) rootSelected={this.rootSelected} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} getTransform={dxf} focus={this.props.focus} CollectionDoc={this.props.CollectionView?.props.Document} diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 641797cac..d0b0c8ee6 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -32,6 +32,7 @@ interface ContentFittingDocumentViewProps { CollectionView?: CollectionView; CollectionDoc?: Doc; onClick?: ScriptField; + onDoubleClick?: ScriptField; backgroundColor?: (doc: Doc) => string | undefined; getTransform: () => Transform; addDocument?: (document: Doc) => boolean; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fdcaa2df3..7b28a45f8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -64,6 +64,7 @@ export interface DocumentViewProps { contextMenuItems?: () => { script: ScriptField, label: string }[]; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected onClick?: ScriptField; + onDoubleClick?: ScriptField; onPointerDown?: ScriptField; onPointerUp?: ScriptField; dropAction?: dropActionType; @@ -116,6 +117,7 @@ export class DocumentView extends DocComponent(Docu @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); } @computed get onClickHandler() { return this.props.onClick || Cast(this.layoutDoc.onClick, ScriptField, null) || this.Document.onClick; } + @computed get onDoubleClickHandler() { return this.props.onDoubleClick || Cast(this.layoutDoc.onDoubleClick, ScriptField, null) || this.Document.onDoubleClick; } @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; } @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; } NativeWidth = () => this.nativeWidth; @@ -289,13 +291,22 @@ export class DocumentView extends DocComponent(Docu !this.props.Document.isBackground && this.props.bringToFront(this.props.Document); if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (!(e.nativeEvent as any).formattedHandled) { - const fullScreenAlias = Doc.MakeAlias(this.props.Document); - if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) { - fullScreenAlias.layoutKey = "layout_fullScreen"; + if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + const func = () => this.onDoubleClickHandler.script.run({ + this: this.layoutDoc, + self: this.rootDoc, + thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey + }, console.log); + func(); + } else { + const fullScreenAlias = Doc.MakeAlias(this.props.Document); + if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) { + fullScreenAlias.layoutKey = "layout_fullScreen"; + } + UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap"); + SelectionManager.DeselectAll(); + Doc.UnBrushDoc(this.props.Document); } - UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap"); - SelectionManager.DeselectAll(); - Doc.UnBrushDoc(this.props.Document); } } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself //SelectionManager.DeselectAll(); diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts index aa44cefa0..3d784448d 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -88,8 +88,8 @@ export function DateCast(field: FieldResult) { return Cast(field, DateField, null); } -export function ScriptCast(field: FieldResult) { - return Cast(field, ScriptField, null); +export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { + return Cast(field, ScriptField, defaultVal); } type WithoutList = T extends List ? (R extends RefField ? (R | Promise)[] : R[]) : T; diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 7a0be8863..5ca0d681e 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -81,6 +81,7 @@ export const collectionSchema = createSchema({ childLayout: Doc, // layout template for children of a collecion childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field) onChildClick: ScriptField, // script to run for each child when its clicked + onChildDoubleClick: ScriptField, // script to run for each child when its clicked onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view }); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index eedd3ee67..1be977e4f 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -594,9 +594,13 @@ export class CurrentUserUtils { if (doc.childClickFuncs === undefined) { const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "docCast(thisContainer.target).then((target) => { target && docCast(this.source).then((source) => { target.proto.data = new List([source || this]); } ); } )", - { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200 }); + { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200, scriptKey: "onChildClick" }); - doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget], { title: "on Child Click function templates" }); + const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( + "openOnRight(self.doubleClickView)", + { target: Doc.name }), { title: "On Child Dbl Clicked (open double click view)", _width: 300, _height: 200, scriptKey: "onChildDoubleClick" }); + + doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); } // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. PromiseValue(Cast(doc.childClickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); -- cgit v1.2.3-70-g09d2 From d4dd4ccd299ba2e06b35a6f14f698a26d026aeee Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 11:34:08 -0400 Subject: clean up of childclick template func menus --- src/client/documents/Documents.ts | 2 +- src/client/views/collections/CollectionView.tsx | 16 ++++++++++------ src/server/authentication/models/current_user_utils.ts | 10 +++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 436e59daf..0809ae24f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -95,7 +95,7 @@ export interface DocumentOptions { hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - scriptKey?: string; // the script key that a child click func script template document will write into + targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; backgroundColor?: string | ScriptField; // background color for data doc _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2c52097aa..79bb6c41b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -247,16 +247,20 @@ export class CollectionView extends Touchable { const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - const funcs = [{ key: "onChildClick", name: "On Child Clicked", script: undefined as any as ScriptField }, - { key: "onChildDoubleClick", name: "On Child Double Clicked", script: undefined as any as ScriptField }]; - DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick => - funcs.push({ key: StrCast(childClick.scriptKey), name: StrCast(childClick.title), script: ScriptCast(childClick.data) })); + const funcs = [ + { key: "onChildClick", name: "On Child Clicked" }, + { key: "onChildDoubleClick", name: "On Child Double Clicked" }]; funcs.map(func => onClicks.push({ description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { - func.script && (this.props.Document[func.key] = ObjectField.MakeCopy(func.script)); - ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }, func.script); + ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }); } })); + DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick => + onClicks.push({ + description: `Set child ${childClick.title}`, + icon: "edit", + event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), + })); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); const more = ContextMenu.Instance.findByDescription("More..."); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 1be977e4f..2c861d4fa 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -593,12 +593,16 @@ export class CurrentUserUtils { static setupClickEditorTemplates(doc: Doc) { if (doc.childClickFuncs === undefined) { const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => { target && docCast(this.source).then((source) => { target.proto.data = new List([source || this]); } ); } )", - { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200, scriptKey: "onChildClick" }); + "docCast(thisContainer.target).then((target) => {" + + " target && docCast(this.source).then((source) => { " + + " target.proto.data = new List([source || this]); " + + " }); " + + "})", + { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" }); const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", - { target: Doc.name }), { title: "On Child Dbl Clicked (open double click view)", _width: 300, _height: 200, scriptKey: "onChildDoubleClick" }); + { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); } -- cgit v1.2.3-70-g09d2 From baae91e7829676f03878696e9b13e1bdb4fb3c33 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 11:40:57 -0400 Subject: from last --- src/client/views/collections/CollectionView.tsx | 2 +- src/server/authentication/models/current_user_utils.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 79bb6c41b..8d8c321e8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -255,7 +255,7 @@ export class CollectionView extends Touchable { ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name }); } })); - DocListCast(Cast(Doc.UserDoc().childClickFuncs, Doc, null).data).forEach(childClick => + DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick => onClicks.push({ description: `Set child ${childClick.title}`, icon: "edit", diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 2c861d4fa..e49cc4804 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -591,7 +591,7 @@ export class CurrentUserUtils { } static setupClickEditorTemplates(doc: Doc) { - if (doc.childClickFuncs === undefined) { + if (doc["clickFuncs-child"] === undefined) { const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "docCast(thisContainer.target).then((target) => {" + " target && docCast(this.source).then((source) => { " + @@ -604,10 +604,10 @@ export class CurrentUserUtils { "openOnRight(self.doubleClickView)", { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" }); - doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); + doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" }); } // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. - PromiseValue(Cast(doc.childClickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); if (doc.clickFuncs === undefined) { const onClick = Docs.Create.ScriptingDocument(undefined, { -- cgit v1.2.3-70-g09d2 From 22748f8d35235941fc6622b19a2d4d3f809ccee7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 30 Apr 2020 17:16:14 -0400 Subject: working version of snapping with resize / templates / centers --- .VSCodeCounter/details.md | 661 +++++++++++++++++ .VSCodeCounter/results.csv | 648 ++++++++++++++++ .VSCodeCounter/results.md | 164 +++++ .VSCodeCounter/results.txt | 813 +++++++++++++++++++++ package-lock.json | 81 +- src/Utils.ts | 4 +- src/client/util/DragManager.ts | 41 +- src/client/views/MainView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 70 +- .../views/nodes/formattedText/DashFieldView.tsx | 4 - .../authentication/models/current_user_utils.ts | 6 +- 11 files changed, 2395 insertions(+), 105 deletions(-) create mode 100644 .VSCodeCounter/details.md create mode 100644 .VSCodeCounter/results.csv create mode 100644 .VSCodeCounter/results.md create mode 100644 .VSCodeCounter/results.txt (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/.VSCodeCounter/details.md b/.VSCodeCounter/details.md new file mode 100644 index 000000000..2f988953b --- /dev/null +++ b/.VSCodeCounter/details.md @@ -0,0 +1,661 @@ +# Details + +Date : 2020-04-30 14:40:16 + +Directory /Users/bcz/Documents/GitHub/Dash-Web + +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +[summary](results.md) + +## Files +| filename | language | code | comment | blank | total | +| :--- | :--- | ---: | ---: | ---: | ---: | +| [README.md](file:///Users/bcz/Documents/GitHub/Dash-Web/README.md) | Markdown | 6 | 0 | 3 | 9 | +| [build/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/build/index.html) | HTML | 9 | 0 | 3 | 12 | +| [dash.bat](file:///Users/bcz/Documents/GitHub/Dash-Web/dash.bat) | Batch | 2 | 0 | 1 | 3 | +| [deploy/assets/env.json](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/assets/env.json) | JSON | 15 | 0 | 0 | 15 | +| [deploy/assets/pdf.worker.js](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/assets/pdf.worker.js) | JavaScript | 55,662 | 174 | 686 | 56,522 | +| [deploy/debug/repl.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/repl.html) | HTML | 11 | 0 | 3 | 14 | +| [deploy/debug/test.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/test.html) | HTML | 10 | 0 | 3 | 13 | +| [deploy/debug/viewer.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/debug/viewer.html) | HTML | 11 | 0 | 3 | 14 | +| [deploy/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/index.html) | HTML | 13 | 0 | 3 | 16 | +| [deploy/mobile/image.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/mobile/image.html) | HTML | 12 | 0 | 3 | 15 | +| [deploy/mobile/ink.html](file:///Users/bcz/Documents/GitHub/Dash-Web/deploy/mobile/ink.html) | HTML | 10 | 0 | 3 | 13 | +| [package-lock.json](file:///Users/bcz/Documents/GitHub/Dash-Web/package-lock.json) | JSON | 18,689 | 0 | 1 | 18,690 | +| [package.json](file:///Users/bcz/Documents/GitHub/Dash-Web/package.json) | JSON | 266 | 0 | 1 | 267 | +| [sentence_parser.py](file:///Users/bcz/Documents/GitHub/Dash-Web/sentence_parser.py) | Python | 6 | 0 | 1 | 7 | +| [session.config.json](file:///Users/bcz/Documents/GitHub/Dash-Web/session.config.json) | JSON | 12 | 0 | 1 | 13 | +| [solr-8.3.1/bin/install_solr_service.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/install_solr_service.sh) | Shell Script | 307 | 29 | 35 | 371 | +| [solr-8.3.1/bin/oom_solr.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/oom_solr.sh) | Shell Script | 13 | 15 | 3 | 31 | +| [solr-8.3.1/bin/solr.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.cmd) | Batch | 1,782 | 43 | 210 | 2,035 | +| [solr-8.3.1/bin/solr.in.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.in.cmd) | Batch | 16 | 133 | 29 | 178 | +| [solr-8.3.1/bin/solr.in.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/bin/solr.in.sh) | Shell Script | 0 | 172 | 34 | 206 | +| [solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd) | Batch | 82 | 0 | 26 | 108 | +| [solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json) | JSON | 4,465 | 0 | 1 | 4,466 | +| [solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml) | XML | 1,734 | 63 | 10 | 1,807 | +| [solr-8.3.1/docs/images/solr.svg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/docs/images/solr.svg) | XML | 39 | 0 | 1 | 40 | +| [solr-8.3.1/docs/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/docs/index.html) | HTML | 20 | 0 | 1 | 21 | +| [solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml) | XML | 21 | 6 | 9 | 36 | +| [solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml) | XML | 20 | 37 | 8 | 65 | +| [solr-8.3.1/example/example-DIH/solr/atom/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/atom/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml) | XML | 26 | 0 | 4 | 30 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml) | XML | 292 | 958 | 104 | 1,354 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/db/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/db/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml) | XML | 8 | 4 | 1 | 13 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml) | XML | 294 | 958 | 105 | 1,357 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/mail/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/mail/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr.xml) | XML | 2 | 0 | 1 | 3 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml) | XML | 8 | 16 | 2 | 26 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml) | XML | 292 | 958 | 102 | 1,352 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/example/example-DIH/solr/solr/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/solr/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml) | XML | 17 | 38 | 7 | 62 | +| [solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml) | XML | 17 | 3 | 7 | 27 | +| [solr-8.3.1/example/example-DIH/solr/tika/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/example-DIH/solr/tika/core.properties) | Properties | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/exampledocs/books.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/books.json) | JSON | 51 | 0 | 1 | 52 | +| [solr-8.3.1/example/exampledocs/gb18030-example.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/gb18030-example.xml) | XML | 14 | 16 | 3 | 33 | +| [solr-8.3.1/example/exampledocs/hd.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/hd.xml) | XML | 33 | 20 | 4 | 57 | +| [solr-8.3.1/example/exampledocs/ipod_other.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/ipod_other.xml) | XML | 32 | 20 | 9 | 61 | +| [solr-8.3.1/example/exampledocs/ipod_video.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/ipod_video.xml) | XML | 21 | 18 | 2 | 41 | +| [solr-8.3.1/example/exampledocs/manufacturers.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/manufacturers.xml) | XML | 57 | 16 | 3 | 76 | +| [solr-8.3.1/example/exampledocs/mem.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/mem.xml) | XML | 45 | 24 | 9 | 78 | +| [solr-8.3.1/example/exampledocs/money.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/money.xml) | XML | 42 | 17 | 7 | 66 | +| [solr-8.3.1/example/exampledocs/monitor.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/monitor.xml) | XML | 14 | 18 | 3 | 35 | +| [solr-8.3.1/example/exampledocs/monitor2.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/monitor2.xml) | XML | 13 | 18 | 3 | 34 | +| [solr-8.3.1/example/exampledocs/mp500.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/mp500.xml) | XML | 23 | 18 | 3 | 44 | +| [solr-8.3.1/example/exampledocs/sample.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/sample.html) | HTML | 13 | 0 | 1 | 14 | +| [solr-8.3.1/example/exampledocs/sd500.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/sd500.xml) | XML | 19 | 18 | 2 | 39 | +| [solr-8.3.1/example/exampledocs/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/solr.xml) | XML | 20 | 16 | 3 | 39 | +| [solr-8.3.1/example/exampledocs/test_utf8.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/test_utf8.sh) | Shell Script | 57 | 21 | 16 | 94 | +| [solr-8.3.1/example/exampledocs/utf8-example.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/utf8-example.xml) | XML | 19 | 20 | 4 | 43 | +| [solr-8.3.1/example/exampledocs/vidcard.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/exampledocs/vidcard.xml) | XML | 40 | 21 | 2 | 63 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources.properties) | Properties | 72 | 6 | 5 | 83 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties) | Properties | 18 | 0 | 1 | 19 | +| [solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties) | Properties | 18 | 0 | 3 | 21 | +| [solr-8.3.1/example/files/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/example/files/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/example/files/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/params.json) | JSON | 34 | 0 | 1 | 35 | +| [solr-8.3.1/example/files/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/solrconfig.xml) | XML | 298 | 979 | 102 | 1,379 | +| [solr-8.3.1/example/files/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/update-script.js) | JavaScript | 80 | 13 | 23 | 116 | +| [solr-8.3.1/example/files/conf/velocity/dropit.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/dropit.js) | JavaScript | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js) | JavaScript | 0 | 0 | 2 | 2 | +| [solr-8.3.1/example/files/conf/velocity/js/dropit.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/dropit.js) | JavaScript | 64 | 15 | 19 | 98 | +| [solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js) | JavaScript | 620 | 68 | 76 | 764 | +| [solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js) | JavaScript | 46 | 16 | 9 | 71 | +| [solr-8.3.1/example/films/film_data_generator.py](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/film_data_generator.py) | Python | 82 | 24 | 12 | 118 | +| [solr-8.3.1/example/films/films.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/films.json) | JSON | 15,830 | 0 | 1 | 15,831 | +| [solr-8.3.1/example/films/films.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/example/films/films.xml) | XML | 11,438 | 0 | 1 | 11,439 | +| [solr-8.3.1/server/contexts/solr-jetty-context.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/contexts/solr-jetty-context.xml) | XML | 8 | 0 | 1 | 9 | +| [solr-8.3.1/server/etc/jetty-http.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-http.xml) | XML | 33 | 15 | 4 | 52 | +| [solr-8.3.1/server/etc/jetty-https.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-https.xml) | XML | 56 | 16 | 5 | 77 | +| [solr-8.3.1/server/etc/jetty-https8.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-https8.xml) | XML | 34 | 32 | 4 | 70 | +| [solr-8.3.1/server/etc/jetty-ssl.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty-ssl.xml) | XML | 23 | 11 | 4 | 38 | +| [solr-8.3.1/server/etc/jetty.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/jetty.xml) | XML | 110 | 94 | 18 | 222 | +| [solr-8.3.1/server/etc/webdefault.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/etc/webdefault.xml) | XML | 272 | 232 | 24 | 528 | +| [solr-8.3.1/server/resources/jetty-logging.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/jetty-logging.properties) | Properties | 1 | 0 | 1 | 2 | +| [solr-8.3.1/server/resources/log4j2-console.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/log4j2-console.xml) | XML | 19 | 43 | 6 | 68 | +| [solr-8.3.1/server/resources/log4j2.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/resources/log4j2.xml) | XML | 54 | 80 | 9 | 143 | +| [solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh) | Shell Script | 152 | 2 | 23 | 177 | +| [solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat) | Batch | 11 | 8 | 7 | 26 | +| [solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh) | Shell Script | 9 | 9 | 9 | 27 | +| [solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml) | XML | 62 | 42 | 11 | 115 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css) | CSS | 237 | 19 | 48 | 304 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css) | CSS | 402 | 55 | 9 | 466 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css) | CSS | 594 | 23 | 106 | 723 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css) | CSS | 296 | 18 | 65 | 379 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css) | CSS | 647 | 19 | 106 | 772 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css) | CSS | 171 | 18 | 37 | 226 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css) | CSS | 134 | 18 | 28 | 180 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css) | CSS | 292 | 18 | 61 | 371 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css) | CSS | 131 | 23 | 26 | 180 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css) | CSS | 29 | 18 | 7 | 54 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css) | CSS | 164 | 18 | 35 | 217 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css) | CSS | 24 | 18 | 6 | 48 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css) | CSS | 1 | 26 | 2 | 29 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css) | CSS | 1 | 22 | 2 | 25 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css) | CSS | 303 | 19 | 63 | 385 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css) | CSS | 80 | 18 | 12 | 110 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css) | CSS | 257 | 18 | 56 | 331 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css) | CSS | 20 | 18 | 5 | 43 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css) | CSS | 172 | 18 | 31 | 221 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css) | CSS | 120 | 18 | 25 | 163 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css) | CSS | 404 | 18 | 79 | 501 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css) | CSS | 596 | 20 | 112 | 728 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css) | CSS | 133 | 18 | 22 | 173 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css) | CSS | 178 | 22 | 34 | 234 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css) | CSS | 43 | 18 | 4 | 65 | +| [solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css) | CSS | 119 | 18 | 24 | 161 | +| [solr-8.3.1/server/solr-webapp/webapp/img/solr.svg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/img/solr.svg) | XML | 39 | 0 | 1 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/index.html) | HTML | 203 | 16 | 38 | 257 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js) | JavaScript | 512 | 19 | 31 | 562 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js) | JavaScript | 8 | 16 | 4 | 28 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js) | JavaScript | 161 | 18 | 23 | 202 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js) | JavaScript | 847 | 51 | 124 | 1,022 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js) | JavaScript | 43 | 18 | 2 | 63 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js) | JavaScript | 18 | 16 | 6 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js) | JavaScript | 244 | 19 | 27 | 290 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js) | JavaScript | 69 | 16 | 9 | 94 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js) | JavaScript | 151 | 16 | 14 | 181 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js) | JavaScript | 234 | 25 | 44 | 303 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js) | JavaScript | 107 | 18 | 13 | 138 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js) | JavaScript | 72 | 16 | 13 | 101 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js) | JavaScript | 61 | 23 | 14 | 98 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js) | JavaScript | 27 | 16 | 3 | 46 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js) | JavaScript | 112 | 35 | 12 | 159 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js) | JavaScript | 269 | 30 | 19 | 318 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js) | JavaScript | 130 | 19 | 19 | 168 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js) | JavaScript | 88 | 19 | 14 | 121 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js) | JavaScript | 178 | 18 | 40 | 236 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js) | JavaScript | 524 | 19 | 69 | 612 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js) | JavaScript | 64 | 16 | 20 | 100 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js) | JavaScript | 173 | 16 | 51 | 240 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js) | JavaScript | 33 | 16 | 2 | 51 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js) | JavaScript | 14 | 22 | 2 | 38 | +| [solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js) | JavaScript | 311 | 18 | 11 | 340 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js) | JavaScript | 112 | 24 | 4 | 140 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js) | JavaScript | 73 | 135 | 22 | 230 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js) | JavaScript | 2 | 29 | 1 | 32 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js) | JavaScript | 7 | 29 | 1 | 37 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js) | JavaScript | 316 | 636 | 67 | 1,019 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js) | JavaScript | 9 | 29 | 1 | 39 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js) | JavaScript | 311 | 328 | 65 | 704 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js) | JavaScript | 10 | 29 | 1 | 40 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js) | JavaScript | 157 | 48 | 13 | 218 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js) | JavaScript | 1 | 42 | 3 | 46 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular.js) | JavaScript | 10,845 | 13,211 | 2,038 | 26,094 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js) | JavaScript | 73 | 201 | 0 | 274 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js) | JavaScript | 1,151 | 36 | 8 | 1,195 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js) | JavaScript | 2 | 29 | 0 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/d3.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/d3.js) | JavaScript | 7,720 | 519 | 1,135 | 9,374 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js) | JavaScript | 2 | 29 | 1 | 32 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js) | JavaScript | 3 | 26 | 2 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js) | JavaScript | 3 | 26 | 1 | 30 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js) | JavaScript | 2 | 26 | 3 | 31 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js) | JavaScript | 3,222 | 228 | 85 | 3,535 | +| [solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js) | JavaScript | 66 | 23 | 13 | 102 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html) | HTML | 21 | 16 | 10 | 47 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html) | HTML | 87 | 16 | 26 | 129 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html) | HTML | 263 | 16 | 24 | 303 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html) | HTML | 30 | 16 | 4 | 50 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html) | HTML | 48 | 16 | 22 | 86 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/collections.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/collections.html) | HTML | 301 | 16 | 79 | 396 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html) | HTML | 125 | 16 | 66 | 207 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/cores.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/cores.html) | HTML | 142 | 16 | 67 | 225 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html) | HTML | 142 | 16 | 52 | 210 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/documents.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/documents.html) | HTML | 83 | 20 | 9 | 112 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/files.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/files.html) | HTML | 17 | 16 | 15 | 48 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/index.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/index.html) | HTML | 135 | 42 | 85 | 262 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html) | HTML | 10 | 16 | 2 | 28 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html) | HTML | 35 | 16 | 6 | 57 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/logging.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/logging.html) | HTML | 40 | 16 | 2 | 58 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/login.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/login.html) | HTML | 134 | 16 | 11 | 161 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html) | HTML | 48 | 17 | 8 | 73 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/query.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/query.html) | HTML | 270 | 16 | 84 | 370 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/replication.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/replication.html) | HTML | 153 | 16 | 71 | 240 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/schema.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/schema.html) | HTML | 336 | 16 | 104 | 456 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/segments.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/segments.html) | HTML | 70 | 16 | 14 | 100 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/stream.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/stream.html) | HTML | 40 | 16 | 9 | 65 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/threads.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/threads.html) | HTML | 36 | 16 | 14 | 66 | +| [solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html) | HTML | 5 | 16 | 3 | 24 | +| [solr-8.3.1/server/solr/configsets/_default/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/_default/conf/params.json) | JSON | 20 | 0 | 1 | 21 | +| [solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml) | XML | 296 | 976 | 98 | 1,370 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json) | JSON | 1 | 0 | 1 | 2 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json) | JSON | 38 | 0 | 1 | 39 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json) | JSON | 11 | 0 | 1 | 12 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml) | XML | 13 | 11 | 1 | 25 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml) | XML | 13 | 6 | 1 | 20 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml) | XML | 45 | 19 | 4 | 68 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml) | XML | 3 | 37 | 3 | 43 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json) | JSON | 11 | 0 | 1 | 12 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml) | XML | 410 | 1,097 | 124 | 1,631 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js) | JavaScript | 15 | 26 | 13 | 54 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css) | CSS | 34 | 9 | 6 | 49 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js) | JavaScript | 620 | 68 | 76 | 764 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css) | CSS | 185 | 0 | 47 | 232 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl) | XSL | 98 | 20 | 15 | 133 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl) | XSL | 40 | 20 | 8 | 68 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl) | XSL | 41 | 20 | 6 | 67 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl) | XSL | 301 | 19 | 18 | 338 | +| [solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl) | XSL | 35 | 25 | 11 | 71 | +| [solr-8.3.1/server/solr/dash/conf/params.json](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/params.json) | JSON | 20 | 0 | 0 | 20 | +| [solr-8.3.1/server/solr/dash/conf/schema.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/schema.xml) | XML | 51 | 4 | 7 | 62 | +| [solr-8.3.1/server/solr/dash/conf/solrconfig.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/conf/solrconfig.xml) | XML | 270 | 962 | 97 | 1,329 | +| [solr-8.3.1/server/solr/dash/core.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/dash/core.properties) | Properties | 4 | 2 | 1 | 7 | +| [solr-8.3.1/server/solr/solr.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/solr.xml) | XML | 21 | 25 | 11 | 57 | +| [solr-8.3.1/server/solr/zoo.cfg](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/solr/zoo.cfg) | Properties | 3 | 25 | 4 | 32 | +| [solr-8.3.1/server/tmp/start_3204295554151338130.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_3204295554151338130.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_5812170489311981381.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_5812170489311981381.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_6476327636763392575.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_6476327636763392575.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_7329004517204835686.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_7329004517204835686.properties) | Properties | 9 | 2 | 1 | 12 | +| [solr-8.3.1/server/tmp/start_9067375725008958788.properties](file:///Users/bcz/Documents/GitHub/Dash-Web/solr-8.3.1/server/tmp/start_9067375725008958788.properties) | Properties | 9 | 2 | 1 | 12 | +| [src/Utils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/Utils.ts) | TypeScript | 441 | 23 | 81 | 545 | +| [src/client/ClientRecommender.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/ClientRecommender.scss) | SCSS | 9 | 1 | 2 | 12 | +| [src/client/ClientRecommender.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/ClientRecommender.tsx) | TypeScript React | 320 | 61 | 44 | 425 | +| [src/client/DocServer.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/DocServer.ts) | TypeScript | 279 | 136 | 65 | 480 | +| [src/client/Network.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/Network.ts) | TypeScript | 34 | 0 | 5 | 39 | +| [src/client/apis/GoogleAuthenticationManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/GoogleAuthenticationManager.scss) | SCSS | 16 | 0 | 3 | 19 | +| [src/client/apis/GoogleAuthenticationManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/GoogleAuthenticationManager.tsx) | TypeScript React | 115 | 2 | 11 | 128 | +| [src/client/apis/IBM_Recommender.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/IBM_Recommender.ts) | TypeScript | 0 | 33 | 7 | 40 | +| [src/client/apis/google_docs/GoogleApiClientUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/google_docs/GoogleApiClientUtils.ts) | TypeScript | 225 | 9 | 27 | 261 | +| [src/client/apis/google_docs/GooglePhotosClientUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/google_docs/GooglePhotosClientUtils.ts) | TypeScript | 318 | 0 | 46 | 364 | +| [src/client/apis/youtube/YoutubeBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/youtube/YoutubeBox.scss) | SCSS | 105 | 5 | 16 | 126 | +| [src/client/apis/youtube/YoutubeBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/apis/youtube/YoutubeBox.tsx) | TypeScript React | 274 | 48 | 40 | 362 | +| [src/client/cognitive_services/CognitiveServices.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/cognitive_services/CognitiveServices.ts) | TypeScript | 342 | 10 | 57 | 409 | +| [src/client/documents/DocumentTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/documents/DocumentTypes.ts) | TypeScript | 32 | 2 | 3 | 37 | +| [src/client/documents/Documents.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/documents/Documents.ts) | TypeScript | 807 | 141 | 87 | 1,035 | +| [src/client/goldenLayout.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/goldenLayout.d.ts) | TypeScript | 2 | 0 | 1 | 3 | +| [src/client/goldenLayout.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/goldenLayout.js) | JavaScript | 3,084 | 1,571 | 720 | 5,375 | +| [src/client/util/DictationManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DictationManager.ts) | TypeScript | 312 | 25 | 51 | 388 | +| [src/client/util/DocumentManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DocumentManager.ts) | TypeScript | 207 | 16 | 21 | 244 | +| [src/client/util/DragManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DragManager.ts) | TypeScript | 504 | 11 | 35 | 550 | +| [src/client/util/DropConverter.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/DropConverter.ts) | TypeScript | 70 | 6 | 2 | 78 | +| [src/client/util/History.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/History.ts) | TypeScript | 166 | 13 | 27 | 206 | +| [src/client/util/Import & Export/DirectoryImportBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/DirectoryImportBox.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/util/Import & Export/DirectoryImportBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/DirectoryImportBox.tsx) | TypeScript React | 393 | 0 | 31 | 424 | +| [src/client/util/Import & Export/ImageUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/ImageUtils.ts) | TypeScript | 35 | 0 | 4 | 39 | +| [src/client/util/Import & Export/ImportMetadataEntry.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Import%20%26%20Export/ImportMetadataEntry.tsx) | TypeScript React | 132 | 0 | 17 | 149 | +| [src/client/util/InteractionUtils.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/InteractionUtils.tsx) | TypeScript React | 122 | 112 | 21 | 255 | +| [src/client/util/KeyCodes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/KeyCodes.ts) | TypeScript | 100 | 36 | 0 | 136 | +| [src/client/util/LinkManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/LinkManager.ts) | TypeScript | 161 | 30 | 23 | 214 | +| [src/client/util/ProsemirrorCopy/prompt.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/ProsemirrorCopy/prompt.js) | JavaScript | 128 | 30 | 22 | 180 | +| [src/client/util/Scripting.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Scripting.ts) | TypeScript | 247 | 15 | 30 | 292 | +| [src/client/util/ScrollBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/ScrollBox.tsx) | TypeScript React | 19 | 0 | 2 | 21 | +| [src/client/util/SearchUtil.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SearchUtil.ts) | TypeScript | 123 | 4 | 18 | 145 | +| [src/client/util/SelectionManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SelectionManager.ts) | TypeScript | 69 | 5 | 15 | 89 | +| [src/client/util/SerializationHelper.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SerializationHelper.ts) | TypeScript | 113 | 15 | 15 | 143 | +| [src/client/util/SettingsManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SettingsManager.scss) | SCSS | 111 | 0 | 25 | 136 | +| [src/client/util/SettingsManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SettingsManager.tsx) | TypeScript React | 114 | 0 | 17 | 131 | +| [src/client/util/SharingManager.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SharingManager.scss) | SCSS | 122 | 0 | 18 | 140 | +| [src/client/util/SharingManager.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/SharingManager.tsx) | TypeScript React | 273 | 0 | 25 | 298 | +| [src/client/util/Transform.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/Transform.ts) | TypeScript | 76 | 0 | 23 | 99 | +| [src/client/util/TypedEvent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/TypedEvent.ts) | TypeScript | 29 | 3 | 8 | 40 | +| [src/client/util/UndoManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/UndoManager.ts) | TypeScript | 167 | 1 | 27 | 195 | +| [src/client/util/clamp.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/clamp.js) | JavaScript | 14 | 0 | 1 | 15 | +| [src/client/util/convertToCSSPTValue.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/convertToCSSPTValue.js) | JavaScript | 31 | 4 | 8 | 43 | +| [src/client/util/jsx-decl.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/jsx-decl.d.ts) | TypeScript | 1 | 0 | 1 | 2 | +| [src/client/util/request-image-size.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/request-image-size.js) | JavaScript | 51 | 10 | 14 | 75 | +| [src/client/util/toCSSLineSpacing.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/util/toCSSLineSpacing.js) | JavaScript | 35 | 15 | 14 | 64 | +| [src/client/views/AntimodeMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/AntimodeMenu.scss) | SCSS | 35 | 1 | 6 | 42 | +| [src/client/views/AntimodeMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/AntimodeMenu.tsx) | TypeScript React | 121 | 14 | 22 | 157 | +| [src/client/views/ContextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenu.scss) | SCSS | 130 | 17 | 14 | 161 | +| [src/client/views/ContextMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenu.tsx) | TypeScript React | 258 | 3 | 33 | 294 | +| [src/client/views/ContextMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ContextMenuItem.tsx) | TypeScript React | 107 | 0 | 10 | 117 | +| [src/client/views/DictationOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DictationOverlay.tsx) | TypeScript React | 64 | 0 | 7 | 71 | +| [src/client/views/DocComponent.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocComponent.tsx) | TypeScript React | 87 | 20 | 15 | 122 | +| [src/client/views/DocumentButtonBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentButtonBar.scss) | SCSS | 89 | 0 | 16 | 105 | +| [src/client/views/DocumentButtonBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentButtonBar.tsx) | TypeScript React | 284 | 4 | 27 | 315 | +| [src/client/views/DocumentDecorations.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.scss) | SCSS | 319 | 0 | 46 | 365 | +| [src/client/views/DocumentDecorations.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx) | TypeScript React | 479 | 2 | 26 | 507 | +| [src/client/views/EditableView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/EditableView.scss) | SCSS | 22 | 0 | 3 | 25 | +| [src/client/views/EditableView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx) | TypeScript React | 149 | 19 | 16 | 184 | +| [src/client/views/GestureOverlay.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GestureOverlay.scss) | SCSS | 56 | 0 | 8 | 64 | +| [src/client/views/GestureOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GestureOverlay.tsx) | TypeScript React | 711 | 45 | 67 | 823 | +| [src/client/views/GlobalKeyHandler.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/GlobalKeyHandler.ts) | TypeScript | 232 | 10 | 27 | 269 | +| [src/client/views/InkingControl.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingControl.scss) | SCSS | 127 | 4 | 0 | 131 | +| [src/client/views/InkingControl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingControl.tsx) | TypeScript React | 78 | 3 | 10 | 91 | +| [src/client/views/InkingStroke.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingStroke.scss) | SCSS | 7 | 0 | 0 | 7 | +| [src/client/views/InkingStroke.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/InkingStroke.tsx) | TypeScript React | 63 | 0 | 5 | 68 | +| [src/client/views/KeyphraseQueryView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/KeyphraseQueryView.scss) | SCSS | 7 | 0 | 1 | 8 | +| [src/client/views/KeyphraseQueryView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/KeyphraseQueryView.tsx) | TypeScript React | 30 | 2 | 3 | 35 | +| [src/client/views/Main.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Main.scss) | SCSS | 51 | 8 | 10 | 69 | +| [src/client/views/Main.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Main.tsx) | TypeScript React | 22 | 1 | 2 | 25 | +| [src/client/views/MainView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainView.scss) | SCSS | 138 | 1 | 21 | 160 | +| [src/client/views/MainView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx) | TypeScript React | 553 | 13 | 36 | 602 | +| [src/client/views/MainViewModal.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewModal.scss) | SCSS | 24 | 0 | 1 | 25 | +| [src/client/views/MainViewModal.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewModal.tsx) | TypeScript React | 39 | 0 | 5 | 44 | +| [src/client/views/MainViewNotifs.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewNotifs.scss) | SCSS | 17 | 0 | 1 | 18 | +| [src/client/views/MainViewNotifs.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MainViewNotifs.tsx) | TypeScript React | 29 | 0 | 4 | 33 | +| [src/client/views/MetadataEntryMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MetadataEntryMenu.scss) | SCSS | 79 | 0 | 14 | 93 | +| [src/client/views/MetadataEntryMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/MetadataEntryMenu.tsx) | TypeScript React | 207 | 0 | 16 | 223 | +| [src/client/views/OCRUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OCRUtils.ts) | TypeScript | 2 | 2 | 4 | 8 | +| [src/client/views/OverlayView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OverlayView.scss) | SCSS | 41 | 0 | 6 | 47 | +| [src/client/views/OverlayView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/OverlayView.tsx) | TypeScript React | 194 | 4 | 19 | 217 | +| [src/client/views/Palette.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Palette.scss) | SCSS | 26 | 0 | 4 | 30 | +| [src/client/views/Palette.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Palette.tsx) | TypeScript React | 65 | 0 | 5 | 70 | +| [src/client/views/PreviewCursor.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/PreviewCursor.scss) | SCSS | 9 | 0 | 1 | 10 | +| [src/client/views/PreviewCursor.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/PreviewCursor.tsx) | TypeScript React | 115 | 8 | 9 | 132 | +| [src/client/views/RecommendationsBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/RecommendationsBox.scss) | SCSS | 52 | 10 | 8 | 70 | +| [src/client/views/RecommendationsBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/RecommendationsBox.tsx) | TypeScript React | 116 | 67 | 17 | 200 | +| [src/client/views/ScriptBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptBox.scss) | SCSS | 15 | 0 | 2 | 17 | +| [src/client/views/ScriptBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptBox.tsx) | TypeScript React | 112 | 2 | 12 | 126 | +| [src/client/views/ScriptingRepl.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptingRepl.scss) | SCSS | 42 | 0 | 9 | 51 | +| [src/client/views/ScriptingRepl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/ScriptingRepl.tsx) | TypeScript React | 220 | 1 | 24 | 245 | +| [src/client/views/SearchDocBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/SearchDocBox.tsx) | TypeScript React | 359 | 17 | 55 | 431 | +| [src/client/views/TemplateMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TemplateMenu.scss) | SCSS | 46 | 0 | 5 | 51 | +| [src/client/views/TemplateMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TemplateMenu.tsx) | TypeScript React | 167 | 1 | 14 | 182 | +| [src/client/views/Templates.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Templates.tsx) | TypeScript React | 34 | 0 | 8 | 42 | +| [src/client/views/TouchScrollableMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/TouchScrollableMenu.tsx) | TypeScript React | 52 | 0 | 7 | 59 | +| [src/client/views/Touchable.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/Touchable.tsx) | TypeScript React | 172 | 28 | 39 | 239 | +| [src/client/views/_nodeModuleOverrides.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/_nodeModuleOverrides.scss) | SCSS | 9 | 9 | 4 | 22 | +| [src/client/views/animationtimeline/Keyframe.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Keyframe.scss) | SCSS | 83 | 5 | 17 | 105 | +| [src/client/views/animationtimeline/Keyframe.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Keyframe.tsx) | TypeScript React | 468 | 50 | 42 | 560 | +| [src/client/views/animationtimeline/Timeline.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Timeline.scss) | SCSS | 264 | 14 | 44 | 322 | +| [src/client/views/animationtimeline/Timeline.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Timeline.tsx) | TypeScript React | 467 | 97 | 58 | 622 | +| [src/client/views/animationtimeline/TimelineMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineMenu.scss) | SCSS | 75 | 2 | 17 | 94 | +| [src/client/views/animationtimeline/TimelineMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineMenu.tsx) | TypeScript React | 68 | 0 | 10 | 78 | +| [src/client/views/animationtimeline/TimelineOverview.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineOverview.scss) | SCSS | 89 | 6 | 12 | 107 | +| [src/client/views/animationtimeline/TimelineOverview.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/TimelineOverview.tsx) | TypeScript React | 155 | 0 | 27 | 182 | +| [src/client/views/animationtimeline/Track.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Track.scss) | SCSS | 13 | 0 | 2 | 15 | +| [src/client/views/animationtimeline/Track.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/animationtimeline/Track.tsx) | TypeScript React | 284 | 63 | 33 | 380 | +| [src/client/views/collections/CollectionCarouselView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionCarouselView.scss) | SCSS | 37 | 0 | 1 | 38 | +| [src/client/views/collections/CollectionCarouselView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionCarouselView.tsx) | TypeScript React | 110 | 1 | 10 | 121 | +| [src/client/views/collections/CollectionDockingView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionDockingView.scss) | SCSS | 391 | 7 | 60 | 458 | +| [src/client/views/collections/CollectionDockingView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionDockingView.tsx) | TypeScript React | 707 | 61 | 65 | 833 | +| [src/client/views/collections/CollectionLinearView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionLinearView.scss) | SCSS | 68 | 0 | 10 | 78 | +| [src/client/views/collections/CollectionLinearView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionLinearView.tsx) | TypeScript React | 125 | 1 | 9 | 135 | +| [src/client/views/collections/CollectionMapView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMapView.scss) | SCSS | 27 | 0 | 3 | 30 | +| [src/client/views/collections/CollectionMapView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMapView.tsx) | TypeScript React | 235 | 10 | 18 | 263 | +| [src/client/views/collections/CollectionMasonryViewFieldRow.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionMasonryViewFieldRow.tsx) | TypeScript React | 308 | 0 | 24 | 332 | +| [src/client/views/collections/CollectionPileView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionPileView.scss) | SCSS | 8 | 0 | 1 | 9 | +| [src/client/views/collections/CollectionPileView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionPileView.tsx) | TypeScript React | 112 | 5 | 11 | 128 | +| [src/client/views/collections/CollectionSchemaCells.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaCells.tsx) | TypeScript React | 274 | 17 | 38 | 329 | +| [src/client/views/collections/CollectionSchemaHeaders.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaHeaders.tsx) | TypeScript React | 318 | 5 | 41 | 364 | +| [src/client/views/collections/CollectionSchemaMovableTableHOC.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx) | TypeScript React | 216 | 0 | 27 | 243 | +| [src/client/views/collections/CollectionSchemaView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaView.scss) | SCSS | 406 | 4 | 88 | 498 | +| [src/client/views/collections/CollectionSchemaView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSchemaView.tsx) | TypeScript React | 651 | 20 | 82 | 753 | +| [src/client/views/collections/CollectionStackingView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingView.scss) | SCSS | 353 | 1 | 50 | 404 | +| [src/client/views/collections/CollectionStackingView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingView.tsx) | TypeScript React | 430 | 5 | 25 | 460 | +| [src/client/views/collections/CollectionStackingViewFieldColumn.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStackingViewFieldColumn.tsx) | TypeScript React | 367 | 0 | 27 | 394 | +| [src/client/views/collections/CollectionStaffView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStaffView.scss) | SCSS | 12 | 0 | 1 | 13 | +| [src/client/views/collections/CollectionStaffView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionStaffView.tsx) | TypeScript React | 46 | 0 | 7 | 53 | +| [src/client/views/collections/CollectionSubView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionSubView.tsx) | TypeScript React | 390 | 7 | 25 | 422 | +| [src/client/views/collections/CollectionTimeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTimeView.scss) | SCSS | 80 | 0 | 13 | 93 | +| [src/client/views/collections/CollectionTimeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTimeView.tsx) | TypeScript React | 177 | 0 | 15 | 192 | +| [src/client/views/collections/CollectionTreeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTreeView.scss) | SCSS | 125 | 4 | 23 | 152 | +| [src/client/views/collections/CollectionTreeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionTreeView.tsx) | TypeScript React | 801 | 19 | 40 | 860 | +| [src/client/views/collections/CollectionView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.scss) | SCSS | 70 | 0 | 8 | 78 | +| [src/client/views/collections/CollectionView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx) | TypeScript React | 457 | 13 | 34 | 504 | +| [src/client/views/collections/CollectionViewChromes.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionViewChromes.scss) | SCSS | 308 | 4 | 45 | 357 | +| [src/client/views/collections/CollectionViewChromes.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionViewChromes.tsx) | TypeScript React | 393 | 67 | 47 | 507 | +| [src/client/views/collections/KeyRestrictionRow.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/KeyRestrictionRow.tsx) | TypeScript React | 49 | 2 | 4 | 55 | +| [src/client/views/collections/ParentDocumentSelector.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/ParentDocumentSelector.scss) | SCSS | 54 | 0 | 2 | 56 | +| [src/client/views/collections/ParentDocumentSelector.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/ParentDocumentSelector.tsx) | TypeScript React | 120 | 0 | 11 | 131 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx) | TypeScript React | 422 | 10 | 27 | 459 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss) | SCSS | 19 | 0 | 1 | 20 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx) | TypeScript React | 110 | 5 | 4 | 119 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss) | SCSS | 11 | 0 | 0 | 11 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx) | TypeScript React | 44 | 0 | 2 | 46 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss) | SCSS | 20 | 1 | 3 | 24 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx) | TypeScript React | 67 | 0 | 12 | 79 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss) | SCSS | 95 | 9 | 17 | 121 | +| [src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx) | TypeScript React | 1,186 | 47 | 97 | 1,330 | +| [src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx) | TypeScript React | 52 | 0 | 5 | 57 | +| [src/client/views/collections/collectionFreeForm/MarqueeView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeView.scss) | SCSS | 30 | 0 | 2 | 32 | +| [src/client/views/collections/collectionFreeForm/MarqueeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionFreeForm/MarqueeView.tsx) | TypeScript React | 484 | 105 | 33 | 622 | +| [src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss) | SCSS | 28 | 0 | 6 | 34 | +| [src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx) | TypeScript React | 202 | 72 | 23 | 297 | +| [src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss) | SCSS | 29 | 0 | 6 | 35 | +| [src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx) | TypeScript React | 204 | 72 | 22 | 298 | +| [src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx) | TypeScript React | 94 | 0 | 9 | 103 | +| [src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx) | TypeScript React | 51 | 0 | 5 | 56 | +| [src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx) | TypeScript React | 51 | 0 | 5 | 56 | +| [src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx) | TypeScript React | 92 | 0 | 9 | 101 | +| [src/client/views/globalCssVariables.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/globalCssVariables.scss) | SCSS | 30 | 10 | 3 | 43 | +| [src/client/views/globalCssVariables.scss.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/globalCssVariables.scss.d.ts) | TypeScript | 9 | 0 | 2 | 11 | +| [src/client/views/linking/LinkEditor.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkEditor.scss) | SCSS | 124 | 1 | 25 | 150 | +| [src/client/views/linking/LinkEditor.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkEditor.tsx) | TypeScript React | 252 | 7 | 50 | 309 | +| [src/client/views/linking/LinkMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenu.scss) | SCSS | 40 | 0 | 13 | 53 | +| [src/client/views/linking/LinkMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenu.tsx) | TypeScript React | 65 | 1 | 10 | 76 | +| [src/client/views/linking/LinkMenuGroup.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuGroup.tsx) | TypeScript React | 84 | 0 | 10 | 94 | +| [src/client/views/linking/LinkMenuItem.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuItem.scss) | SCSS | 75 | 1 | 11 | 87 | +| [src/client/views/linking/LinkMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/linking/LinkMenuItem.tsx) | TypeScript React | 107 | 0 | 19 | 126 | +| [src/client/views/nodes/AudioBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/AudioBox.scss) | SCSS | 146 | 0 | 0 | 146 | +| [src/client/views/nodes/AudioBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/AudioBox.tsx) | TypeScript React | 261 | 2 | 24 | 287 | +| [src/client/views/nodes/CollectionFreeFormDocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/CollectionFreeFormDocumentView.scss) | SCSS | 8 | 0 | 0 | 8 | +| [src/client/views/nodes/CollectionFreeFormDocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/CollectionFreeFormDocumentView.tsx) | TypeScript React | 124 | 0 | 7 | 131 | +| [src/client/views/nodes/ColorBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ColorBox.scss) | SCSS | 22 | 0 | 1 | 23 | +| [src/client/views/nodes/ColorBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ColorBox.tsx) | TypeScript React | 28 | 0 | 4 | 32 | +| [src/client/views/nodes/ContentFittingDocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ContentFittingDocumentView.scss) | SCSS | 20 | 0 | 4 | 24 | +| [src/client/views/nodes/ContentFittingDocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ContentFittingDocumentView.tsx) | TypeScript React | 117 | 0 | 7 | 124 | +| [src/client/views/nodes/DocumentBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentBox.scss) | SCSS | 14 | 0 | 0 | 14 | +| [src/client/views/nodes/DocumentBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentBox.tsx) | TypeScript React | 154 | 0 | 4 | 158 | +| [src/client/views/nodes/DocumentContentsView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentContentsView.tsx) | TypeScript React | 183 | 10 | 17 | 210 | +| [src/client/views/nodes/DocumentIcon.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentIcon.tsx) | TypeScript React | 60 | 0 | 5 | 65 | +| [src/client/views/nodes/DocumentView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.scss) | SCSS | 110 | 1 | 15 | 126 | +| [src/client/views/nodes/DocumentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx) | TypeScript React | 1,041 | 54 | 94 | 1,189 | +| [src/client/views/nodes/FaceRectangle.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FaceRectangle.tsx) | TypeScript React | 25 | 0 | 4 | 29 | +| [src/client/views/nodes/FaceRectangles.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FaceRectangles.tsx) | TypeScript React | 41 | 0 | 5 | 46 | +| [src/client/views/nodes/FieldTextBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FieldTextBox.scss) | SCSS | 12 | 0 | 3 | 15 | +| [src/client/views/nodes/FieldView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FieldView.tsx) | TypeScript React | 89 | 43 | 4 | 136 | +| [src/client/views/nodes/FontIconBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FontIconBox.scss) | SCSS | 25 | 0 | 2 | 27 | +| [src/client/views/nodes/FontIconBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/FontIconBox.tsx) | TypeScript React | 58 | 0 | 5 | 63 | +| [src/client/views/nodes/ImageBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ImageBox.scss) | SCSS | 135 | 0 | 17 | 152 | +| [src/client/views/nodes/ImageBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ImageBox.tsx) | TypeScript React | 430 | 10 | 35 | 475 | +| [src/client/views/nodes/KeyValueBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValueBox.scss) | SCSS | 120 | 0 | 3 | 123 | +| [src/client/views/nodes/KeyValueBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValueBox.tsx) | TypeScript React | 244 | 1 | 26 | 271 | +| [src/client/views/nodes/KeyValuePair.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValuePair.scss) | SCSS | 55 | 1 | 4 | 60 | +| [src/client/views/nodes/KeyValuePair.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/KeyValuePair.tsx) | TypeScript React | 125 | 2 | 8 | 135 | +| [src/client/views/nodes/LabelBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LabelBox.scss) | SCSS | 31 | 0 | 4 | 35 | +| [src/client/views/nodes/LabelBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LabelBox.tsx) | TypeScript React | 86 | 1 | 9 | 96 | +| [src/client/views/nodes/LinkAnchorBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkAnchorBox.scss) | SCSS | 27 | 0 | 2 | 29 | +| [src/client/views/nodes/LinkAnchorBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkAnchorBox.tsx) | TypeScript React | 141 | 0 | 9 | 150 | +| [src/client/views/nodes/LinkBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkBox.scss) | SCSS | 3 | 0 | 0 | 3 | +| [src/client/views/nodes/LinkBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/LinkBox.tsx) | TypeScript React | 33 | 0 | 3 | 36 | +| [src/client/views/nodes/PDFBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PDFBox.scss) | SCSS | 200 | 0 | 19 | 219 | +| [src/client/views/nodes/PDFBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PDFBox.tsx) | TypeScript React | 246 | 0 | 19 | 265 | +| [src/client/views/nodes/PresBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PresBox.scss) | SCSS | 52 | 0 | 1 | 53 | +| [src/client/views/nodes/PresBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/PresBox.tsx) | TypeScript React | 268 | 31 | 32 | 331 | +| [src/client/views/nodes/QueryBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/QueryBox.scss) | SCSS | 5 | 0 | 0 | 5 | +| [src/client/views/nodes/QueryBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/QueryBox.tsx) | TypeScript React | 37 | 0 | 4 | 41 | +| [src/client/views/nodes/RadialMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenu.scss) | SCSS | 60 | 3 | 7 | 70 | +| [src/client/views/nodes/RadialMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenu.tsx) | TypeScript React | 174 | 26 | 36 | 236 | +| [src/client/views/nodes/RadialMenuItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/RadialMenuItem.tsx) | TypeScript React | 101 | 0 | 16 | 117 | +| [src/client/views/nodes/ScreenshotBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScreenshotBox.scss) | SCSS | 40 | 6 | 5 | 51 | +| [src/client/views/nodes/ScreenshotBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScreenshotBox.tsx) | TypeScript React | 174 | 2 | 18 | 194 | +| [src/client/views/nodes/ScriptingBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScriptingBox.scss) | SCSS | 33 | 0 | 3 | 36 | +| [src/client/views/nodes/ScriptingBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/ScriptingBox.tsx) | TypeScript React | 87 | 0 | 12 | 99 | +| [src/client/views/nodes/SliderBox-components.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox-components.tsx) | TypeScript React | 220 | 12 | 25 | 257 | +| [src/client/views/nodes/SliderBox-tooltip.css](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox-tooltip.css) | CSS | 30 | 0 | 3 | 33 | +| [src/client/views/nodes/SliderBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox.scss) | SCSS | 7 | 0 | 0 | 7 | +| [src/client/views/nodes/SliderBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/SliderBox.tsx) | TypeScript React | 117 | 0 | 8 | 125 | +| [src/client/views/nodes/VideoBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/VideoBox.scss) | SCSS | 65 | 3 | 6 | 74 | +| [src/client/views/nodes/VideoBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/VideoBox.tsx) | TypeScript React | 343 | 2 | 34 | 379 | +| [src/client/views/nodes/WebBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/WebBox.scss) | SCSS | 109 | 0 | 18 | 127 | +| [src/client/views/nodes/WebBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/WebBox.tsx) | TypeScript React | 345 | 15 | 35 | 395 | +| [src/client/views/nodes/formattedText/DashDocCommentView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashDocCommentView.tsx) | TypeScript React | 82 | 0 | 13 | 95 | +| [src/client/views/nodes/formattedText/DashDocView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashDocView.tsx) | TypeScript React | 223 | 9 | 37 | 269 | +| [src/client/views/nodes/formattedText/DashFieldView.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashFieldView.scss) | SCSS | 34 | 0 | 2 | 36 | +| [src/client/views/nodes/formattedText/DashFieldView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/DashFieldView.tsx) | TypeScript React | 178 | 13 | 20 | 211 | +| [src/client/views/nodes/formattedText/FootnoteView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FootnoteView.tsx) | TypeScript React | 131 | 13 | 19 | 163 | +| [src/client/views/nodes/formattedText/FormattedTextBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBox.scss) | SCSS | 220 | 11 | 34 | 265 | +| [src/client/views/nodes/formattedText/FormattedTextBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBox.tsx) | TypeScript React | 1,169 | 68 | 93 | 1,330 | +| [src/client/views/nodes/formattedText/FormattedTextBoxComment.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss) | SCSS | 33 | 0 | 0 | 33 | +| [src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx) | TypeScript React | 218 | 11 | 8 | 237 | +| [src/client/views/nodes/formattedText/ImageResizeView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ImageResizeView.tsx) | TypeScript React | 113 | 0 | 25 | 138 | +| [src/client/views/nodes/formattedText/ParagraphNodeSpec.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts) | TypeScript | 108 | 9 | 26 | 143 | +| [src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts) | TypeScript | 231 | 0 | 11 | 242 | +| [src/client/views/nodes/formattedText/RichTextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextMenu.scss) | SCSS | 100 | 2 | 19 | 121 | +| [src/client/views/nodes/formattedText/RichTextMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextMenu.tsx) | TypeScript React | 754 | 12 | 109 | 875 | +| [src/client/views/nodes/formattedText/RichTextRules.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextRules.ts) | TypeScript | 308 | 7 | 5 | 320 | +| [src/client/views/nodes/formattedText/RichTextSchema.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/RichTextSchema.tsx) | TypeScript React | 465 | 33 | 39 | 537 | +| [src/client/views/nodes/formattedText/SummaryView.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/SummaryView.tsx) | TypeScript React | 67 | 0 | 14 | 81 | +| [src/client/views/nodes/formattedText/TooltipTextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/TooltipTextMenu.scss) | SCSS | 306 | 6 | 61 | 373 | +| [src/client/views/nodes/formattedText/marks_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/marks_rts.ts) | TypeScript | 259 | 15 | 23 | 297 | +| [src/client/views/nodes/formattedText/nodes_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/nodes_rts.ts) | TypeScript | 224 | 21 | 19 | 264 | +| [src/client/views/nodes/formattedText/prosemirrorPatches.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/prosemirrorPatches.js) | JavaScript | 118 | 12 | 9 | 139 | +| [src/client/views/nodes/formattedText/schema_rts.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/nodes/formattedText/schema_rts.ts) | TypeScript | 12 | 8 | 6 | 26 | +| [src/client/views/pdf/Annotation.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/Annotation.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/views/pdf/Annotation.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/Annotation.tsx) | TypeScript React | 114 | 0 | 16 | 130 | +| [src/client/views/pdf/PDFMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFMenu.scss) | SCSS | 6 | 0 | 0 | 6 | +| [src/client/views/pdf/PDFMenu.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFMenu.tsx) | TypeScript React | 102 | 0 | 21 | 123 | +| [src/client/views/pdf/PDFViewer.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFViewer.scss) | SCSS | 74 | 4 | 10 | 88 | +| [src/client/views/pdf/PDFViewer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/pdf/PDFViewer.tsx) | TypeScript React | 662 | 23 | 46 | 731 | +| [src/client/views/presentationview/PresElementBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/presentationview/PresElementBox.scss) | SCSS | 93 | 0 | 10 | 103 | +| [src/client/views/presentationview/PresElementBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/presentationview/PresElementBox.tsx) | TypeScript React | 179 | 31 | 14 | 224 | +| [src/client/views/search/CheckBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CheckBox.scss) | SCSS | 50 | 1 | 8 | 59 | +| [src/client/views/search/CheckBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CheckBox.tsx) | TypeScript React | 42 | 74 | 15 | 131 | +| [src/client/views/search/CollectionFilters.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CollectionFilters.scss) | SCSS | 17 | 0 | 3 | 20 | +| [src/client/views/search/CollectionFilters.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/CollectionFilters.tsx) | TypeScript React | 69 | 0 | 14 | 83 | +| [src/client/views/search/FieldFilters.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FieldFilters.scss) | SCSS | 10 | 1 | 1 | 12 | +| [src/client/views/search/FieldFilters.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FieldFilters.tsx) | TypeScript React | 34 | 0 | 7 | 41 | +| [src/client/views/search/FilterBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FilterBox.scss) | SCSS | 153 | 0 | 25 | 178 | +| [src/client/views/search/FilterBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/FilterBox.tsx) | TypeScript React | 354 | 24 | 54 | 432 | +| [src/client/views/search/IconBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconBar.scss) | SCSS | 9 | 0 | 1 | 10 | +| [src/client/views/search/IconBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconBar.tsx) | TypeScript React | 69 | 1 | 17 | 87 | +| [src/client/views/search/IconButton.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconButton.scss) | SCSS | 46 | 1 | 6 | 53 | +| [src/client/views/search/IconButton.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/IconButton.tsx) | TypeScript React | 170 | 2 | 19 | 191 | +| [src/client/views/search/NaviconButton.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/NaviconButton.scss) | SCSS | 58 | 0 | 11 | 69 | +| [src/client/views/search/NaviconButton.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/NaviconButton.tsx) | TypeScript React | 32 | 0 | 5 | 37 | +| [src/client/views/search/SearchBox.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchBox.scss) | SCSS | 203 | 82 | 51 | 336 | +| [src/client/views/search/SearchBox.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchBox.tsx) | TypeScript React | 530 | 47 | 94 | 671 | +| [src/client/views/search/SearchItem.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchItem.scss) | SCSS | 138 | 0 | 25 | 163 | +| [src/client/views/search/SearchItem.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SearchItem.tsx) | TypeScript React | 272 | 2 | 29 | 303 | +| [src/client/views/search/SelectorContextMenu.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/SelectorContextMenu.scss) | SCSS | 12 | 1 | 3 | 16 | +| [src/client/views/search/ToggleBar.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/ToggleBar.scss) | SCSS | 35 | 2 | 4 | 41 | +| [src/client/views/search/ToggleBar.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/search/ToggleBar.tsx) | TypeScript React | 77 | 0 | 9 | 86 | +| [src/client/views/webcam/DashWebRTCVideo.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/DashWebRTCVideo.scss) | SCSS | 70 | 4 | 9 | 83 | +| [src/client/views/webcam/DashWebRTCVideo.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/DashWebRTCVideo.tsx) | TypeScript React | 67 | 6 | 16 | 89 | +| [src/client/views/webcam/WebCamLogic.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/client/views/webcam/WebCamLogic.js) | JavaScript | 234 | 7 | 51 | 292 | +| [src/debug/Repl.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Repl.tsx) | TypeScript React | 59 | 0 | 7 | 66 | +| [src/debug/Test.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Test.tsx) | TypeScript React | 12 | 0 | 2 | 14 | +| [src/debug/Viewer.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/debug/Viewer.tsx) | TypeScript React | 173 | 0 | 19 | 192 | +| [src/extensions/ArrayExtensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/ArrayExtensions.ts) | TypeScript | 26 | 5 | 6 | 37 | +| [src/extensions/General/Extensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/General/Extensions.ts) | TypeScript | 7 | 0 | 2 | 9 | +| [src/extensions/General/ExtensionsTypings.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/General/ExtensionsTypings.ts) | TypeScript | 7 | 0 | 1 | 8 | +| [src/extensions/StringExtensions.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/extensions/StringExtensions.ts) | TypeScript | 13 | 0 | 4 | 17 | +| [src/mobile/ImageUpload.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/ImageUpload.scss) | SCSS | 30 | 0 | 4 | 34 | +| [src/mobile/ImageUpload.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/ImageUpload.tsx) | TypeScript React | 78 | 41 | 12 | 131 | +| [src/mobile/InkControls.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/InkControls.tsx) | TypeScript React | 0 | 0 | 1 | 1 | +| [src/mobile/MobileInkOverlay.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInkOverlay.scss) | SCSS | 33 | 1 | 5 | 39 | +| [src/mobile/MobileInkOverlay.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInkOverlay.tsx) | TypeScript React | 162 | 3 | 26 | 191 | +| [src/mobile/MobileInterface.scss](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInterface.scss) | SCSS | 17 | 0 | 2 | 19 | +| [src/mobile/MobileInterface.tsx](file:///Users/bcz/Documents/GitHub/Dash-Web/src/mobile/MobileInterface.tsx) | TypeScript React | 297 | 15 | 32 | 344 | +| [src/new_fields/CursorField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/CursorField.ts) | TypeScript | 54 | 0 | 12 | 66 | +| [src/new_fields/DateField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/DateField.ts) | TypeScript | 30 | 0 | 7 | 37 | +| [src/new_fields/Doc.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Doc.ts) | TypeScript | 897 | 85 | 76 | 1,058 | +| [src/new_fields/FieldSymbols.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/FieldSymbols.ts) | TypeScript | 11 | 0 | 2 | 13 | +| [src/new_fields/HtmlField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/HtmlField.ts) | TypeScript | 22 | 0 | 5 | 27 | +| [src/new_fields/IconField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/IconField.ts) | TypeScript | 22 | 0 | 5 | 27 | +| [src/new_fields/InkField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/InkField.ts) | TypeScript | 41 | 0 | 10 | 51 | +| [src/new_fields/List.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/List.ts) | TypeScript | 244 | 38 | 20 | 302 | +| [src/new_fields/ListSpec.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ListSpec.ts) | TypeScript | 0 | 0 | 1 | 1 | +| [src/new_fields/ObjectField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ObjectField.ts) | TypeScript | 16 | 0 | 4 | 20 | +| [src/new_fields/PresField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/PresField.ts) | TypeScript | 3 | 1 | 2 | 6 | +| [src/new_fields/Proxy.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Proxy.ts) | TypeScript | 95 | 2 | 14 | 111 | +| [src/new_fields/RefField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RefField.ts) | TypeScript | 17 | 0 | 5 | 22 | +| [src/new_fields/RichTextField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RichTextField.ts) | TypeScript | 33 | 0 | 8 | 41 | +| [src/new_fields/RichTextUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/RichTextUtils.ts) | TypeScript | 455 | 8 | 56 | 519 | +| [src/new_fields/Schema.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Schema.ts) | TypeScript | 107 | 5 | 8 | 120 | +| [src/new_fields/SchemaHeaderField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/SchemaHeaderField.ts) | TypeScript | 104 | 4 | 14 | 122 | +| [src/new_fields/ScriptField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/ScriptField.ts) | TypeScript | 137 | 21 | 19 | 177 | +| [src/new_fields/Types.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/Types.ts) | TypeScript | 86 | 5 | 17 | 108 | +| [src/new_fields/URLField.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/URLField.ts) | TypeScript | 45 | 0 | 9 | 54 | +| [src/new_fields/documentSchemas.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/documentSchemas.ts) | TypeScript | 87 | 0 | 6 | 93 | +| [src/new_fields/util.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/new_fields/util.ts) | TypeScript | 176 | 3 | 15 | 194 | +| [src/pen-gestures/GestureUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/pen-gestures/GestureUtils.ts) | TypeScript | 41 | 0 | 5 | 46 | +| [src/pen-gestures/ndollar.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/pen-gestures/ndollar.ts) | TypeScript | 356 | 172 | 22 | 550 | +| [src/scraping/acm/.gitignore](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/.gitignore) | Ignore | 2 | 0 | 0 | 2 | +| [src/scraping/acm/debug.log](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/debug.log) | log | 38 | 0 | 1 | 39 | +| [src/scraping/acm/index.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/index.js) | JavaScript | 82 | 185 | 13 | 280 | +| [src/scraping/acm/package.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/acm/package.json) | JSON | 17 | 0 | 1 | 18 | +| [src/scraping/buxton/.idea/buxton.iml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/buxton.iml) | XML | 8 | 0 | 0 | 8 | +| [src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml) | XML | 6 | 0 | 0 | 6 | +| [src/scraping/buxton/.idea/misc.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/misc.xml) | XML | 4 | 0 | 0 | 4 | +| [src/scraping/buxton/.idea/modules.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/modules.xml) | XML | 8 | 0 | 0 | 8 | +| [src/scraping/buxton/.idea/vcs.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/vcs.xml) | XML | 6 | 0 | 0 | 6 | +| [src/scraping/buxton/.idea/workspace.xml](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/.idea/workspace.xml) | XML | 173 | 0 | 0 | 173 | +| [src/scraping/buxton/final/BuxtonImporter.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/final/BuxtonImporter.ts) | TypeScript | 228 | 142 | 26 | 396 | +| [src/scraping/buxton/jsonifier.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/jsonifier.py) | Python | 183 | 1 | 48 | 232 | +| [src/scraping/buxton/narratives.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/narratives.py) | Python | 11 | 19 | 9 | 39 | +| [src/scraping/buxton/narratives/chord_keyboards.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/narratives/chord_keyboards.json) | JSON | 39 | 0 | 0 | 39 | +| [src/scraping/buxton/scraper.py](file:///Users/bcz/Documents/GitHub/Dash-Web/src/scraping/buxton/scraper.py) | Python | 350 | 5 | 78 | 433 | +| [src/server/ActionUtilities.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ActionUtilities.ts) | TypeScript | 136 | 1 | 23 | 160 | +| [src/server/ApiManagers/ApiManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/ApiManager.ts) | TypeScript | 8 | 0 | 3 | 11 | +| [src/server/ApiManagers/DeleteManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/DeleteManager.ts) | TypeScript | 71 | 0 | 11 | 82 | +| [src/server/ApiManagers/DownloadManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/DownloadManager.ts) | TypeScript | 173 | 80 | 16 | 269 | +| [src/server/ApiManagers/GeneralGoogleManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/GeneralGoogleManager.ts) | TypeScript | 53 | 0 | 8 | 61 | +| [src/server/ApiManagers/GooglePhotosManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/GooglePhotosManager.ts) | TypeScript | 190 | 119 | 22 | 331 | +| [src/server/ApiManagers/PDFManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/PDFManager.ts) | TypeScript | 103 | 0 | 12 | 115 | +| [src/server/ApiManagers/SearchManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/SearchManager.ts) | TypeScript | 200 | 0 | 15 | 215 | +| [src/server/ApiManagers/SessionManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/SessionManager.ts) | TypeScript | 56 | 0 | 11 | 67 | +| [src/server/ApiManagers/UploadManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UploadManager.ts) | TypeScript | 226 | 1 | 20 | 247 | +| [src/server/ApiManagers/UserManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UserManager.ts) | TypeScript | 101 | 4 | 21 | 126 | +| [src/server/ApiManagers/UtilManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ApiManagers/UtilManager.ts) | TypeScript | 42 | 22 | 10 | 74 | +| [src/server/Client.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Client.ts) | TypeScript | 8 | 0 | 3 | 11 | +| [src/server/DashSession/DashSessionAgent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/DashSessionAgent.ts) | TypeScript | 155 | 52 | 23 | 230 | +| [src/server/DashSession/Session/agents/applied_session_agent.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/applied_session_agent.ts) | TypeScript | 47 | 2 | 9 | 58 | +| [src/server/DashSession/Session/agents/monitor.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/monitor.ts) | TypeScript | 213 | 59 | 26 | 298 | +| [src/server/DashSession/Session/agents/process_message_router.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/process_message_router.ts) | TypeScript | 24 | 10 | 7 | 41 | +| [src/server/DashSession/Session/agents/promisified_ipc_manager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/promisified_ipc_manager.ts) | TypeScript | 106 | 52 | 15 | 173 | +| [src/server/DashSession/Session/agents/server_worker.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/agents/server_worker.ts) | TypeScript | 99 | 46 | 15 | 160 | +| [src/server/DashSession/Session/utilities/repl.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/repl.ts) | TypeScript | 116 | 0 | 12 | 128 | +| [src/server/DashSession/Session/utilities/session_config.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/session_config.ts) | TypeScript | 119 | 0 | 10 | 129 | +| [src/server/DashSession/Session/utilities/utilities.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashSession/Session/utilities/utilities.ts) | TypeScript | 24 | 8 | 5 | 37 | +| [src/server/DashUploadUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/DashUploadUtils.ts) | TypeScript | 285 | 52 | 30 | 367 | +| [src/server/GarbageCollector.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/GarbageCollector.ts) | TypeScript | 138 | 2 | 11 | 151 | +| [src/server/IDatabase.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/IDatabase.ts) | TypeScript | 17 | 0 | 8 | 25 | +| [src/server/MemoryDatabase.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/MemoryDatabase.ts) | TypeScript | 87 | 0 | 14 | 101 | +| [src/server/Message.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Message.ts) | TypeScript | 86 | 0 | 18 | 104 | +| [src/server/PdfTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/PdfTypes.ts) | TypeScript | 19 | 0 | 2 | 21 | +| [src/server/ProcessFactory.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/ProcessFactory.ts) | TypeScript | 34 | 0 | 10 | 44 | +| [src/server/Recommender.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Recommender.ts) | TypeScript | 0 | 120 | 18 | 138 | +| [src/server/RouteManager.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/RouteManager.ts) | TypeScript | 187 | 4 | 19 | 210 | +| [src/server/RouteSubscriber.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/RouteSubscriber.ts) | TypeScript | 21 | 0 | 5 | 26 | +| [src/server/Search.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Search.ts) | TypeScript | 71 | 2 | 8 | 81 | +| [src/server/SharedMediaTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/SharedMediaTypes.ts) | TypeScript | 41 | 0 | 10 | 51 | +| [src/server/Websocket/Websocket.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/Websocket/Websocket.ts) | TypeScript | 263 | 5 | 46 | 314 | +| [src/server/apis/google/GoogleApiServerUtils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/google/GoogleApiServerUtils.ts) | TypeScript | 172 | 168 | 25 | 365 | +| [src/server/apis/google/SharedTypes.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/google/SharedTypes.ts) | TypeScript | 19 | 0 | 2 | 21 | +| [src/server/apis/youtube/youtubeApiSample.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/youtube/youtubeApiSample.d.ts) | TypeScript | 2 | 0 | 0 | 2 | +| [src/server/apis/youtube/youtubeApiSample.js](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/apis/youtube/youtubeApiSample.js) | JavaScript | 135 | 30 | 14 | 179 | +| [src/server/authentication/config/passport.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/config/passport.ts) | TypeScript | 23 | 2 | 4 | 29 | +| [src/server/authentication/controllers/user_controller.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/controllers/user_controller.ts) | TypeScript | 218 | 25 | 25 | 268 | +| [src/server/authentication/models/current_user_utils.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/models/current_user_utils.ts) | TypeScript | 586 | 30 | 57 | 673 | +| [src/server/authentication/models/user_model.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/authentication/models/user_model.ts) | TypeScript | 63 | 9 | 14 | 86 | +| [src/server/credentials/CredentialsLoader.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/credentials/CredentialsLoader.ts) | TypeScript | 24 | 0 | 6 | 30 | +| [src/server/credentials/google_project_credentials.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/credentials/google_project_credentials.json) | JSON | 11 | 0 | 0 | 11 | +| [src/server/database.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/database.ts) | TypeScript | 312 | 0 | 38 | 350 | +| [src/server/downsize.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/downsize.ts) | TypeScript | 34 | 5 | 1 | 40 | +| [src/server/index.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/index.ts) | TypeScript | 107 | 36 | 16 | 159 | +| [src/server/remapUrl.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/remapUrl.ts) | TypeScript | 53 | 4 | 6 | 63 | +| [src/server/server_Initialization.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/server_Initialization.ts) | TypeScript | 138 | 6 | 24 | 168 | +| [src/server/slides.json](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/slides.json) | JSON | 10,820 | 0 | 0 | 10,820 | +| [src/server/updateProtos.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/server/updateProtos.ts) | TypeScript | 11 | 0 | 3 | 14 | +| [src/typings/index.d.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/src/typings/index.d.ts) | TypeScript | 219 | 72 | 38 | 329 | +| [test/test.ts](file:///Users/bcz/Documents/GitHub/Dash-Web/test/test.ts) | TypeScript | 141 | 0 | 20 | 161 | +| [tsconfig.json](file:///Users/bcz/Documents/GitHub/Dash-Web/tsconfig.json) | JSON | 21 | 5 | 0 | 26 | +| [tslint.json](file:///Users/bcz/Documents/GitHub/Dash-Web/tslint.json) | JSON | 30 | 32 | 1 | 63 | +| [views/forgot.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/forgot.pug) | Pug | 19 | 1 | 2 | 22 | +| [views/layout.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/layout.pug) | Pug | 13 | 0 | 1 | 14 | +| [views/login.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/login.pug) | Pug | 24 | 0 | 2 | 26 | +| [views/reset.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/reset.pug) | Pug | 20 | 0 | 2 | 22 | +| [views/signup.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/signup.pug) | Pug | 25 | 0 | 2 | 27 | +| [views/stylesheets/authentication.css](file:///Users/bcz/Documents/GitHub/Dash-Web/views/stylesheets/authentication.css) | CSS | 185 | 4 | 34 | 223 | +| [views/user_activity.pug](file:///Users/bcz/Documents/GitHub/Dash-Web/views/user_activity.pug) | Pug | 18 | 0 | 1 | 19 | +| [webpack.config.js](file:///Users/bcz/Documents/GitHub/Dash-Web/webpack.config.js) | JavaScript | 116 | 0 | 5 | 121 | + +[summary](results.md) \ No newline at end of file diff --git a/.VSCodeCounter/results.csv b/.VSCodeCounter/results.csv new file mode 100644 index 000000000..b31bc8262 --- /dev/null +++ b/.VSCodeCounter/results.csv @@ -0,0 +1,648 @@ +filename, language, Markdown, JavaScript, JSON, Batch, Python, TypeScript, HTML, Pug, Shell Script, CSS, TypeScript React, SCSS, XML, Properties, Ignore, log, XSL, comment, blank, total +README.md, Markdown, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 9 +build/index.html, HTML, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 12 +dash.bat, Batch, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3 +deploy/assets/env.json, JSON, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 +deploy/assets/pdf.worker.js, JavaScript, 0, 55662, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 686, 56522 +deploy/debug/repl.html, HTML, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +deploy/debug/test.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 13 +deploy/debug/viewer.html, HTML, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +deploy/index.html, HTML, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 16 +deploy/mobile/image.html, HTML, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15 +deploy/mobile/ink.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 13 +package-lock.json, JSON, 0, 0, 18689, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 18690 +package.json, JSON, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 267 +sentence_parser.py, Python, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7 +session.config.json, JSON, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 13 +solr-8.3.1/bin/install_solr_service.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 307, 0, 0, 0, 0, 0, 0, 0, 0, 29, 35, 371 +solr-8.3.1/bin/oom_solr.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 15, 3, 31 +solr-8.3.1/bin/solr.cmd, Batch, 0, 0, 0, 1782, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 210, 2035 +solr-8.3.1/bin/solr.in.cmd, Batch, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 29, 178 +solr-8.3.1/bin/solr.in.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 34, 206 +solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd, Batch, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 108 +solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json, JSON, 0, 0, 4465, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4466 +solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1734, 0, 0, 0, 0, 63, 10, 1807 +solr-8.3.1/docs/images/solr.svg, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 1, 40 +solr-8.3.1/docs/index.html, HTML, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 21 +solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 6, 9, 36 +solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 37, 8, 65 +solr-8.3.1/example/example-DIH/solr/atom/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 4, 30 +solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 958, 104, 1354 +solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/db/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 4, 1, 13 +solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 958, 105, 1357 +solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/mail/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 3 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 16, 2, 26 +solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 958, 102, 1352 +solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/example/example-DIH/solr/solr/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 38, 7, 62 +solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 3, 7, 27 +solr-8.3.1/example/example-DIH/solr/tika/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/exampledocs/books.json, JSON, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 52 +solr-8.3.1/example/exampledocs/gb18030-example.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 16, 3, 33 +solr-8.3.1/example/exampledocs/hd.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 20, 4, 57 +solr-8.3.1/example/exampledocs/ipod_other.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 20, 9, 61 +solr-8.3.1/example/exampledocs/ipod_video.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 18, 2, 41 +solr-8.3.1/example/exampledocs/manufacturers.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 16, 3, 76 +solr-8.3.1/example/exampledocs/mem.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 24, 9, 78 +solr-8.3.1/example/exampledocs/money.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 17, 7, 66 +solr-8.3.1/example/exampledocs/monitor.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 18, 3, 35 +solr-8.3.1/example/exampledocs/monitor2.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 18, 3, 34 +solr-8.3.1/example/exampledocs/mp500.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 18, 3, 44 +solr-8.3.1/example/exampledocs/sample.html, HTML, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 14 +solr-8.3.1/example/exampledocs/sd500.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 18, 2, 39 +solr-8.3.1/example/exampledocs/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 16, 3, 39 +solr-8.3.1/example/exampledocs/test_utf8.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 21, 16, 94 +solr-8.3.1/example/exampledocs/utf8-example.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 20, 4, 43 +solr-8.3.1/example/exampledocs/vidcard.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 21, 2, 63 +solr-8.3.1/example/files/browse-resources/velocity/resources.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 6, 5, 83 +solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 1, 19 +solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 3, 21 +solr-8.3.1/example/files/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/example/files/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/example/files/conf/params.json, JSON, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 35 +solr-8.3.1/example/files/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 298, 0, 0, 0, 0, 979, 102, 1379 +solr-8.3.1/example/files/conf/update-script.js, JavaScript, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 23, 116 +solr-8.3.1/example/files/conf/velocity/dropit.js, JavaScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js, JavaScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +solr-8.3.1/example/files/conf/velocity/js/dropit.js, JavaScript, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 19, 98 +solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js, JavaScript, 0, 620, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 76, 764 +solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js, JavaScript, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 71 +solr-8.3.1/example/films/film_data_generator.py, Python, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 12, 118 +solr-8.3.1/example/films/films.json, JSON, 0, 0, 15830, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 15831 +solr-8.3.1/example/films/films.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11438, 0, 0, 0, 0, 0, 1, 11439 +solr-8.3.1/server/contexts/solr-jetty-context.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 1, 9 +solr-8.3.1/server/etc/jetty-http.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 15, 4, 52 +solr-8.3.1/server/etc/jetty-https.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 16, 5, 77 +solr-8.3.1/server/etc/jetty-https8.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 32, 4, 70 +solr-8.3.1/server/etc/jetty-ssl.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 11, 4, 38 +solr-8.3.1/server/etc/jetty.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 94, 18, 222 +solr-8.3.1/server/etc/webdefault.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 232, 24, 528 +solr-8.3.1/server/resources/jetty-logging.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2 +solr-8.3.1/server/resources/log4j2-console.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 43, 6, 68 +solr-8.3.1/server/resources/log4j2.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 80, 9, 143 +solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 0, 2, 23, 177 +solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat, Batch, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 26 +solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh, Shell Script, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 27 +solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 42, 11, 115 +solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 0, 0, 0, 0, 0, 0, 0, 19, 48, 304 +solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 402, 0, 0, 0, 0, 0, 0, 0, 55, 9, 466 +solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 594, 0, 0, 0, 0, 0, 0, 0, 23, 106, 723 +solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 0, 0, 0, 18, 65, 379 +solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 647, 0, 0, 0, 0, 0, 0, 0, 19, 106, 772 +solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 171, 0, 0, 0, 0, 0, 0, 0, 18, 37, 226 +solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 18, 28, 180 +solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 292, 0, 0, 0, 0, 0, 0, 0, 18, 61, 371 +solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 0, 0, 0, 23, 26, 180 +solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 18, 7, 54 +solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 0, 18, 35, 217 +solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 18, 6, 48 +solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 26, 2, 29 +solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 22, 2, 25 +solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 303, 0, 0, 0, 0, 0, 0, 0, 19, 63, 385 +solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 18, 12, 110 +solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 257, 0, 0, 0, 0, 0, 0, 0, 18, 56, 331 +solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 18, 5, 43 +solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 0, 18, 31, 221 +solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 18, 25, 163 +solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 404, 0, 0, 0, 0, 0, 0, 0, 18, 79, 501 +solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 596, 0, 0, 0, 0, 0, 0, 0, 20, 112, 728 +solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0, 18, 22, 173 +solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 0, 0, 0, 0, 22, 34, 234 +solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 18, 4, 65 +solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 18, 24, 161 +solr-8.3.1/server/solr-webapp/webapp/img/solr.svg, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 1, 40 +solr-8.3.1/server/solr-webapp/webapp/index.html, HTML, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 38, 257 +solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js, JavaScript, 0, 512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 31, 562 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js, JavaScript, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 4, 28 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js, JavaScript, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 23, 202 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js, JavaScript, 0, 847, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 124, 1022 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js, JavaScript, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 2, 63 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js, JavaScript, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 6, 40 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js, JavaScript, 0, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 27, 290 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js, JavaScript, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 94 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js, JavaScript, 0, 151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 181 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js, JavaScript, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 44, 303 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js, JavaScript, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 138 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js, JavaScript, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 13, 101 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js, JavaScript, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 14, 98 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js, JavaScript, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 3, 46 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js, JavaScript, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 12, 159 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js, JavaScript, 0, 269, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 19, 318 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js, JavaScript, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 19, 168 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js, JavaScript, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 14, 121 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js, JavaScript, 0, 178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 40, 236 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js, JavaScript, 0, 524, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 69, 612 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js, JavaScript, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 20, 100 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js, JavaScript, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 51, 240 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js, JavaScript, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 51 +solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js, JavaScript, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 2, 38 +solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js, JavaScript, 0, 311, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 11, 340 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js, JavaScript, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 4, 140 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js, JavaScript, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 22, 230 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 32 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js, JavaScript, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 37 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js, JavaScript, 0, 316, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 636, 67, 1019 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js, JavaScript, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 39 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js, JavaScript, 0, 311, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 328, 65, 704 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js, JavaScript, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 40 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js, JavaScript, 0, 157, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 13, 218 +solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js, JavaScript, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 3, 46 +solr-8.3.1/server/solr-webapp/webapp/libs/angular.js, JavaScript, 0, 10845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13211, 2038, 26094 +solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js, JavaScript, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 274 +solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js, JavaScript, 0, 1151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 8, 1195 +solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/d3.js, JavaScript, 0, 7720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 519, 1135, 9374 +solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 1, 32 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js, JavaScript, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 2, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js, JavaScript, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 1, 30 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js, JavaScript, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 3, 31 +solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js, JavaScript, 0, 3222, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 85, 3535 +solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js, JavaScript, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 13, 102 +solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html, HTML, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 10, 47 +solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html, HTML, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 26, 129 +solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html, HTML, 0, 0, 0, 0, 0, 0, 263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 24, 303 +solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html, HTML, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 4, 50 +solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html, HTML, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 22, 86 +solr-8.3.1/server/solr-webapp/webapp/partials/collections.html, HTML, 0, 0, 0, 0, 0, 0, 301, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 79, 396 +solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html, HTML, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 66, 207 +solr-8.3.1/server/solr-webapp/webapp/partials/cores.html, HTML, 0, 0, 0, 0, 0, 0, 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 67, 225 +solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html, HTML, 0, 0, 0, 0, 0, 0, 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 52, 210 +solr-8.3.1/server/solr-webapp/webapp/partials/documents.html, HTML, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 9, 112 +solr-8.3.1/server/solr-webapp/webapp/partials/files.html, HTML, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 15, 48 +solr-8.3.1/server/solr-webapp/webapp/partials/index.html, HTML, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 85, 262 +solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html, HTML, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 28 +solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html, HTML, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 6, 57 +solr-8.3.1/server/solr-webapp/webapp/partials/logging.html, HTML, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2, 58 +solr-8.3.1/server/solr-webapp/webapp/partials/login.html, HTML, 0, 0, 0, 0, 0, 0, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 11, 161 +solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html, HTML, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 8, 73 +solr-8.3.1/server/solr-webapp/webapp/partials/query.html, HTML, 0, 0, 0, 0, 0, 0, 270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 84, 370 +solr-8.3.1/server/solr-webapp/webapp/partials/replication.html, HTML, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 71, 240 +solr-8.3.1/server/solr-webapp/webapp/partials/schema.html, HTML, 0, 0, 0, 0, 0, 0, 336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 104, 456 +solr-8.3.1/server/solr-webapp/webapp/partials/segments.html, HTML, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 100 +solr-8.3.1/server/solr-webapp/webapp/partials/stream.html, HTML, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 9, 65 +solr-8.3.1/server/solr-webapp/webapp/partials/threads.html, HTML, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 14, 66 +solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html, HTML, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 3, 24 +solr-8.3.1/server/solr/configsets/_default/conf/params.json, JSON, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 21 +solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 976, 98, 1370 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json, JSON, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json, JSON, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 39 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 11, 1, 25 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 6, 1, 20 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 19, 4, 68 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 37, 3, 43 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 410, 0, 0, 0, 0, 1097, 124, 1631 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js, JavaScript, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 13, 54 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 9, 6, 49 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js, JavaScript, 0, 620, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 76, 764 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 0, 0, 0, 0, 0, 0, 0, 0, 47, 232 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98, 20, 15, 133 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 20, 8, 68 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 20, 6, 67 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 19, 18, 338 +solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl, XSL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 25, 11, 71 +solr-8.3.1/server/solr/dash/conf/params.json, JSON, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 +solr-8.3.1/server/solr/dash/conf/schema.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 4, 7, 62 +solr-8.3.1/server/solr/dash/conf/solrconfig.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 270, 0, 0, 0, 0, 962, 97, 1329 +solr-8.3.1/server/solr/dash/core.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 7 +solr-8.3.1/server/solr/solr.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 25, 11, 57 +solr-8.3.1/server/solr/zoo.cfg, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 25, 4, 32 +solr-8.3.1/server/tmp/start_3204295554151338130.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_5812170489311981381.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_6476327636763392575.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_7329004517204835686.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +solr-8.3.1/server/tmp/start_9067375725008958788.properties, Properties, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 2, 1, 12 +src/Utils.ts, TypeScript, 0, 0, 0, 0, 0, 441, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 81, 545 +src/client/ClientRecommender.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 1, 2, 12 +src/client/ClientRecommender.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 320, 0, 0, 0, 0, 0, 0, 61, 44, 425 +src/client/DocServer.ts, TypeScript, 0, 0, 0, 0, 0, 279, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 65, 480 +src/client/Network.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 39 +src/client/apis/GoogleAuthenticationManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 3, 19 +src/client/apis/GoogleAuthenticationManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 2, 11, 128 +src/client/apis/IBM_Recommender.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 7, 40 +src/client/apis/google_docs/GoogleApiClientUtils.ts, TypeScript, 0, 0, 0, 0, 0, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 27, 261 +src/client/apis/google_docs/GooglePhotosClientUtils.ts, TypeScript, 0, 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 364 +src/client/apis/youtube/YoutubeBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 0, 0, 0, 0, 0, 5, 16, 126 +src/client/apis/youtube/YoutubeBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274, 0, 0, 0, 0, 0, 0, 48, 40, 362 +src/client/cognitive_services/CognitiveServices.ts, TypeScript, 0, 0, 0, 0, 0, 342, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 57, 409 +src/client/documents/DocumentTypes.ts, TypeScript, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 37 +src/client/documents/Documents.ts, TypeScript, 0, 0, 0, 0, 0, 807, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 87, 1035 +src/client/goldenLayout.d.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3 +src/client/goldenLayout.js, JavaScript, 0, 3084, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1571, 720, 5375 +src/client/util/DictationManager.ts, TypeScript, 0, 0, 0, 0, 0, 312, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 51, 388 +src/client/util/DocumentManager.ts, TypeScript, 0, 0, 0, 0, 0, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 21, 244 +src/client/util/DragManager.ts, TypeScript, 0, 0, 0, 0, 0, 504, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 35, 550 +src/client/util/DropConverter.ts, TypeScript, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 78 +src/client/util/History.ts, TypeScript, 0, 0, 0, 0, 0, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 27, 206 +src/client/util/Import & Export/DirectoryImportBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/util/Import & Export/DirectoryImportBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 393, 0, 0, 0, 0, 0, 0, 0, 31, 424 +src/client/util/Import & Export/ImageUtils.ts, TypeScript, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 39 +src/client/util/Import & Export/ImportMetadataEntry.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 0, 0, 0, 0, 0, 0, 0, 17, 149 +src/client/util/InteractionUtils.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 112, 21, 255 +src/client/util/KeyCodes.ts, TypeScript, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 136 +src/client/util/LinkManager.ts, TypeScript, 0, 0, 0, 0, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 23, 214 +src/client/util/ProsemirrorCopy/prompt.js, JavaScript, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 22, 180 +src/client/util/Scripting.ts, TypeScript, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 30, 292 +src/client/util/ScrollBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/client/util/SearchUtil.ts, TypeScript, 0, 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 18, 145 +src/client/util/SelectionManager.ts, TypeScript, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 15, 89 +src/client/util/SerializationHelper.ts, TypeScript, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 143 +src/client/util/SettingsManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 25, 136 +src/client/util/SettingsManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 17, 131 +src/client/util/SharingManager.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, 0, 18, 140 +src/client/util/SharingManager.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 273, 0, 0, 0, 0, 0, 0, 0, 25, 298 +src/client/util/Transform.ts, TypeScript, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 99 +src/client/util/TypedEvent.ts, TypeScript, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 40 +src/client/util/UndoManager.ts, TypeScript, 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 27, 195 +src/client/util/clamp.js, JavaScript, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 15 +src/client/util/convertToCSSPTValue.js, JavaScript, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 8, 43 +src/client/util/jsx-decl.d.ts, TypeScript, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2 +src/client/util/request-image-size.js, JavaScript, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 14, 75 +src/client/util/toCSSLineSpacing.js, JavaScript, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 14, 64 +src/client/views/AntimodeMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 1, 6, 42 +src/client/views/AntimodeMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 0, 0, 0, 0, 0, 0, 14, 22, 157 +src/client/views/ContextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 130, 0, 0, 0, 0, 0, 17, 14, 161 +src/client/views/ContextMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 258, 0, 0, 0, 0, 0, 0, 3, 33, 294 +src/client/views/ContextMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 10, 117 +src/client/views/DictationOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 7, 71 +src/client/views/DocComponent.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 20, 15, 122 +src/client/views/DocumentButtonBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 16, 105 +src/client/views/DocumentButtonBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284, 0, 0, 0, 0, 0, 0, 4, 27, 315 +src/client/views/DocumentDecorations.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 319, 0, 0, 0, 0, 0, 0, 46, 365 +src/client/views/DocumentDecorations.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 479, 0, 0, 0, 0, 0, 0, 2, 26, 507 +src/client/views/EditableView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 3, 25 +src/client/views/EditableView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 19, 16, 184 +src/client/views/GestureOverlay.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 8, 64 +src/client/views/GestureOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 711, 0, 0, 0, 0, 0, 0, 45, 67, 823 +src/client/views/GlobalKeyHandler.ts, TypeScript, 0, 0, 0, 0, 0, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 27, 269 +src/client/views/InkingControl.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 0, 0, 4, 0, 131 +src/client/views/InkingControl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 3, 10, 91 +src/client/views/InkingStroke.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7 +src/client/views/InkingStroke.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 5, 68 +src/client/views/KeyphraseQueryView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 1, 8 +src/client/views/KeyphraseQueryView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 2, 3, 35 +src/client/views/Main.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 8, 10, 69 +src/client/views/Main.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 2, 25 +src/client/views/MainView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 1, 21, 160 +src/client/views/MainView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 553, 0, 0, 0, 0, 0, 0, 13, 36, 602 +src/client/views/MainViewModal.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 1, 25 +src/client/views/MainViewModal.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 5, 44 +src/client/views/MainViewNotifs.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 1, 18 +src/client/views/MainViewNotifs.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 4, 33 +src/client/views/MetadataEntryMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, 0, 0, 0, 14, 93 +src/client/views/MetadataEntryMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 0, 0, 0, 0, 0, 16, 223 +src/client/views/OCRUtils.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 8 +src/client/views/OverlayView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 6, 47 +src/client/views/OverlayView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 4, 19, 217 +src/client/views/Palette.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 4, 30 +src/client/views/Palette.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 5, 70 +src/client/views/PreviewCursor.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1, 10 +src/client/views/PreviewCursor.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, 8, 9, 132 +src/client/views/RecommendationsBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 10, 8, 70 +src/client/views/RecommendationsBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 67, 17, 200 +src/client/views/ScriptBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 2, 17 +src/client/views/ScriptBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 2, 12, 126 +src/client/views/ScriptingRepl.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 9, 51 +src/client/views/ScriptingRepl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 1, 24, 245 +src/client/views/SearchDocBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 359, 0, 0, 0, 0, 0, 0, 17, 55, 431 +src/client/views/TemplateMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 5, 51 +src/client/views/TemplateMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 0, 0, 1, 14, 182 +src/client/views/Templates.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 8, 42 +src/client/views/TouchScrollableMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 7, 59 +src/client/views/Touchable.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 28, 39, 239 +src/client/views/_nodeModuleOverrides.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 9, 4, 22 +src/client/views/animationtimeline/Keyframe.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, 0, 0, 5, 17, 105 +src/client/views/animationtimeline/Keyframe.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 468, 0, 0, 0, 0, 0, 0, 50, 42, 560 +src/client/views/animationtimeline/Timeline.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 264, 0, 0, 0, 0, 0, 14, 44, 322 +src/client/views/animationtimeline/Timeline.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 467, 0, 0, 0, 0, 0, 0, 97, 58, 622 +src/client/views/animationtimeline/TimelineMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 2, 17, 94 +src/client/views/animationtimeline/TimelineMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 10, 78 +src/client/views/animationtimeline/TimelineOverview.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 6, 12, 107 +src/client/views/animationtimeline/TimelineOverview.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155, 0, 0, 0, 0, 0, 0, 0, 27, 182 +src/client/views/animationtimeline/Track.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 2, 15 +src/client/views/animationtimeline/Track.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 284, 0, 0, 0, 0, 0, 0, 63, 33, 380 +src/client/views/collections/CollectionCarouselView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 1, 38 +src/client/views/collections/CollectionCarouselView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 1, 10, 121 +src/client/views/collections/CollectionDockingView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 391, 0, 0, 0, 0, 0, 7, 60, 458 +src/client/views/collections/CollectionDockingView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 707, 0, 0, 0, 0, 0, 0, 61, 65, 833 +src/client/views/collections/CollectionLinearView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 10, 78 +src/client/views/collections/CollectionLinearView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 1, 9, 135 +src/client/views/collections/CollectionMapView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 3, 30 +src/client/views/collections/CollectionMapView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 235, 0, 0, 0, 0, 0, 0, 10, 18, 263 +src/client/views/collections/CollectionMasonryViewFieldRow.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 0, 0, 24, 332 +src/client/views/collections/CollectionPileView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 9 +src/client/views/collections/CollectionPileView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 5, 11, 128 +src/client/views/collections/CollectionSchemaCells.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 274, 0, 0, 0, 0, 0, 0, 17, 38, 329 +src/client/views/collections/CollectionSchemaHeaders.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 5, 41, 364 +src/client/views/collections/CollectionSchemaMovableTableHOC.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 0, 0, 0, 0, 27, 243 +src/client/views/collections/CollectionSchemaView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 406, 0, 0, 0, 0, 0, 4, 88, 498 +src/client/views/collections/CollectionSchemaView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 651, 0, 0, 0, 0, 0, 0, 20, 82, 753 +src/client/views/collections/CollectionStackingView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 353, 0, 0, 0, 0, 0, 1, 50, 404 +src/client/views/collections/CollectionStackingView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 430, 0, 0, 0, 0, 0, 0, 5, 25, 460 +src/client/views/collections/CollectionStackingViewFieldColumn.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367, 0, 0, 0, 0, 0, 0, 0, 27, 394 +src/client/views/collections/CollectionStaffView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 1, 13 +src/client/views/collections/CollectionStaffView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 7, 53 +src/client/views/collections/CollectionSubView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 390, 0, 0, 0, 0, 0, 0, 7, 25, 422 +src/client/views/collections/CollectionTimeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 13, 93 +src/client/views/collections/CollectionTimeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 0, 15, 192 +src/client/views/collections/CollectionTreeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 4, 23, 152 +src/client/views/collections/CollectionTreeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 801, 0, 0, 0, 0, 0, 0, 19, 40, 860 +src/client/views/collections/CollectionView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, 8, 78 +src/client/views/collections/CollectionView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 457, 0, 0, 0, 0, 0, 0, 13, 34, 504 +src/client/views/collections/CollectionViewChromes.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 4, 45, 357 +src/client/views/collections/CollectionViewChromes.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 393, 0, 0, 0, 0, 0, 0, 67, 47, 507 +src/client/views/collections/KeyRestrictionRow.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 2, 4, 55 +src/client/views/collections/ParentDocumentSelector.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 2, 56 +src/client/views/collections/ParentDocumentSelector.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 11, 131 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 422, 0, 0, 0, 0, 0, 0, 10, 27, 459 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 1, 20 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 5, 4, 119 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 11 +src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 2, 46 +src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 1, 3, 24 +src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 12, 79 +src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 9, 17, 121 +src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1186, 0, 0, 0, 0, 0, 0, 47, 97, 1330 +src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 5, 57 +src/client/views/collections/collectionFreeForm/MarqueeView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 2, 32 +src/client/views/collections/collectionFreeForm/MarqueeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 484, 0, 0, 0, 0, 0, 0, 105, 33, 622 +src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 6, 34 +src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 72, 23, 297 +src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 6, 35 +src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 0, 0, 0, 0, 0, 0, 72, 22, 298 +src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 9, 103 +src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 5, 56 +src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 5, 56 +src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 9, 101 +src/client/views/globalCssVariables.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 10, 3, 43 +src/client/views/globalCssVariables.scss.d.ts, TypeScript, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 11 +src/client/views/linking/LinkEditor.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 1, 25, 150 +src/client/views/linking/LinkEditor.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 0, 7, 50, 309 +src/client/views/linking/LinkMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 13, 53 +src/client/views/linking/LinkMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 1, 10, 76 +src/client/views/linking/LinkMenuGroup.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 10, 94 +src/client/views/linking/LinkMenuItem.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 0, 1, 11, 87 +src/client/views/linking/LinkMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 19, 126 +src/client/views/nodes/AudioBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146, 0, 0, 0, 0, 0, 0, 0, 146 +src/client/views/nodes/AudioBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 261, 0, 0, 0, 0, 0, 0, 2, 24, 287 +src/client/views/nodes/CollectionFreeFormDocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8 +src/client/views/nodes/CollectionFreeFormDocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 7, 131 +src/client/views/nodes/ColorBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 23 +src/client/views/nodes/ColorBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 4, 32 +src/client/views/nodes/ContentFittingDocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 4, 24 +src/client/views/nodes/ContentFittingDocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 7, 124 +src/client/views/nodes/DocumentBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 14 +src/client/views/nodes/DocumentBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 4, 158 +src/client/views/nodes/DocumentContentsView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 10, 17, 210 +src/client/views/nodes/DocumentIcon.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 5, 65 +src/client/views/nodes/DocumentView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 0, 0, 0, 0, 1, 15, 126 +src/client/views/nodes/DocumentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1041, 0, 0, 0, 0, 0, 0, 54, 94, 1189 +src/client/views/nodes/FaceRectangle.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 4, 29 +src/client/views/nodes/FaceRectangles.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 5, 46 +src/client/views/nodes/FieldTextBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 3, 15 +src/client/views/nodes/FieldView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 43, 4, 136 +src/client/views/nodes/FontIconBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 2, 27 +src/client/views/nodes/FontIconBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 5, 63 +src/client/views/nodes/ImageBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 17, 152 +src/client/views/nodes/ImageBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 430, 0, 0, 0, 0, 0, 0, 10, 35, 475 +src/client/views/nodes/KeyValueBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 3, 123 +src/client/views/nodes/KeyValueBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 1, 26, 271 +src/client/views/nodes/KeyValuePair.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 1, 4, 60 +src/client/views/nodes/KeyValuePair.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 2, 8, 135 +src/client/views/nodes/LabelBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 4, 35 +src/client/views/nodes/LabelBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 1, 9, 96 +src/client/views/nodes/LinkAnchorBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 2, 29 +src/client/views/nodes/LinkAnchorBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 141, 0, 0, 0, 0, 0, 0, 0, 9, 150 +src/client/views/nodes/LinkBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3 +src/client/views/nodes/LinkBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 3, 36 +src/client/views/nodes/PDFBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 19, 219 +src/client/views/nodes/PDFBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 19, 265 +src/client/views/nodes/PresBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 1, 53 +src/client/views/nodes/PresBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 268, 0, 0, 0, 0, 0, 0, 31, 32, 331 +src/client/views/nodes/QueryBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 5 +src/client/views/nodes/QueryBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 4, 41 +src/client/views/nodes/RadialMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 3, 7, 70 +src/client/views/nodes/RadialMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 0, 0, 0, 26, 36, 236 +src/client/views/nodes/RadialMenuItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 16, 117 +src/client/views/nodes/ScreenshotBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 6, 5, 51 +src/client/views/nodes/ScreenshotBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 0, 0, 0, 0, 0, 0, 2, 18, 194 +src/client/views/nodes/ScriptingBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 3, 36 +src/client/views/nodes/ScriptingBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 12, 99 +src/client/views/nodes/SliderBox-components.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 12, 25, 257 +src/client/views/nodes/SliderBox-tooltip.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 3, 33 +src/client/views/nodes/SliderBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7 +src/client/views/nodes/SliderBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 8, 125 +src/client/views/nodes/VideoBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 3, 6, 74 +src/client/views/nodes/VideoBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 343, 0, 0, 0, 0, 0, 0, 2, 34, 379 +src/client/views/nodes/WebBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, 0, 0, 18, 127 +src/client/views/nodes/WebBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 345, 0, 0, 0, 0, 0, 0, 15, 35, 395 +src/client/views/nodes/formattedText/DashDocCommentView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 13, 95 +src/client/views/nodes/formattedText/DashDocView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 9, 37, 269 +src/client/views/nodes/formattedText/DashFieldView.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 2, 36 +src/client/views/nodes/formattedText/DashFieldView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 0, 0, 0, 13, 20, 211 +src/client/views/nodes/formattedText/FootnoteView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 0, 0, 0, 0, 0, 0, 13, 19, 163 +src/client/views/nodes/formattedText/FormattedTextBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 11, 34, 265 +src/client/views/nodes/formattedText/FormattedTextBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1169, 0, 0, 0, 0, 0, 0, 68, 93, 1330 +src/client/views/nodes/formattedText/FormattedTextBoxComment.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 33 +src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 0, 0, 0, 0, 0, 0, 11, 8, 237 +src/client/views/nodes/formattedText/ImageResizeView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 25, 138 +src/client/views/nodes/formattedText/ParagraphNodeSpec.ts, TypeScript, 0, 0, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 26, 143 +src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts, TypeScript, 0, 0, 0, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 242 +src/client/views/nodes/formattedText/RichTextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 2, 19, 121 +src/client/views/nodes/formattedText/RichTextMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 754, 0, 0, 0, 0, 0, 0, 12, 109, 875 +src/client/views/nodes/formattedText/RichTextRules.ts, TypeScript, 0, 0, 0, 0, 0, 308, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 5, 320 +src/client/views/nodes/formattedText/RichTextSchema.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 465, 0, 0, 0, 0, 0, 0, 33, 39, 537 +src/client/views/nodes/formattedText/SummaryView.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 14, 81 +src/client/views/nodes/formattedText/TooltipTextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 306, 0, 0, 0, 0, 0, 6, 61, 373 +src/client/views/nodes/formattedText/marks_rts.ts, TypeScript, 0, 0, 0, 0, 0, 259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 23, 297 +src/client/views/nodes/formattedText/nodes_rts.ts, TypeScript, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 264 +src/client/views/nodes/formattedText/prosemirrorPatches.js, JavaScript, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 9, 139 +src/client/views/nodes/formattedText/schema_rts.ts, TypeScript, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 26 +src/client/views/pdf/Annotation.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/views/pdf/Annotation.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 16, 130 +src/client/views/pdf/PDFMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6 +src/client/views/pdf/PDFMenu.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 21, 123 +src/client/views/pdf/PDFViewer.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 4, 10, 88 +src/client/views/pdf/PDFViewer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 662, 0, 0, 0, 0, 0, 0, 23, 46, 731 +src/client/views/presentationview/PresElementBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 0, 0, 0, 0, 0, 0, 10, 103 +src/client/views/presentationview/PresElementBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 179, 0, 0, 0, 0, 0, 0, 31, 14, 224 +src/client/views/search/CheckBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 1, 8, 59 +src/client/views/search/CheckBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 74, 15, 131 +src/client/views/search/CollectionFilters.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 3, 20 +src/client/views/search/CollectionFilters.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 14, 83 +src/client/views/search/FieldFilters.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 1, 1, 12 +src/client/views/search/FieldFilters.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 7, 41 +src/client/views/search/FilterBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 25, 178 +src/client/views/search/FilterBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 354, 0, 0, 0, 0, 0, 0, 24, 54, 432 +src/client/views/search/IconBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 1, 10 +src/client/views/search/IconBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 1, 17, 87 +src/client/views/search/IconButton.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 1, 6, 53 +src/client/views/search/IconButton.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170, 0, 0, 0, 0, 0, 0, 2, 19, 191 +src/client/views/search/NaviconButton.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 0, 0, 0, 0, 0, 0, 11, 69 +src/client/views/search/NaviconButton.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 5, 37 +src/client/views/search/SearchBox.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 0, 0, 82, 51, 336 +src/client/views/search/SearchBox.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 530, 0, 0, 0, 0, 0, 0, 47, 94, 671 +src/client/views/search/SearchItem.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 25, 163 +src/client/views/search/SearchItem.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 0, 0, 0, 0, 0, 0, 2, 29, 303 +src/client/views/search/SelectorContextMenu.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 1, 3, 16 +src/client/views/search/ToggleBar.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 2, 4, 41 +src/client/views/search/ToggleBar.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 9, 86 +src/client/views/webcam/DashWebRTCVideo.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 4, 9, 83 +src/client/views/webcam/DashWebRTCVideo.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 6, 16, 89 +src/client/views/webcam/WebCamLogic.js, JavaScript, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 51, 292 +src/debug/Repl.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 7, 66 +src/debug/Test.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 2, 14 +src/debug/Viewer.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 19, 192 +src/extensions/ArrayExtensions.ts, TypeScript, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 37 +src/extensions/General/Extensions.ts, TypeScript, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 9 +src/extensions/General/ExtensionsTypings.ts, TypeScript, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 8 +src/extensions/StringExtensions.ts, TypeScript, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 17 +src/mobile/ImageUpload.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 4, 34 +src/mobile/ImageUpload.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 0, 0, 0, 0, 0, 0, 41, 12, 131 +src/mobile/InkControls.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +src/mobile/MobileInkOverlay.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 1, 5, 39 +src/mobile/MobileInkOverlay.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162, 0, 0, 0, 0, 0, 0, 3, 26, 191 +src/mobile/MobileInterface.scss, SCSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 2, 19 +src/mobile/MobileInterface.tsx, TypeScript React, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 297, 0, 0, 0, 0, 0, 0, 15, 32, 344 +src/new_fields/CursorField.ts, TypeScript, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 66 +src/new_fields/DateField.ts, TypeScript, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 37 +src/new_fields/Doc.ts, TypeScript, 0, 0, 0, 0, 0, 897, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 76, 1058 +src/new_fields/FieldSymbols.ts, TypeScript, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 13 +src/new_fields/HtmlField.ts, TypeScript, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 27 +src/new_fields/IconField.ts, TypeScript, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 27 +src/new_fields/InkField.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 51 +src/new_fields/List.ts, TypeScript, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 20, 302 +src/new_fields/ListSpec.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 +src/new_fields/ObjectField.ts, TypeScript, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 20 +src/new_fields/PresField.ts, TypeScript, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 6 +src/new_fields/Proxy.ts, TypeScript, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 14, 111 +src/new_fields/RefField.ts, TypeScript, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 22 +src/new_fields/RichTextField.ts, TypeScript, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 41 +src/new_fields/RichTextUtils.ts, TypeScript, 0, 0, 0, 0, 0, 455, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 56, 519 +src/new_fields/Schema.ts, TypeScript, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 120 +src/new_fields/SchemaHeaderField.ts, TypeScript, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 14, 122 +src/new_fields/ScriptField.ts, TypeScript, 0, 0, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 19, 177 +src/new_fields/Types.ts, TypeScript, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 17, 108 +src/new_fields/URLField.ts, TypeScript, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 54 +src/new_fields/documentSchemas.ts, TypeScript, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 93 +src/new_fields/util.ts, TypeScript, 0, 0, 0, 0, 0, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15, 194 +src/pen-gestures/GestureUtils.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 46 +src/pen-gestures/ndollar.ts, TypeScript, 0, 0, 0, 0, 0, 356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 22, 550 +src/scraping/acm/.gitignore, Ignore, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2 +src/scraping/acm/debug.log, log, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 1, 39 +src/scraping/acm/index.js, JavaScript, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 13, 280 +src/scraping/acm/package.json, JSON, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 18 +src/scraping/buxton/.idea/buxton.iml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 8 +src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6 +src/scraping/buxton/.idea/misc.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4 +src/scraping/buxton/.idea/modules.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 8 +src/scraping/buxton/.idea/vcs.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 6 +src/scraping/buxton/.idea/workspace.xml, XML, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 173 +src/scraping/buxton/final/BuxtonImporter.ts, TypeScript, 0, 0, 0, 0, 0, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 142, 26, 396 +src/scraping/buxton/jsonifier.py, Python, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 48, 232 +src/scraping/buxton/narratives.py, Python, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 9, 39 +src/scraping/buxton/narratives/chord_keyboards.json, JSON, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 +src/scraping/buxton/scraper.py, Python, 0, 0, 0, 0, 350, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 78, 433 +src/server/ActionUtilities.ts, TypeScript, 0, 0, 0, 0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 23, 160 +src/server/ApiManagers/ApiManager.ts, TypeScript, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 11 +src/server/ApiManagers/DeleteManager.ts, TypeScript, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 82 +src/server/ApiManagers/DownloadManager.ts, TypeScript, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 16, 269 +src/server/ApiManagers/GeneralGoogleManager.ts, TypeScript, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 61 +src/server/ApiManagers/GooglePhotosManager.ts, TypeScript, 0, 0, 0, 0, 0, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 22, 331 +src/server/ApiManagers/PDFManager.ts, TypeScript, 0, 0, 0, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 115 +src/server/ApiManagers/SearchManager.ts, TypeScript, 0, 0, 0, 0, 0, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 215 +src/server/ApiManagers/SessionManager.ts, TypeScript, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 67 +src/server/ApiManagers/UploadManager.ts, TypeScript, 0, 0, 0, 0, 0, 226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 20, 247 +src/server/ApiManagers/UserManager.ts, TypeScript, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 21, 126 +src/server/ApiManagers/UtilManager.ts, TypeScript, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 10, 74 +src/server/Client.ts, TypeScript, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 11 +src/server/DashSession/DashSessionAgent.ts, TypeScript, 0, 0, 0, 0, 0, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 23, 230 +src/server/DashSession/Session/agents/applied_session_agent.ts, TypeScript, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 9, 58 +src/server/DashSession/Session/agents/monitor.ts, TypeScript, 0, 0, 0, 0, 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 26, 298 +src/server/DashSession/Session/agents/process_message_router.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 41 +src/server/DashSession/Session/agents/promisified_ipc_manager.ts, TypeScript, 0, 0, 0, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 15, 173 +src/server/DashSession/Session/agents/server_worker.ts, TypeScript, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 15, 160 +src/server/DashSession/Session/utilities/repl.ts, TypeScript, 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 128 +src/server/DashSession/Session/utilities/session_config.ts, TypeScript, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 129 +src/server/DashSession/Session/utilities/utilities.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 37 +src/server/DashUploadUtils.ts, TypeScript, 0, 0, 0, 0, 0, 285, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 30, 367 +src/server/GarbageCollector.ts, TypeScript, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 11, 151 +src/server/IDatabase.ts, TypeScript, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 25 +src/server/MemoryDatabase.ts, TypeScript, 0, 0, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 101 +src/server/Message.ts, TypeScript, 0, 0, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 104 +src/server/PdfTypes.ts, TypeScript, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/server/ProcessFactory.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 44 +src/server/Recommender.ts, TypeScript, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 18, 138 +src/server/RouteManager.ts, TypeScript, 0, 0, 0, 0, 0, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 19, 210 +src/server/RouteSubscriber.ts, TypeScript, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 26 +src/server/Search.ts, TypeScript, 0, 0, 0, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 8, 81 +src/server/SharedMediaTypes.ts, TypeScript, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 51 +src/server/Websocket/Websocket.ts, TypeScript, 0, 0, 0, 0, 0, 263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 46, 314 +src/server/apis/google/GoogleApiServerUtils.ts, TypeScript, 0, 0, 0, 0, 0, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 25, 365 +src/server/apis/google/SharedTypes.ts, TypeScript, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 21 +src/server/apis/youtube/youtubeApiSample.d.ts, TypeScript, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 +src/server/apis/youtube/youtubeApiSample.js, JavaScript, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 14, 179 +src/server/authentication/config/passport.ts, TypeScript, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 29 +src/server/authentication/controllers/user_controller.ts, TypeScript, 0, 0, 0, 0, 0, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 268 +src/server/authentication/models/current_user_utils.ts, TypeScript, 0, 0, 0, 0, 0, 586, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 57, 673 +src/server/authentication/models/user_model.ts, TypeScript, 0, 0, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 86 +src/server/credentials/CredentialsLoader.ts, TypeScript, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 30 +src/server/credentials/google_project_credentials.json, JSON, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 +src/server/database.ts, TypeScript, 0, 0, 0, 0, 0, 312, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 350 +src/server/downsize.ts, TypeScript, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 40 +src/server/index.ts, TypeScript, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 16, 159 +src/server/remapUrl.ts, TypeScript, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 63 +src/server/server_Initialization.ts, TypeScript, 0, 0, 0, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 24, 168 +src/server/slides.json, JSON, 0, 0, 10820, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10820 +src/server/updateProtos.ts, TypeScript, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 14 +src/typings/index.d.ts, TypeScript, 0, 0, 0, 0, 0, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 38, 329 +test/test.ts, TypeScript, 0, 0, 0, 0, 0, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 161 +tsconfig.json, JSON, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 26 +tslint.json, JSON, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 1, 63 +views/forgot.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 22 +views/layout.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 14 +views/login.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 26 +views/reset.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 22 +views/signup.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 27 +views/stylesheets/authentication.css, CSS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 0, 0, 0, 0, 0, 0, 0, 4, 34, 223 +views/user_activity.pug, Pug, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 19 +webpack.config.js, JavaScript, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 121 +Total, -, 6, 89717, 50401, 1893, 632, 15246, 2883, 119, 538, 5982, 30250, 7726, 17257, 161, 2, 38, 2060, 32987, 15880, 273778 \ No newline at end of file diff --git a/.VSCodeCounter/results.md b/.VSCodeCounter/results.md new file mode 100644 index 000000000..3265d800b --- /dev/null +++ b/.VSCodeCounter/results.md @@ -0,0 +1,164 @@ +# Summary + +Date : 2020-04-30 14:40:17 + +Directory /Users/bcz/Documents/GitHub/Dash-Web + +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +[details](details.md) + +## Languages +| language | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| JavaScript | 69 | 89,717 | 18,520 | 5,866 | 114,103 | +| JSON | 20 | 50,401 | 37 | 14 | 50,452 | +| TypeScript React | 140 | 30,250 | 1,878 | 3,154 | 35,282 | +| XML | 73 | 17,257 | 8,281 | 992 | 26,530 | +| TypeScript | 115 | 15,246 | 2,095 | 1,958 | 19,299 | +| SCSS | 104 | 7,726 | 255 | 1,136 | 9,117 | +| CSS | 30 | 5,982 | 549 | 1,095 | 7,626 | +| HTML | 34 | 2,883 | 431 | 848 | 4,162 | +| XSL | 20 | 2,060 | 416 | 232 | 2,708 | +| Batch | 5 | 1,893 | 184 | 273 | 2,350 | +| Python | 5 | 632 | 49 | 148 | 829 | +| Shell Script | 6 | 538 | 248 | 120 | 906 | +| Properties | 16 | 161 | 43 | 30 | 234 | +| Pug | 6 | 119 | 1 | 10 | 130 | +| log | 1 | 38 | 0 | 1 | 39 | +| Markdown | 1 | 6 | 0 | 3 | 9 | +| Ignore | 1 | 2 | 0 | 0 | 2 | + +## Directories +| path | files | code | comment | blank | total | +| :--- | ---: | ---: | ---: | ---: | ---: | +| . | 646 | 224,911 | 32,987 | 15,880 | 273,778 | +| build | 1 | 9 | 0 | 3 | 12 | +| deploy | 8 | 55,744 | 174 | 704 | 56,622 | +| deploy/assets | 2 | 55,677 | 174 | 686 | 56,537 | +| deploy/debug | 3 | 32 | 0 | 9 | 41 | +| deploy/mobile | 2 | 22 | 0 | 6 | 28 | +| solr-8.3.1 | 236 | 80,866 | 26,654 | 7,861 | 115,381 | +| solr-8.3.1/bin | 5 | 2,118 | 392 | 311 | 2,821 | +| solr-8.3.1/contrib | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter/bin | 1 | 82 | 0 | 26 | 108 | +| solr-8.3.1/contrib/prometheus-exporter/conf | 2 | 6,199 | 63 | 11 | 6,273 | +| solr-8.3.1/docs | 2 | 59 | 0 | 2 | 61 | +| solr-8.3.1/docs/images | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/example | 82 | 32,009 | 5,063 | 942 | 38,014 | +| solr-8.3.1/example/example-DIH | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH/solr | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH/solr/atom | 3 | 41 | 43 | 19 | 103 | +| solr-8.3.1/example/example-DIH/solr/atom/conf | 2 | 41 | 43 | 17 | 101 | +| solr-8.3.1/example/example-DIH/solr/db | 14 | 935 | 1,167 | 191 | 2,293 | +| solr-8.3.1/example/example-DIH/solr/db/conf | 13 | 935 | 1,167 | 189 | 2,291 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/mail | 14 | 919 | 1,171 | 189 | 2,279 | +| solr-8.3.1/example/example-DIH/solr/mail/conf | 13 | 919 | 1,171 | 187 | 2,277 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/solr | 14 | 917 | 1,183 | 187 | 2,287 | +| solr-8.3.1/example/example-DIH/solr/solr/conf | 13 | 917 | 1,183 | 185 | 2,285 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/tika | 3 | 34 | 41 | 16 | 91 | +| solr-8.3.1/example/example-DIH/solr/tika/conf | 2 | 34 | 41 | 14 | 89 | +| solr-8.3.1/example/exampledocs | 17 | 513 | 281 | 75 | 869 | +| solr-8.3.1/example/files | 13 | 1,298 | 1,153 | 250 | 2,701 | +| solr-8.3.1/example/files/browse-resources | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/browse-resources/velocity | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/conf | 10 | 1,190 | 1,147 | 241 | 2,578 | +| solr-8.3.1/example/files/conf/velocity | 5 | 730 | 99 | 108 | 937 | +| solr-8.3.1/example/files/conf/velocity/js | 3 | 730 | 99 | 104 | 933 | +| solr-8.3.1/example/films | 3 | 27,350 | 24 | 14 | 27,388 | +| solr-8.3.1/server | 144 | 40,399 | 21,136 | 6,569 | 68,104 | +| solr-8.3.1/server/contexts | 1 | 8 | 0 | 1 | 9 | +| solr-8.3.1/server/etc | 6 | 528 | 400 | 59 | 987 | +| solr-8.3.1/server/resources | 3 | 74 | 123 | 16 | 213 | +| solr-8.3.1/server/scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/scripts/cloud-scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/solr | 27 | 2,612 | 3,377 | 557 | 6,546 | +| solr-8.3.1/server/solr-webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp/webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF | 1 | 62 | 42 | 11 | 115 | +| solr-8.3.1/server/solr-webapp/webapp/css | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/img | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/js | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers | 23 | 3,627 | 478 | 544 | 4,649 | +| solr-8.3.1/server/solr-webapp/webapp/libs | 21 | 24,087 | 15,683 | 3,464 | 43,234 | +| solr-8.3.1/server/solr-webapp/webapp/partials | 24 | 2,571 | 415 | 787 | 3,773 | +| solr-8.3.1/server/solr/configsets | 21 | 2,243 | 2,359 | 437 | 5,039 | +| solr-8.3.1/server/solr/configsets/_default | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/_default/conf | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity | 3 | 839 | 77 | 129 | 1,045 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/server/solr/dash | 4 | 345 | 968 | 105 | 1,418 | +| solr-8.3.1/server/solr/dash/conf | 3 | 341 | 966 | 104 | 1,411 | +| solr-8.3.1/server/tmp | 5 | 45 | 10 | 5 | 60 | +| src | 384 | 68,699 | 6,117 | 7,234 | 82,050 | +| src/client | 277 | 46,644 | 4,305 | 5,743 | 56,692 | +| src/client/apis | 7 | 1,053 | 97 | 150 | 1,300 | +| src/client/apis/google_docs | 2 | 543 | 9 | 73 | 625 | +| src/client/apis/youtube | 2 | 379 | 53 | 56 | 488 | +| src/client/cognitive_services | 1 | 342 | 10 | 57 | 409 | +| src/client/documents | 2 | 839 | 143 | 90 | 1,072 | +| src/client/util | 30 | 3,931 | 351 | 515 | 4,797 | +| src/client/util/Import & Export | 4 | 566 | 0 | 52 | 618 | +| src/client/util/ProsemirrorCopy | 1 | 128 | 30 | 22 | 180 | +| src/client/views | 231 | 36,751 | 1,935 | 4,094 | 42,780 | +| src/client/views/animationtimeline | 10 | 1,966 | 237 | 262 | 2,465 | +| src/client/views/collections | 53 | 11,516 | 574 | 1,153 | 13,243 | +| src/client/views/collections/collectionFreeForm | 12 | 2,540 | 177 | 203 | 2,920 | +| src/client/views/collections/collectionMulticolumn | 8 | 751 | 144 | 85 | 980 | +| src/client/views/linking | 7 | 747 | 10 | 138 | 895 | +| src/client/views/nodes | 75 | 12,034 | 475 | 1,218 | 13,727 | +| src/client/views/nodes/formattedText | 22 | 5,353 | 250 | 592 | 6,195 | +| src/client/views/pdf | 6 | 964 | 27 | 93 | 1,084 | +| src/client/views/presentationview | 2 | 272 | 31 | 24 | 327 | +| src/client/views/search | 21 | 2,380 | 238 | 401 | 3,019 | +| src/client/views/webcam | 3 | 371 | 17 | 76 | 464 | +| src/debug | 3 | 244 | 0 | 28 | 272 | +| src/extensions | 4 | 53 | 5 | 13 | 71 | +| src/extensions/General | 2 | 14 | 0 | 3 | 17 | +| src/mobile | 7 | 617 | 60 | 82 | 759 | +| src/new_fields | 22 | 2,682 | 172 | 315 | 3,169 | +| src/pen-gestures | 2 | 397 | 172 | 27 | 596 | +| src/scraping | 15 | 1,155 | 352 | 176 | 1,683 | +| src/scraping/acm | 4 | 139 | 185 | 15 | 339 | +| src/scraping/buxton | 11 | 1,016 | 167 | 161 | 1,344 | +| src/scraping/buxton/.idea | 6 | 205 | 0 | 0 | 205 | +| src/scraping/buxton/.idea/inspectionProfiles | 1 | 6 | 0 | 0 | 6 | +| src/scraping/buxton/final | 1 | 228 | 142 | 26 | 396 | +| src/scraping/buxton/narratives | 1 | 39 | 0 | 0 | 39 | +| src/server | 52 | 16,247 | 956 | 731 | 17,934 | +| src/server/ApiManagers | 11 | 1,223 | 226 | 149 | 1,598 | +| src/server/DashSession | 9 | 903 | 229 | 122 | 1,254 | +| src/server/DashSession/Session | 8 | 748 | 177 | 99 | 1,024 | +| src/server/DashSession/Session/agents | 5 | 489 | 169 | 72 | 730 | +| src/server/DashSession/Session/utilities | 3 | 259 | 8 | 27 | 294 | +| src/server/Websocket | 1 | 263 | 5 | 46 | 314 | +| src/server/apis | 4 | 328 | 198 | 41 | 567 | +| src/server/apis/google | 2 | 191 | 168 | 27 | 386 | +| src/server/apis/youtube | 2 | 137 | 30 | 14 | 181 | +| src/server/authentication | 4 | 890 | 66 | 100 | 1,056 | +| src/server/authentication/config | 1 | 23 | 2 | 4 | 29 | +| src/server/authentication/controllers | 1 | 218 | 25 | 25 | 268 | +| src/server/authentication/models | 2 | 649 | 39 | 71 | 759 | +| src/server/credentials | 2 | 35 | 0 | 6 | 41 | +| src/typings | 1 | 219 | 72 | 38 | 329 | +| test | 1 | 141 | 0 | 20 | 161 | +| views | 7 | 304 | 5 | 44 | 353 | +| views/stylesheets | 1 | 185 | 4 | 34 | 223 | + +[details](details.md) \ No newline at end of file diff --git a/.VSCodeCounter/results.txt b/.VSCodeCounter/results.txt new file mode 100644 index 000000000..aaf54b147 --- /dev/null +++ b/.VSCodeCounter/results.txt @@ -0,0 +1,813 @@ +Date : 2020-04-30 14:40:16 +Directory : /Users/bcz/Documents/GitHub/Dash-Web +Total : 646 files, 224911 codes, 32987 comments, 15880 blanks, all 273778 lines + +Languages ++------------------+------------+------------+------------+------------+------------+ +| language | files | code | comment | blank | total | ++------------------+------------+------------+------------+------------+------------+ +| JavaScript | 69 | 89,717 | 18,520 | 5,866 | 114,103 | +| JSON | 20 | 50,401 | 37 | 14 | 50,452 | +| TypeScript React | 140 | 30,250 | 1,878 | 3,154 | 35,282 | +| XML | 73 | 17,257 | 8,281 | 992 | 26,530 | +| TypeScript | 115 | 15,246 | 2,095 | 1,958 | 19,299 | +| SCSS | 104 | 7,726 | 255 | 1,136 | 9,117 | +| CSS | 30 | 5,982 | 549 | 1,095 | 7,626 | +| HTML | 34 | 2,883 | 431 | 848 | 4,162 | +| XSL | 20 | 2,060 | 416 | 232 | 2,708 | +| Batch | 5 | 1,893 | 184 | 273 | 2,350 | +| Python | 5 | 632 | 49 | 148 | 829 | +| Shell Script | 6 | 538 | 248 | 120 | 906 | +| Properties | 16 | 161 | 43 | 30 | 234 | +| Pug | 6 | 119 | 1 | 10 | 130 | +| log | 1 | 38 | 0 | 1 | 39 | +| Markdown | 1 | 6 | 0 | 3 | 9 | +| Ignore | 1 | 2 | 0 | 0 | 2 | ++------------------+------------+------------+------------+------------+------------+ + +Directories ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| path | files | code | comment | blank | total | ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ +| . | 646 | 224,911 | 32,987 | 15,880 | 273,778 | +| solr-8.3.1 | 236 | 80,866 | 26,654 | 7,861 | 115,381 | +| src | 384 | 68,699 | 6,117 | 7,234 | 82,050 | +| deploy | 8 | 55,744 | 174 | 704 | 56,622 | +| deploy/assets | 2 | 55,677 | 174 | 686 | 56,537 | +| src/client | 277 | 46,644 | 4,305 | 5,743 | 56,692 | +| solr-8.3.1/server | 144 | 40,399 | 21,136 | 6,569 | 68,104 | +| solr-8.3.1/server/solr-webapp/webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| solr-8.3.1/server/solr-webapp | 99 | 36,960 | 17,207 | 5,892 | 60,059 | +| src/client/views | 231 | 36,751 | 1,935 | 4,094 | 42,780 | +| solr-8.3.1/example | 82 | 32,009 | 5,063 | 942 | 38,014 | +| solr-8.3.1/example/films | 3 | 27,350 | 24 | 14 | 27,388 | +| solr-8.3.1/server/solr-webapp/webapp/libs | 21 | 24,087 | 15,683 | 3,464 | 43,234 | +| src/server | 52 | 16,247 | 956 | 731 | 17,934 | +| src/client/views/nodes | 75 | 12,034 | 475 | 1,218 | 13,727 | +| src/client/views/collections | 53 | 11,516 | 574 | 1,153 | 13,243 | +| solr-8.3.1/contrib | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter | 3 | 6,281 | 63 | 37 | 6,381 | +| solr-8.3.1/contrib/prometheus-exporter/conf | 2 | 6,199 | 63 | 11 | 6,273 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular | 26 | 5,548 | 536 | 1,005 | 7,089 | +| solr-8.3.1/server/solr-webapp/webapp/css | 26 | 5,548 | 536 | 1,005 | 7,089 | +| src/client/views/nodes/formattedText | 22 | 5,353 | 250 | 592 | 6,195 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular | 25 | 4,450 | 515 | 586 | 5,551 | +| solr-8.3.1/server/solr-webapp/webapp/js | 25 | 4,450 | 515 | 586 | 5,551 | +| src/client/util | 30 | 3,931 | 351 | 515 | 4,797 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers | 23 | 3,627 | 478 | 544 | 4,649 | +| solr-8.3.1/example/example-DIH/solr | 49 | 2,848 | 3,605 | 603 | 7,056 | +| solr-8.3.1/example/example-DIH | 49 | 2,848 | 3,605 | 603 | 7,056 | +| src/new_fields | 22 | 2,682 | 172 | 315 | 3,169 | +| solr-8.3.1/server/solr | 27 | 2,612 | 3,377 | 557 | 6,546 | +| solr-8.3.1/server/solr-webapp/webapp/partials | 24 | 2,571 | 415 | 787 | 3,773 | +| src/client/views/collections/collectionFreeForm | 12 | 2,540 | 177 | 203 | 2,920 | +| src/client/views/search | 21 | 2,380 | 238 | 401 | 3,019 | +| solr-8.3.1/server/solr/configsets | 21 | 2,243 | 2,359 | 437 | 5,039 | +| solr-8.3.1/bin | 5 | 2,118 | 392 | 311 | 2,821 | +| src/client/views/animationtimeline | 10 | 1,966 | 237 | 262 | 2,465 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs | 19 | 1,927 | 1,383 | 338 | 3,648 | +| solr-8.3.1/example/files | 13 | 1,298 | 1,153 | 250 | 2,701 | +| src/server/ApiManagers | 11 | 1,223 | 226 | 149 | 1,598 | +| solr-8.3.1/example/files/conf | 10 | 1,190 | 1,147 | 241 | 2,578 | +| src/scraping | 15 | 1,155 | 352 | 176 | 1,683 | +| src/client/apis | 7 | 1,053 | 97 | 150 | 1,300 | +| src/scraping/buxton | 11 | 1,016 | 167 | 161 | 1,344 | +| src/client/views/pdf | 6 | 964 | 27 | 93 | 1,084 | +| solr-8.3.1/example/example-DIH/solr/db | 14 | 935 | 1,167 | 191 | 2,293 | +| solr-8.3.1/example/example-DIH/solr/db/conf | 13 | 935 | 1,167 | 189 | 2,291 | +| solr-8.3.1/example/example-DIH/solr/mail | 14 | 919 | 1,171 | 189 | 2,279 | +| solr-8.3.1/example/example-DIH/solr/mail/conf | 13 | 919 | 1,171 | 187 | 2,277 | +| solr-8.3.1/example/example-DIH/solr/solr/conf | 13 | 917 | 1,183 | 185 | 2,285 | +| solr-8.3.1/example/example-DIH/solr/solr | 14 | 917 | 1,183 | 187 | 2,287 | +| src/server/DashSession | 9 | 903 | 229 | 122 | 1,254 | +| src/server/authentication | 4 | 890 | 66 | 100 | 1,056 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity | 3 | 839 | 77 | 129 | 1,045 | +| src/client/documents | 2 | 839 | 143 | 90 | 1,072 | +| src/client/views/collections/collectionMulticolumn | 8 | 751 | 144 | 85 | 980 | +| src/server/DashSession/Session | 8 | 748 | 177 | 99 | 1,024 | +| src/client/views/linking | 7 | 747 | 10 | 138 | 895 | +| solr-8.3.1/example/files/conf/velocity | 5 | 730 | 99 | 108 | 937 | +| solr-8.3.1/example/files/conf/velocity/js | 3 | 730 | 99 | 104 | 933 | +| src/server/authentication/models | 2 | 649 | 39 | 71 | 759 | +| src/mobile | 7 | 617 | 60 | 82 | 759 | +| src/client/util/Import & Export | 4 | 566 | 0 | 52 | 618 | +| src/client/apis/google_docs | 2 | 543 | 9 | 73 | 625 | +| solr-8.3.1/server/etc | 6 | 528 | 400 | 59 | 987 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt | 5 | 515 | 104 | 58 | 677 | +| solr-8.3.1/example/exampledocs | 17 | 513 | 281 | 75 | 869 | +| src/server/DashSession/Session/agents | 5 | 489 | 169 | 72 | 730 | +| src/pen-gestures | 2 | 397 | 172 | 27 | 596 | +| src/client/apis/youtube | 2 | 379 | 53 | 56 | 488 | +| src/client/views/webcam | 3 | 371 | 17 | 76 | 464 | +| solr-8.3.1/server/solr/dash | 4 | 345 | 968 | 105 | 1,418 | +| src/client/cognitive_services | 1 | 342 | 10 | 57 | 409 | +| solr-8.3.1/server/solr/dash/conf | 3 | 341 | 966 | 104 | 1,411 | +| src/server/apis | 4 | 328 | 198 | 41 | 567 | +| solr-8.3.1/server/solr/configsets/_default/conf | 2 | 316 | 976 | 99 | 1,391 | +| solr-8.3.1/server/solr/configsets/_default | 2 | 316 | 976 | 99 | 1,391 | +| views | 7 | 304 | 5 | 44 | 353 | +| src/client/views/presentationview | 2 | 272 | 31 | 24 | 327 | +| src/server/Websocket | 1 | 263 | 5 | 46 | 314 | +| src/server/DashSession/Session/utilities | 3 | 259 | 8 | 27 | 294 | +| src/debug | 3 | 244 | 0 | 28 | 272 | +| src/scraping/buxton/final | 1 | 228 | 142 | 26 | 396 | +| src/typings | 1 | 219 | 72 | 38 | 329 | +| src/server/authentication/controllers | 1 | 218 | 25 | 25 | 268 | +| src/scraping/buxton/.idea | 6 | 205 | 0 | 0 | 205 | +| src/server/apis/google | 2 | 191 | 168 | 27 | 386 | +| views/stylesheets | 1 | 185 | 4 | 34 | 223 | +| solr-8.3.1/server/scripts/cloud-scripts | 3 | 172 | 19 | 39 | 230 | +| solr-8.3.1/server/scripts | 3 | 172 | 19 | 39 | 230 | +| test | 1 | 141 | 0 | 20 | 161 | +| src/scraping/acm | 4 | 139 | 185 | 15 | 339 | +| src/server/apis/youtube | 2 | 137 | 30 | 14 | 181 | +| src/client/util/ProsemirrorCopy | 1 | 128 | 30 | 22 | 180 | +| solr-8.3.1/example/files/browse-resources/velocity | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/example/files/browse-resources | 3 | 108 | 6 | 9 | 123 | +| solr-8.3.1/contrib/prometheus-exporter/bin | 1 | 82 | 0 | 26 | 108 | +| solr-8.3.1/server/resources | 3 | 74 | 123 | 16 | 213 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF | 1 | 62 | 42 | 11 | 115 | +| solr-8.3.1/docs | 2 | 59 | 0 | 2 | 61 | +| src/extensions | 4 | 53 | 5 | 13 | 71 | +| solr-8.3.1/server/tmp | 5 | 45 | 10 | 5 | 60 | +| solr-8.3.1/example/example-DIH/solr/atom/conf | 2 | 41 | 43 | 17 | 101 | +| solr-8.3.1/example/example-DIH/solr/atom | 3 | 41 | 43 | 19 | 103 | +| solr-8.3.1/server/solr-webapp/webapp/img | 1 | 39 | 0 | 1 | 40 | +| solr-8.3.1/docs/images | 1 | 39 | 0 | 1 | 40 | +| src/scraping/buxton/narratives | 1 | 39 | 0 | 0 | 39 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2 | 3 | 39 | 23 | 3 | 65 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering | 3 | 39 | 23 | 3 | 65 | +| src/server/credentials | 2 | 35 | 0 | 6 | 41 | +| solr-8.3.1/example/example-DIH/solr/tika | 3 | 34 | 41 | 16 | 91 | +| solr-8.3.1/example/example-DIH/solr/tika/conf | 2 | 34 | 41 | 14 | 89 | +| deploy/debug | 3 | 32 | 0 | 9 | 41 | +| src/server/authentication/config | 1 | 23 | 2 | 4 | 29 | +| deploy/mobile | 2 | 22 | 0 | 6 | 28 | +| src/extensions/General | 2 | 14 | 0 | 3 | 17 | +| build | 1 | 9 | 0 | 3 | 12 | +| solr-8.3.1/server/contexts | 1 | 8 | 0 | 1 | 9 | +| src/scraping/buxton/.idea/inspectionProfiles | 1 | 6 | 0 | 0 | 6 | ++-------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+ + +Files ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| filename | language | code | comment | blank | total | ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ +| README.md | Markdown | 6 | 0 | 3 | 9 | +| build/index.html | HTML | 9 | 0 | 3 | 12 | +| dash.bat | Batch | 2 | 0 | 1 | 3 | +| deploy/assets/env.json | JSON | 15 | 0 | 0 | 15 | +| deploy/assets/pdf.worker.js | JavaScript | 55,662 | 174 | 686 | 56,522 | +| deploy/debug/repl.html | HTML | 11 | 0 | 3 | 14 | +| deploy/debug/test.html | HTML | 10 | 0 | 3 | 13 | +| deploy/debug/viewer.html | HTML | 11 | 0 | 3 | 14 | +| deploy/index.html | HTML | 13 | 0 | 3 | 16 | +| deploy/mobile/image.html | HTML | 12 | 0 | 3 | 15 | +| deploy/mobile/ink.html | HTML | 10 | 0 | 3 | 13 | +| package-lock.json | JSON | 18,689 | 0 | 1 | 18,690 | +| package.json | JSON | 266 | 0 | 1 | 267 | +| sentence_parser.py | Python | 6 | 0 | 1 | 7 | +| session.config.json | JSON | 12 | 0 | 1 | 13 | +| solr-8.3.1/bin/install_solr_service.sh | Shell Script | 307 | 29 | 35 | 371 | +| solr-8.3.1/bin/oom_solr.sh | Shell Script | 13 | 15 | 3 | 31 | +| solr-8.3.1/bin/solr.cmd | Batch | 1,782 | 43 | 210 | 2,035 | +| solr-8.3.1/bin/solr.in.cmd | Batch | 16 | 133 | 29 | 178 | +| solr-8.3.1/bin/solr.in.sh | Shell Script | 0 | 172 | 34 | 206 | +| solr-8.3.1/contrib/prometheus-exporter/bin/solr-exporter.cmd | Batch | 82 | 0 | 26 | 108 | +| solr-8.3.1/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json | JSON | 4,465 | 0 | 1 | 4,466 | +| solr-8.3.1/contrib/prometheus-exporter/conf/solr-exporter-config.xml | XML | 1,734 | 63 | 10 | 1,807 | +| solr-8.3.1/docs/images/solr.svg | XML | 39 | 0 | 1 | 40 | +| solr-8.3.1/docs/index.html | HTML | 20 | 0 | 1 | 21 | +| solr-8.3.1/example/example-DIH/solr/atom/conf/atom-data-config.xml | XML | 21 | 6 | 9 | 36 | +| solr-8.3.1/example/example-DIH/solr/atom/conf/solrconfig.xml | XML | 20 | 37 | 8 | 65 | +| solr-8.3.1/example/example-DIH/solr/atom/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/db/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/db/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/db/conf/db-data-config.xml | XML | 26 | 0 | 4 | 30 | +| solr-8.3.1/example/example-DIH/solr/db/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/db/conf/solrconfig.xml | XML | 292 | 958 | 104 | 1,354 | +| solr-8.3.1/example/example-DIH/solr/db/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/db/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/db/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/mail-data-config.xml | XML | 8 | 4 | 1 | 13 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/solrconfig.xml | XML | 294 | 958 | 105 | 1,357 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/mail/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/mail/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/solr.xml | XML | 2 | 0 | 1 | 3 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/solr-data-config.xml | XML | 8 | 16 | 2 | 26 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/solrconfig.xml | XML | 292 | 958 | 102 | 1,352 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/example/example-DIH/solr/solr/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/example/example-DIH/solr/solr/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/example-DIH/solr/tika/conf/solrconfig.xml | XML | 17 | 38 | 7 | 62 | +| solr-8.3.1/example/example-DIH/solr/tika/conf/tika-data-config.xml | XML | 17 | 3 | 7 | 27 | +| solr-8.3.1/example/example-DIH/solr/tika/core.properties | Properties | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/exampledocs/books.json | JSON | 51 | 0 | 1 | 52 | +| solr-8.3.1/example/exampledocs/gb18030-example.xml | XML | 14 | 16 | 3 | 33 | +| solr-8.3.1/example/exampledocs/hd.xml | XML | 33 | 20 | 4 | 57 | +| solr-8.3.1/example/exampledocs/ipod_other.xml | XML | 32 | 20 | 9 | 61 | +| solr-8.3.1/example/exampledocs/ipod_video.xml | XML | 21 | 18 | 2 | 41 | +| solr-8.3.1/example/exampledocs/manufacturers.xml | XML | 57 | 16 | 3 | 76 | +| solr-8.3.1/example/exampledocs/mem.xml | XML | 45 | 24 | 9 | 78 | +| solr-8.3.1/example/exampledocs/money.xml | XML | 42 | 17 | 7 | 66 | +| solr-8.3.1/example/exampledocs/monitor.xml | XML | 14 | 18 | 3 | 35 | +| solr-8.3.1/example/exampledocs/monitor2.xml | XML | 13 | 18 | 3 | 34 | +| solr-8.3.1/example/exampledocs/mp500.xml | XML | 23 | 18 | 3 | 44 | +| solr-8.3.1/example/exampledocs/sample.html | HTML | 13 | 0 | 1 | 14 | +| solr-8.3.1/example/exampledocs/sd500.xml | XML | 19 | 18 | 2 | 39 | +| solr-8.3.1/example/exampledocs/solr.xml | XML | 20 | 16 | 3 | 39 | +| solr-8.3.1/example/exampledocs/test_utf8.sh | Shell Script | 57 | 21 | 16 | 94 | +| solr-8.3.1/example/exampledocs/utf8-example.xml | XML | 19 | 20 | 4 | 43 | +| solr-8.3.1/example/exampledocs/vidcard.xml | XML | 40 | 21 | 2 | 63 | +| solr-8.3.1/example/files/browse-resources/velocity/resources.properties | Properties | 72 | 6 | 5 | 83 | +| solr-8.3.1/example/files/browse-resources/velocity/resources_de_DE.properties | Properties | 18 | 0 | 1 | 19 | +| solr-8.3.1/example/files/browse-resources/velocity/resources_fr_FR.properties | Properties | 18 | 0 | 3 | 21 | +| solr-8.3.1/example/files/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/example/files/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/example/files/conf/params.json | JSON | 34 | 0 | 1 | 35 | +| solr-8.3.1/example/files/conf/solrconfig.xml | XML | 298 | 979 | 102 | 1,379 | +| solr-8.3.1/example/files/conf/update-script.js | JavaScript | 80 | 13 | 23 | 116 | +| solr-8.3.1/example/files/conf/velocity/dropit.js | JavaScript | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/files/conf/velocity/jquery.tx3-tag-cloud.js | JavaScript | 0 | 0 | 2 | 2 | +| solr-8.3.1/example/files/conf/velocity/js/dropit.js | JavaScript | 64 | 15 | 19 | 98 | +| solr-8.3.1/example/files/conf/velocity/js/jquery.autocomplete.js | JavaScript | 620 | 68 | 76 | 764 | +| solr-8.3.1/example/files/conf/velocity/js/jquery.tx3-tag-cloud.js | JavaScript | 46 | 16 | 9 | 71 | +| solr-8.3.1/example/films/film_data_generator.py | Python | 82 | 24 | 12 | 118 | +| solr-8.3.1/example/films/films.json | JSON | 15,830 | 0 | 1 | 15,831 | +| solr-8.3.1/example/films/films.xml | XML | 11,438 | 0 | 1 | 11,439 | +| solr-8.3.1/server/contexts/solr-jetty-context.xml | XML | 8 | 0 | 1 | 9 | +| solr-8.3.1/server/etc/jetty-http.xml | XML | 33 | 15 | 4 | 52 | +| solr-8.3.1/server/etc/jetty-https.xml | XML | 56 | 16 | 5 | 77 | +| solr-8.3.1/server/etc/jetty-https8.xml | XML | 34 | 32 | 4 | 70 | +| solr-8.3.1/server/etc/jetty-ssl.xml | XML | 23 | 11 | 4 | 38 | +| solr-8.3.1/server/etc/jetty.xml | XML | 110 | 94 | 18 | 222 | +| solr-8.3.1/server/etc/webdefault.xml | XML | 272 | 232 | 24 | 528 | +| solr-8.3.1/server/resources/jetty-logging.properties | Properties | 1 | 0 | 1 | 2 | +| solr-8.3.1/server/resources/log4j2-console.xml | XML | 19 | 43 | 6 | 68 | +| solr-8.3.1/server/resources/log4j2.xml | XML | 54 | 80 | 9 | 143 | +| solr-8.3.1/server/scripts/cloud-scripts/snapshotscli.sh | Shell Script | 152 | 2 | 23 | 177 | +| solr-8.3.1/server/scripts/cloud-scripts/zkcli.bat | Batch | 11 | 8 | 7 | 26 | +| solr-8.3.1/server/scripts/cloud-scripts/zkcli.sh | Shell Script | 9 | 9 | 9 | 27 | +| solr-8.3.1/server/solr-webapp/webapp/WEB-INF/web.xml | XML | 62 | 42 | 11 | 115 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/analysis.css | CSS | 237 | 19 | 48 | 304 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/chosen.css | CSS | 402 | 55 | 9 | 466 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/cloud.css | CSS | 594 | 23 | 106 | 723 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/collections.css | CSS | 296 | 18 | 65 | 379 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/common.css | CSS | 647 | 19 | 106 | 772 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/cores.css | CSS | 171 | 18 | 37 | 226 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/dashboard.css | CSS | 134 | 18 | 28 | 180 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/dataimport.css | CSS | 292 | 18 | 61 | 371 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/documents.css | CSS | 131 | 23 | 26 | 180 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/files.css | CSS | 29 | 18 | 7 | 54 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/index.css | CSS | 164 | 18 | 35 | 217 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/java-properties.css | CSS | 24 | 18 | 6 | 48 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.min.css | CSS | 1 | 26 | 2 | 29 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/jquery-ui.structure.min.css | CSS | 1 | 22 | 2 | 25 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/logging.css | CSS | 303 | 19 | 63 | 385 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/login.css | CSS | 80 | 18 | 12 | 110 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/menu.css | CSS | 257 | 18 | 56 | 331 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/overview.css | CSS | 20 | 18 | 5 | 43 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/plugins.css | CSS | 172 | 18 | 31 | 221 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/query.css | CSS | 120 | 18 | 25 | 163 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/replication.css | CSS | 404 | 18 | 79 | 501 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/schema.css | CSS | 596 | 20 | 112 | 728 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/segments.css | CSS | 133 | 18 | 22 | 173 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/stream.css | CSS | 178 | 22 | 34 | 234 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/suggestions.css | CSS | 43 | 18 | 4 | 65 | +| solr-8.3.1/server/solr-webapp/webapp/css/angular/threads.css | CSS | 119 | 18 | 24 | 161 | +| solr-8.3.1/server/solr-webapp/webapp/img/solr.svg | XML | 39 | 0 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/index.html | HTML | 203 | 16 | 38 | 257 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/app.js | JavaScript | 512 | 19 | 31 | 562 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/alias-overview.js | JavaScript | 8 | 16 | 4 | 28 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/analysis.js | JavaScript | 161 | 18 | 23 | 202 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cloud.js | JavaScript | 847 | 51 | 124 | 1,022 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cluster-suggestions.js | JavaScript | 43 | 18 | 2 | 63 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collection-overview.js | JavaScript | 18 | 16 | 6 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/collections.js | JavaScript | 244 | 19 | 27 | 290 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/core-overview.js | JavaScript | 69 | 16 | 9 | 94 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/cores.js | JavaScript | 151 | 16 | 14 | 181 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/dataimport.js | JavaScript | 234 | 25 | 44 | 303 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/documents.js | JavaScript | 107 | 18 | 13 | 138 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/files.js | JavaScript | 72 | 16 | 13 | 101 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/index.js | JavaScript | 61 | 23 | 14 | 98 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/java-properties.js | JavaScript | 27 | 16 | 3 | 46 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/logging.js | JavaScript | 112 | 35 | 12 | 159 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/login.js | JavaScript | 269 | 30 | 19 | 318 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/plugins.js | JavaScript | 130 | 19 | 19 | 168 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/query.js | JavaScript | 88 | 19 | 14 | 121 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/replication.js | JavaScript | 178 | 18 | 40 | 236 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/schema.js | JavaScript | 524 | 19 | 69 | 612 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/segments.js | JavaScript | 64 | 16 | 20 | 100 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/stream.js | JavaScript | 173 | 16 | 51 | 240 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/threads.js | JavaScript | 33 | 16 | 2 | 51 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/controllers/unknown.js | JavaScript | 14 | 22 | 2 | 38 | +| solr-8.3.1/server/solr-webapp/webapp/js/angular/services.js | JavaScript | 311 | 18 | 11 | 340 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-chosen.js | JavaScript | 112 | 24 | 4 | 140 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.js | JavaScript | 73 | 135 | 22 | 230 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-cookies.min.js | JavaScript | 2 | 29 | 1 | 32 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-resource.min.js | JavaScript | 7 | 29 | 1 | 37 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.js | JavaScript | 316 | 636 | 67 | 1,019 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-route.min.js | JavaScript | 9 | 29 | 1 | 39 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.js | JavaScript | 311 | 328 | 65 | 704 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-sanitize.min.js | JavaScript | 10 | 29 | 1 | 40 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.js | JavaScript | 157 | 48 | 13 | 218 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular-utf8-base64.min.js | JavaScript | 1 | 42 | 3 | 46 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular.js | JavaScript | 10,845 | 13,211 | 2,038 | 26,094 | +| solr-8.3.1/server/solr-webapp/webapp/libs/angular.min.js | JavaScript | 73 | 201 | 0 | 274 | +| solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.js | JavaScript | 1,151 | 36 | 8 | 1,195 | +| solr-8.3.1/server/solr-webapp/webapp/libs/chosen.jquery.min.js | JavaScript | 2 | 29 | 0 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/d3.js | JavaScript | 7,720 | 519 | 1,135 | 9,374 | +| solr-8.3.1/server/solr-webapp/webapp/libs/highlight.js | JavaScript | 2 | 29 | 1 | 32 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-1.7.2.min.js | JavaScript | 3 | 26 | 2 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-2.1.3.min.js | JavaScript | 3 | 26 | 1 | 30 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery-ui.min.js | JavaScript | 2 | 26 | 3 | 31 | +| solr-8.3.1/server/solr-webapp/webapp/libs/jquery.jstree.js | JavaScript | 3,222 | 228 | 85 | 3,535 | +| solr-8.3.1/server/solr-webapp/webapp/libs/ngtimeago.js | JavaScript | 66 | 23 | 13 | 102 | +| solr-8.3.1/server/solr-webapp/webapp/partials/alias_overview.html | HTML | 21 | 16 | 10 | 47 | +| solr-8.3.1/server/solr-webapp/webapp/partials/analysis.html | HTML | 87 | 16 | 26 | 129 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cloud.html | HTML | 263 | 16 | 24 | 303 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cluster_suggestions.html | HTML | 30 | 16 | 4 | 50 | +| solr-8.3.1/server/solr-webapp/webapp/partials/collection_overview.html | HTML | 48 | 16 | 22 | 86 | +| solr-8.3.1/server/solr-webapp/webapp/partials/collections.html | HTML | 301 | 16 | 79 | 396 | +| solr-8.3.1/server/solr-webapp/webapp/partials/core_overview.html | HTML | 125 | 16 | 66 | 207 | +| solr-8.3.1/server/solr-webapp/webapp/partials/cores.html | HTML | 142 | 16 | 67 | 225 | +| solr-8.3.1/server/solr-webapp/webapp/partials/dataimport.html | HTML | 142 | 16 | 52 | 210 | +| solr-8.3.1/server/solr-webapp/webapp/partials/documents.html | HTML | 83 | 20 | 9 | 112 | +| solr-8.3.1/server/solr-webapp/webapp/partials/files.html | HTML | 17 | 16 | 15 | 48 | +| solr-8.3.1/server/solr-webapp/webapp/partials/index.html | HTML | 135 | 42 | 85 | 262 | +| solr-8.3.1/server/solr-webapp/webapp/partials/java-properties.html | HTML | 10 | 16 | 2 | 28 | +| solr-8.3.1/server/solr-webapp/webapp/partials/logging-levels.html | HTML | 35 | 16 | 6 | 57 | +| solr-8.3.1/server/solr-webapp/webapp/partials/logging.html | HTML | 40 | 16 | 2 | 58 | +| solr-8.3.1/server/solr-webapp/webapp/partials/login.html | HTML | 134 | 16 | 11 | 161 | +| solr-8.3.1/server/solr-webapp/webapp/partials/plugins.html | HTML | 48 | 17 | 8 | 73 | +| solr-8.3.1/server/solr-webapp/webapp/partials/query.html | HTML | 270 | 16 | 84 | 370 | +| solr-8.3.1/server/solr-webapp/webapp/partials/replication.html | HTML | 153 | 16 | 71 | 240 | +| solr-8.3.1/server/solr-webapp/webapp/partials/schema.html | HTML | 336 | 16 | 104 | 456 | +| solr-8.3.1/server/solr-webapp/webapp/partials/segments.html | HTML | 70 | 16 | 14 | 100 | +| solr-8.3.1/server/solr-webapp/webapp/partials/stream.html | HTML | 40 | 16 | 9 | 65 | +| solr-8.3.1/server/solr-webapp/webapp/partials/threads.html | HTML | 36 | 16 | 14 | 66 | +| solr-8.3.1/server/solr-webapp/webapp/partials/unknown.html | HTML | 5 | 16 | 3 | 24 | +| solr-8.3.1/server/solr/configsets/_default/conf/params.json | JSON | 20 | 0 | 1 | 21 | +| solr-8.3.1/server/solr/configsets/_default/conf/solrconfig.xml | XML | 296 | 976 | 98 | 1,370 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_rest_managed.json | JSON | 1 | 0 | 1 | 2 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_stopwords_english.json | JSON | 38 | 0 | 1 | 39 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/_schema_analysis_synonyms_english.json | JSON | 11 | 0 | 1 | 12 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/kmeans-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/lingo-attributes.xml | XML | 13 | 11 | 1 | 25 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/clustering/carrot2/stc-attributes.xml | XML | 13 | 6 | 1 | 20 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/currency.xml | XML | 45 | 19 | 4 | 68 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml | XML | 3 | 37 | 3 | 43 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/params.json | JSON | 11 | 0 | 1 | 12 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml | XML | 410 | 1,097 | 124 | 1,631 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/update-script.js | JavaScript | 15 | 26 | 13 | 54 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.css | CSS | 34 | 9 | 6 | 49 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/jquery.autocomplete.js | JavaScript | 620 | 68 | 76 | 764 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/velocity/main.css | CSS | 185 | 0 | 47 | 232 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example.xsl | XSL | 98 | 20 | 15 | 133 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_atom.xsl | XSL | 40 | 20 | 8 | 68 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/example_rss.xsl | XSL | 41 | 20 | 6 | 67 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/luke.xsl | XSL | 301 | 19 | 18 | 338 | +| solr-8.3.1/server/solr/configsets/sample_techproducts_configs/conf/xslt/updateXml.xsl | XSL | 35 | 25 | 11 | 71 | +| solr-8.3.1/server/solr/dash/conf/params.json | JSON | 20 | 0 | 0 | 20 | +| solr-8.3.1/server/solr/dash/conf/schema.xml | XML | 51 | 4 | 7 | 62 | +| solr-8.3.1/server/solr/dash/conf/solrconfig.xml | XML | 270 | 962 | 97 | 1,329 | +| solr-8.3.1/server/solr/dash/core.properties | Properties | 4 | 2 | 1 | 7 | +| solr-8.3.1/server/solr/solr.xml | XML | 21 | 25 | 11 | 57 | +| solr-8.3.1/server/solr/zoo.cfg | Properties | 3 | 25 | 4 | 32 | +| solr-8.3.1/server/tmp/start_3204295554151338130.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_5812170489311981381.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_6476327636763392575.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_7329004517204835686.properties | Properties | 9 | 2 | 1 | 12 | +| solr-8.3.1/server/tmp/start_9067375725008958788.properties | Properties | 9 | 2 | 1 | 12 | +| src/Utils.ts | TypeScript | 441 | 23 | 81 | 545 | +| src/client/ClientRecommender.scss | SCSS | 9 | 1 | 2 | 12 | +| src/client/ClientRecommender.tsx | TypeScript React | 320 | 61 | 44 | 425 | +| src/client/DocServer.ts | TypeScript | 279 | 136 | 65 | 480 | +| src/client/Network.ts | TypeScript | 34 | 0 | 5 | 39 | +| src/client/apis/GoogleAuthenticationManager.scss | SCSS | 16 | 0 | 3 | 19 | +| src/client/apis/GoogleAuthenticationManager.tsx | TypeScript React | 115 | 2 | 11 | 128 | +| src/client/apis/IBM_Recommender.ts | TypeScript | 0 | 33 | 7 | 40 | +| src/client/apis/google_docs/GoogleApiClientUtils.ts | TypeScript | 225 | 9 | 27 | 261 | +| src/client/apis/google_docs/GooglePhotosClientUtils.ts | TypeScript | 318 | 0 | 46 | 364 | +| src/client/apis/youtube/YoutubeBox.scss | SCSS | 105 | 5 | 16 | 126 | +| src/client/apis/youtube/YoutubeBox.tsx | TypeScript React | 274 | 48 | 40 | 362 | +| src/client/cognitive_services/CognitiveServices.ts | TypeScript | 342 | 10 | 57 | 409 | +| src/client/documents/DocumentTypes.ts | TypeScript | 32 | 2 | 3 | 37 | +| src/client/documents/Documents.ts | TypeScript | 807 | 141 | 87 | 1,035 | +| src/client/goldenLayout.d.ts | TypeScript | 2 | 0 | 1 | 3 | +| src/client/goldenLayout.js | JavaScript | 3,084 | 1,571 | 720 | 5,375 | +| src/client/util/DictationManager.ts | TypeScript | 312 | 25 | 51 | 388 | +| src/client/util/DocumentManager.ts | TypeScript | 207 | 16 | 21 | 244 | +| src/client/util/DragManager.ts | TypeScript | 504 | 11 | 35 | 550 | +| src/client/util/DropConverter.ts | TypeScript | 70 | 6 | 2 | 78 | +| src/client/util/History.ts | TypeScript | 166 | 13 | 27 | 206 | +| src/client/util/Import & Export/DirectoryImportBox.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/util/Import & Export/DirectoryImportBox.tsx | TypeScript React | 393 | 0 | 31 | 424 | +| src/client/util/Import & Export/ImageUtils.ts | TypeScript | 35 | 0 | 4 | 39 | +| src/client/util/Import & Export/ImportMetadataEntry.tsx | TypeScript React | 132 | 0 | 17 | 149 | +| src/client/util/InteractionUtils.tsx | TypeScript React | 122 | 112 | 21 | 255 | +| src/client/util/KeyCodes.ts | TypeScript | 100 | 36 | 0 | 136 | +| src/client/util/LinkManager.ts | TypeScript | 161 | 30 | 23 | 214 | +| src/client/util/ProsemirrorCopy/prompt.js | JavaScript | 128 | 30 | 22 | 180 | +| src/client/util/Scripting.ts | TypeScript | 247 | 15 | 30 | 292 | +| src/client/util/ScrollBox.tsx | TypeScript React | 19 | 0 | 2 | 21 | +| src/client/util/SearchUtil.ts | TypeScript | 123 | 4 | 18 | 145 | +| src/client/util/SelectionManager.ts | TypeScript | 69 | 5 | 15 | 89 | +| src/client/util/SerializationHelper.ts | TypeScript | 113 | 15 | 15 | 143 | +| src/client/util/SettingsManager.scss | SCSS | 111 | 0 | 25 | 136 | +| src/client/util/SettingsManager.tsx | TypeScript React | 114 | 0 | 17 | 131 | +| src/client/util/SharingManager.scss | SCSS | 122 | 0 | 18 | 140 | +| src/client/util/SharingManager.tsx | TypeScript React | 273 | 0 | 25 | 298 | +| src/client/util/Transform.ts | TypeScript | 76 | 0 | 23 | 99 | +| src/client/util/TypedEvent.ts | TypeScript | 29 | 3 | 8 | 40 | +| src/client/util/UndoManager.ts | TypeScript | 167 | 1 | 27 | 195 | +| src/client/util/clamp.js | JavaScript | 14 | 0 | 1 | 15 | +| src/client/util/convertToCSSPTValue.js | JavaScript | 31 | 4 | 8 | 43 | +| src/client/util/jsx-decl.d.ts | TypeScript | 1 | 0 | 1 | 2 | +| src/client/util/request-image-size.js | JavaScript | 51 | 10 | 14 | 75 | +| src/client/util/toCSSLineSpacing.js | JavaScript | 35 | 15 | 14 | 64 | +| src/client/views/AntimodeMenu.scss | SCSS | 35 | 1 | 6 | 42 | +| src/client/views/AntimodeMenu.tsx | TypeScript React | 121 | 14 | 22 | 157 | +| src/client/views/ContextMenu.scss | SCSS | 130 | 17 | 14 | 161 | +| src/client/views/ContextMenu.tsx | TypeScript React | 258 | 3 | 33 | 294 | +| src/client/views/ContextMenuItem.tsx | TypeScript React | 107 | 0 | 10 | 117 | +| src/client/views/DictationOverlay.tsx | TypeScript React | 64 | 0 | 7 | 71 | +| src/client/views/DocComponent.tsx | TypeScript React | 87 | 20 | 15 | 122 | +| src/client/views/DocumentButtonBar.scss | SCSS | 89 | 0 | 16 | 105 | +| src/client/views/DocumentButtonBar.tsx | TypeScript React | 284 | 4 | 27 | 315 | +| src/client/views/DocumentDecorations.scss | SCSS | 319 | 0 | 46 | 365 | +| src/client/views/DocumentDecorations.tsx | TypeScript React | 479 | 2 | 26 | 507 | +| src/client/views/EditableView.scss | SCSS | 22 | 0 | 3 | 25 | +| src/client/views/EditableView.tsx | TypeScript React | 149 | 19 | 16 | 184 | +| src/client/views/GestureOverlay.scss | SCSS | 56 | 0 | 8 | 64 | +| src/client/views/GestureOverlay.tsx | TypeScript React | 711 | 45 | 67 | 823 | +| src/client/views/GlobalKeyHandler.ts | TypeScript | 232 | 10 | 27 | 269 | +| src/client/views/InkingControl.scss | SCSS | 127 | 4 | 0 | 131 | +| src/client/views/InkingControl.tsx | TypeScript React | 78 | 3 | 10 | 91 | +| src/client/views/InkingStroke.scss | SCSS | 7 | 0 | 0 | 7 | +| src/client/views/InkingStroke.tsx | TypeScript React | 63 | 0 | 5 | 68 | +| src/client/views/KeyphraseQueryView.scss | SCSS | 7 | 0 | 1 | 8 | +| src/client/views/KeyphraseQueryView.tsx | TypeScript React | 30 | 2 | 3 | 35 | +| src/client/views/Main.scss | SCSS | 51 | 8 | 10 | 69 | +| src/client/views/Main.tsx | TypeScript React | 22 | 1 | 2 | 25 | +| src/client/views/MainView.scss | SCSS | 138 | 1 | 21 | 160 | +| src/client/views/MainView.tsx | TypeScript React | 553 | 13 | 36 | 602 | +| src/client/views/MainViewModal.scss | SCSS | 24 | 0 | 1 | 25 | +| src/client/views/MainViewModal.tsx | TypeScript React | 39 | 0 | 5 | 44 | +| src/client/views/MainViewNotifs.scss | SCSS | 17 | 0 | 1 | 18 | +| src/client/views/MainViewNotifs.tsx | TypeScript React | 29 | 0 | 4 | 33 | +| src/client/views/MetadataEntryMenu.scss | SCSS | 79 | 0 | 14 | 93 | +| src/client/views/MetadataEntryMenu.tsx | TypeScript React | 207 | 0 | 16 | 223 | +| src/client/views/OCRUtils.ts | TypeScript | 2 | 2 | 4 | 8 | +| src/client/views/OverlayView.scss | SCSS | 41 | 0 | 6 | 47 | +| src/client/views/OverlayView.tsx | TypeScript React | 194 | 4 | 19 | 217 | +| src/client/views/Palette.scss | SCSS | 26 | 0 | 4 | 30 | +| src/client/views/Palette.tsx | TypeScript React | 65 | 0 | 5 | 70 | +| src/client/views/PreviewCursor.scss | SCSS | 9 | 0 | 1 | 10 | +| src/client/views/PreviewCursor.tsx | TypeScript React | 115 | 8 | 9 | 132 | +| src/client/views/RecommendationsBox.scss | SCSS | 52 | 10 | 8 | 70 | +| src/client/views/RecommendationsBox.tsx | TypeScript React | 116 | 67 | 17 | 200 | +| src/client/views/ScriptBox.scss | SCSS | 15 | 0 | 2 | 17 | +| src/client/views/ScriptBox.tsx | TypeScript React | 112 | 2 | 12 | 126 | +| src/client/views/ScriptingRepl.scss | SCSS | 42 | 0 | 9 | 51 | +| src/client/views/ScriptingRepl.tsx | TypeScript React | 220 | 1 | 24 | 245 | +| src/client/views/SearchDocBox.tsx | TypeScript React | 359 | 17 | 55 | 431 | +| src/client/views/TemplateMenu.scss | SCSS | 46 | 0 | 5 | 51 | +| src/client/views/TemplateMenu.tsx | TypeScript React | 167 | 1 | 14 | 182 | +| src/client/views/Templates.tsx | TypeScript React | 34 | 0 | 8 | 42 | +| src/client/views/TouchScrollableMenu.tsx | TypeScript React | 52 | 0 | 7 | 59 | +| src/client/views/Touchable.tsx | TypeScript React | 172 | 28 | 39 | 239 | +| src/client/views/_nodeModuleOverrides.scss | SCSS | 9 | 9 | 4 | 22 | +| src/client/views/animationtimeline/Keyframe.scss | SCSS | 83 | 5 | 17 | 105 | +| src/client/views/animationtimeline/Keyframe.tsx | TypeScript React | 468 | 50 | 42 | 560 | +| src/client/views/animationtimeline/Timeline.scss | SCSS | 264 | 14 | 44 | 322 | +| src/client/views/animationtimeline/Timeline.tsx | TypeScript React | 467 | 97 | 58 | 622 | +| src/client/views/animationtimeline/TimelineMenu.scss | SCSS | 75 | 2 | 17 | 94 | +| src/client/views/animationtimeline/TimelineMenu.tsx | TypeScript React | 68 | 0 | 10 | 78 | +| src/client/views/animationtimeline/TimelineOverview.scss | SCSS | 89 | 6 | 12 | 107 | +| src/client/views/animationtimeline/TimelineOverview.tsx | TypeScript React | 155 | 0 | 27 | 182 | +| src/client/views/animationtimeline/Track.scss | SCSS | 13 | 0 | 2 | 15 | +| src/client/views/animationtimeline/Track.tsx | TypeScript React | 284 | 63 | 33 | 380 | +| src/client/views/collections/CollectionCarouselView.scss | SCSS | 37 | 0 | 1 | 38 | +| src/client/views/collections/CollectionCarouselView.tsx | TypeScript React | 110 | 1 | 10 | 121 | +| src/client/views/collections/CollectionDockingView.scss | SCSS | 391 | 7 | 60 | 458 | +| src/client/views/collections/CollectionDockingView.tsx | TypeScript React | 707 | 61 | 65 | 833 | +| src/client/views/collections/CollectionLinearView.scss | SCSS | 68 | 0 | 10 | 78 | +| src/client/views/collections/CollectionLinearView.tsx | TypeScript React | 125 | 1 | 9 | 135 | +| src/client/views/collections/CollectionMapView.scss | SCSS | 27 | 0 | 3 | 30 | +| src/client/views/collections/CollectionMapView.tsx | TypeScript React | 235 | 10 | 18 | 263 | +| src/client/views/collections/CollectionMasonryViewFieldRow.tsx | TypeScript React | 308 | 0 | 24 | 332 | +| src/client/views/collections/CollectionPileView.scss | SCSS | 8 | 0 | 1 | 9 | +| src/client/views/collections/CollectionPileView.tsx | TypeScript React | 112 | 5 | 11 | 128 | +| src/client/views/collections/CollectionSchemaCells.tsx | TypeScript React | 274 | 17 | 38 | 329 | +| src/client/views/collections/CollectionSchemaHeaders.tsx | TypeScript React | 318 | 5 | 41 | 364 | +| src/client/views/collections/CollectionSchemaMovableTableHOC.tsx | TypeScript React | 216 | 0 | 27 | 243 | +| src/client/views/collections/CollectionSchemaView.scss | SCSS | 406 | 4 | 88 | 498 | +| src/client/views/collections/CollectionSchemaView.tsx | TypeScript React | 651 | 20 | 82 | 753 | +| src/client/views/collections/CollectionStackingView.scss | SCSS | 353 | 1 | 50 | 404 | +| src/client/views/collections/CollectionStackingView.tsx | TypeScript React | 430 | 5 | 25 | 460 | +| src/client/views/collections/CollectionStackingViewFieldColumn.tsx | TypeScript React | 367 | 0 | 27 | 394 | +| src/client/views/collections/CollectionStaffView.scss | SCSS | 12 | 0 | 1 | 13 | +| src/client/views/collections/CollectionStaffView.tsx | TypeScript React | 46 | 0 | 7 | 53 | +| src/client/views/collections/CollectionSubView.tsx | TypeScript React | 390 | 7 | 25 | 422 | +| src/client/views/collections/CollectionTimeView.scss | SCSS | 80 | 0 | 13 | 93 | +| src/client/views/collections/CollectionTimeView.tsx | TypeScript React | 177 | 0 | 15 | 192 | +| src/client/views/collections/CollectionTreeView.scss | SCSS | 125 | 4 | 23 | 152 | +| src/client/views/collections/CollectionTreeView.tsx | TypeScript React | 801 | 19 | 40 | 860 | +| src/client/views/collections/CollectionView.scss | SCSS | 70 | 0 | 8 | 78 | +| src/client/views/collections/CollectionView.tsx | TypeScript React | 457 | 13 | 34 | 504 | +| src/client/views/collections/CollectionViewChromes.scss | SCSS | 308 | 4 | 45 | 357 | +| src/client/views/collections/CollectionViewChromes.tsx | TypeScript React | 393 | 67 | 47 | 507 | +| src/client/views/collections/KeyRestrictionRow.tsx | TypeScript React | 49 | 2 | 4 | 55 | +| src/client/views/collections/ParentDocumentSelector.scss | SCSS | 54 | 0 | 2 | 56 | +| src/client/views/collections/ParentDocumentSelector.tsx | TypeScript React | 120 | 0 | 11 | 131 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | TypeScript React | 422 | 10 | 27 | 459 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss | SCSS | 19 | 0 | 1 | 20 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | TypeScript React | 110 | 5 | 4 | 119 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss | SCSS | 11 | 0 | 0 | 11 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx | TypeScript React | 44 | 0 | 2 | 46 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss | SCSS | 20 | 1 | 3 | 24 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx | TypeScript React | 67 | 0 | 12 | 79 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss | SCSS | 95 | 9 | 17 | 121 | +| src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | TypeScript React | 1,186 | 47 | 97 | 1,330 | +| src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | TypeScript React | 52 | 0 | 5 | 57 | +| src/client/views/collections/collectionFreeForm/MarqueeView.scss | SCSS | 30 | 0 | 2 | 32 | +| src/client/views/collections/collectionFreeForm/MarqueeView.tsx | TypeScript React | 484 | 105 | 33 | 622 | +| src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss | SCSS | 28 | 0 | 6 | 34 | +| src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx | TypeScript React | 202 | 72 | 23 | 297 | +| src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss | SCSS | 29 | 0 | 6 | 35 | +| src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx | TypeScript React | 204 | 72 | 22 | 298 | +| src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx | TypeScript React | 94 | 0 | 9 | 103 | +| src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx | TypeScript React | 51 | 0 | 5 | 56 | +| src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx | TypeScript React | 51 | 0 | 5 | 56 | +| src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx | TypeScript React | 92 | 0 | 9 | 101 | +| src/client/views/globalCssVariables.scss | SCSS | 30 | 10 | 3 | 43 | +| src/client/views/globalCssVariables.scss.d.ts | TypeScript | 9 | 0 | 2 | 11 | +| src/client/views/linking/LinkEditor.scss | SCSS | 124 | 1 | 25 | 150 | +| src/client/views/linking/LinkEditor.tsx | TypeScript React | 252 | 7 | 50 | 309 | +| src/client/views/linking/LinkMenu.scss | SCSS | 40 | 0 | 13 | 53 | +| src/client/views/linking/LinkMenu.tsx | TypeScript React | 65 | 1 | 10 | 76 | +| src/client/views/linking/LinkMenuGroup.tsx | TypeScript React | 84 | 0 | 10 | 94 | +| src/client/views/linking/LinkMenuItem.scss | SCSS | 75 | 1 | 11 | 87 | +| src/client/views/linking/LinkMenuItem.tsx | TypeScript React | 107 | 0 | 19 | 126 | +| src/client/views/nodes/AudioBox.scss | SCSS | 146 | 0 | 0 | 146 | +| src/client/views/nodes/AudioBox.tsx | TypeScript React | 261 | 2 | 24 | 287 | +| src/client/views/nodes/CollectionFreeFormDocumentView.scss | SCSS | 8 | 0 | 0 | 8 | +| src/client/views/nodes/CollectionFreeFormDocumentView.tsx | TypeScript React | 124 | 0 | 7 | 131 | +| src/client/views/nodes/ColorBox.scss | SCSS | 22 | 0 | 1 | 23 | +| src/client/views/nodes/ColorBox.tsx | TypeScript React | 28 | 0 | 4 | 32 | +| src/client/views/nodes/ContentFittingDocumentView.scss | SCSS | 20 | 0 | 4 | 24 | +| src/client/views/nodes/ContentFittingDocumentView.tsx | TypeScript React | 117 | 0 | 7 | 124 | +| src/client/views/nodes/DocumentBox.scss | SCSS | 14 | 0 | 0 | 14 | +| src/client/views/nodes/DocumentBox.tsx | TypeScript React | 154 | 0 | 4 | 158 | +| src/client/views/nodes/DocumentContentsView.tsx | TypeScript React | 183 | 10 | 17 | 210 | +| src/client/views/nodes/DocumentIcon.tsx | TypeScript React | 60 | 0 | 5 | 65 | +| src/client/views/nodes/DocumentView.scss | SCSS | 110 | 1 | 15 | 126 | +| src/client/views/nodes/DocumentView.tsx | TypeScript React | 1,041 | 54 | 94 | 1,189 | +| src/client/views/nodes/FaceRectangle.tsx | TypeScript React | 25 | 0 | 4 | 29 | +| src/client/views/nodes/FaceRectangles.tsx | TypeScript React | 41 | 0 | 5 | 46 | +| src/client/views/nodes/FieldTextBox.scss | SCSS | 12 | 0 | 3 | 15 | +| src/client/views/nodes/FieldView.tsx | TypeScript React | 89 | 43 | 4 | 136 | +| src/client/views/nodes/FontIconBox.scss | SCSS | 25 | 0 | 2 | 27 | +| src/client/views/nodes/FontIconBox.tsx | TypeScript React | 58 | 0 | 5 | 63 | +| src/client/views/nodes/ImageBox.scss | SCSS | 135 | 0 | 17 | 152 | +| src/client/views/nodes/ImageBox.tsx | TypeScript React | 430 | 10 | 35 | 475 | +| src/client/views/nodes/KeyValueBox.scss | SCSS | 120 | 0 | 3 | 123 | +| src/client/views/nodes/KeyValueBox.tsx | TypeScript React | 244 | 1 | 26 | 271 | +| src/client/views/nodes/KeyValuePair.scss | SCSS | 55 | 1 | 4 | 60 | +| src/client/views/nodes/KeyValuePair.tsx | TypeScript React | 125 | 2 | 8 | 135 | +| src/client/views/nodes/LabelBox.scss | SCSS | 31 | 0 | 4 | 35 | +| src/client/views/nodes/LabelBox.tsx | TypeScript React | 86 | 1 | 9 | 96 | +| src/client/views/nodes/LinkAnchorBox.scss | SCSS | 27 | 0 | 2 | 29 | +| src/client/views/nodes/LinkAnchorBox.tsx | TypeScript React | 141 | 0 | 9 | 150 | +| src/client/views/nodes/LinkBox.scss | SCSS | 3 | 0 | 0 | 3 | +| src/client/views/nodes/LinkBox.tsx | TypeScript React | 33 | 0 | 3 | 36 | +| src/client/views/nodes/PDFBox.scss | SCSS | 200 | 0 | 19 | 219 | +| src/client/views/nodes/PDFBox.tsx | TypeScript React | 246 | 0 | 19 | 265 | +| src/client/views/nodes/PresBox.scss | SCSS | 52 | 0 | 1 | 53 | +| src/client/views/nodes/PresBox.tsx | TypeScript React | 268 | 31 | 32 | 331 | +| src/client/views/nodes/QueryBox.scss | SCSS | 5 | 0 | 0 | 5 | +| src/client/views/nodes/QueryBox.tsx | TypeScript React | 37 | 0 | 4 | 41 | +| src/client/views/nodes/RadialMenu.scss | SCSS | 60 | 3 | 7 | 70 | +| src/client/views/nodes/RadialMenu.tsx | TypeScript React | 174 | 26 | 36 | 236 | +| src/client/views/nodes/RadialMenuItem.tsx | TypeScript React | 101 | 0 | 16 | 117 | +| src/client/views/nodes/ScreenshotBox.scss | SCSS | 40 | 6 | 5 | 51 | +| src/client/views/nodes/ScreenshotBox.tsx | TypeScript React | 174 | 2 | 18 | 194 | +| src/client/views/nodes/ScriptingBox.scss | SCSS | 33 | 0 | 3 | 36 | +| src/client/views/nodes/ScriptingBox.tsx | TypeScript React | 87 | 0 | 12 | 99 | +| src/client/views/nodes/SliderBox-components.tsx | TypeScript React | 220 | 12 | 25 | 257 | +| src/client/views/nodes/SliderBox-tooltip.css | CSS | 30 | 0 | 3 | 33 | +| src/client/views/nodes/SliderBox.scss | SCSS | 7 | 0 | 0 | 7 | +| src/client/views/nodes/SliderBox.tsx | TypeScript React | 117 | 0 | 8 | 125 | +| src/client/views/nodes/VideoBox.scss | SCSS | 65 | 3 | 6 | 74 | +| src/client/views/nodes/VideoBox.tsx | TypeScript React | 343 | 2 | 34 | 379 | +| src/client/views/nodes/WebBox.scss | SCSS | 109 | 0 | 18 | 127 | +| src/client/views/nodes/WebBox.tsx | TypeScript React | 345 | 15 | 35 | 395 | +| src/client/views/nodes/formattedText/DashDocCommentView.tsx | TypeScript React | 82 | 0 | 13 | 95 | +| src/client/views/nodes/formattedText/DashDocView.tsx | TypeScript React | 223 | 9 | 37 | 269 | +| src/client/views/nodes/formattedText/DashFieldView.scss | SCSS | 34 | 0 | 2 | 36 | +| src/client/views/nodes/formattedText/DashFieldView.tsx | TypeScript React | 178 | 13 | 20 | 211 | +| src/client/views/nodes/formattedText/FootnoteView.tsx | TypeScript React | 131 | 13 | 19 | 163 | +| src/client/views/nodes/formattedText/FormattedTextBox.scss | SCSS | 220 | 11 | 34 | 265 | +| src/client/views/nodes/formattedText/FormattedTextBox.tsx | TypeScript React | 1,169 | 68 | 93 | 1,330 | +| src/client/views/nodes/formattedText/FormattedTextBoxComment.scss | SCSS | 33 | 0 | 0 | 33 | +| src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | TypeScript React | 218 | 11 | 8 | 237 | +| src/client/views/nodes/formattedText/ImageResizeView.tsx | TypeScript React | 113 | 0 | 25 | 138 | +| src/client/views/nodes/formattedText/ParagraphNodeSpec.ts | TypeScript | 108 | 9 | 26 | 143 | +| src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | TypeScript | 231 | 0 | 11 | 242 | +| src/client/views/nodes/formattedText/RichTextMenu.scss | SCSS | 100 | 2 | 19 | 121 | +| src/client/views/nodes/formattedText/RichTextMenu.tsx | TypeScript React | 754 | 12 | 109 | 875 | +| src/client/views/nodes/formattedText/RichTextRules.ts | TypeScript | 308 | 7 | 5 | 320 | +| src/client/views/nodes/formattedText/RichTextSchema.tsx | TypeScript React | 465 | 33 | 39 | 537 | +| src/client/views/nodes/formattedText/SummaryView.tsx | TypeScript React | 67 | 0 | 14 | 81 | +| src/client/views/nodes/formattedText/TooltipTextMenu.scss | SCSS | 306 | 6 | 61 | 373 | +| src/client/views/nodes/formattedText/marks_rts.ts | TypeScript | 259 | 15 | 23 | 297 | +| src/client/views/nodes/formattedText/nodes_rts.ts | TypeScript | 224 | 21 | 19 | 264 | +| src/client/views/nodes/formattedText/prosemirrorPatches.js | JavaScript | 118 | 12 | 9 | 139 | +| src/client/views/nodes/formattedText/schema_rts.ts | TypeScript | 12 | 8 | 6 | 26 | +| src/client/views/pdf/Annotation.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/views/pdf/Annotation.tsx | TypeScript React | 114 | 0 | 16 | 130 | +| src/client/views/pdf/PDFMenu.scss | SCSS | 6 | 0 | 0 | 6 | +| src/client/views/pdf/PDFMenu.tsx | TypeScript React | 102 | 0 | 21 | 123 | +| src/client/views/pdf/PDFViewer.scss | SCSS | 74 | 4 | 10 | 88 | +| src/client/views/pdf/PDFViewer.tsx | TypeScript React | 662 | 23 | 46 | 731 | +| src/client/views/presentationview/PresElementBox.scss | SCSS | 93 | 0 | 10 | 103 | +| src/client/views/presentationview/PresElementBox.tsx | TypeScript React | 179 | 31 | 14 | 224 | +| src/client/views/search/CheckBox.scss | SCSS | 50 | 1 | 8 | 59 | +| src/client/views/search/CheckBox.tsx | TypeScript React | 42 | 74 | 15 | 131 | +| src/client/views/search/CollectionFilters.scss | SCSS | 17 | 0 | 3 | 20 | +| src/client/views/search/CollectionFilters.tsx | TypeScript React | 69 | 0 | 14 | 83 | +| src/client/views/search/FieldFilters.scss | SCSS | 10 | 1 | 1 | 12 | +| src/client/views/search/FieldFilters.tsx | TypeScript React | 34 | 0 | 7 | 41 | +| src/client/views/search/FilterBox.scss | SCSS | 153 | 0 | 25 | 178 | +| src/client/views/search/FilterBox.tsx | TypeScript React | 354 | 24 | 54 | 432 | +| src/client/views/search/IconBar.scss | SCSS | 9 | 0 | 1 | 10 | +| src/client/views/search/IconBar.tsx | TypeScript React | 69 | 1 | 17 | 87 | +| src/client/views/search/IconButton.scss | SCSS | 46 | 1 | 6 | 53 | +| src/client/views/search/IconButton.tsx | TypeScript React | 170 | 2 | 19 | 191 | +| src/client/views/search/NaviconButton.scss | SCSS | 58 | 0 | 11 | 69 | +| src/client/views/search/NaviconButton.tsx | TypeScript React | 32 | 0 | 5 | 37 | +| src/client/views/search/SearchBox.scss | SCSS | 203 | 82 | 51 | 336 | +| src/client/views/search/SearchBox.tsx | TypeScript React | 530 | 47 | 94 | 671 | +| src/client/views/search/SearchItem.scss | SCSS | 138 | 0 | 25 | 163 | +| src/client/views/search/SearchItem.tsx | TypeScript React | 272 | 2 | 29 | 303 | +| src/client/views/search/SelectorContextMenu.scss | SCSS | 12 | 1 | 3 | 16 | +| src/client/views/search/ToggleBar.scss | SCSS | 35 | 2 | 4 | 41 | +| src/client/views/search/ToggleBar.tsx | TypeScript React | 77 | 0 | 9 | 86 | +| src/client/views/webcam/DashWebRTCVideo.scss | SCSS | 70 | 4 | 9 | 83 | +| src/client/views/webcam/DashWebRTCVideo.tsx | TypeScript React | 67 | 6 | 16 | 89 | +| src/client/views/webcam/WebCamLogic.js | JavaScript | 234 | 7 | 51 | 292 | +| src/debug/Repl.tsx | TypeScript React | 59 | 0 | 7 | 66 | +| src/debug/Test.tsx | TypeScript React | 12 | 0 | 2 | 14 | +| src/debug/Viewer.tsx | TypeScript React | 173 | 0 | 19 | 192 | +| src/extensions/ArrayExtensions.ts | TypeScript | 26 | 5 | 6 | 37 | +| src/extensions/General/Extensions.ts | TypeScript | 7 | 0 | 2 | 9 | +| src/extensions/General/ExtensionsTypings.ts | TypeScript | 7 | 0 | 1 | 8 | +| src/extensions/StringExtensions.ts | TypeScript | 13 | 0 | 4 | 17 | +| src/mobile/ImageUpload.scss | SCSS | 30 | 0 | 4 | 34 | +| src/mobile/ImageUpload.tsx | TypeScript React | 78 | 41 | 12 | 131 | +| src/mobile/InkControls.tsx | TypeScript React | 0 | 0 | 1 | 1 | +| src/mobile/MobileInkOverlay.scss | SCSS | 33 | 1 | 5 | 39 | +| src/mobile/MobileInkOverlay.tsx | TypeScript React | 162 | 3 | 26 | 191 | +| src/mobile/MobileInterface.scss | SCSS | 17 | 0 | 2 | 19 | +| src/mobile/MobileInterface.tsx | TypeScript React | 297 | 15 | 32 | 344 | +| src/new_fields/CursorField.ts | TypeScript | 54 | 0 | 12 | 66 | +| src/new_fields/DateField.ts | TypeScript | 30 | 0 | 7 | 37 | +| src/new_fields/Doc.ts | TypeScript | 897 | 85 | 76 | 1,058 | +| src/new_fields/FieldSymbols.ts | TypeScript | 11 | 0 | 2 | 13 | +| src/new_fields/HtmlField.ts | TypeScript | 22 | 0 | 5 | 27 | +| src/new_fields/IconField.ts | TypeScript | 22 | 0 | 5 | 27 | +| src/new_fields/InkField.ts | TypeScript | 41 | 0 | 10 | 51 | +| src/new_fields/List.ts | TypeScript | 244 | 38 | 20 | 302 | +| src/new_fields/ListSpec.ts | TypeScript | 0 | 0 | 1 | 1 | +| src/new_fields/ObjectField.ts | TypeScript | 16 | 0 | 4 | 20 | +| src/new_fields/PresField.ts | TypeScript | 3 | 1 | 2 | 6 | +| src/new_fields/Proxy.ts | TypeScript | 95 | 2 | 14 | 111 | +| src/new_fields/RefField.ts | TypeScript | 17 | 0 | 5 | 22 | +| src/new_fields/RichTextField.ts | TypeScript | 33 | 0 | 8 | 41 | +| src/new_fields/RichTextUtils.ts | TypeScript | 455 | 8 | 56 | 519 | +| src/new_fields/Schema.ts | TypeScript | 107 | 5 | 8 | 120 | +| src/new_fields/SchemaHeaderField.ts | TypeScript | 104 | 4 | 14 | 122 | +| src/new_fields/ScriptField.ts | TypeScript | 137 | 21 | 19 | 177 | +| src/new_fields/Types.ts | TypeScript | 86 | 5 | 17 | 108 | +| src/new_fields/URLField.ts | TypeScript | 45 | 0 | 9 | 54 | +| src/new_fields/documentSchemas.ts | TypeScript | 87 | 0 | 6 | 93 | +| src/new_fields/util.ts | TypeScript | 176 | 3 | 15 | 194 | +| src/pen-gestures/GestureUtils.ts | TypeScript | 41 | 0 | 5 | 46 | +| src/pen-gestures/ndollar.ts | TypeScript | 356 | 172 | 22 | 550 | +| src/scraping/acm/.gitignore | Ignore | 2 | 0 | 0 | 2 | +| src/scraping/acm/debug.log | log | 38 | 0 | 1 | 39 | +| src/scraping/acm/index.js | JavaScript | 82 | 185 | 13 | 280 | +| src/scraping/acm/package.json | JSON | 17 | 0 | 1 | 18 | +| src/scraping/buxton/.idea/buxton.iml | XML | 8 | 0 | 0 | 8 | +| src/scraping/buxton/.idea/inspectionProfiles/profiles_settings.xml | XML | 6 | 0 | 0 | 6 | +| src/scraping/buxton/.idea/misc.xml | XML | 4 | 0 | 0 | 4 | +| src/scraping/buxton/.idea/modules.xml | XML | 8 | 0 | 0 | 8 | +| src/scraping/buxton/.idea/vcs.xml | XML | 6 | 0 | 0 | 6 | +| src/scraping/buxton/.idea/workspace.xml | XML | 173 | 0 | 0 | 173 | +| src/scraping/buxton/final/BuxtonImporter.ts | TypeScript | 228 | 142 | 26 | 396 | +| src/scraping/buxton/jsonifier.py | Python | 183 | 1 | 48 | 232 | +| src/scraping/buxton/narratives.py | Python | 11 | 19 | 9 | 39 | +| src/scraping/buxton/narratives/chord_keyboards.json | JSON | 39 | 0 | 0 | 39 | +| src/scraping/buxton/scraper.py | Python | 350 | 5 | 78 | 433 | +| src/server/ActionUtilities.ts | TypeScript | 136 | 1 | 23 | 160 | +| src/server/ApiManagers/ApiManager.ts | TypeScript | 8 | 0 | 3 | 11 | +| src/server/ApiManagers/DeleteManager.ts | TypeScript | 71 | 0 | 11 | 82 | +| src/server/ApiManagers/DownloadManager.ts | TypeScript | 173 | 80 | 16 | 269 | +| src/server/ApiManagers/GeneralGoogleManager.ts | TypeScript | 53 | 0 | 8 | 61 | +| src/server/ApiManagers/GooglePhotosManager.ts | TypeScript | 190 | 119 | 22 | 331 | +| src/server/ApiManagers/PDFManager.ts | TypeScript | 103 | 0 | 12 | 115 | +| src/server/ApiManagers/SearchManager.ts | TypeScript | 200 | 0 | 15 | 215 | +| src/server/ApiManagers/SessionManager.ts | TypeScript | 56 | 0 | 11 | 67 | +| src/server/ApiManagers/UploadManager.ts | TypeScript | 226 | 1 | 20 | 247 | +| src/server/ApiManagers/UserManager.ts | TypeScript | 101 | 4 | 21 | 126 | +| src/server/ApiManagers/UtilManager.ts | TypeScript | 42 | 22 | 10 | 74 | +| src/server/Client.ts | TypeScript | 8 | 0 | 3 | 11 | +| src/server/DashSession/DashSessionAgent.ts | TypeScript | 155 | 52 | 23 | 230 | +| src/server/DashSession/Session/agents/applied_session_agent.ts | TypeScript | 47 | 2 | 9 | 58 | +| src/server/DashSession/Session/agents/monitor.ts | TypeScript | 213 | 59 | 26 | 298 | +| src/server/DashSession/Session/agents/process_message_router.ts | TypeScript | 24 | 10 | 7 | 41 | +| src/server/DashSession/Session/agents/promisified_ipc_manager.ts | TypeScript | 106 | 52 | 15 | 173 | +| src/server/DashSession/Session/agents/server_worker.ts | TypeScript | 99 | 46 | 15 | 160 | +| src/server/DashSession/Session/utilities/repl.ts | TypeScript | 116 | 0 | 12 | 128 | +| src/server/DashSession/Session/utilities/session_config.ts | TypeScript | 119 | 0 | 10 | 129 | +| src/server/DashSession/Session/utilities/utilities.ts | TypeScript | 24 | 8 | 5 | 37 | +| src/server/DashUploadUtils.ts | TypeScript | 285 | 52 | 30 | 367 | +| src/server/GarbageCollector.ts | TypeScript | 138 | 2 | 11 | 151 | +| src/server/IDatabase.ts | TypeScript | 17 | 0 | 8 | 25 | +| src/server/MemoryDatabase.ts | TypeScript | 87 | 0 | 14 | 101 | +| src/server/Message.ts | TypeScript | 86 | 0 | 18 | 104 | +| src/server/PdfTypes.ts | TypeScript | 19 | 0 | 2 | 21 | +| src/server/ProcessFactory.ts | TypeScript | 34 | 0 | 10 | 44 | +| src/server/Recommender.ts | TypeScript | 0 | 120 | 18 | 138 | +| src/server/RouteManager.ts | TypeScript | 187 | 4 | 19 | 210 | +| src/server/RouteSubscriber.ts | TypeScript | 21 | 0 | 5 | 26 | +| src/server/Search.ts | TypeScript | 71 | 2 | 8 | 81 | +| src/server/SharedMediaTypes.ts | TypeScript | 41 | 0 | 10 | 51 | +| src/server/Websocket/Websocket.ts | TypeScript | 263 | 5 | 46 | 314 | +| src/server/apis/google/GoogleApiServerUtils.ts | TypeScript | 172 | 168 | 25 | 365 | +| src/server/apis/google/SharedTypes.ts | TypeScript | 19 | 0 | 2 | 21 | +| src/server/apis/youtube/youtubeApiSample.d.ts | TypeScript | 2 | 0 | 0 | 2 | +| src/server/apis/youtube/youtubeApiSample.js | JavaScript | 135 | 30 | 14 | 179 | +| src/server/authentication/config/passport.ts | TypeScript | 23 | 2 | 4 | 29 | +| src/server/authentication/controllers/user_controller.ts | TypeScript | 218 | 25 | 25 | 268 | +| src/server/authentication/models/current_user_utils.ts | TypeScript | 586 | 30 | 57 | 673 | +| src/server/authentication/models/user_model.ts | TypeScript | 63 | 9 | 14 | 86 | +| src/server/credentials/CredentialsLoader.ts | TypeScript | 24 | 0 | 6 | 30 | +| src/server/credentials/google_project_credentials.json | JSON | 11 | 0 | 0 | 11 | +| src/server/database.ts | TypeScript | 312 | 0 | 38 | 350 | +| src/server/downsize.ts | TypeScript | 34 | 5 | 1 | 40 | +| src/server/index.ts | TypeScript | 107 | 36 | 16 | 159 | +| src/server/remapUrl.ts | TypeScript | 53 | 4 | 6 | 63 | +| src/server/server_Initialization.ts | TypeScript | 138 | 6 | 24 | 168 | +| src/server/slides.json | JSON | 10,820 | 0 | 0 | 10,820 | +| src/server/updateProtos.ts | TypeScript | 11 | 0 | 3 | 14 | +| src/typings/index.d.ts | TypeScript | 219 | 72 | 38 | 329 | +| test/test.ts | TypeScript | 141 | 0 | 20 | 161 | +| tsconfig.json | JSON | 21 | 5 | 0 | 26 | +| tslint.json | JSON | 30 | 32 | 1 | 63 | +| views/forgot.pug | Pug | 19 | 1 | 2 | 22 | +| views/layout.pug | Pug | 13 | 0 | 1 | 14 | +| views/login.pug | Pug | 24 | 0 | 2 | 26 | +| views/reset.pug | Pug | 20 | 0 | 2 | 22 | +| views/signup.pug | Pug | 25 | 0 | 2 | 27 | +| views/stylesheets/authentication.css | CSS | 185 | 4 | 34 | 223 | +| views/user_activity.pug | Pug | 18 | 0 | 1 | 19 | +| webpack.config.js | JavaScript | 116 | 0 | 5 | 121 | +| Total | | 224,911 | 32,987 | 15,880 | 273,778 | ++-------------------------------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bc8ca01f5..959fc41ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2723,6 +2723,43 @@ } } }, + "canvas": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", + "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.11.0", + "simple-get": "^3.0.3" + }, + "dependencies": { + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -2848,8 +2885,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -2867,13 +2903,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2886,18 +2920,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3000,8 +3031,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3011,7 +3041,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3024,20 +3053,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3054,7 +3080,6 @@ "mkdirp": { "version": "0.5.3", "bundled": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -3110,8 +3135,7 @@ }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "npm-packlist": { "version": "1.4.8", @@ -3136,8 +3160,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3147,7 +3170,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3216,8 +3238,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -3247,7 +3268,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3265,7 +3285,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3304,13 +3323,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } } diff --git a/src/Utils.ts b/src/Utils.ts index ad12c68a1..23b59ac9d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -4,7 +4,7 @@ import { Socket, Room } from 'socket.io'; import { Message } from './server/Message'; export namespace Utils { - export const DRAG_THRESHOLD = 4; + export let DRAG_THRESHOLD = 4; export function GenerateGuid(): string { return v4(); @@ -512,7 +512,7 @@ export function setupMoveUpEvents( (target as any)._downY = (target as any)._lastY = e.clientY; const _moveEvent = (e: PointerEvent): void => { - if (Math.abs(e.clientX - (target as any)._downX) > 4 || Math.abs(e.clientY - (target as any)._downY) > 4) { + if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) { if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) { document.removeEventListener("pointermove", _moveEvent); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c03d9ea1b..c48611eff 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,5 +1,5 @@ import { Doc, Field, DocListCast } from "../../new_fields/Doc"; -import { Cast, ScriptCast, StrCast } from "../../new_fields/Types"; +import { Cast, ScriptCast, StrCast, NumCast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; @@ -305,33 +305,20 @@ export namespace DragManager { } export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { - let thisX = e.pageX; - let thisY = e.pageY; - const currLeft = e.pageX - xFromLeft; - const currTop = e.pageY - yFromTop; - const currRight = e.pageX + xFromRight; - const currBottom = e.pageY + yFromBottom; - const closestLeft = vertSnapLines.length ? vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev) : currLeft; - const closestTop = horizSnapLines.length ? horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev) : currTop; - const closestRight = vertSnapLines.length ? vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev) : currRight; - const closestBottom = horizSnapLines.length ? horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev) : currBottom; - const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft); - const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop); - const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight); - const distFromClosestBottom = Math.abs(e.pageY + yFromBottom - closestBottom); - if (distFromClosestLeft < 10 && distFromClosestLeft < distFromClosestRight) { - thisX = closestLeft + xFromLeft; - } - else if (distFromClosestRight < 10) { - thisX = closestRight - xFromRight; - } - if (distFromClosestTop < 10 && distFromClosestTop < distFromClosestBottom) { - thisY = closestTop + yFromTop; - } - else if (distFromClosestBottom < 10) { - thisY = closestBottom - yFromBottom; + const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + const snapVal = (pts: number[], drag: number, snapLines: number[]) => { + if (snapLines.length) { + const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt + const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines + const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest)); + const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i])); + const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2; + return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag; + } + return drag; } - return { thisX, thisY }; + + return { thisX: snapVal([xFromLeft, xFromRight], e.pageX, vertSnapLines), thisY: snapVal([yFromTop, yFromBottom], e.pageY, horizSnapLines) }; } export let docsBeingDragged: Doc[] = []; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e5a8ebcb5..72dfdf75c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -588,12 +588,12 @@ export class MainView extends React.Component { {// TO VIEW SNAP LINES - /*
+
- {this._hLines?.map(l => )} - {this._vLines?.map(l => )} + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} -
*/} +
}
); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 11d0f298d..763a6c605 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -869,6 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView { + const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); + const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; + const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); + const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { + if (this.intersectRect(docDims(doc), rect)) { + snappableDocs.push(doc); + } + } + const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to; + const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs + + const horizLines: number[] = []; + const vertLines: number[] = []; + snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { + const { left, top, width, height } = docDims(doc); + const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); + const docSize = this.getTransform().inverse().transformDirection(width, height); + + horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line + vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line + }); + DragManager.SetSnapLines(horizLines, vertLines); + } onPointerOver = (e: React.PointerEvent) => { if (SelectionManager.GetIsDragging()) { - const size = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); - const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; - const selection: Doc[] = []; - const docDims = (doc: Doc, layoutDoc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) }); - const compareDoc = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { - if (this.intersectRect(docDims(doc, Doc.Layout(doc)), rect)) { - selection.push(doc); - } - } - const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => compareDoc(doc, selRect)); // first try foreground docs - !selection.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => compareDoc(doc, selRect)); // then background docs - !selection.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => compareDoc(doc, otherBounds)); // then floating docs - - const horizLines: number[] = []; - const vertLines: number[] = []; - selection.filter(doc => !DragManager.docsBeingDragged.includes(doc)).forEach(doc => { - const { left, top, width, height } = docDims(doc, Doc.Layout(doc)); - const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); - const docSize = this.getTransform().inverse().transformDirection(width, height); - - horizLines.push(topLeftInScreen[1]); // top line - horizLines.push(topLeftInScreen[1] + docSize[1]); // bottom line - horizLines.push(topLeftInScreen[1] + docSize[1] / 2); // horiz center line - vertLines.push(topLeftInScreen[0]);//left line - vertLines.push(topLeftInScreen[0] + docSize[0]);// right line - vertLines.push(topLeftInScreen[0] + docSize[0] / 2);// vert center line - }); - DragManager.SetSnapLines(horizLines, vertLines); + this.setupDragLines(e); } e.stopPropagation(); } @@ -1273,12 +1273,12 @@ export class CollectionFreeFormView extends CollectionSubView
{// uncomment to show snap lines - /*
- - {this._hLines?.map(l => )} - {this._vLines?.map(l => )} - -
*/} +
+ + {this._hLines?.map(l => )} + {this._vLines?.map(l => )} + +
}
; } } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 422710c3e..1b22ed4cd 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -34,8 +34,6 @@ export class DashFieldView { docid={node.attrs.docid} width={node.attrs.width} height={node.attrs.height} - view={view} - getPos={getPos} tbox={tbox} />, this._fieldWrapper); (this as any).dom = this._fieldWrapper; @@ -49,8 +47,6 @@ export class DashFieldView { interface IDashFieldViewInternal { fieldKey: string; docid: string; - view: any; - getPos: any; tbox: FormattedTextBox; width: number; height: number; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index e49cc4804..663343f47 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,5 +1,6 @@ import { action, computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; +import { Utils } from "../../../Utils"; import { DocServer } from "../../../client/DocServer"; import { Docs, DocumentOptions } from "../../../client/documents/Documents"; import { UndoManager } from "../../../client/util/UndoManager"; @@ -7,7 +8,7 @@ import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; -import { Cast, PromiseValue, StrCast } from "../../../new_fields/Types"; +import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { nullAudio, ImageField } from "../../../new_fields/URLField"; import { DragManager } from "../../../client/util/DragManager"; @@ -628,6 +629,9 @@ export class CurrentUserUtils { new InkingControl(); doc.title = Doc.CurrentUserEmail; doc.activePen = doc; + doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // + doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // + Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing -- cgit v1.2.3-70-g09d2 From 1660defc561c904217ed5be34cd6e0fe64736fe1 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 30 Apr 2020 19:06:42 -0700 Subject: commented Buxton importer --- src/scraping/buxton/final/BuxtonImporter.ts | 212 +++++++++++++++++---- .../authentication/models/current_user_utils.ts | 1 - 2 files changed, 179 insertions(+), 34 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index 713207a07..21363f848 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -10,6 +10,10 @@ import { parseXml } from "libxmljs"; import { strictEqual } from "assert"; import { Readable, PassThrough } from "stream"; +/** + * This is an arbitrary bundle of data that gets populated + * in extractFileContents + */ interface DocumentContents { body: string; imageData: ImageData[]; @@ -19,6 +23,10 @@ interface DocumentContents { longDescription: string; } +/** + * A rough schema for everything that Bill has + * included for each document + */ export interface DeviceDocument { title: string; shortDescription: string; @@ -33,36 +41,65 @@ export interface DeviceDocument { attribute: string; __images: ImageData[]; hyperlinks: string[]; - captions: string[]; - embeddedFileNames: string[]; + captions: string[]; // from the table column + embeddedFileNames: string[]; // from the table column } +/** + * A layer of abstraction around a single parsing + * attempt. The error is not a TypeScript error, but + * rather an invalidly formatted value for a given key. + */ export interface AnalysisResult { device?: DeviceDocument; - errors?: { [key: string]: string }; + invalid?: { [deviceProperty: string]: string }; } +/** + * A mini API that takes in a string and returns + * either the given T or an error indicating that the + * transformation was rejected. + */ type Transformer = (raw: string) => TransformResult; interface TransformResult { transformed?: T; error?: string; } +/** + * Simple bundle counting successful and failed imports + */ export interface ImportResults { deviceCount: number; errorCount: number; } +/** + * Definitions for callback functions. Such instances are + * just invoked by when a single document has been parsed + * or the entire import is over. As of this writing, these + * callbacks are supplied by WebSocket.ts and used to inform + * the client of these events. + */ type ResultCallback = (result: AnalysisResult) => void; type TerminatorCallback = (result: ImportResults) => void; -interface Processor { - exp: RegExp; - matchIndex?: number; - transformer?: Transformer; - required?: boolean; +/** + * Defines everything needed to define how a single key should be + * formatted within the plain body text. The association between + * keys and their format definitions is stored FormatMap + */ +interface ValueFormatDefinition { + exp: RegExp; // the expression that the key's value should match + matchIndex?: number; // defaults to 0, but can be overridden to account for grouping in @param exp + transformer?: Transformer; // if desirable, how to transform the Regex match + required?: boolean; // defaults to true, confirms that for a whole document to be counted successful, + // all of its required values should be present and properly formatted } +/** + * The basic data we extract from each image in the document + */ interface ImageData { url: string; nativeWidth: number; @@ -71,6 +108,10 @@ interface ImageData { namespace Utilities { + /** + * Numeric 'try parse', fits with the Transformer API + * @param raw the serialized number + */ export function numberValue(raw: string): TransformResult { const transformed = Number(raw); if (isNaN(transformed)) { @@ -79,18 +120,32 @@ namespace Utilities { return { transformed }; } + /** + * A simple tokenizer that splits along 'and' and commas, and removes duplicates + * Helpful mainly for attribute and primary key lists + * @param raw the string to tokenize + */ export function collectUniqueTokens(raw: string): TransformResult { const pieces = raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).filter(piece => piece.length); const unique = new Set(pieces.map(token => token.toLowerCase().trim())); return { transformed: Array.from(unique).map(capitalize).sort() }; } + /** + * Tries to correct XML text parsing artifact where some sentences lose their separating space, + * and others gain excess whitespace + * @param raw + */ export function correctSentences(raw: string): TransformResult { raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight(); raw = raw.replace(/\s{2,}/g, " "); return { transformed: raw }; } + /** + * Simple capitalization + * @param word to capitalize + */ export function capitalize(word: string): string { const clean = word.trim(); if (!clean.length) { @@ -99,6 +154,12 @@ namespace Utilities { return word.charAt(0).toUpperCase() + word.slice(1); } + /** + * Streams the requeted file at the relative path to the + * root of the zip, then parses it with a library + * @param zip the zip instance data source + * @param relativePath the path to a .xml file within the zip to parse + */ export async function readAndParseXml(zip: any, relativePath: string) { console.log(`Text streaming ${relativePath}`); const contents = await new Promise((resolve, reject) => { @@ -111,13 +172,17 @@ namespace Utilities { stream.on('end', () => resolve(body)); }); }); - return parseXml(contents); } - } -const RegexMap = new Map>([ +/** + * Defines how device values should be formatted. As you can see, the formatting is + * not super consistent and has changed over time as edge cases have been found, but this + * at least imposes some constraints, and will notify you if a document doesn't match the specifications + * in this map. + */ +const FormatMap = new Map>([ ["title", { exp: /contact\s+(.*)Short Description:/ }], @@ -189,17 +254,25 @@ const RegexMap = new Map>([ }], ]); -const sourceDir = path.resolve(__dirname, "source"); -const outDir = path.resolve(__dirname, "json"); -const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton"); -const successOut = "buxton.json"; -const failOut = "incomplete.json"; -const deviceKeys = Array.from(RegexMap.keys()); - +const sourceDir = path.resolve(__dirname, "source"); // where the Word documents are assumed to be stored +const outDir = path.resolve(__dirname, "json"); // where the JSON output of these device documents will be written +const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton"); // where, in the server, these images will be written +const successOut = "buxton.json"; // the JSON list representing properly formatted documents +const failOut = "incomplete.json"; // the JSON list representing improperly formatted documents +const deviceKeys = Array.from(FormatMap.keys()); // a way to iterate through all keys of the DeviceDocument interface + +/** + * Starts by REMOVING ALL EXISTING BUXTON RESOURCES. This might need to be + * changed going forward + * @param emitter the callback when each document is completed + * @param terminator the callback when the entire import is completed + */ export default async function executeImport(emitter: ResultCallback, terminator: TerminatorCallback) { try { + // get all Word documents in the source directory const contents = readdirSync(sourceDir); const wordDocuments = contents.filter(file => /.*\.docx?$/.test(file)).map(file => `${sourceDir}/${file}`); + // removal takes place here [outDir, imageDir].forEach(dir => { rimraf.sync(dir); mkdirSync(dir); @@ -216,19 +289,28 @@ export default async function executeImport(emitter: ResultCallback, terminator: } } +/** + * Parse every Word document in the directory, notifying any callers as needed + * at each iteration via the emitter. + * @param wordDocuments the string list of Word document names to parse + * @param emitter the callback when each document is completed + * @param terminator the callback when the entire import is completed + */ async function parseFiles(wordDocuments: string[], emitter: ResultCallback, terminator: TerminatorCallback): Promise { + // execute parent-most parse function const results: AnalysisResult[] = []; for (const filePath of wordDocuments) { - const fileName = path.basename(filePath).replace("Bill_Notes_", ""); + const fileName = path.basename(filePath).replace("Bill_Notes_", ""); // not strictly needed, but cleaner console.log(cyan(`\nExtracting contents from ${fileName}...`)); const result = analyze(fileName, await extractFileContents(filePath)); emitter(result); results.push(result); } + // collect information about errors and successes const masterDevices: DeviceDocument[] = []; const masterErrors: { [key: string]: string }[] = []; - results.forEach(({ device, errors }) => { + results.forEach(({ device, invalid: errors }) => { if (device) { masterDevices.push(device); } else if (errors) { @@ -236,24 +318,45 @@ async function parseFiles(wordDocuments: string[], emitter: ResultCallback, term } }); + // something went wrong, since errors and successes should sum to total inputs const total = wordDocuments.length; if (masterDevices.length + masterErrors.length !== total) { throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`); } + // write the external JSON representations of this import console.log(); await writeOutputFile(successOut, masterDevices, total, true); await writeOutputFile(failOut, masterErrors, total, false); console.log(); + // notify the caller that the import has finished terminator({ deviceCount: masterDevices.length, errorCount: masterErrors.length }); return masterDevices; } +/** + * XPath definitions for desired XML targets in respective hierarchies. + * + * For table cells, can be read as: "find me anything that looks like in XML, whose + * parent looks like , whose parent looks like " + * + * + * + * + * + * These are found by trial and error, and using an online XML parser / prettifier + * to inspect the structure, since the Node XML library does not expose the parsed + * structure very well for searching, say in the debug console. + */ const tableCellXPath = '//*[name()="w:tbl"]/*[name()="w:tr"]/*[name()="w:tc"]'; const hyperlinkXPath = '//*[name()="Relationship" and contains(@Type, "hyperlink")]'; +/** + * The meat of the script, images and text content are extracted here + * @param pathToDocument the path to the document relative to the root of the zip + */ async function extractFileContents(pathToDocument: string): Promise { console.log('Extracting text...'); const zip = new StreamZip({ file: pathToDocument, storeEntries: true }); @@ -261,22 +364,30 @@ async function extractFileContents(pathToDocument: string): Promise node.text().trim()); + // preserve paragraph formatting and line breaks that would otherwise get lost in the plain text parsing + // of the XML hierarchy const paragraphs = document.find('//*[name()="w:p"]').map(node => Utilities.correctSentences(node.text()).transformed!); const start = paragraphs.indexOf(paragraphs.find(el => /Bill Buxton[’']s Notes/.test(el))!) + 1; const end = paragraphs.indexOf("Device Details"); const longDescription = paragraphs.slice(start, end).filter(paragraph => paragraph.length).join("\n\n"); - const { length } = captionTargets; + // extract captions from the table cells + const tableRowsFlattened = document.find(tableCellXPath).map(node => node.text().trim()); + const { length } = tableRowsFlattened; strictEqual(length > 3, true, "No captions written."); strictEqual(length % 3 === 0, true, "Improper caption formatting."); - for (let i = 3; i < captionTargets.length; i += 3) { - const row = captionTargets.slice(i, i + 3); + // break the flat list of strings into groups of three, since there + // currently are three columns in the table. Thus, each group represents + // a row in the table, where the first row has no text content since it's + // the image, the second has the file name and the third has the caption + for (let i = 3; i < tableRowsFlattened.length; i += 3) { + const row = tableRowsFlattened.slice(i, i + 3); embeddedFileNames.push(row[1]); captions.push(row[2]); } @@ -286,23 +397,34 @@ async function extractFileContents(pathToDocument: string): Promise el.attrs()[2].value()); console.log("Text extracted."); + // write out the images for this document console.log("Beginning image extraction..."); const imageData = await writeImages(zip); console.log(`Extracted ${imageData.length} images.`); + // cleanup zip.close(); return { body, longDescription, imageData, captions, embeddedFileNames, hyperlinks }; } +// zip relative path from root expression / filter used to isolate only media assets const imageEntry = /^word\/media\/\w+\.(jpeg|jpg|png|gif)/; -interface Dimensions { +/** + * Image dimensions and file suffix, + */ +interface ImageAttrs { width: number; height: number; type: string; } +/** + * For each image, stream the file, get its size, check if it's an icon + * (if it is, ignore it) + * @param zip the zip instance data source + */ async function writeImages(zip: any): Promise { const allEntries = Object.values(zip.entries()).map(({ name }) => name); const imageEntries = allEntries.filter(name => imageEntry.test(name)); @@ -315,8 +437,8 @@ async function writeImages(zip: any): Promise { }); for (const mediaPath of imageEntries) { - const { width, height, type } = await new Promise(async resolve => { - const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => { + const { width, height, type } = await new Promise(async resolve => { + const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: ImageAttrs) => { readStream.destroy(); resolve(dimensions); }).on("error", () => readStream.destroy()); @@ -324,11 +446,14 @@ async function writeImages(zip: any): Promise { readStream.pipe(sizeStream); }); + // if it's not an icon, by this rough heuristic, i.e. is it not square if (Math.abs(width - height) > 10) { valid.push({ width, height, type, mediaPath }); } } + // for each valid image, output the _o, _l, _m, and _s files + // THIS IS WHERE THE SCRIPT SPENDS MOST OF ITS TIME for (const { type, width, height, mediaPath } of valid) { const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`; await DashUploadUtils.outputResizedImages(() => getImageStream(mediaPath), generatedFileName, imageDir); @@ -342,6 +467,14 @@ async function writeImages(zip: any): Promise { return imageUrls; } +/** + * Takes the results of extractFileContents, which relative to this is sort of the + * external media / preliminary text processing, and now tests the given file name to + * with those value definitions to make sure the body of the document contains all + * required fields, properly formatted + * @param fileName the file whose body to inspect + * @param contents the data already computed / parsed by extractFileContents + */ function analyze(fileName: string, contents: DocumentContents): AnalysisResult { const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents; const device: any = { @@ -354,43 +487,56 @@ function analyze(fileName: string, contents: DocumentContents): AnalysisResult { const errors: { [key: string]: string } = { fileName }; for (const key of deviceKeys) { - const { exp, transformer, matchIndex, required } = RegexMap.get(key)!; + const { exp, transformer, matchIndex, required } = FormatMap.get(key)!; const matches = exp.exec(body); let captured: string; - if (matches && (captured = matches[matchIndex ?? 1])) { - captured = captured.replace(/\s{2,}/g, " "); + // if we matched and we got the specific match we're after + if (matches && (captured = matches[matchIndex ?? 1])) { // matchIndex defaults to 1 + captured = captured.replace(/\s{2,}/g, " "); // remove excess whitespace + // if supplied, apply the required transformation (recall this is specified in FormatMap) if (transformer) { const { error, transformed } = transformer(captured); if (error) { + // we hit a snag trying to transform the valid match + // still counts as a fundamental error errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`; continue; } captured = transformed; } - device[key] = captured; } else if (required ?? true) { + // the field was either implicitly or explicitly required, and failed to match the definition in + // FormatMap errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`; continue; } } + // print errors - this can be removed const errorKeys = Object.keys(errors); if (errorKeys.length > 1) { console.log(red(`@ ${cyan(fileName.toUpperCase())}...`)); errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key]))); - return { errors }; + return { invalid: errors }; } return { device }; } +/** + * A utility function that writes the JSON results for this import out to the desired path + * @param relativePath where to write the JSON file + * @param data valid device document objects, or errors + * @param total used for more informative printing + * @param success whether or not the caller is writing the successful parses or the failures + */ async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) { console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`)); return new Promise((resolve, reject) => { const destination = path.resolve(outDir, relativePath); - const contents = JSON.stringify(data, undefined, 4); + const contents = JSON.stringify(data, undefined, 4); // format the JSON writeFile(destination, contents, err => err ? reject(err) : resolve()); }); } \ No newline at end of file diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 663343f47..d7cc1e6bf 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -9,7 +9,6 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types"; -import { Utils } from "../../../Utils"; import { nullAudio, ImageField } from "../../../new_fields/URLField"; import { DragManager } from "../../../client/util/DragManager"; import { InkingControl } from "../../../client/views/InkingControl"; -- cgit v1.2.3-70-g09d2 From ff7c7d40b1fcdf74b539c7d97f36707ff1521d2e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 1 May 2020 01:46:07 -0400 Subject: fixed presentations to allow drag and drop. fixed pres box to use RenderData instead of modifying presentation elements with unnecessary info like their containing PresBox and their presentation index position. COnverted COntentFIttingDocumentView to use DocumentView's props --- src/client/documents/Documents.ts | 1 + src/client/views/MainView.tsx | 4 +- src/client/views/SearchDocBox.tsx | 10 ++++- .../views/collections/CollectionCarouselView.tsx | 8 +++- .../views/collections/CollectionSchemaView.tsx | 14 ++++--- .../views/collections/CollectionStackingView.tsx | 15 ++++--- .../views/collections/CollectionTreeView.tsx | 20 +++++---- src/client/views/collections/CollectionView.tsx | 14 ++++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- .../CollectionMulticolumnView.tsx | 14 ++++--- .../CollectionMultirowView.tsx | 14 ++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 8 ++-- .../views/nodes/ContentFittingDocumentView.tsx | 49 ++++------------------ src/client/views/nodes/DocumentBox.tsx | 15 ++++--- src/client/views/nodes/DocumentView.tsx | 20 ++++----- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/PresBox.tsx | 21 ++++------ .../formattedText/FormattedTextBoxComment.tsx | 12 ++++-- .../views/nodes/formattedText/RichTextMenu.scss | 3 ++ .../views/presentationview/PresElementBox.tsx | 25 +++++++---- src/client/views/search/SearchItem.tsx | 12 ++++-- src/new_fields/documentSchemas.ts | 2 +- .../authentication/models/current_user_utils.ts | 2 +- 23 files changed, 150 insertions(+), 137 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2e81d5fa6..228a6af97 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -81,6 +81,7 @@ export interface DocumentOptions { author?: string; dropAction?: dropActionType; childDropAction?: dropActionType; + targetDropAction?: dropActionType; layoutKey?: string; type?: string; title?: string; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 65e4eb036..a29a6baac 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -597,8 +597,8 @@ export class MainView extends React.Component { {// TO VIEW SNAP LINES
- {this._hLines?.map(l => )} - {this._vLines?.map(l => )} + {this._hLines?.map((l: any) => )} + {this._vLines?.map((l: any) => )}
} diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx index 799fa9d85..7bd689b19 100644 --- a/src/client/views/SearchDocBox.tsx +++ b/src/client/views/SearchDocBox.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; -import { returnFalse } from "../../Utils"; +import { returnFalse, returnZero } from "../../Utils"; import { Docs } from "../documents/Documents"; import { SearchUtil } from "../util/SearchUtil"; import { EditableView } from "./EditableView"; @@ -399,7 +399,13 @@ export class SearchDocBox extends React.Component { + bringToFront={returnFalse} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + NativeWidth={returnZero} + NativeHeight={returnZero} + parentActive={this.props.active} + ScreenToLocalTransform={this.props.ScreenToLocalTransform}>
; const CarouselDocument = makeInterface(documentSchema); @@ -49,9 +50,12 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + bringToFront={returnFalse} + parentActive={this.props.active} + />
doc) { {!this.previewDocument ? (null) : doc) { rootSelected={this.rootSelected} PanelWidth={this.previewWidth} PanelHeight={this.previewHeight} - getTransform={this.getPreviewTransform} - CollectionDoc={this.props.CollectionView?.props.Document} - CollectionView={this.props.CollectionView} + ScreenToLocalTransform={this.getPreviewTransform} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} - active={this.props.active} + parentActive={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} />}
; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 556d7df5c..6c230d5b1 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; -import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils"; +import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils"; import { DragManager, dropActionType } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; @@ -165,12 +165,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const height = () => this.getDocHeight(doc); return doc) { dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} onDoubleClick={this.onChildDoubleClickHandler} - getTransform={dxf} + ScreenToLocalTransform={dxf} focus={this.props.focus} - CollectionDoc={this.props.CollectionView?.props.Document} - CollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - active={this.props.active} + parentActive={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.addDocTab} + bringToFront={returnFalse} + ContentScaling={returnOne} pinToPres={this.props.pinToPres} />; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index d938bd7ad..71358a8ec 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -353,27 +353,31 @@ class TreeView extends React.Component { return
+ pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} + />
; } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 8d8c321e8..561226de5 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,12 +113,16 @@ export class CollectionView extends Touchable { @action.bound addDocument(doc: Doc): boolean { - const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[this.props.fieldKey]); - !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there - // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + if (this.props.addDocument) { + this.props.addDocument(doc); + } else { + const targetDataDoc = this.props.Document[DataSym]; + const docList = DocListCast(targetDataDoc[this.props.fieldKey]); + !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there + // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + } doc.context = this.props.Document; - targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); Doc.GetProto(doc).lastOpened = new DateField; return true; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 763a6c605..b4eb22444 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -877,6 +877,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (SelectionManager.GetIsDragging()) { - this.setupDragLines(e); + this.setupDragLines(); } e.stopPropagation(); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 66d441115..b3a6a9deb 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -14,7 +14,7 @@ import "./collectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; import { List } from '../../../../new_fields/List'; -import { returnZero } from '../../../../Utils'; +import { returnZero, returnFalse, returnOne } from '../../../../Utils'; type MulticolumnDocument = makeInterface<[typeof documentSchema]>; const MulticolumnDocument = makeInterface(documentSchema); @@ -216,7 +216,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ; } /** diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 615efdb39..0fb29ca61 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import { Doc } from '../../../../new_fields/Doc'; import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; -import { Utils, returnZero } from '../../../../Utils'; +import { Utils, returnZero, returnFalse, returnOne } from '../../../../Utils'; import "./collectionMultirowView.scss"; import { computed, trace, observable, action } from 'mobx'; import { Transform } from '../../../util/Transform'; @@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return ; } /** diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1c7d116c5..24468dcc1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -115,13 +115,11 @@ export class CollectionFreeFormDocumentView extends DocComponent : } diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index d0b0c8ee6..637fd5acc 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -3,51 +3,16 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import "react-table/react-table.css"; import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; import { emptyFunction, returnOne } from "../../../Utils"; -import { Transform } from "../../util/Transform"; -import { CollectionView } from "../collections/CollectionView"; import '../DocumentDecorations.scss'; -import { DocumentView } from "../nodes/DocumentView"; +import { DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import "./ContentFittingDocumentView.scss"; -import { dropActionType } from "../../util/DragManager"; -interface ContentFittingDocumentViewProps { - Document: Doc; - DataDocument?: Doc; - LayoutDoc?: () => Opt; - NativeWidth?: () => number; - NativeHeight?: () => number; - FreezeDimensions?: boolean; - LibraryPath: Doc[]; - renderDepth: number; - fitToBox?: boolean; - layoutKey?: string; - dropAction?: dropActionType; - PanelWidth: () => number; - PanelHeight: () => number; - focus?: (doc: Doc) => void; - CollectionView?: CollectionView; - CollectionDoc?: Doc; - onClick?: ScriptField; - onDoubleClick?: ScriptField; - backgroundColor?: (doc: Doc) => string | undefined; - getTransform: () => Transform; - addDocument?: (document: Doc) => boolean; - moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean; - removeDocument?: (document: Doc) => boolean; - active: (outsideReaction: boolean) => boolean; - whenActiveChanged: (isActive: boolean) => void; - addDocTab: (document: Doc, where: string) => boolean; - pinToPres: (document: Doc) => void; - dontRegisterView?: boolean; - rootSelected: (outsideReaction?: boolean) => boolean; -} @observer -export class ContentFittingDocumentView extends React.Component{ +export class ContentFittingDocumentView extends React.Component{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); } @computed get freezeDimensions() { return this.props.FreezeDimensions; } @@ -68,7 +33,7 @@ export class ContentFittingDocumentView extends React.Component this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); + private getTransform = () => this.props.ScreenToLocalTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; } private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; } @@ -90,7 +55,7 @@ export class ContentFittingDocumentView extends React.Component ; return contents; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 085637440..f555d6eef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -77,6 +77,7 @@ export interface DocumentViewProps { setupDragLines?: () => void; renderDepth: number; ContentScaling: () => number; + RenderData?: () => Doc; PanelWidth: () => number; PanelHeight: () => number; pointerEvents?: boolean; @@ -992,6 +993,7 @@ export class DocumentView extends DocComponent(Docu LayoutDoc={this.props.LayoutDoc} makeLink={this.makeLink} rootSelected={this.rootSelected} + RenderData={this.props.RenderData} dontRegisterView={this.props.dontRegisterView} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} @@ -1112,17 +1114,13 @@ export class DocumentView extends DocComponent(Docu Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } } - @observable _animate = 0; + @observable _animateScalingTo = 0; switchViews = action((custom: boolean, view: string) => { - SelectionManager.SetIsDragging(true); - this._animate = 0.1; + this._animateScalingTo = 0.1; // shrink doc setTimeout(action(() => { this.setCustomView(custom, view); - this._animate = 1; - setTimeout(action(() => { - this._animate = 0; - SelectionManager.SetIsDragging(false); - }), 400); + this._animateScalingTo = 1; // expand it + setTimeout(action(() => this._animateScalingTo = 0), 400); }), 400); }); @@ -1156,9 +1154,9 @@ export class DocumentView extends DocComponent(Docu !entered && Doc.UnBrushDoc(this.props.Document); })} style={{ - transformOrigin: this._animate ? "center center" : undefined, - transform: this._animate ? `scale(${this._animate})` : undefined, - transition: !this._animate ? StrCast(this.Document.transition) : this._animate < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out", + transformOrigin: this._animateScalingTo ? "center center" : undefined, + transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, + transition: !this._animateScalingTo ? StrCast(this.Document.transition) : this._animateScalingTo < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out", pointerEvents: this.ignorePointerEvents ? "none" : undefined, color: StrCast(this.layoutDoc.color, "inherit"), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 0b9edbcd3..1efee4f5a 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -56,6 +56,7 @@ export interface FieldViewProps { width?: number; background?: string; color?: string; + RenderData?: () => Doc; } @observer diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index f91a809bb..3fcc97473 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -16,6 +16,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; +import { List } from "../../../new_fields/List"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -23,20 +24,15 @@ const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - private _childReaction: IReactionDisposer | undefined; @observable _isChildActive = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); } componentDidMount() { + this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; - this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true }); } - componentWillUnmount() { - this._childReaction?.(); - } - updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc; @undoBatch @@ -247,16 +243,12 @@ export class PresBox extends ViewBoxBaseComponent initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => { const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; - docList.forEach(doc => { - doc.presBox = this.rootDoc; // give contained documents a reference to the presentation - doc.presCollapsedHeight = hgt; // set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din - }); + this.rootDoc.presCollapsedHeight = hgt; } addDocument = (doc: Doc) => { - const newPinDoc = Doc.MakeAlias(doc); - newPinDoc.presentationTargetDoc = doc; - return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc); + doc.presentationTargetDoc = doc.aliasOf; + return Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); } removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); @@ -280,8 +272,10 @@ export class PresBox extends ViewBoxBaseComponent this.updateMinimize(e, this.rootDoc._viewType = viewType); }); + returnSelf = () => this.rootDoc; childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined; render() { + this.rootDoc.presOrderedDocs = new List(this.childDocs.map((child, i) => child)); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; this.initializeViewAliases(this.childDocs, mode); return
@@ -314,6 +308,7 @@ export class PresBox extends ViewBoxBaseComponent childLayoutTemplate={this.childLayoutTemplate} addDocument={this.addDocument} removeDocument={returnFalse} + RenderData={this.returnSelf} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> : (null) diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index f9e4c5210..9ad5aafb8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; import { Doc, DocCastAsync } from "../../../../new_fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { schema } from "./schema_rts"; @@ -192,18 +192,24 @@ export class FormattedTextBoxComment { fitToBox={true} moveDocument={returnFalse} rootSelected={returnFalse} - getTransform={Transform.Identity} - active={returnFalse} + ScreenToLocalTransform={Transform.Identity} + parentActive={returnFalse} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} renderDepth={1} PanelWidth={() => Math.min(350, NumCast(target._width, 350))} PanelHeight={() => Math.min(250, NumCast(target._height, 250))} focus={emptyFunction} whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} + NativeWidth={returnZero} + NativeHeight={returnZero} />, FormattedTextBoxComment.tooltipText); FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss index 3a16171de..7a0718c16 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.scss +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -55,6 +55,9 @@ color: black; } +} + +.richTextMenu { select { background-color: #323232; color: white; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 66f251b93..1887c8d45 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DataSym } from "../../../new_fields/Doc"; +import { Doc, DataSym, DocListCast } from "../../../new_fields/Doc"; import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; import { createSchema, makeInterface } from '../../../new_fields/Schema'; import { Cast, NumCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -38,13 +38,14 @@ export class PresElementBox extends ViewBoxBaseComponent d === this.rootDoc); } + @computed get presBoxDoc() { return Cast(this.props.RenderData?.().presBox, Doc) as Doc; } @computed get targetDoc() { return this.rootDoc.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presBoxDoc?._itemIndex); } + @computed get collapsedHeight() { return NumCast(this.presBoxDoc?.presCollapsedHeight); } componentDidMount() { - this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.rootDoc.presCollapsedHeight], + this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { @@ -147,7 +148,7 @@ export class PresElementBox extends ViewBoxBaseComponent [xCord, yCord]; - embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - NumCast(this.rootDoc.presCollapsedHeight)); + embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); embedWidth = () => this.props.PanelWidth() - 20; /** * The function that is responsible for rendering the a preview or not for this @@ -158,7 +159,7 @@ export class PresElementBox extends ViewBoxBaseComponent
; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index fe2000700..96f43e931 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; @@ -164,14 +164,20 @@ export class SearchItem extends React.Component { removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} - getTransform={Transform.Identity} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + ScreenToLocalTransform={Transform.Identity} renderDepth={1} PanelWidth={returnXDimension} PanelHeight={returnYDimension} + NativeWidth={returnZero} + NativeHeight={returnZero} focus={emptyFunction} moveDocument={returnFalse} - active={returnFalse} + parentActive={returnFalse} whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} />
; return docview; diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 5ca0d681e..cd4b9d591 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -9,7 +9,7 @@ export const documentSchema = createSchema({ layoutKey: "string", // holds the field key for the field that actually holds the current lyoat title: "string", // document title (can be on either data document or layout) dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move") - targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") + targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index d7cc1e6bf..f37538252 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -578,7 +578,7 @@ export class CurrentUserUtils { } if (doc.activePresentation === undefined) { doc.activePresentation = Docs.Create.PresDocument(new List(), { - title: "Presentation", _viewType: CollectionViewType.Stacking, + title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" }); } -- cgit v1.2.3-70-g09d2 From 37324d134c8d788a73b8b1d35afa7ea6b0e88d37 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 1 May 2020 12:47:51 -0400 Subject: cleaned up buxton template. fixed adding docs to collections. --- src/client/documents/Documents.ts | 7 +++---- src/client/views/collections/CollectionPileView.tsx | 4 ++-- .../views/collections/CollectionStackingView.scss | 7 +++++-- .../CollectionStackingViewFieldColumn.tsx | 6 +----- src/client/views/collections/CollectionView.tsx | 21 ++++++++++++--------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 6 +++--- src/new_fields/Doc.ts | 2 +- src/new_fields/documentSchemas.ts | 4 ++-- .../authentication/models/current_user_utils.ts | 7 ++++--- 11 files changed, 35 insertions(+), 33 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 228a6af97..3d790a485 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,6 +98,7 @@ export interface DocumentOptions { isTemplateDoc?: boolean; targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; + hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) backgroundColor?: string | ScriptField; // background color for data doc _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) color?: string; // foreground color data doc @@ -423,11 +424,9 @@ export namespace Docs { _nativeHeight: nativeHeight })); // the main document we create - const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true }); - const deviceProto = Doc.GetProto(doc); - deviceProto.hero = new ImageField(constructed[0].url); + const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true, hero: new ImageField(constructed[0].url) }); // add the parsed attributes to this main document - Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } }); + Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: Doc.GetProto(doc) } }); Doc.AddDocToList(parentProto, "data", doc); } else if (errors) { console.log(errors); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 3bbfcc4d7..8b8cbc6e8 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -53,7 +53,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { toggleStarburst = action(() => { if (this._layoutEngine === 'starburst') { const defaultSize = 110; - this.layoutDoc.overflow = undefined; + this.layoutDoc._overflow = undefined; this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); @@ -61,7 +61,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { this._layoutEngine = 'pass'; } else { const defaultSize = 25; - this.layoutDoc.overflow = 'visible'; + this.layoutDoc._overflow = 'visible'; !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); if (this._layoutEngine === 'pass') { diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 47faa9239..5eaf29316 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -180,13 +180,16 @@ .collectionStackingView-sectionHeader-subCont { outline: none; border: 0px; - color: $light-color; width: 100%; - color: grey; letter-spacing: 2px; font-size: 75%; transition: transform 0.2s; position: relative; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + color: lightGray; .editableView-container-editing-oneLine, .editableView-container-editing { diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 323d7be25..8a9539eb0 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -322,11 +322,7 @@ export class CollectionStackingViewFieldColumn extends React.Component + style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "inherit" }}> {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 561226de5..91018980c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -71,6 +71,9 @@ export enum CollectionViewType { Map = "map", Pile = "pileup" } +export interface CollectionViewCustomProps { + filterAddDocument: (doc: Doc) => boolean; +} export interface CollectionRenderProps { addDocument: (document: Doc) => boolean; @@ -82,7 +85,7 @@ export interface CollectionRenderProps { } @observer -export class CollectionView extends Touchable { +export class CollectionView extends Touchable { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _isChildActive = false; //TODO should this be observable? @@ -113,15 +116,15 @@ export class CollectionView extends Touchable { @action.bound addDocument(doc: Doc): boolean { - if (this.props.addDocument) { - this.props.addDocument(doc); - } else { - const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[this.props.fieldKey]); - !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there - // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); - targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + if (this.props.filterAddDocument?.(doc) === false) { + return false; } + + const targetDataDoc = this.props.Document[DataSym]; + const docList = DocListCast(targetDataDoc[this.props.fieldKey]); + !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List([...docList, doc])); // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there + // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); doc.context = this.props.Document; Doc.GetProto(doc).lastOpened = new DateField; return true; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c70301b2f..5bac075b4 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -613,7 +613,7 @@ export class MarqueeView extends React.Component e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}> {this._visible ? this.marqueeDiv : null} {this.props.children} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f555d6eef..f1efa48f4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -666,7 +666,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action toggleBackground = (temporary: boolean): void => { - this.Document.overflow = temporary ? "visible" : "hidden"; + this.Document._overflow = temporary ? "visible" : "hidden"; this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true); this.Document.isBackground && this.props.bringToFront(this.Document, true); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 3fcc97473..72bbc9e4b 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -246,9 +246,9 @@ export class PresBox extends ViewBoxBaseComponent this.rootDoc.presCollapsedHeight = hgt; } - addDocument = (doc: Doc) => { + addDocumentFilter = (doc: Doc) => { doc.presentationTargetDoc = doc.aliasOf; - return Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); + return true; } removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); @@ -306,7 +306,7 @@ export class PresBox extends ViewBoxBaseComponent PanelHeight={this.panelHeight} moveDocument={returnFalse} childLayoutTemplate={this.childLayoutTemplate} - addDocument={this.addDocument} + filterAddDocument={this.addDocumentFilter} removeDocument={returnFalse} RenderData={this.returnSelf} focus={this.selectElement} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 77eee03ce..71325d94f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -997,7 +997,7 @@ export namespace Doc { //newCollection.borderRounding = "40px"; newCollection._jitterRotation = 10; newCollection._backgroundColor = "gray"; - newCollection.overflow = "visible"; + newCollection._overflow = "visible"; return newCollection; } diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index cd4b9d591..7bf1c03c8 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -20,6 +20,7 @@ export const documentSchema = createSchema({ _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set _xMargin: "number", // margin added on left/right of most documents to add separation from their container _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container + _overflow: "string", // sets overflow behvavior for CollectionFreeForm views _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document _showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document _showTitleHover: "string", // the showTitle should be shown only on hover @@ -36,7 +37,6 @@ export const documentSchema = createSchema({ color: "string", // foreground color of document backgroundColor: "string", // background color of document opacity: "number", // opacity of document - overflow: "string", // sets overflow behvavior for CollectionFreeForm views creationDate: DateField, // when the document was created links: listSpec(Doc), // computed (readonly) list of links associated with this document onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) @@ -46,7 +46,7 @@ export const documentSchema = createSchema({ dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field - isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee) treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index f37538252..fbb27f9b7 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -89,7 +89,7 @@ export class CurrentUserUtils { const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }); const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true }); - const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 50, _autoHeight: true }); + const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true }); const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true }); const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; @@ -111,8 +111,9 @@ export class CurrentUserUtils { const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); descriptionWrapper.sectionHeaders = new List([ - new SchemaHeaderField("[Long Description]", "LemonChiffon", undefined, undefined, undefined, true), - new SchemaHeaderField("[Details]", "lightBlue", undefined, undefined, undefined, true), + new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false), + new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), + new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), ]); const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts }); detailView.isTemplateDoc = makeTemplate(detailView); -- cgit v1.2.3-70-g09d2 From 72a838b6a6165c7fa3050d78dbf16bf2cca6b840 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 1 May 2020 13:26:25 -0400 Subject: buxton template tweaks --- src/client/documents/Documents.ts | 2 ++ src/client/views/collections/CollectionCarouselView.tsx | 4 ++-- src/server/authentication/models/current_user_utils.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3d790a485..732f9303b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -75,6 +75,8 @@ export interface DocumentOptions { _itemIndex?: number; // which item index the carousel viewer is showing _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) + "_carousel-caption-xMargin"?: number; + "_carousel-caption-yMargin"?: number; x?: number; y?: number; z?: number; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index bfb7134b2..08d49f8a5 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -64,8 +64,8 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) borderRadius: StrCast(this.layoutDoc._captionBorderRounding), }}>
; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index fbb27f9b7..3dbe90653 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -86,7 +86,7 @@ export class CurrentUserUtils { if (doc["template-button-detail"] === undefined) { const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create; - const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }); + const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, backgroundColor: "#9b9b9b3F" }); const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true }); const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true }); -- cgit v1.2.3-70-g09d2 From ae21e365e8ed34060bd94ee04d0d8b3a1f3479f3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 1 May 2020 16:08:35 -0400 Subject: added doubleClick open on right to detailView template for Buxton --- src/client/documents/Documents.ts | 1 + src/client/views/collections/CollectionCarouselView.tsx | 4 +++- src/server/authentication/models/current_user_utils.ts | 12 ++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 732f9303b..a8b10bc7b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -123,6 +123,7 @@ export interface DocumentOptions { borderRounding?: string; boxShadow?: string; dontRegisterChildren?: boolean; + "onDoubleClick-rawScript"?: string // onDoubleClick script in raw text form "onClick-rawScript"?: string; // onClick script in raw text form "onCheckedClick-rawScript"?: string; // onChecked script in raw text form "onCheckedClick-params"?: List; // parameter list for onChecked treeview functions diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 08d49f8a5..4086294ad 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { documentSchema } from '../../../new_fields/documentSchemas'; import { makeInterface } from '../../../new_fields/Schema'; -import { NumCast, StrCast } from '../../../new_fields/Types'; +import { NumCast, StrCast, ScriptCast } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import "./CollectionCarouselView.scss"; @@ -48,6 +48,8 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) <>
(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200 }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onCheckedClick], { title: "onClick funcs" }); + doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" }); } PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); -- cgit v1.2.3-70-g09d2 From 40d5c3acab6dbdc67f6d4bfd15c802da9fe08ca0 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 2 May 2020 00:17:21 -0400 Subject: cleaned up a lot of layoutTemplate/String props. fixed link drawing. --- src/client/documents/Documents.ts | 7 +- src/client/util/DocumentManager.ts | 2 +- src/client/views/DocComponent.tsx | 4 +- src/client/views/DocumentDecorations.scss | 24 +++--- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/MainView.scss | 6 +- .../views/collections/CollectionCarouselView.tsx | 11 +-- .../views/collections/CollectionStackingView.tsx | 29 ++++--- src/client/views/collections/CollectionSubView.tsx | 2 + .../views/collections/CollectionTimeView.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 8 +- src/client/views/collections/CollectionView.tsx | 14 +++- .../views/collections/CollectionViewChromes.tsx | 6 +- .../CollectionFreeFormLinkView.tsx | 2 +- .../CollectionFreeFormLinksView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +-- .../CollectionMulticolumnView.tsx | 5 +- .../CollectionMultirowView.tsx | 5 +- src/client/views/nodes/AudioBox.tsx | 4 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- .../views/nodes/ContentFittingDocumentView.tsx | 5 +- src/client/views/nodes/DocumentBox.tsx | 94 ++++++++++++++------- src/client/views/nodes/DocumentContentsView.tsx | 44 +++++----- src/client/views/nodes/DocumentView.tsx | 29 +++---- src/client/views/nodes/FieldView.tsx | 3 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/LinkAnchorBox.tsx | 1 - src/client/views/nodes/ScreenshotBox.tsx | 6 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/new_fields/Doc.ts | 2 +- src/new_fields/documentSchemas.ts | 95 ++++++++++++---------- .../authentication/models/current_user_utils.ts | 2 +- 32 files changed, 247 insertions(+), 195 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 434b26312..672f94f75 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -95,6 +95,7 @@ export interface DocumentOptions { forceActive?: boolean; layout?: string | Doc; // default layout string for a document childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) + childLayoutString?: string; // template string for collection to use to render its children hideFilterView?: boolean; // whether to hide the filter popout on collections hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template @@ -123,7 +124,7 @@ export interface DocumentOptions { displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) borderRounding?: string; boxShadow?: string; - dontRegisterChildren?: boolean; + dontRegisterChildViews?: boolean; "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form "onClick-rawScript"?: string; // onClick script in raw text form "onCheckedClick-rawScript"?: string; // onChecked script in raw text form @@ -592,9 +593,7 @@ export namespace Docs { linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode; linkDocProto.anchor2_timecode = target.doc.currentTimecode || target.doc.displayTimecode; - if (linkDocProto.layout_key1 === undefined) { - Cast(linkDocProto.proto, Doc, null).layout_key1 = LinkAnchorBox.LayoutString("anchor1"); - Cast(linkDocProto.proto, Doc, null).layout_key2 = LinkAnchorBox.LayoutString("anchor2"); + if (linkDocProto.linkBoxExcludedKeys === undefined) { Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]); Cast(linkDocProto.proto, Doc, null).layoutKey = undefined; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4683e77a8..1ba6f0248 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -117,7 +117,7 @@ export class DocumentManager { pairs.push(...linksList.reduce((pairs, link) => { const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) { + if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== docView1.props.LayoutTemplateString) { pairs.push({ a: dv, b: docView1, l: link }); } }); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 629b0f447..fd0d2bdbb 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -11,7 +11,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) interface DocComponentProps { Document: Doc; - LayoutDoc?: () => Opt; + LayoutTemplate?: () => Opt; } export function DocComponent

(schemaCtor: (doc: Doc) => T) { class Component extends Touchable

{ @@ -20,7 +20,7 @@ export function DocComponent

(schemaCtor: (doc: D // This is the "The Document" -- it encapsulates, data, layout, and any templates @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info - @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutDoc?.()); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { return this.props.Document[DataSym] as Doc; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 61d517d43..4f34eb9e3 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -44,7 +44,6 @@ $linkGap : 3px; .documentDecorations-radius { pointer-events: auto; - background: black; opacity: 1; transform: translate(10px, 10px); grid-row: 4; @@ -88,12 +87,12 @@ $linkGap : 3px; opacity: 1; } #documentDecorations-topLeftResizer { - border-left: black 2px solid; - border-top: black solid 2px; + border-left: 2px solid; + border-top: solid 2px; } #documentDecorations-bottomRightResizer { - border-right: black 2px solid; - border-bottom: black solid 2px; + border-right: 2px solid; + border-bottom: solid 2px; } #documentDecorations-topLeftResizer:hover, #documentDecorations-bottomRightResizer:hover { @@ -111,12 +110,12 @@ $linkGap : 3px; opacity: 1; } #documentDecorations-topRightResizer { - border-right: black 2px solid; - border-top: black 2px solid; + border-right: 2px solid; + border-top: 2px solid; } #documentDecorations-bottomLeftResizer { - border-left: black 2px solid; - border-bottom: black 2px solid; + border-left: 2px solid; + border-bottom: 2px solid; } #documentDecorations-topRightResizer:hover, #documentDecorations-bottomLeftResizer:hover { @@ -136,7 +135,6 @@ $linkGap : 3px; } .documentDecorations-contextMenu { - background: $alt-accent; width: 25px; height: calc(100% + 8px); // 8px for the height of the top resizer bar grid-column-start: 1; @@ -144,14 +142,14 @@ $linkGap : 3px; pointer-events: all; } .documentDecorations-title { - background: $alt-accent; opacity: 1; grid-column-start: 3; grid-column-end: 4; pointer-events: auto; overflow: hidden; text-align: center; - display:flex; + display: flex; + border-bottom: solid 1px; } .publishBox { width: 20px; @@ -168,7 +166,6 @@ $linkGap : 3px; .documentDecorations-closeButton { - background: $alt-accent; opacity: 1; grid-column-start: 4; grid-column-end: 6; @@ -178,7 +175,6 @@ $linkGap : 3px; } .documentDecorations-minimizeButton { - background: $alt-accent; opacity: 1; grid-column-start: 1; grid-column-end: 3; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e2759291a..396fe12b5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DataSym, Field } from "../../new_fields/Doc"; -import { PositionDocument } from '../../new_fields/documentSchemas'; +import { Document } from '../../new_fields/documentSchemas'; import { ScriptField } from '../../new_fields/ScriptField'; import { Cast, StrCast, NumCast } from "../../new_fields/Types"; import { Utils, setupMoveUpEvents, emptyFunction, returnFalse, simulateMouseClick } from "../../Utils"; @@ -301,7 +301,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = PositionDocument(element.rootDoc); + const doc = Document(element.rootDoc); let nwidth = doc._nativeWidth || 0; let nheight = doc._nativeHeight || 0; const width = (doc._width || 0); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 81d427f64..04288a9e1 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -39,7 +39,12 @@ } } +.mainView-container { + color:dimgray; +} + .mainView-container-dark { + color: lightgray; .lm_goldenlayout { background: dimgray; } @@ -54,7 +59,6 @@ } .contextMenu-cont, .contextMenu-item { background: dimGray; - color: lightgray; } .contextMenu-item:hover { background: gray; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4086294ad..769b323ae 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -2,9 +2,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observable, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { documentSchema } from '../../../new_fields/documentSchemas'; +import { documentSchema, collectionSchema } from '../../../new_fields/documentSchemas'; import { makeInterface } from '../../../new_fields/Schema'; -import { NumCast, StrCast, ScriptCast } from '../../../new_fields/Types'; +import { NumCast, StrCast, ScriptCast, Cast } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import "./CollectionCarouselView.scss"; @@ -16,8 +16,8 @@ import { ContextMenu } from '../ContextMenu'; import { ObjectField } from '../../../new_fields/ObjectField'; import { returnFalse } from '../../../Utils'; -type CarouselDocument = makeInterface<[typeof documentSchema,]>; -const CarouselDocument = makeInterface(documentSchema); +type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>; +const CarouselDocument = makeInterface(documentSchema, collectionSchema); @observer export class CollectionCarouselView extends CollectionSubView(CarouselDocument) { @@ -40,7 +40,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) e.stopPropagation(); this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } - panelHeight = () => this.props.PanelHeight() - 50; @computed get content() { const index = NumCast(this.layoutDoc._itemIndex); @@ -51,6 +50,8 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)} onClick={ScriptCast(this.layoutDoc.onChildClick)} renderDepth={this.props.renderDepth + 1} + LayoutTemplate={this.props.ChildLayoutTemplate} + LayoutTemplateString={this.props.ChildLayoutString} Document={this.childLayoutPairs[index].layout} DataDoc={this.childLayoutPairs[index].data} PanelHeight={this.panelHeight} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 01766f65f..eb70cec9d 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,15 +4,17 @@ import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import Switch from 'rc-switch'; -import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc"; +import { DataSym, Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; +import { listSpec, makeInterface } from "../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; -import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils"; +import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from "../../../Utils"; import { DragManager, dropActionType } from "../../util/DragManager"; +import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; @@ -24,11 +26,14 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; -import { SelectionManager } from "../../util/SelectionManager"; +import { ScriptField } from "../../../new_fields/ScriptField"; const _global = (window /* browser */ || global /* node */) as any; +type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; +const StackingDocument = makeInterface(collectionSchema, documentSchema); + @observer -export class CollectionStackingView extends CollectionSubView(doc => doc) { +export class CollectionStackingView extends CollectionSubView(StackingDocument) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef(); _pivotFieldDisposer?: IReactionDisposer; @@ -116,7 +121,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { getSimpleDocHeight(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.()); const nw = NumCast(layoutDoc._nativeWidth); const nh = NumCast(layoutDoc._nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); @@ -160,14 +165,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } return this.props.addDocTab(doc, where); } + getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { - const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.()); + const layoutDoc = Doc.Layout(doc, this.props.ChildLayoutTemplate?.()); const height = () => this.getDocHeight(doc); return doc) { PanelHeight={height} NativeHeight={returnZero} NativeWidth={returnZero} - fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + fitToBox={false} rootSelected={this.rootSelected} dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} @@ -199,13 +206,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { getDocWidth(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.()); const nw = NumCast(layoutDoc._nativeWidth); return Math.min(nw && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); } getDocHeight(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const layoutDoc = Doc.Layout(d, this.props.ChildLayoutTemplate?.()); const nw = NumCast(layoutDoc._nativeWidth); const nh = NumCast(layoutDoc._nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e44bbae78..aaea13ded 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -43,6 +43,8 @@ export interface CollectionViewProps extends FieldViewProps { export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: Opt; children?: never | (() => JSX.Element[]) | React.ReactNode; + ChildLayoutTemplate?: () => Doc; + ChildLayoutString?: string; childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 045134225..a2d4774c8 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -28,7 +28,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @observable _childClickedScript: Opt; @observable _viewDefDivClick: Opt; async componentDidMount() { - const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), ""); + const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), ""); const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! }); @@ -84,7 +84,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @computed get contents() { return

- +
; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 296c1a39c..dc9348664 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -6,7 +6,6 @@ import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField } from '../../../new_fields/RichTextField'; import { Document, listSpec } from '../../../new_fields/Schema'; import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types'; @@ -15,7 +14,6 @@ import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; -import { makeTemplate } from '../../util/DropConverter'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; @@ -481,7 +479,7 @@ class TreeView extends React.Component { parentActive={returnTrue} whenActiveChanged={emptyFunction} bringToFront={emptyFunction} - dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)} + dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildViews)} ContainingCollectionView={undefined} ContainingCollectionDoc={this.props.containingCollection} />} @@ -743,11 +741,11 @@ export class CollectionTreeView extends CollectionSubView boolean; + filterAddDocument: (doc: Doc) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + childLayoutTemplate?: () => Opt; // specify a layout Doc template to use for children of the collection } export interface CollectionRenderProps { @@ -82,6 +83,8 @@ export interface CollectionRenderProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; PanelWidth: () => number; + ChildLayoutTemplate?: () => Doc; + ChildLayoutString?: string; } @observer @@ -245,8 +248,8 @@ export class CollectionView extends Touchable this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" }); } - if (this.props.Document.childDetailView instanceof Doc) { - layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailView as Doc, "onRight"), icon: "project-diagram" }); + if (this.props.Document.childClickedOpenTemplateView instanceof Doc) { + layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" }); } layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); @@ -474,6 +477,7 @@ export class CollectionView extends Touchable
; } + childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); render() { TraceMobx(); @@ -483,7 +487,9 @@ export class CollectionView extends Touchable click item view", - script: "this.target.childDetailView = getDocTemplate(this.source?.[0])", - immediate: (source: Doc[]) => this.target.childDetailView = Doc.getDocTemplate(source?.[0]), + params: ["target", "source"], title: "=> click clicked open view", + script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])", + immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]), initialize: emptyFunction, }; _contentCommand = { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index cf12ef382..d67d1993e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -40,7 +40,7 @@ export class CollectionFreeFormLinkView extends React.Component - c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK && + c.a.props.Document.type === DocumentType.LINK && c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed ).map(c => ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 45ef0455e..707e103fb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; -import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; +import { documentSchema, collectionSchema } from "../../../../new_fields/documentSchemas"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkData, InkField, InkTool, PointData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; @@ -66,8 +66,8 @@ export const panZoomSchema = createSchema({ fitH: "number" }); -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema); export type collectionFreeformViewProps = { forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; @@ -858,7 +858,6 @@ export class CollectionFreeFormView extends CollectionSubView BoolCast(this.Document.useClusters); parentActive = () => this.props.active() || this.backgroundActive ? true : false; - childLayoutFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, @@ -868,7 +867,8 @@ export class CollectionFreeFormView extends CollectionSubView(PositionDocument) { +export class CollectionFreeFormDocumentView extends DocComponent(Document) { @observable _animPos: number[] | undefined = undefined; random(min: number, max: number) { // min should not be equal to max const mseed = Math.abs(this.X * this.Y); diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 637fd5acc..3c2c6c87e 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -14,7 +14,7 @@ import "./ContentFittingDocumentView.scss"; @observer export class ContentFittingDocumentView extends React.Component{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive - private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); } + private get layoutDoc() { return this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document); } @computed get freezeDimensions() { return this.props.FreezeDimensions; } nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); @@ -56,7 +56,8 @@ export class ContentFittingDocumentView extends React.Component; -const DocHolderBoxDocument = makeInterface(documentSchema); +type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>; +const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema); @observer export class DocHolderBox extends ViewBoxAnnotatableComponent(DocHolderBoxDocument) { @@ -43,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent this.toggleLockSelection, icon: "expand-arrows-alt" }); funcs.push({ description: (this.layoutDoc.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.layoutDoc.excludeCollections = !this.layoutDoc.excludeCollections, icon: "expand-arrows-alt" }); funcs.push({ description: `${this.layoutDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.layoutDoc.forceActive = !this.layoutDoc.forceActive, icon: "project-diagram" }); - funcs.push({ description: `Show ${this.layoutDoc.childTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childTemplateName = this.layoutDoc.childTemplateName ? undefined : "keyValue", icon: "project-diagram" }); + funcs.push({ description: `Show ${this.layoutDoc.childLayoutTemplateName !== "keyValue" ? "key values" : "contents"}`, event: () => this.layoutDoc.childLayoutString = this.layoutDoc.childLayoutString ? undefined : "", icon: "project-diagram" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } @@ -103,6 +105,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent this.props.PanelWidth() - 2 * this.xPad; pheight = () => this.props.PanelHeight() - 2 * this.yPad; getTransform = () => this.props.ScreenToLocalTransform().translate(-this.xPad, -this.yPad); + isActive = () => this.active() || !this.props.renderDepth; get renderContents() { const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); const childTemplateName = StrCast(this.layoutDoc.childTemplateName); @@ -112,33 +115,62 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent; + const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString ? + : + ; return contents; } render() { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 4d20d3e2c..749fb98be 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -96,8 +96,6 @@ export class DocumentContentsView extends React.Component boolean, select: (ctrl: boolean) => void, layoutKey: string, - forceLayout?: string, - forceFieldKey?: string, hideOnLeave?: boolean, makeLink?: () => Opt, // function to call when a link is made }> { @@ -105,6 +103,7 @@ export class DocumentContentsView extends React.Componentawaiting layout

"; // const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string"); // bcz: replaced this with below... is it right? + if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString; const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string"); if (this.props.layoutKey === "layout_keyValue") { return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data")); @@ -127,8 +126,8 @@ export class DocumentContentsView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) : - this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ? - - : - { console.log(test); }} - />; + { console.log(test); }} + />; } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f1efa48f4..7c7c03db2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -5,22 +5,21 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; -import { Document, PositionDocument } from '../../../new_fields/documentSchemas'; +import { Document } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; import { InkTool } from '../../../new_fields/InkField'; -import { RichTextField } from '../../../new_fields/RichTextField'; import { listSpec } from "../../../new_fields/Schema"; import { SchemaHeaderField } from '../../../new_fields/SchemaHeaderField'; import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField'; +import { ImageField } from '../../../new_fields/URLField'; import { TraceMobx } from '../../../new_fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { ClientRecommender } from '../../ClientRecommender'; import { DocServer } from "../../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; +import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DocumentManager } from "../../util/DocumentManager"; @@ -42,6 +41,7 @@ import { InkingControl } from '../InkingControl'; import { KeyphraseQueryView } from '../KeyphraseQueryView'; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; +import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); @@ -58,7 +58,8 @@ export interface DocumentViewProps { NativeHeight: () => number; Document: Doc; DataDoc?: Doc; - LayoutDoc?: () => Opt; + LayoutTemplateString?: string; + LayoutTemplate?: () => Opt; LibraryPath: Doc[]; fitToBox?: boolean; contextMenuItems?: () => { script: ScriptField, label: string }[]; @@ -454,8 +455,8 @@ export class DocumentView extends DocComponent(Docu const dY = -1 * Math.sign(dH); if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = PositionDocument(this.props.Document); - const layoutDoc = PositionDocument(Doc.Layout(this.props.Document)); + const doc = Document(this.props.Document); + const layoutDoc = Document(Doc.Layout(this.props.Document)); let nwidth = layoutDoc._nativeWidth || 0; let nheight = layoutDoc._nativeHeight || 0; const width = (layoutDoc._width || 0); @@ -984,13 +985,15 @@ export class DocumentView extends DocComponent(Docu @computed get contents() { TraceMobx(); return (<> - (Docu ); } - linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); // used to decide whether a link anchor view should be created or not. // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. @@ -1049,12 +1051,12 @@ export class DocumentView extends DocComponent(Docu ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox PanelWidth={this.anchorPanelWidth} PanelHeight={this.anchorPanelHeight} - layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne} backgroundColor={returnTransparent} removeDocument={this.hideLinkAnchor} pointerEvents={false} - LayoutDoc={undefined} + LayoutTemplate={undefined} + LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />); } @computed get innards() { @@ -1073,8 +1075,7 @@ export class DocumentView extends DocComponent(Docu
`} ContentScaling={this.childScaling} ChromeHeight={this.chromeHeight} isSelected={this.isSelected} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 1efee4f5a..40d55ce38 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,13 +50,12 @@ export interface FieldViewProps { setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; - childLayoutTemplate?: () => Opt; + RenderData?: () => Doc; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) height?: number; width?: number; background?: string; color?: string; - RenderData?: () => Doc; } @observer diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 2970674a2..d43936949 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -36,7 +36,7 @@ export class KeyValueBox extends React.Component { @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } - get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; } + get fieldDocToLayout() { return this.props.Document; } @action onEnterKey = (e: React.KeyboardEvent): void => { diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 6c50abf21..bc36e056e 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -17,7 +17,6 @@ import { LinkEditor } from "../linking/LinkEditor"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SelectionManager } from "../../util/SelectionManager"; import { TraceMobx } from "../../../new_fields/util"; -import { DocumentView } from "./DocumentView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 125690dc7..a0ecc9ff5 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; -import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; +import { documentSchema } from "../../../new_fields/documentSchemas"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; @@ -20,8 +20,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./ScreenshotBox.scss"; const path = require('path'); -type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; -const ScreenshotDocument = makeInterface(documentSchema, positionSchema); +type ScreenshotDocument = makeInterface<[typeof documentSchema]>; +const ScreenshotDocument = makeInterface(documentSchema); library.add(faVideo); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 613929bca..266b7f43f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -21,14 +21,14 @@ import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; +import { documentSchema } from "../../../new_fields/documentSchemas"; const path = require('path'); export const timeSchema = createSchema({ currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first }); -type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; -const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); +type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>; +const VideoDocument = makeInterface(documentSchema, timeSchema); library.add(faVideo); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 71325d94f..9256f82c2 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -772,7 +772,7 @@ export namespace Doc { export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; } - export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layout_key1" : "layout_key2"; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index fd9a304f9..e71fc27f3 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -4,13 +4,25 @@ import { Doc } from "./Doc"; import { DateField } from "./DateField"; export const documentSchema = createSchema({ + // content properties type: "string", // enumerated type of document -- should be template-specific (ie, start with an '_') - layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below - layoutKey: "string", // holds the field key for the field that actually holds the current lyoat title: "string", // document title (can be on either data document or layout) - dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move") - targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") - childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") + isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field + creationDate: DateField, // when the document was created + links: listSpec(Doc), // computed (readonly) list of links associated with this document + + // "Location" properties in a very general sense + currentTimecode: "number", // current play back time of a temporal document (video / audio) + displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + x: "number", // x coordinate when in a freeform view + y: "number", // y coordinate when in a freeform view + z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview + zIndex: "number", // zIndex of a document in a freeform view + scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) + scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) + + // appearance properties on the layout _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set _nativeHeight: "number", // " @@ -27,59 +39,57 @@ export const documentSchema = createSchema({ _showAudio: "boolean", // whether to show the audio record icon on documents _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents _LODdisable: "boolean", // whether to disbale LOD switching for CollectionFreeFormViews - _pivotField: "string", // specifies which field should be used as the timeline/pivot axis + _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis _replacedChrome: "string", // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's. _chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed' - _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle) _fontSize: "number", _fontFamily: "string", - isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations - color: "string", // foreground color of document + + // appearance properties on the data document backgroundColor: "string", // background color of document + borderRounding: "string", // border radius rounding of document + boxShadow: "string", // the amount of shadow around the perimeter of a document + color: "string", // foreground color of document + fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view + fontSize: "string", + layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + letterSpacing: "string", opacity: "number", // opacity of document - creationDate: DateField, // when the document was created - links: listSpec(Doc), // computed (readonly) list of links associated with this document + strokeWidth: "number", + textTransform: "string", + treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden + treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree + treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) + + // interaction and linking properties + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. - removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped - isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field - isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee) - treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden - treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree - treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) - currentTimecode: "number", // current play back time of a temporal document (video / audio) followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) + isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations + isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked + isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee) lockedPosition: "boolean", // whether the document can be moved (dragged) lockedTransform: "boolean", // whether the document can be panned/zoomed - inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently - borderRounding: "string", // border radius rounding of document - heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) - isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked - ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) - scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. - scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that ) - scrollTop: "number", // scroll position of a scrollable document (pdf, text, web) - strokeWidth: "number", - fontSize: "string", - fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view - letterSpacing: "string", - textTransform: "string", - childTemplateName: "string" // the name of a template to use to override the layoutKey when rendering a document in DocHolderBox -}); -export const positionSchema = createSchema({ - zIndex: "number", - x: "number", - y: "number", - z: "number", + // drag drop properties + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. + dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move") + targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") + childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped }); + export const collectionSchema = createSchema({ - childLayoutTemplate: Doc, // layout template for children of a collecion - childDetailView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field) + childLayoutTemplateName: "string", // the name of a template to use to override the layoutKey when rendering a document -- ONLY used in DocHolderBox + childLayoutTemplate: Doc, // layout template to use to render children of a collecion + childLayoutString: "string", //layout string to use to render children of a collection + childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template) + dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. onChildClick: ScriptField, // script to run for each child when its clicked onChildDoubleClick: ScriptField, // script to run for each child when its clicked onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view @@ -87,6 +97,3 @@ export const collectionSchema = createSchema({ export type Document = makeInterface<[typeof documentSchema]>; export const Document = makeInterface(documentSchema); - -export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; -export const PositionDocument = makeInterface(documentSchema, positionSchema); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 2ae42bf52..b9de93559 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -485,7 +485,7 @@ export class CurrentUserUtils { _width: 50, _height: 25, title: "Library", _fontSize: 10, letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], { - title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true + title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true })) as any as Doc, targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc, onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") -- cgit v1.2.3-70-g09d2 From 100ad0da00f2a5cea13508abc0c3a8c644095d65 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 2 May 2020 14:31:16 -0400 Subject: turn off targetDropAction when dropping in same colleciton. cleaned up PresBox stuff to use single template to render all contents (which are otherwise unmodified). --- src/client/util/DragManager.ts | 4 +- .../views/collections/CollectionCarouselView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 1 - src/client/views/collections/CollectionSubView.tsx | 11 +++- src/client/views/collections/CollectionView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 2 - src/client/views/nodes/FieldView.tsx | 3 +- src/client/views/nodes/PresBox.tsx | 72 ++++++++++++---------- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +- .../views/presentationview/PresElementBox.tsx | 16 ++--- src/new_fields/documentSchemas.ts | 2 +- .../authentication/models/current_user_utils.ts | 7 +-- 13 files changed, 71 insertions(+), 60 deletions(-) (limited to 'src/server/authentication/models/current_user_utils.ts') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c06ad3d60..041f2fc2c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -185,7 +185,8 @@ export namespace DragManager { export function MakeDropTarget( element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, - doc?: Doc + doc?: Doc, + preDropFunc?: (e: Event, de: DropEvent) => void, ): DragDropDisposer { if ("canDrop" in element.dataset) { throw new Error( @@ -199,6 +200,7 @@ export namespace DragManager { if (de.complete.docDragData && doc?.targetDropAction) { de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType; } + preDropFunc?.(e, de); }; element.addEventListener("dashOnDrop", handler); doc && element.addEventListener("dashPreDrop", preDropHandler); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 769b323ae..a04136e51 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -69,7 +69,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) + Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"} />
; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index eb70cec9d..1fd5c3f44 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -178,7 +178,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) LibraryPath={this.props.LibraryPath} FreezeDimensions={this.props.freezeChildDimensions} renderDepth={this.props.renderDepth + 1} - RenderData={this.props.RenderData} PanelWidth={width} PanelHeight={height} NativeHeight={returnZero} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index aaea13ded..af642bc52 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -67,7 +67,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: this.multiTouchDisposer?.(); if (ele) { this._mainCont = ele; - this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } @@ -195,6 +195,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { } + protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) { + if (de.complete.docDragData) { + if (de.complete.docDragData.draggedDocuments.some(d => this.childDocs.includes(d))) { + de.complete.docDragData.dropAction = "move"; + } + e.stopPropagation(); + } + } + @undoBatch @action protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index d2afb4855..cb7d86e00 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -74,6 +74,7 @@ export enum CollectionViewType { export interface CollectionViewCustomProps { filterAddDocument: (doc: Doc) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) childLayoutTemplate?: () => Opt; // specify a layout Doc template to use for children of the collection + childLayoutString?: string; // specify a layout string to use for children of the collection } export interface CollectionRenderProps { @@ -478,6 +479,7 @@ export class CollectionView extends Touchable; } childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); + childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString); render() { TraceMobx(); @@ -489,7 +491,7 @@ export class CollectionView extends Touchable void; renderDepth: number; ContentScaling: () => number; - RenderData?: () => Doc; PanelWidth: () => number; PanelHeight: () => number; pointerEvents?: boolean; @@ -996,7 +995,6 @@ export class DocumentView extends DocComponent(Docu LayoutTemplate={this.props.LayoutTemplate} makeLink={this.makeLink} rootSelected={this.rootSelected} - RenderData={this.props.RenderData} dontRegisterView={this.props.dontRegisterView} fitToBox={this.props.fitToBox} LibraryPath={this.props.LibraryPath} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 40d55ce38..016d2a1ae 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,12 +50,13 @@ export interface FieldViewProps { setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; - RenderData?: () => Doc; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) height?: number; width?: number; background?: string; color?: string; + xMargin?: number; + yMargin?: number; } @observer diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 6e3420f22..53b6aa408 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -17,6 +17,8 @@ import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; import { List } from "../../../new_fields/List"; +import { Docs } from "../../documents/Documents"; +import { PrefetchProxy } from "../../../new_fields/Proxy"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -24,14 +26,32 @@ const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + _docsDisposer: IReactionDisposer | undefined; + _viewDisposer: IReactionDisposer | undefined; @observable _isChildActive = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } - @computed get currentIndex() { return NumCast(this.rootDoc._itemIndex); } + @computed get currentIndex() { return NumCast(this.presElement?.currentIndex); } + @computed get presElement() { return Cast(this.rootDoc.presElement, Doc, null); } + constructor(props: any) { + super(props); + if (!this.presElement) { + this.rootDoc.presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ + title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" + })); + } + } + + componentWillUnmount() { + this._docsDisposer?.(); + this._viewDisposer?.(); + } componentDidMount() { this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; + this._docsDisposer = reaction(() => this.childDocs, docs => this.presElement.presOrderedDocs = new List(docs), { fireImmediately: true }); + this._viewDisposer = reaction(() => this.rootDoc._viewType, viewType => this.presElement.presCollapsedHeight = viewType === CollectionViewType.Tree ? 50 : 46, { fireImmediately: true }); } updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc; @@ -166,17 +186,16 @@ export class PresBox extends ViewBoxBaseComponent } } - //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. public gotoDocument = (index: number, fromDoc: number) => { this.updateCurrentPresentation(); Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { - this.rootDoc._itemIndex = index; + this.presElement.currentIndex = index; - if (!this.layoutDoc.presStatus) { - this.layoutDoc.presStatus = true; + if (!this.presElement.presStatus) { + this.presElement.presStatus = true; this.startPresentation(index); } @@ -189,10 +208,10 @@ export class PresBox extends ViewBoxBaseComponent //The function that starts or resets presentaton functionally, depending on status flag. startOrResetPres = () => { this.updateCurrentPresentation(); - if (this.layoutDoc.presStatus) { + if (this.presElement.presStatus) { this.resetPresentation(); } else { - this.layoutDoc.presStatus = true; + this.presElement.presStatus = true; this.startPresentation(0); this.gotoDocument(0, this.currentIndex); } @@ -204,7 +223,7 @@ export class PresBox extends ViewBoxBaseComponent this.updateCurrentPresentation(); this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1); this.rootDoc._itemIndex = 0; - this.layoutDoc.presStatus = false; + this.presElement.presStatus = false; } //The function that starts the presentation, also checking if actions should be applied @@ -241,43 +260,31 @@ export class PresBox extends ViewBoxBaseComponent } }); - initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => { - const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; - this.rootDoc.presCollapsedHeight = hgt; - } + @undoBatch + viewChanged = action((e: React.ChangeEvent) => { + //@ts-ignore + const viewType = e.target.selectedOptions[0].value as CollectionViewType; + viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here + this.updateMinimize(e, this.rootDoc._viewType = viewType); + }); + whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); addDocumentFilter = (doc: Doc) => { - doc.presentationTargetDoc = doc.aliasOf; + doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); + !this.childDocs.includes(doc) && (doc.presZoomButton = true); return true; } - + childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); - selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.rootDoc._itemIndex)); - getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight - panelHeight = () => this.props.PanelHeight() - 20; - active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) - whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); - - @undoBatch - viewChanged = action((e: React.ChangeEvent) => { - //@ts-ignore - const viewType = e.target.selectedOptions[0].value as CollectionViewType; - viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - this.updateMinimize(e, this.rootDoc._viewType = viewType); - }); - - returnSelf = () => this.rootDoc; - childLayoutTemplate = () => this.rootDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc()["template-presentation"], Doc, null) : undefined; render() { this.rootDoc.presOrderedDocs = new List(this.childDocs.map((child, i) => child)); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - this.initializeViewAliases(this.childDocs, mode); return