diff options
Diffstat (limited to 'src/client/util/RichTextSchema.tsx')
-rw-r--r-- | src/client/util/RichTextSchema.tsx | 498 |
1 files changed, 347 insertions, 151 deletions
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0a717dff1..c07ebe2ed 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,4 +1,4 @@ -import { action, observable, runInAction, reaction, IReactionDisposer } from "mobx"; +import { reaction, IReactionDisposer, observable, runInAction } from "mobx"; import { baseKeymap, toggleMark } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; @@ -8,7 +8,7 @@ import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-s import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { Doc, WidthSym, HeightSym, DataSym, Field } from "../../new_fields/Doc"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { DocumentView } from "../views/nodes/DocumentView"; @@ -16,10 +16,15 @@ import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { Transform } from "./Transform"; import React = require("react"); -import { BoolCast, NumCast } from "../../new_fields/Types"; +import { BoolCast, NumCast, StrCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { ComputedField } from "../../new_fields/ScriptField"; +import { observer } from "mobx-react"; +import { Id } from "../../new_fields/FieldSymbols"; +import { OnChangeHandler } from "react-color/lib/components/common/ColorWrap"; -const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], +const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; // :: Object @@ -30,7 +35,6 @@ export const nodes: { [index: string]: NodeSpec } = { content: "block+" }, - footnote: { group: "inline", content: "inline*", @@ -45,15 +49,6 @@ export const nodes: { [index: string]: NodeSpec } = { parseDOM: [{ tag: "footnote" }] }, - // // :: NodeSpec A plain paragraph textblock. Represented in the DOM - // // as a `<p>` element. - // paragraph: { - // content: "inline*", - // group: "block", - // parseDOM: [{ tag: "p" }], - // toDOM() { return pDOM; } - // }, - paragraph: ParagraphNodeSpec, // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. @@ -107,7 +102,19 @@ export const nodes: { [index: string]: NodeSpec } = { group: "inline" }, - star: { + 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 }, @@ -119,15 +126,6 @@ export const nodes: { [index: string]: NodeSpec } = { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; }, - // parseDOM: [{ - // tag: "star", getAttrs(dom: any) { - // return { - // visibility: dom.getAttribute("visibility"), - // oldtext: dom.getAttribute("oldtext"), - // oldtextlen: dom.getAttribute("oldtextlen"), - // } - // } - // }] }, // :: NodeSpec An inline image (`<img>`) node. Supports `src`, @@ -171,21 +169,27 @@ export const nodes: { [index: string]: NodeSpec } = { 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: 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? + draggable: false, toDOM(node) { const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; return ["div", { ...node.attrs, ...attrs }]; @@ -235,20 +239,21 @@ export const nodes: { [index: string]: NodeSpec } = { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, setFontSize: { default: undefined }, - setFontFamily: { default: undefined }, + setFontFamily: { default: "inherit" }, + setFontColor: { default: "inherit" }, inheritedFontSize: { default: undefined }, - visibility: { default: true } + visibility: { default: true }, + indent: { default: undefined } }, toDOM(node: Node<any>) { - const bs = node.attrs.bulletStyle; if (node.attrs.mapStyle === "bullet") return ['ul', 0]; - const decMap = bs ? "decimal" + bs : ""; - const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; - let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; - let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; - let ffam = node.attrs.setFontFamily; - return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }]; + 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;` }]; } }, @@ -271,10 +276,7 @@ export const nodes: { [index: string]: NodeSpec } = { ...listItem, content: 'paragraph block*', toDOM(node: any) { - const bs = node.attrs.bulletStyle; - const decMap = bs ? "decimal" + bs : ""; - const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; - let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : ""; + 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]; } @@ -293,6 +295,8 @@ export const marks: { [index: string]: MarkSpec } = { link: { attrs: { href: {}, + targetId: { 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 @@ -300,29 +304,30 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, parseDOM: [{ tag: "a[href]", getAttrs(dom: any) { - return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") }; + 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, title: `${node.attrs.title}` }, 0]; + ["a", { ...node.attrs, id: node.attrs.targetId, title: `${node.attrs.title}` }, 0]; } }, + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. - color: { + pFontColor: { attrs: { color: { default: "#000" } }, - inclusive: false, + 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', { style: 'color: black' }]; + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; } }, @@ -330,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { highlight: { default: "transparent" } }, - inclusive: false, + inclusive: true, parseDOM: [{ tag: "span", getAttrs(dom: any) { return { highlight: dom.getAttribute("backgroundColor") }; @@ -413,16 +418,16 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight: { + summarizeInclusive: { parseDOM: [ { tag: "span", getAttrs: (p: any) => { if (typeof (p) !== "string") { - let style = getComputedStyle(p); + const style = getComputedStyle(p); if (style.textDecoration === "underline") return null; if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { return null; } } @@ -433,6 +438,31 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: true, toDOM() { return ['span', { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' + }]; + } + }, + + summarize: { + inclusive: false, + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline") return null; + if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && + p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + return null; + } + } + return false; + } + }, + ], + toDOM() { + return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' }]; } @@ -444,7 +474,7 @@ export const marks: { [index: string]: MarkSpec } = { tag: "span", getAttrs: (p: any) => { if (typeof (p) !== "string") { - let style = getComputedStyle(p); + const style = getComputedStyle(p); if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { return null; } @@ -475,35 +505,30 @@ export const marks: { [index: string]: MarkSpec } = { user_mark: { attrs: { userid: { default: "" }, - opened: { default: true }, modified: { default: "when?" }, // 5 second intervals since 1970 }, group: "inline", toDOM(node: any) { - let uid = node.attrs.userid.replace(".", "").replace("@", ""); - let min = Math.round(node.attrs.modified / 12); - let hr = Math.round(min / 60); - let day = Math.round(hr / 60 / 24); - let remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return node.attrs.opened ? - ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] : - ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]]; + 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: "" }, - opened: { default: true }, modified: { default: "when?" }, // 5 second intervals since 1970 tag: { default: "" } }, group: "inline", + inclusive: false, toDOM(node: any) { - let uid = node.attrs.userid.replace(".", "").replace("@", ""); - return node.attrs.opened ? - ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] : - ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]]; + const uid = node.attrs.userid.replace(".", "").replace("@", ""); + return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; } }, @@ -521,7 +546,7 @@ export const marks: { [index: string]: MarkSpec } = { }, parseDOM: [{ tag: "span", getAttrs(dom: any) { - let cstyle = getComputedStyle(dom); + 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" }; @@ -537,18 +562,6 @@ export const marks: { [index: string]: MarkSpec } = { }] }, - pFontColor: { - attrs: { - color: { default: "yellow" } - }, - parseDOM: [{ style: 'background: #d9dbdd' }], - toDOM: (node) => { - return ['span', { - style: `color: ${node.attrs.color}` - }]; - } - }, - /** FONT SIZES */ pFontSize: { attrs: { @@ -586,7 +599,7 @@ export class ImageResizeView { this._handle.style.display = "none"; this._handle.style.bottom = "-10px"; this._handle.style.right = "-10px"; - let self = this; + const self = this; this._img.onclick = function (e: any) { e.stopPropagation(); e.preventDefault(); @@ -607,8 +620,8 @@ export class ImageResizeView { this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); - let wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); - let hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); + 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) => { @@ -621,7 +634,7 @@ export class ImageResizeView { const onpointerup = () => { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); - let pos = view.state.selection.from; + 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)))); }; @@ -648,6 +661,58 @@ export class ImageResizeView { } } + +export class DashDocCommentView { + _collapsed: HTMLElement; + _view: any; + constructor(node: any, view: any, getPos: any) { + this._collapsed = document.createElement("span"); + this._collapsed.className = "formattedTextBox-inlineComment"; + this._collapsed.id = "DashDocCommentView-" + node.attrs.docid; + this._view = view; + 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; + }; + this._collapsed.onpointerdown = (e: any) => { + e.stopPropagation(); + }; + 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(); + }; + this._collapsed.onpointerenter = (e: any) => { + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + e.preventDefault(); + e.stopPropagation(); + }; + 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; + } + selectNode() { } +} + export class DashDocView { _dashSpan: HTMLDivElement; _outer: HTMLElement; @@ -656,81 +721,210 @@ export class DashDocView { _textBox: FormattedTextBox; getDocTransform = () => { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); + 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!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1; + contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? 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.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.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"; - let removeDoc = () => { - let pos = getPos(); - let ns = new NodeSelection(view.state.doc.resolve(pos)); + 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; }; - DocServer.GetRefField(node.attrs.docid).then(async dashDoc => { - if (dashDoc instanceof Doc) { - self._dashDoc = dashDoc; - if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") { - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" })); - } - this._reactionDisposer && this._reactionDisposer(); - this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => { - this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px"; - this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px"; + const alias = node.attrs.alias; + + const docid = node.attrs.docid || 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 ? "_" + node.attrs.fieldKey : ""); + self.doRender(aliasedDoc, removeDoc, node, view, getPos); + } }); - ReactDOM.render(<DocumentView - fitToBox={BoolCast(dashDoc.fitToBox)} - Document={dashDoc} - addDocument={returnFalse} - removeDocument={removeDoc} - ruleProvider={undefined} - ScreenToLocalTransform={this.getDocTransform} - addDocTab={self._textBox.props.addDocTab} - pinToPres={returnFalse} - renderDepth={1} - PanelWidth={self._dashDoc[WidthSym]} - PanelHeight={self._dashDoc[HeightSym]} - focus={emptyFunction} - backgroundColor={returnEmptyString} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={emptyFunction} - zoomToScale={emptyFunction} - getScale={returnOne} - dontRegisterView={true} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - ContentScaling={this.contentScaling} - />, this._dashSpan); + } else { + self.doRender(dashDoc, removeDoc, node, view, getPos); } }); - let self = this; - this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); }; + 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; + 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 self = this; + const finalLayout = Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.Document) ? this._textBox.dataDoc : undefined); + if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0); + else { + 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._textTemplate = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); + } + } + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => [finalLayout[WidthSym](), finalLayout[HeightSym]()], (dim) => { + 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"; + }, { fireImmediately: true }); + ReactDOM.render(<DocumentView + Document={finalLayout} + DataDoc={!node.attrs.docid ? this._textBox.dataDoc : undefined} + LibraryPath={this._textBox.props.LibraryPath} + fitToBox={BoolCast(dashDoc._fitToBox)} + addDocument={returnFalse} + removeDocument={removeDoc} + ScreenToLocalTransform={this.getDocTransform} + addDocTab={this._textBox.props.addDocTab} + pinToPres={returnFalse} + renderDepth={1} + PanelWidth={finalLayout[WidthSym]} + PanelHeight={finalLayout[HeightSym]} + focus={this.outerFocus} + backgroundColor={returnEmptyString} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + dontRegisterView={false} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + ContentScaling={this.contentScaling} + />, this._dashSpan); + } + } destroy() { this._reactionDisposer && this._reactionDisposer(); } } + +export class DashFieldView { + _fieldWrapper: HTMLDivElement; + _labelSpan: HTMLSpanElement; + _fieldSpan: HTMLDivElement; + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + @observable _dashDoc: Doc | undefined; + _fieldKey: string; + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldKey = node.attrs.fieldKey; + this._textBoxDoc = tbox.props.Document; + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + + this._fieldSpan = document.createElement("div"); + this._fieldSpan.id = Utils.GenerateGuid(); + this._fieldSpan.contentEditable = "true"; + this._fieldSpan.style.position = "relative"; + this._fieldSpan.style.display = "inline-block"; + this._fieldSpan.style.minWidth = "50px"; + this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)"; + this._fieldSpan.addEventListener("input", this.onchanged); + this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldSpan.onmousedown = function (e: any) { + console.log(e); + e.stopPropagation(); + }; + const self = this; + this._fieldSpan.onkeydown = function (e: any) { + e.stopPropagation(); + if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) { + if (window.getSelection) { + var range = document.createRange(); + range.selectNodeContents(self._fieldSpan); + window.getSelection()!.removeAllRanges(); + window.getSelection()!.addRange(range); + } + e.preventDefault(); + } + }; + + this._labelSpan = document.createElement("span"); + this._labelSpan.style.position = "relative"; + this._labelSpan.style.display = "inline"; + this._labelSpan.style.fontWeight = "bold"; + this._labelSpan.style.fontSize = "larger"; + this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `; + if (node.attrs.docid) { + const self = this; + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => self._dashDoc = dashDoc)); + } else { + this._dashDoc = tbox.props.DataDoc || tbox.dataDoc; + } + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => this._dashDoc?.[node.attrs.fieldKey], fval => this._fieldSpan.innerHTML = Field.toString(fval as Field) || "(null)", { fireImmediately: true }); + + this._fieldWrapper.appendChild(this._labelSpan); + this._fieldWrapper.appendChild(this._fieldSpan); + (this as any).dom = this._fieldWrapper; + } + onchanged = () => { + this._reactionDisposer?.(); + this._dashDoc![this._fieldKey] = this._fieldSpan.innerText; + this._reactionDisposer = reaction(() => this._dashDoc?.[this._fieldKey], fval => this._fieldSpan.innerHTML = Field.toString(fval as Field) || "(null)"); + + } + destroy() { + this._reactionDisposer?.(); + } + selectNode() { } +} + 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 @@ -771,7 +965,7 @@ export class FootnoteView { } open() { // Append a tooltip to the outer node - let tooltip = this.dom.appendChild(document.createElement("div")); + const tooltip = this.dom.appendChild(document.createElement("div")); tooltip.className = "footnote-tooltip"; // And put a sub-ProseMirror into that this.innerView = new EditorView(tooltip, { @@ -786,7 +980,8 @@ export class FootnoteView { }), new Plugin({ view(newView) { - return FormattedTextBox.getToolTip(newView); + // TODO -- make this work with RichTextMenu + // return FormattedTextBox.getToolTip(newView); } }) ], @@ -826,14 +1021,14 @@ export class FootnoteView { this.dom.textContent = ""; } dispatchInner(tr: any) { - let { state, transactions } = this.innerView.state.applyTransaction(tr); + const { state, transactions } = this.innerView.state.applyTransaction(tr); this.innerView.updateState(state); if (!tr.getMeta("fromOutside")) { - let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); - for (let transaction of transactions) { - let steps = transaction.steps; - for (let step of steps) { + 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)); } } @@ -844,11 +1039,11 @@ export class FootnoteView { if (!node.sameMarkup(this.node)) return false; this.node = node; if (this.innerView) { - let state = this.innerView.state; - let start = node.content.findDiffStart(state.doc.content); + 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); - let overlap = start - Math.min(endA, endB); + const overlap = start - Math.min(endA, endB); if (overlap > 0) { endA += overlap; endB += overlap; } this.innerView.dispatch( state.tr @@ -870,7 +1065,7 @@ export class FootnoteView { ignoreMutation() { return true; } } -export class SummarizedView { +export class SummaryView { _collapsed: HTMLElement; _view: any; constructor(node: any, view: any, getPos: any) { @@ -894,7 +1089,7 @@ export class SummarizedView { 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 + setNodeMarkup(getPos(), undefined, attrs)); // update the attrs e.preventDefault(); e.stopPropagation(); this._collapsed.className = this.className(visible); @@ -908,15 +1103,16 @@ export class SummarizedView { className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); updateSummarizedText(start?: any) { - let mark = this._view.state.schema.marks.highlight.create(); + const mtype = this._view.state.schema.marks.summarize; + const mtypeInc = this._view.state.schema.marks.summarizeInclusive; let endPos = start; - let visited = new Set(); + 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 === mark.type)) { + if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); endPos = i + node.nodeSize - 1; } @@ -940,8 +1136,8 @@ export const schema = new Schema({ nodes, marks }); const fromJson = schema.nodeFromJSON; schema.nodeFromJSON = (json: any) => { - let node = fromJson(json); - if (json.type === "star") { + const node = fromJson(json); + if (json.type === schema.marks.summarize.name) { node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); } return node; |