From d9227908ba8e8db5084c468a242b2839ab11a33d Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 4 May 2020 12:50:46 -0400 Subject: fixed links and snap lines broken by moving things into DragManager. --- src/client/views/nodes/DocHolderBox.scss | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/client/views/nodes/DocHolderBox.scss (limited to 'src/client/views/nodes/DocHolderBox.scss') diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss new file mode 100644 index 000000000..3a27c16c1 --- /dev/null +++ b/src/client/views/nodes/DocHolderBox.scss @@ -0,0 +1,15 @@ +.documentBox-container { + width: 100%; + height: 100%; + pointer-events: all; + background: rgb(241, 239, 235); + position: absolute; + .documentBox-lock { + margin: auto; + color: white; + position: absolute; + } + .contentFittingDocumentView { + position: absolute; + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From a67dc240024ccb329f2509c70556a7b78ca2c4ab Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 5 May 2020 20:22:49 -0400 Subject: fixed text selection in docHolder boxes by changing formattedTextBox not to stopPropagation on pointerDowns. added a googleDoc field on documents to open the same Dash-googleDoc doc each time. --- src/client/views/DocumentButtonBar.tsx | 13 +- src/client/views/nodes/DocHolderBox.scss | 1 + src/client/views/nodes/DocHolderBox.tsx | 145 +++++++++++---------- src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/FieldView.tsx | 1 + .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- 6 files changed, 89 insertions(+), 77 deletions(-) (limited to 'src/client/views/nodes/DocHolderBox.scss') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 7ba47deb5..db566ee48 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; -import { NumCast, StrCast } from "../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../new_fields/Types"; import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { UndoManager } from "../util/UndoManager"; @@ -170,11 +170,16 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView | style={{ backgroundColor: this.pullColor }} onPointerEnter={e => (e.altKey || e.shiftKey) && runInAction(() => this.openHover = true)} onPointerLeave={action(() => this.openHover = false)} - onClick={e => { + onClick={async (e: React.MouseEvent) => { if (e.shiftKey) { e.preventDefault(); - CollectionDockingView.AddRightSplit(Docs.Create.WebDocument(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`, - { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false })); + let googleDoc = await Cast(dataDoc.googleDoc, Doc); + if (!googleDoc) { + googleDoc = Docs.Create.WebDocument(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`, + { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false }); + dataDoc.googleDoc = googleDoc; + } + CollectionDockingView.AddRightSplit(googleDoc); } else if (e.altKey) { e.preventDefault(); window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`); diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss index 3a27c16c1..4949d16a3 100644 --- a/src/client/views/nodes/DocHolderBox.scss +++ b/src/client/views/nodes/DocHolderBox.scss @@ -8,6 +8,7 @@ margin: auto; color: white; position: absolute; + padding: 3px; } .contentFittingDocumentView { position: absolute; diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 9787877cc..17b2daabc 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -3,7 +3,7 @@ import { action, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, Field } from "../../../new_fields/Doc"; import { collectionSchema, documentSchema } from "../../../new_fields/documentSchemas"; -import { makeInterface } from "../../../new_fields/Schema"; +import { makeInterface, listSpec } from "../../../new_fields/Schema"; import { ComputedField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { TraceMobx } from "../../../new_fields/util"; @@ -32,7 +32,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent(); _curSelection = -1; componentDidMount() { - this._prevSelectionDisposer = reaction(() => this.layoutDoc[this.props.fieldKey], (data) => { + this._prevSelectionDisposer = reaction(() => this.dataDoc[this.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); @@ -53,13 +53,13 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { - this.layoutDoc[this.props.fieldKey] = this.layoutDoc[this.props.fieldKey]; + this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey]; } showSelection = () => { - this.layoutDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); + this.dataDoc[this.fieldKey] = ComputedField.MakeFunction(`selectedDocs(self,this.excludeCollections,[_last_])?.[0]`); } isSelectionLocked = () => { - const kvpstring = Field.toKeyValueString(this.layoutDoc, this.props.fieldKey); + const kvpstring = Field.toKeyValueString(this.dataDoc, this.fieldKey); return !kvpstring || kvpstring.includes("DOC"); } toggleLockSelection = () => { @@ -69,13 +69,13 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { this.lockSelection(); if (this._curSelection > 0) { - this.layoutDoc[this.props.fieldKey] = this._selections[--this._curSelection]; + this.dataDoc[this.fieldKey] = this._selections[--this._curSelection]; return true; } } nextSelection = () => { if (this._curSelection < this._selections.length - 1 && this._selections.length) { - this.layoutDoc[this.props.fieldKey] = this._selections[++this._curSelection]; + this.dataDoc[this.fieldKey] = this._selections[++this._curSelection]; return true; } } @@ -89,8 +89,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { let hitWidget: boolean | undefined = false; if (this._contRef.current!.getBoundingClientRect().top + this.yPad > e.clientY) hitWidget = (() => { this.props.select(false); return true; })(); @@ -107,67 +107,70 @@ 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; - layoutTemplateDoc = () => Cast(this.props.Document.childLayoutTemplate, Doc, null); + isActive = (outsideReaction: boolean) => this.active(outsideReaction) || this.props.renderDepth <= 1; + layoutTemplateDoc = () => Cast(this.layoutDoc.childLayoutTemplate, Doc, null); get renderContents() { - const containedDoc = Cast(this.dataDoc[this.props.fieldKey], Doc, null); + const containedDoc = Cast(this.dataDoc[this.fieldKey], Doc, null); const layoutTemplate = StrCast(this.layoutDoc.childLayoutString); - const contents = !(containedDoc instanceof Doc) ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? - : - ; + const contents = !(containedDoc instanceof Doc) || + Cast(containedDoc[Doc.LayoutFieldKey(containedDoc)], listSpec(Doc), null)?.includes(this.rootDoc) + ? (null) : this.layoutDoc.childLayoutString || this.layoutTemplateDoc() ? + : + ; return contents; } render() { @@ -183,7 +186,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent {this.renderContents}
+ style={{ marginTop: - this.yPad, background: "black" }}>
; @@ -195,13 +198,13 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent { this._dropDisposer?.(); - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.rootDoc)); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5a2ad8c88..2d5d7d231 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -63,6 +63,7 @@ export interface DocumentViewProps { LayoutTemplate?: () => Opt; LibraryPath: Doc[]; fitToBox?: boolean; + ignoreAutoHeight?: boolean; contextMenuItems?: () => { script: ScriptField, label: string }[]; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected onClick?: ScriptField; @@ -1009,6 +1010,7 @@ export class DocumentView extends DocComponent(Docu renderDepth={this.props.renderDepth} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} + ignoreAutoHeight={this.props.ignoreAutoHeight} focus={this.props.focus} parentActive={this.props.parentActive} whenActiveChanged={this.props.whenActiveChanged} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 016d2a1ae..7db634e74 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -43,6 +43,7 @@ export interface FieldViewProps { whenActiveChanged: (isActive: boolean) => void; dontRegisterView?: boolean; focus: (doc: Doc) => void; + ignoreAutoHeight?: boolean; PanelWidth: () => number; PanelHeight: () => number; NativeHeight: () => number; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ce096f81b..5dc22d6f6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -985,7 +985,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } 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(); + //e.stopPropagation(); } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1217,7 +1217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action tryUpdateHeight(limitHeight?: number) { let scrollHeight = this._ref.current?.scrollHeight; - if (this.layoutDoc._autoHeight && scrollHeight && + if (this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && 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; -- cgit v1.2.3-70-g09d2 From fa826d828b0fc20afde675ffb060e4f24ca310d3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 6 May 2020 02:01:08 -0400 Subject: fixed makeClone to handle text hyperlinks fixed documentBox background color. fixed formattedtextbox sidebar clicking to sto propagation. --- src/client/views/MainView.tsx | 4 +- src/client/views/nodes/DocHolderBox.scss | 1 - src/client/views/nodes/DocHolderBox.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 123 +++++++-------------- src/new_fields/Doc.ts | 72 +++++++----- src/new_fields/documentSchemas.ts | 1 + 6 files changed, 92 insertions(+), 114 deletions(-) (limited to 'src/client/views/nodes/DocHolderBox.scss') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1a285d4ec..e67f2f3a2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -300,7 +300,7 @@ export class MainView extends React.Component { defaultBackgroundColors = (doc: Doc) => { if (this.darkScheme) { - switch (doc.type) { + switch (doc?.type) { case DocumentType.RTF || DocumentType.LABEL || DocumentType.BUTTON: return "#2d2d2d"; case DocumentType.LINK: case DocumentType.COL: { @@ -309,7 +309,7 @@ export class MainView extends React.Component { default: return "black"; } } else { - switch (doc.type) { + switch (doc?.type) { case DocumentType.RTF: return "#f1efeb"; case DocumentType.BUTTON: case DocumentType.LABEL: return "lightgray"; diff --git a/src/client/views/nodes/DocHolderBox.scss b/src/client/views/nodes/DocHolderBox.scss index 4949d16a3..6a9ef0b6f 100644 --- a/src/client/views/nodes/DocHolderBox.scss +++ b/src/client/views/nodes/DocHolderBox.scss @@ -2,7 +2,6 @@ width: 100%; height: 100%; pointer-events: all; - background: rgb(241, 239, 235); position: absolute; .documentBox-lock { margin: auto; diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 17b2daabc..af8dc704c 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -122,6 +122,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent; 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; @@ -161,7 +158,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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); + else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id); }); }); }); @@ -199,7 +196,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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); // the actual text in the text box + const curTemp = Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField); // the actual text in the text box const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); @@ -207,7 +204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = true; this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (curText !== curLayout?.Text) { + if (json !== curLayout?.Data) { this.dataDoc[this.props.fieldKey] = new RichTextField(json, 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 } @@ -241,7 +238,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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 alink = DocUtils.MakeLink({ doc: this.rootDoc }, { 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] @@ -280,7 +277,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp protected createDropTarget = (ele: HTMLDivElement) => { this.ProseRef = ele; this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); } @undoBatch @@ -399,26 +396,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } 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 + setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, + () => (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")); } - sidebarMove = (e: PointerEvent) => { + sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { 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); + this.layoutDoc._sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; + return false; } - 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); } @@ -426,12 +412,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const cm = ContextMenu.Instance; 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: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); !this.layoutDoc.isTemplateDoc && funcs.push({ description: "Make Template", event: () => { this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document); + Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); this.layoutDoc.isTemplateDoc && funcs.push({ @@ -451,9 +437,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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" }); + funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); const highlighting: ContextMenuProps[] = []; @@ -480,7 +466,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DocListCast(noteTypesDoc?.data).forEach(note => { changeItems.push({ description: StrCast(note.title), event: undoBatch(() => { - Doc.setNativeView(this.props.Document); + Doc.setNativeView(this.rootDoc); Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); }), icon: "eye" }); @@ -517,7 +503,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action toggleMenubar = () => { - this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled"; + this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled"; } recordBullet = async () => { @@ -630,10 +616,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ); this._disposers.editorState = reaction( () => { - if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { + if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[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; + return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data; }, incomingValue => { if (incomingValue !== undefined && this._editorView && !this._applyingChange) { @@ -690,7 +676,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const nodes: Node[] = []; frag.forEach((node, index) => { const examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { + if (examinedNode?.textContent) { nodes.push(examinedNode); start += index; } @@ -707,7 +693,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; }; - let start = -1; + let start = 0; if (this._editorView && scrollToLinkID) { const editor = this._editorView; const ret = findLinkFrag(editor.state.doc.content, editor); @@ -728,7 +714,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); - this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), + this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos), pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } ); @@ -849,7 +835,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp targetAnnotations?.push(pdfRegion); }); - const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted"); + const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted"); if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); const linkId = link[Id]; @@ -886,8 +872,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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); + const useTemplate = !curText?.Text && this.layoutDoc[this.props.fieldKey + "-textTemplate"]; + const rtfField = Cast((useTemplate && this.layoutDoc[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { const self = this; this._editorView?.destroy(); @@ -983,9 +969,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp 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 === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar + e.stopPropagation(); // if the text box is selected, then it consumes all down events } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1023,7 +1009,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // jump rich text menu to this textbox const bounds = this._ref.current?.getBoundingClientRect(); - if (bounds && this.props.Document._chromeStatus !== "disabled") { + if (bounds && this.layoutDoc._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) { @@ -1059,43 +1045,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } 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); } + if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events + e.stopPropagation(); + } } // 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. @@ -1212,7 +1169,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onscrolled = (ev: React.UIEvent) => { - this.props.Document.scrollPos = this._scrollRef.current!.scrollTop; + this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop; } @action tryUpdateHeight(limitHeight?: number) { @@ -1243,7 +1200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._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")); } @@ -1306,8 +1263,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} /> - {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? -
this.toggleSidebar()} /> : + {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? +
:
-
this.toggleSidebar()} /> +
} - {!this.props.Document._showAudio ? (null) : + {!this.layoutDoc._showAudio ? (null) :
{ runInAction(() => this._recording = !this._recording); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index b41007e9c..2d4a4539e 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; import { UndoManager } from "../client/util/UndoManager"; -import { intersectRect } from "../Utils"; +import { intersectRect, Utils } from "../Utils"; import { HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update, Copy } from "./FieldSymbols"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; @@ -20,6 +20,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u import { Docs, DocumentOptions } from "../client/documents/Documents"; import { PdfField, VideoField, AudioField, ImageField } from "./URLField"; import { LinkManager } from "../client/util/LinkManager"; +import { string32 } from "../../deploy/assets/pdf.worker"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -590,47 +591,64 @@ export namespace Doc { return copy; } - export function MakeClone(doc: Doc, cloneMap: Map = new Map()): Doc { + export function MakeClone(doc: Doc): Doc { + let cloneMap = new Map(); + let rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; + const copy = Doc.makeClone(doc, cloneMap, rtfMap); + rtfMap.map(({ copy, key, field }) => { + const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + }; + const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { + const mapped = cloneMap.get(id); + return href + (mapped ? mapped[Id] : id); + }; + const regex = `(${Utils.prepend("/doc/")})([^"]*)`; + var re = new RegExp(regex, "g"); + copy[key] = new RichTextField(field.Data.replace(/("docid":|"targetId":|"linkId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); + }); + return copy; + } + + export function makeClone(doc: Doc, cloneMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc { if (Doc.IsBaseProto(doc)) return doc; - if (cloneMap.get(doc)) return cloneMap.get(doc)!; + if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); - cloneMap.set(doc, copy); - if (LinkManager.Instance.getAllLinks().includes(doc)) LinkManager.Instance.addLink(copy); + cloneMap.set(doc[Id], copy); + if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy); const exclude = Cast(doc.excludeFields, listSpec("string"), []); Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = (field: ObjectField) => { + const list = Cast(doc[key], listSpec(Doc)); + if (list !== undefined && !(list instanceof Promise)) { + copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.makeClone(d as Doc, cloneMap, rtfs))); + } else if (doc[key] instanceof Doc) + copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields + else { + copy[key] = ObjectField.MakeCopy(field); + if (field instanceof RichTextField) { + if (field.Data.includes('"docid":') || field.Data.includes('"targetId":') || field.Data.includes('"linkId":')) { + rtfs.push({ copy, key, field }); + } + } + } + } if (key === "proto") { if (doc[key] instanceof Doc) { - copy[key] = Doc.MakeClone(doc[key]!, cloneMap); + copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs); } } else { if (field instanceof RefField) { copy[key] = field; } else if (cfield instanceof ComputedField) { copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); - if (field instanceof ObjectField) { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.MakeClone(d as Doc, cloneMap))); - } else { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? - undefined : Doc.MakeClone(doc[key] as Doc, cloneMap) : // reference documents except copy documents that are expanded teplate fields - ObjectField.MakeCopy(field); - } - } + (key === "links" && field instanceof ObjectField) && copyObjectField(field); } else if (field instanceof ObjectField) { - const list = Cast(doc[key], listSpec(Doc)); - if (list !== undefined && !(list instanceof Promise)) { - copy[key] = new List(list.filter(d => d instanceof Doc).map(d => Doc.MakeClone(d as Doc, cloneMap))); - } else { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? - undefined : Doc.MakeClone(doc[key] as Doc, cloneMap) : // reference documents except copy documents that are expanded teplate fields - ObjectField.MakeCopy(field); - } + copyObjectField(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... } else { @@ -640,7 +658,7 @@ export namespace Doc { }); Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true); copy.cloneOf = doc; - cloneMap.set(doc, copy); + cloneMap.set(doc[Id], copy); return copy; } diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e7d27d81e..cacba43b6 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -44,6 +44,7 @@ export const documentSchema = createSchema({ _chromeStatus: "string", // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed' _fontSize: "number", _fontFamily: "string", + _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar // appearance properties on the data document backgroundColor: "string", // background color of document -- cgit v1.2.3-70-g09d2