From 2d3de245e31e0c45c1e64720816c3d6d219efcd3 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 18:53:08 -0400 Subject: Update FormattedTextBox.tsx --- src/client/views/nodes/FormattedTextBox.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bb44c5ac6..b46502c1d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -811,8 +811,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. + if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); e.preventDefault(); + setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + SelectionManager.DeselectAll(); + SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + }, 0); } function timenow() { var now = new Date(); -- cgit v1.2.3-70-g09d2 From 4921ef0121818381e4139137c0d204d41cb64a20 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 18:57:23 -0400 Subject: reverted back to semi-working version of outline mode --- src/client/views/nodes/FormattedTextBox.tsx | 9 +++++---- src/server/index.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b46502c1d..4cb52ec78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -813,10 +813,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); e.preventDefault(); - setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - SelectionManager.DeselectAll(); - SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - }, 0); + // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo + // setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + // SelectionManager.DeselectAll(); + // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + // }, 0); } function timenow() { var now = new Date(); diff --git a/src/server/index.ts b/src/server/index.ts index fca90a585..17cd59ec7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport({ scale: 1 }); + let viewport = page.getViewport(1); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From 155cf5c5a1b45aafe7a5632f2ee6ecf957f04dde Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 3 Sep 2019 23:07:42 -0400 Subject: fixes for bullets --- src/client/util/ProsemirrorExampleTransfer.ts | 59 +++++++++++++++++++-------- src/client/util/RichTextSchema.tsx | 11 +++-- 2 files changed, 48 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 419311df8..5016e72c6 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -93,35 +93,63 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); + let updateOrderedList = (start: number, rangeStart: any, delta: number, tx2: Transaction, forward: boolean) => { + let bs = rangeStart.attrs.bulletStyle; + bs + delta > 0 && tx2.setNodeMarkup(start, rangeStart.type, { mapStyle: rangeStart.attrs.mapStyle, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks); + + let brk = false; + rangeStart.descendants((node: any, offset: any, index: any) => { + if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { + if (!brk && (bs !== node.attrs.bulletStyle || delta > 0 || (forward && bs > 1))) { + tx2.setNodeMarkup(start + offset + 1, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: node.attrs.bulletStyle + delta }, node.marks); + } else { + brk = true; + } + } + }); + } + let updateBullets = (tx2: Transaction, refStart: number, delta: number) => { - for (let i = refStart; i > 0; i--) { + let i = refStart; + for (let i = refStart; i >= 0; i--) { let testPos = tx2.doc.nodeAt(i); - if (testPos && testPos.type === schema.nodes.list_item) { + if (!testPos) { + for (let i = refStart + 1; i <= tx2.doc.nodeSize; i++) { + try { + let testPos = tx2.doc.nodeAt(i); + if (testPos && testPos.type === schema.nodes.ordered_list) { + updateOrderedList(i, testPos, delta, tx2, true); + break; + } + } catch (e) { + break; + } + } + break; + } + if ((testPos.type === schema.nodes.list_item || testPos.type === schema.nodes.ordered_list)) { let start = i; let preve = i > 0 && tx2.doc.nodeAt(start - 1); if (preve && preve.type === schema.nodes.ordered_list) { start = start - 1; } let rangeStart = tx2.doc.nodeAt(start); - if (rangeStart && rangeStart.type === schema.nodes.ordered_list) { - tx2.setNodeMarkup(start, rangeStart.type, { ...rangeStart.attrs, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks); + if (rangeStart) { + updateOrderedList(start, rangeStart, delta, tx2, false); } - rangeStart && rangeStart.descendants((node: any, offset: any, index: any) => { - if (node.type === schema.nodes.ordered_list) { - tx2.setNodeMarkup(start + offset + 1, node.type, { ...node.attrs, bulletStyle: node.attrs.bulletStyle + delta }, node.marks); - } - }); break; } } } + bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { var ref = state.selection; var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - updateBullets(tx2, range!.start, 1); + var range = state.selection.$from.blockRange(state.selection.$to); + updateBullets(tx2, range!.start - 1, 1); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); @@ -129,13 +157,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); let newstate = state.applyTransaction(sxf); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { - for (let i = range!.start; i >= 0; i--) { - let rangeStart = tx2.doc.nodeAt(i); - if (rangeStart && rangeStart.type === schema.nodes.ordered_list) { - tx2.setNodeMarkup(i, rangeStart.type, { ...rangeStart.attrs, bulletStyle: 1 }, rangeStart.marks); - break; - } - } + updateBullets(tx2, range!.start, 1); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); @@ -151,9 +173,10 @@ export default function buildKeymap>(schema: S, mapKeys?: var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); let tr = state.tr; - updateBullets(tr, range!.start, -1); if (!liftListItem(schema.nodes.list_item)(tr, (tx2: Transaction) => { + var range = tx2.selection.$from.blockRange(tx2.selection.$to); + updateBullets(tx2, range!.start, -1); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 25d972857..75e982872 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -203,7 +203,6 @@ export const nodes: { [index: string]: NodeSpec } = { 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; - for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map; return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; @@ -216,7 +215,6 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], toDOM(node: Node) { - for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = ""; return ['ul', 0]; } }, @@ -231,12 +229,17 @@ export const nodes: { [index: string]: NodeSpec } = { // }, list_item: { attrs: { - className: { default: "" } + bulletStyle: { default: 0 }, + mapStyle: { default: "decimal" }, }, ...listItem, content: 'paragraph block*', toDOM(node: any) { - return ["li", { class: node.attrs.className }, 0]; + 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 : multiMap; + return ["li", { class: `${map}` }, 0]; } }, }; -- cgit v1.2.3-70-g09d2 From 2262d7562b57c37c238fc8a7c373304984a5fe3b Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 4 Sep 2019 17:49:19 -0400 Subject: prosemirror restructuring. --- src/client/util/ProsemirrorExampleTransfer.ts | 71 +++++----------------- src/client/util/RichTextSchema.tsx | 71 +++++++++++----------- src/client/util/TooltipTextMenu.tsx | 37 ++++++----- src/client/util/prosemirrorPatches.js | 30 +++++++++ src/client/views/OverlayView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/client/views/nodes/FormattedTextBoxComment.tsx | 4 +- src/client/views/pdf/Page.tsx | 2 +- 8 files changed, 107 insertions(+), 116 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 5016e72c6..9c4b2d3d1 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -2,8 +2,8 @@ import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setB import { redo, undo } from "prosemirror-history"; import { undoInputRule } from "prosemirror-inputrules"; import { Schema } from "prosemirror-model"; -import { liftListItem, } from "./prosemirrorPatches.js"; -import { splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list"; +import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; +import { splitListItem, wrapInList, } from "prosemirror-schema-list"; import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; import { TooltipTextMenu } from "./TooltipTextMenu"; @@ -62,7 +62,7 @@ export default function buildKeymap>(schema: S, mapKeys?: return true; } return false; - }) + }); let cmd = chainCommands(exitCode, (state, dispatch) => { @@ -93,54 +93,16 @@ export default function buildKeymap>(schema: S, mapKeys?: bind("Mod-s", TooltipTextMenu.insertStar); - let updateOrderedList = (start: number, rangeStart: any, delta: number, tx2: Transaction, forward: boolean) => { - let bs = rangeStart.attrs.bulletStyle; - bs + delta > 0 && tx2.setNodeMarkup(start, rangeStart.type, { mapStyle: rangeStart.attrs.mapStyle, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks); - - let brk = false; - rangeStart.descendants((node: any, offset: any, index: any) => { + let updateBullets = (tx2: Transaction) => { + tx2.doc.descendants((node: any, offset: any, index: any) => { if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { - if (!brk && (bs !== node.attrs.bulletStyle || delta > 0 || (forward && bs > 1))) { - tx2.setNodeMarkup(start + offset + 1, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: node.attrs.bulletStyle + delta }, node.marks); - } else { - brk = true; - } + let path = (tx2.doc.resolve(offset) as any).path; + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0); + if (node.type === schema.nodes.ordered_list) depth++; + tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); } }); - } - - let updateBullets = (tx2: Transaction, refStart: number, delta: number) => { - let i = refStart; - for (let i = refStart; i >= 0; i--) { - let testPos = tx2.doc.nodeAt(i); - if (!testPos) { - for (let i = refStart + 1; i <= tx2.doc.nodeSize; i++) { - try { - let testPos = tx2.doc.nodeAt(i); - if (testPos && testPos.type === schema.nodes.ordered_list) { - updateOrderedList(i, testPos, delta, tx2, true); - break; - } - } catch (e) { - break; - } - } - break; - } - if ((testPos.type === schema.nodes.list_item || testPos.type === schema.nodes.ordered_list)) { - let start = i; - let preve = i > 0 && tx2.doc.nodeAt(start - 1); - if (preve && preve.type === schema.nodes.ordered_list) { - start = start - 1; - } - let rangeStart = tx2.doc.nodeAt(start); - if (rangeStart) { - updateOrderedList(start, rangeStart, delta, tx2, false); - } - break; - } - } - } + }; bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -148,8 +110,7 @@ export default function buildKeymap>(schema: S, mapKeys?: var range = ref.$from.blockRange(ref.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - var range = state.selection.$from.blockRange(state.selection.$to); - updateBullets(tx2, range!.start - 1, 1); + updateBullets(tx2); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); @@ -157,7 +118,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); let newstate = state.applyTransaction(sxf); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { - updateBullets(tx2, range!.start, 1); + updateBullets(tx2); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); @@ -169,14 +130,10 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - var range = state.selection.$from.blockRange(state.selection.$to); var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - let tr = state.tr; - - if (!liftListItem(schema.nodes.list_item)(tr, (tx2: Transaction) => { - var range = tx2.selection.$from.blockRange(tx2.selection.$to); - updateBullets(tx2, range!.start, -1); + if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + updateBullets(tx2); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); dispatch(tx2); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 75e982872..3174d668c 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -597,8 +597,6 @@ export class ImageResizeView { } export class OrderedListView { - constructor(node: any, view: any, getPos: any) { } - 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 } @@ -613,33 +611,33 @@ export class FootnoteView { constructor(node: any, view: any, getPos: any) { // We'll need these later - this.node = node - this.outerView = view - this.getPos = getPos + 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 + this.innerView = null; } selectNode() { const attrs = { ...this.node.attrs }; attrs.visibility = true; - this.dom.classList.add("ProseMirror-selectednode") - if (!this.innerView) this.open() + 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() + this.dom.classList.remove("ProseMirror-selectednode"); + if (this.innerView) this.close(); } open() { - if (!(this.outerView as any).isOverlay) return; + if (!this.outerView.isOverlay) return; // Append a tooltip to the outer node - let tooltip = this.dom.appendChild(document.createElement("div")) + let tooltip = this.dom.appendChild(document.createElement("div")); tooltip.className = "footnote-tooltip"; // And put a sub-ProseMirror into that this.innerView = new EditorView(tooltip, { @@ -679,56 +677,55 @@ export class FootnoteView { if (this.innerView) this.close(); else { this.open(); - } } close() { - this.innerView && this.innerView.destroy() - this.innerView = null - this.dom.textContent = "" + this.innerView && this.innerView.destroy(); + this.innerView = null; + this.dom.textContent = ""; } dispatchInner(tr: any) { - let { state, transactions } = this.innerView.state.applyTransaction(tr) - this.innerView.updateState(state) + let { 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 i = 0; i < transactions.length; i++) { - let steps = transactions[i].steps - for (let j = 0; j < steps.length; j++) - outerTr.step(steps[j].map(offsetMap)) + let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); + for (let steps of transactions) { + for (let step of steps) { + outerTr.step(step.map(offsetMap)); + } } - if (outerTr.docChanged) this.outerView.dispatch(outerTr) + if (outerTr.docChanged) this.outerView.dispatch(outerTr); } } update(node: any) { - if (!node.sameMarkup(this.node)) return false - this.node = node + 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) - if (start != null) { - let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content) - let overlap = start - Math.min(endA, endB) - if (overlap > 0) { endA += overlap; endB += overlap } + let state = this.innerView.state; + let 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); + if (overlap > 0) { endA += overlap; endB += overlap; } this.innerView.dispatch( state.tr .replace(start, endB, node.slice(start, endA)) - .setMeta("fromOutside", true)) + .setMeta("fromOutside", true)); } } - return true + return true; } destroy() { - if (this.innerView) this.close() + if (this.innerView) this.close(); } stopEvent(event: any) { - return this.innerView && this.innerView.dom.contains(event.target) + return this.innerView && this.innerView.dom.contains(event.target); } - ignoreMutation() { return true } + ignoreMutation() { return true; } } export class SummarizedView { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index ce7b04e31..6d375fc1d 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -4,7 +4,7 @@ import { action, observable } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Field, Opt } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; @@ -509,30 +509,37 @@ export class TooltipTextMenu { } } + updateBullets = (tx2: Transaction, style: string) => { + tx2.doc.descendants((node: any, offset: any, index: any) => { + if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { + let path = (tx2.doc.resolve(offset) as any).path; + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0); + if (node.type === schema.nodes.ordered_list) depth++; + tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks); + } + }); + }; //remove all node typeand apply the passed-in one to the selected text - changeToNodeType(nodeType: NodeType | undefined, view: EditorView) { + changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { //remove oldif (nodeType) { //add new if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); } else { - var ref = view.state.selection; - var range = ref.$from.blockRange(ref.$to); var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); - wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2)); - let path = resolvedPos.path; - for (let i = path.length - 1; i > 0; i--) { - if (path[i].type === schema.nodes.ordered_list) { - path[i].attrs.bulletStyle = 1; - path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle; - break; - } - } + if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); marks && tx2.ensureMarks([...marks]); marks && tx2.setStoredMarks([...marks]); view.dispatch(tx2); - }); + })) { + let tx2 = view.state.tr; + this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); + marks && tx2.ensureMarks([...marks]); + marks && tx2.setStoredMarks([...marks]); + + view.dispatch(tx2); + } } } diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js index c273c2323..6bf4395ad 100644 --- a/src/client/util/prosemirrorPatches.js +++ b/src/client/util/prosemirrorPatches.js @@ -6,6 +6,7 @@ var prosemirrorTransform = require('prosemirror-transform'); var prosemirrorModel = require('prosemirror-model'); exports.liftListItem = liftListItem; +exports.sinkListItem = sinkListItem; // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool // Create a command to lift the list item around the selection up into // a wrapping list. @@ -59,4 +60,33 @@ function liftOutOfList(tr, dispatch, range) { 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, 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 + } } \ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 54ab8696e..fe06e4440 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -191,7 +191,7 @@ export class OverlayView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} /> ; - }) + }); } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 4cb52ec78..f9ad040a2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -174,7 +174,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe syncNodeSelection(view: any, sel: any) { if (sel instanceof NodeSelection) { var desc = view.docView.descAt(sel.from); - if (desc != view.lastSelectedViewDesc) { + if (desc !== view.lastSelectedViewDesc) { if (view.lastSelectedViewDesc) { view.lastSelectedViewDesc.deselectNode(); view.lastSelectedViewDesc = null; @@ -635,8 +635,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe nodeViews: { image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, - ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); }, - footnote(node, view, getPos) { return new FootnoteView(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, diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 255000936..2d2fff06d 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -92,7 +92,7 @@ export class FormattedTextBoxComment { if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; - let set = "none" + let set = "none"; if (state.selection.$from) { let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); @@ -130,7 +130,7 @@ export class FormattedTextBoxComment { if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title))); + (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title))); } // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 8df2dce29..856e883e7 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -64,7 +64,7 @@ export default class Page extends React.Component { // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { this._state = "rendering"; - let viewport = page.getViewport({ scale: scale }); + let viewport = page.getViewport(scale); this._canvas.current.width = this._width = viewport.width; this._canvas.current.height = this._height = viewport.height; this.props.pageLoaded(viewport); -- cgit v1.2.3-70-g09d2 From a017d20fb40ffb5b99b06b716ac5a6f8635c45e6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 4 Sep 2019 20:43:28 -0400 Subject: added collapsing bullets --- src/client/util/ProsemirrorExampleTransfer.ts | 2 +- src/client/util/RichTextSchema.tsx | 8 ++++++-- src/client/views/nodes/FormattedTextBox.tsx | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 9c4b2d3d1..9f6da7ade 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -99,7 +99,7 @@ export default function buildKeymap>(schema: S, mapKeys?: let path = (tx2.doc.resolve(offset) as any).path; let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) depth++; - tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks); } }); }; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3174d668c..2b689b7ef 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -197,13 +197,15 @@ export const nodes: { [index: string]: NodeSpec } = { attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, + visibility: { default: true } }, toDOM(node: Node) { 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 : multiMap; - return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0]; + return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; } @@ -231,6 +233,7 @@ export const nodes: { [index: string]: NodeSpec } = { attrs: { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, + visibility: { default: true } }, ...listItem, content: 'paragraph block*', @@ -239,7 +242,8 @@ export const nodes: { [index: string]: NodeSpec } = { 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; - return ["li", { class: `${map}` }, 0]; + return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; + //return ["li", { class: `${map}` }, 0]; } }, }; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f9ad040a2..2dbfeeba7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -672,6 +672,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onPointerDown = (e: React.PointerEvent): void => { + let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + if (pos) { + if (e.nativeEvent.offsetX < 40) { + let node = this._editorView!.state.doc.nodeAt(pos.pos); + let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; + if (node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } + } if (this.props.onClick && e.button === 0) { e.preventDefault(); } -- cgit v1.2.3-70-g09d2 From a936f08e3c18ad34bbe74133eaa768c7bda832dc Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 4 Sep 2019 21:00:30 -0400 Subject: cleanup --- src/client/views/nodes/FormattedTextBox.tsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2dbfeeba7..d811273e8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -75,6 +75,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _editorView: Opt; private _applyingChange: boolean = false; private _linkClicked = ""; + private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; private _reactionDisposer: Opt; private _searchReactionDisposer?: Lambda; @@ -671,17 +672,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView && this._editorView.destroy(); } + onPointerDown = (e: React.PointerEvent): void => { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - if (pos) { - if (e.nativeEvent.offsetX < 40) { - let node = this._editorView!.state.doc.nodeAt(pos.pos); - let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; - if (node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); - } - } - } + pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { e.preventDefault(); } @@ -778,6 +772,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { + if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { + let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + if (pos) { + let node = this._editorView!.state.doc.nodeAt(pos.pos); + let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; + if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } + } this._proseRef!.focus(); if (this._linkClicked) { this._linkClicked = ""; -- cgit v1.2.3-70-g09d2 From 179b2c7c68e3d240f3f39083bdb686ad31761c04 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 5 Sep 2019 12:00:18 -0400 Subject: fixed some template issues. added attribution for pasted text --- src/client/util/RichTextSchema.tsx | 11 ++++-- .../views/collections/CollectionDockingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 4 ++ src/client/views/nodes/FormattedTextBox.scss | 3 ++ src/client/views/nodes/FormattedTextBox.tsx | 44 ++++------------------ src/client/views/nodes/PDFBox.tsx | 15 +++++--- src/new_fields/Doc.ts | 4 +- 7 files changed, 35 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 2b689b7ef..9c38ee458 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -205,7 +205,7 @@ export const nodes: { [index: string]: NodeSpec } = { 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; return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; } @@ -262,7 +262,8 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { href: {}, location: { default: null }, - title: { default: null } + title: { default: null }, + docref: { default: false } }, inclusive: false, parseDOM: [{ @@ -270,7 +271,11 @@ export const marks: { [index: string]: MarkSpec } = { return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") }; } }], - toDOM(node: any) { return ["a", node.attrs, 0]; } + toDOM(node: any) { + return node.attrs.docref && node.attrs.title ? + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs }, 0]; + } }, // :: MarkSpec An emphasis mark. Rendered as an `` element. diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 95f94875c..fb8b0c41b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -607,7 +607,7 @@ export class DockedFrameRenderer extends React.Component { } return Transform.Identity(); } - get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } + get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { if (doc.dockingConfig) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 97b31bf2a..654ff2279 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -310,6 +310,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { let cols = Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + if (isNaN(cols)) { + console.log("naN"); + cols = 1; + } return
{!heading ? (null) :
{ - if (e.clipboardData && this._editorView) { - // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`; - // for (let i = 0; i < e.clipboardData.items.length; i++) { - // let item = e.clipboardData.items.item(i); - // console.log(item) - // if (item.type === "text/plain") { - // console.log("plain") - // item.getAsString((text) => { - // let pdfPasteIndex = text.indexOf(pdfPasteText); - // if (pdfPasteIndex > -1) { - // let insertText = text.substr(0, pdfPasteIndex); - // const tx = this._editorView!.state.tr.insertText(insertText); - // // tx.setSelection(new Selection(tx.)) - // const state = this._editorView!.state; - // this._editorView!.dispatch(tx); - // if (FormattedTextBox._toolTipTextMenu) { - // // this._toolTipTextMenu.makeLinkWithState(state) - // } - // e.stopPropagation(); - // e.preventDefault(); - // } - // }); - // } - // } - } - } - // this should be internal to prosemirror, but is needed // here to make sure that footnote view nodes in the overlay editor // get removed when they're not selected. + syncNodeSelection(view: any, sel: any) { if (sel instanceof NodeSelection) { var desc = view.docView.descAt(sel.from); @@ -363,8 +336,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentDidMount() { - document.addEventListener("paste", this.paste); - if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { @@ -584,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); linkId = link[Id]; - let frag = addMarkToFrag(slice.content); + let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title))); slice = new Slice(frag, slice.openStart, slice.openEnd); var tr = view.state.tr.replaceSelection(slice); view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); @@ -594,19 +565,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return true; - function addMarkToFrag(frag: Fragment) { + function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; - frag.forEach(node => nodes.push(addLinkMark(node))); + frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node) { + function addLinkMark(node: Node, title: string) { if (!node.isText) { - const content = addMarkToFrag(node.content); + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title)); 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" }); + const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true }); if (linkIndex !== -1) { marks.splice(linkIndex, 1, link); } else { @@ -668,7 +639,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); - document.removeEventListener("paste", this.paste); this._editorView && this._editorView.destroy(); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 18f82ff47..df35b603c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -33,7 +33,9 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _pdf: Opt; @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; } - @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } + @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + + @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } private _mainCont: React.RefObject = React.createRef(); @@ -48,7 +50,7 @@ export class PDFBox extends DocComponent(PdfDocumen componentDidMount() { this.props.setPdfBox && this.props.setPdfBox(this); - const pdfUrl = Cast(this.props.Document.data, PdfField); + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl instanceof PdfField) { Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); } @@ -78,7 +80,7 @@ export class PDFBox extends DocComponent(PdfDocumen @action public GotoPage(p: number) { - if (p > 0 && p <= NumCast(this.props.Document.numPages)) { + if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { this.props.Document.curPage = p; this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight); } @@ -87,7 +89,7 @@ export class PDFBox extends DocComponent(PdfDocumen @action public ForwardPage() { let cp = this.GetPage() + 1; - if (cp <= NumCast(this.props.Document.numPages)) { + if (cp <= NumCast(this.dataDoc.numPages)) { this.props.Document.curPage = cp; this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight); } @@ -185,11 +187,12 @@ export class PDFBox extends DocComponent(PdfDocumen } } + render() { - const pdfUrl = Cast(this.props.Document.data, PdfField); + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return (!(pdfUrl instanceof PdfField) || !this._pdf ? -
{`pdf, ${this.props.Document.data}, not found`}
: +
{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}
:
Date: Thu, 5 Sep 2019 13:22:53 -0400 Subject: fixed default link following behavior for link menu items --- src/client/views/linking/LinkFollowBox.tsx | 3 +-- src/client/views/linking/LinkMenuItem.tsx | 40 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 74663f9af..11d495c97 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -17,7 +17,6 @@ import { listSpec } from "../../../new_fields/Schema"; import { DocServer } from "../../DocServer"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; enum FollowModes { OPENTAB = "Open in Tab", @@ -321,7 +320,7 @@ export class LinkFollowBox extends React.Component { } //set this to be the default link behavior, can be any of the above - public defaultLinkBehavior: (options?: any) => void = this.openLinkTab; + public defaultLinkBehavior: (options?: any) => string = (options?: any) => "inTab"; @action currentLinkBehavior = () => { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6895dae9a..8ea43d521 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -1,26 +1,17 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp, faGlobeAsia } from '@fortawesome/free-solid-svg-icons'; +import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; -import { DocumentManager } from "../../util/DocumentManager"; -import { undoBatch } from "../../util/UndoManager"; -import './LinkMenu.scss'; -import React = require("react"); -import { Doc, DocListCastAsync, WidthSym } from '../../../new_fields/Doc'; -import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; -import { observable, action, computed } from 'mobx'; -import { LinkManager } from '../../util/LinkManager'; +import { Doc } from '../../../new_fields/Doc'; +import { Cast, StrCast } from '../../../new_fields/Types'; import { DragLinkAsDocument } from '../../util/DragManager'; -import { CollectionDockingView } from '../collections/CollectionDockingView'; -import { SelectionManager } from '../../util/SelectionManager'; -import { CollectionViewType } from '../collections/CollectionBaseView'; -import { DocumentView } from '../nodes/DocumentView'; -import { SearchUtil } from '../../util/SearchUtil'; -import { LinkFollowBox } from './LinkFollowBox'; +import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; import { MainView } from '../MainView'; -import { Docs } from '../../documents/Documents'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { LinkFollowBox } from './LinkFollowBox'; +import './LinkMenu.scss'; +import React = require("react"); library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -37,7 +28,9 @@ interface LinkMenuItemProps { export class LinkMenuItem extends React.Component { private _drag = React.createRef(); @observable private _showMore: boolean = false; - @action toggleShowMore() { this._showMore = !this._showMore; } + @action toggleShowMore() { + this._showMore = !this._showMore; + } onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -75,6 +68,11 @@ export class LinkMenuItem extends React.Component { onLinkButtonUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); + + if (LinkFollowBox.Instance !== undefined) { + LinkFollowBox.Instance.props.Document.isMinimized = false; + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + } e.stopPropagation(); } @@ -98,9 +96,9 @@ export class LinkMenuItem extends React.Component { @action.bound async followDefault() { if (LinkFollowBox.Instance !== undefined) { - LinkFollowBox.Instance.props.Document.isMinimized = false; - LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); - LinkFollowBox.Instance.defaultLinkBehavior(); + if (LinkFollowBox.Instance.defaultLinkBehavior() === "inTab") { + this.props.addDocTab(this.props.destinationDoc, undefined, "inTab"); + } } } -- cgit v1.2.3-70-g09d2 From 717e32f8f140127a628c5ea4b7543c1703459e15 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 5 Sep 2019 13:37:05 -0400 Subject: restructured follow link again --- src/client/views/linking/LinkFollowBox.tsx | 18 ++++++++++++------ src/client/views/linking/LinkMenuItem.tsx | 6 +++--- 2 files changed, 15 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 11d495c97..13a341543 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -17,6 +17,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { DocServer } from "../../DocServer"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { docs_v1 } from "googleapis"; enum FollowModes { OPENTAB = "Open in Tab", @@ -197,6 +198,12 @@ export class LinkFollowBox extends React.Component { } + _addDocTab: (undefined | ((doc: Doc, dataDoc: Doc | undefined, where: string) => void)); + + setAddDocTab = (addFunc: (doc: Doc, dataDoc: Doc | undefined, where: string) => void) => { + this._addDocTab = addFunc; + } + @undoBatch openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => { if (LinkFollowBox.destinationDoc) { @@ -239,7 +246,7 @@ export class LinkFollowBox extends React.Component { let targetContext = await Cast(proto.targetContext, Doc); let sourceContext = await Cast(proto.sourceContext, Doc); - let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + let dockingFunc = (document: Doc) => { this._addDocTab && this._addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); @@ -265,9 +272,8 @@ export class LinkFollowBox extends React.Component { openLinkTab = () => { if (LinkFollowBox.destinationDoc) { let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - // THIS IS EMPTY FUNCTION - this.props.addDocTab(fullScreenAlias, undefined, "inTab"); - console.log(this.props.addDocTab); + // this.prosp.addDocTab is empty -- use the link source's addDocTab + this._addDocTab && this._addDocTab(fullScreenAlias, undefined, "inTab"); this.highlightDoc(); SelectionManager.DeselectAll(); @@ -284,7 +290,7 @@ export class LinkFollowBox extends React.Component { options.context.panX = newPanX; options.context.panY = newPanY; } - this.props.addDocTab(options.context, undefined, "inTab"); + this._addDocTab && this._addDocTab(options.context, undefined, "inTab"); if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom }); this.highlightDoc(); @@ -320,7 +326,7 @@ export class LinkFollowBox extends React.Component { } //set this to be the default link behavior, can be any of the above - public defaultLinkBehavior: (options?: any) => string = (options?: any) => "inTab"; + public defaultLinkBehavior: (options?: any) => void = this.openLinkTab; @action currentLinkBehavior = () => { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 8ea43d521..19a0023e9 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -72,6 +72,7 @@ export class LinkMenuItem extends React.Component { if (LinkFollowBox.Instance !== undefined) { LinkFollowBox.Instance.props.Document.isMinimized = false; LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkFollowBox.Instance.setAddDocTab(this.props.addDocTab); } e.stopPropagation(); } @@ -96,9 +97,8 @@ export class LinkMenuItem extends React.Component { @action.bound async followDefault() { if (LinkFollowBox.Instance !== undefined) { - if (LinkFollowBox.Instance.defaultLinkBehavior() === "inTab") { - this.props.addDocTab(this.props.destinationDoc, undefined, "inTab"); - } + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkFollowBox.Instance.defaultLinkBehavior(); } } -- cgit v1.2.3-70-g09d2 From e5b52b1083c86b946a2758983e397826579d2282 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 01:41:06 -0400 Subject: fixed summarizing in text notes. --- src/Utils.ts | 14 +++ src/client/util/RichTextSchema.tsx | 157 ++++++++++++--------------- src/client/util/TooltipTextMenu.tsx | 18 ++- src/client/views/nodes/FormattedTextBox.scss | 20 ++++ src/client/views/nodes/FormattedTextBox.tsx | 41 ++----- src/new_fields/Doc.ts | 2 +- 6 files changed, 120 insertions(+), 132 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 959b89fe5..f805ae872 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -133,6 +133,20 @@ export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => vo return dup; } +export function timenow() { + var now = new Date(); + let ampm = 'am'; + let h = now.getHours(); + let m: any = now.getMinutes(); + let s: any = now.getSeconds(); + if (h >= 12) { + if (h > 12) h -= 12; + ampm = 'pm'; + } + if (m < 10) m = '0' + m; + return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; +} + export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); } export function returnTrue() { return true; } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 2b689b7ef..a8ba0a4be 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,4 +1,4 @@ -import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; +import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; import { TextSelection, EditorState } from "prosemirror-state"; import { Doc } from "../../new_fields/Doc"; @@ -7,6 +7,8 @@ import { EditorView } from "prosemirror-view"; import { keymap } from "prosemirror-keymap"; import { undo, redo } from "prosemirror-history"; import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands"; +import { Domain } from "domain"; +import { DOM } from "@fortawesome/fontawesome-svg-core"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -42,14 +44,6 @@ export const nodes: { [index: string]: NodeSpec } = { toDOM() { return pDOM; } }, - // starmine: { - // inline: true, - // attrs: { oldtext: { default: "" } }, - // group: "inline", - // toDOM() { return ["star", "㊉"]; }, - // parseDOM: [{ tag: "star" }] - // }, - // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. blockquote: { content: "block+", @@ -107,10 +101,9 @@ export const nodes: { [index: string]: NodeSpec } = { visibility: { default: false }, text: { default: undefined }, textslice: { default: undefined }, - textlen: { default: 0 } - }, group: "inline", + inclusive: false, toDOM(node) { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; @@ -205,12 +198,10 @@ export const nodes: { [index: string]: NodeSpec } = { 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; return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; - //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] : - // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"]; + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, - //this doesn't currently work for some reason + bullet_list: { ...bulletList, content: 'list_item+', @@ -221,14 +212,6 @@ export const nodes: { [index: string]: NodeSpec } = { } }, - //bullet_list: { - // content: 'list_item+', - // group: 'block', - //active: blockActive(schema.nodes.bullet_list), - //enable: wrapInList(schema.nodes.bullet_list), - //run: wrapInList(schema.nodes.bullet_list), - //select: state => true, - // }, list_item: { attrs: { bulletStyle: { default: 0 }, @@ -251,7 +234,6 @@ export const nodes: { [index: string]: NodeSpec } = { const emDOM: DOMOutputSpecArray = ["em", 0]; const strongDOM: DOMOutputSpecArray = ["strong", 0]; const codeDOM: DOMOutputSpecArray = ["code", 0]; -const underlineDOM: DOMOutputSpecArray = ["underline", 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { @@ -289,16 +271,6 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return strongDOM; } }, - underline: { - parseDOM: [ - { tag: 'u' }, - { style: 'text-decoration=underline' } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline' - }] - }, - strikethrough: { parseDOM: [ { tag: 'strike' }, @@ -340,14 +312,52 @@ export const marks: { [index: string]: MarkSpec } = { }, highlight: { - parseDOM: [{ style: 'text-decoration: underline' }], + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + let 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; + } + }, + ], + inclusive: false, + priority: 100, toDOM() { return ['span', { - style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)' + 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") { + let style = getComputedStyle(p); + if (style.textDecoration === "underline") + return null; + if (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: { parseDOM: [{ style: 'background: yellow' }], toDOM() { @@ -530,9 +540,6 @@ export const marks: { [index: string]: MarkSpec } = { }] }, }; -function getFontSize(element: any) { - return parseFloat((getComputedStyle(element) as any).fontSize); -} export class ImageResizeView { _handle: HTMLElement; @@ -733,73 +740,52 @@ export class FootnoteView { } export class SummarizedView { - // TODO: highlight text that is summarized. to find end of region, walk along mark _collapsed: HTMLElement; _view: any; constructor(node: any, view: any, getPos: any) { this._collapsed = document.createElement("span"); - this._collapsed.textContent = node.attrs.visibility ? "㊀" : "㊉"; - this._collapsed.style.opacity = "0.5"; - this._collapsed.style.position = "relative"; - this._collapsed.style.width = "40px"; - this._collapsed.style.height = "20px"; - let self = this; + 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 = function (e: any) { - if (node.attrs.visibility) { - // node.attrs.visibility = !node.attrs.visibility; - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight); - let length = to - from; - let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length); - // update attrs of node - attrs.text = newSelection.content(); - attrs.textslice = newSelection.content().toJSON(); - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { })); - let marks = view.state.storedMarks.filter((m: any) => m.type !== view.state.schema.marks.highlight); - view.state.storedMarks = marks; - self._collapsed.textContent = "㊉"; - } else { - // node.attrs.visibility = !node.attrs.visibility; - let y = getPos(); - const attrs = { ...node.attrs }; - attrs.visibility = !attrs.visibility; - view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs)); - let mark = view.state.schema.mark(view.state.schema.marks.highlight); - view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1))); - const from = view.state.selection.from; - let size = node.attrs.text.size; - view.dispatch(view.state.tr.replaceSelection(node.attrs.text).addMark(from, from + size, mark).removeStoredMark(mark)); - self._collapsed.textContent = "㊀"; + + 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, 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() { } + selectNode() { } + + deselectNode() { } - updateSummarizedText(start?: any, mark?: any) { - let $start = this._view.state.doc.resolve(start); + className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); + + updateSummarizedText(start?: any) { + let mark = this._view.state.schema.marks.highlight.create(); let endPos = start; - let _mark = this._view.state.schema.mark(this._view.state.schema.marks.highlight); let 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 === mark.type)) { visited.add(node); endPos = i + node.nodeSize - 1; } @@ -807,10 +793,7 @@ export class SummarizedView { } }); } - return { from: start, to: endPos }; - } - - deselectNode() { + return TextSelection.create(this._view.state.doc, start, endPos); } } // :: Schema diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 6d375fc1d..020c51c36 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -307,10 +307,9 @@ export class TooltipTextMenu { { handlers: { dragComplete: action(() => { - // let m = dragData.droppedDocuments; let linkDoc = dragData.linkDocument; let proto = Doc.GetProto(linkDoc); - if (docView && docView.props.ContainingCollectionView) { + if (proto && docView && docView.props.ContainingCollectionView) { proto.sourceContext = docView.props.ContainingCollectionView.props.Document; } linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab"); @@ -322,8 +321,6 @@ export class TooltipTextMenu { e.preventDefault(); }; this.linkEditor.appendChild(this.linkDrag); - // this.linkEditor.appendChild(this.linkText); - // this.linkEditor.appendChild(linkBtn); this.tooltip.appendChild(this.linkEditor); } @@ -432,11 +429,13 @@ export class TooltipTextMenu { } public static insertStar(state: EditorState, dispatch: any) { - let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from }); - if (dispatch) { - //console.log(newNode.attrs.text.toString()); - dispatch(state.tr.replaceSelectionWith(newNode)); - } + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); return true; } @@ -637,7 +636,6 @@ export class TooltipTextMenu { Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); - }); } } diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 8f47402c4..0de050e79 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -131,6 +131,26 @@ footnote::after { width: 0; } +.formattedTextBox-summarizer { + opacity :0.5; + position: relative; + width:40px; + height:20px; +} +.formattedTextBox-summarizer::after{ + content: "←" ; +} + +.formattedTextBox-summarizer-collapsed { + opacity :0.5; + position: relative; + width:40px; + height:20px; +} +.formattedTextBox-summarizer-collapsed::after { + content: "..."; +} + ol { counter-reset: deci1 0;} .decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 } .decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d811273e8..006a33011 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -15,7 +15,7 @@ import { List } from '../../../new_fields/List'; import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Utils, numberRange } from '../../../Utils'; +import { Utils, numberRange, timenow } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; @@ -117,6 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch public setFontColor(color: string) { + this._editorView!.state.storedMarks if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false; if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) { this.props.Document.color = color; @@ -654,10 +655,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } else if (this.props.isOverlay) this._editorView!.focus(); - var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks()); - let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })]; - this._editorView!.state.storedMarks = newMarks; - + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). + addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); } componentWillUnmount() { @@ -681,10 +680,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); - if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) { - //this._toolTipTextMenu.tooltip.style.opacity = "0"; - } } + this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). + addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; @@ -744,13 +742,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { - let view = this._editorView!; - let mid = view.state.doc.resolve(Math.round((start + end) / 2)); - let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); - view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid, mid))); - } - @action onFocused = (e: React.FocusEvent): void => { document.removeEventListener("keypress", this.recordKeyHandler); @@ -774,7 +765,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onClick = (e: React.MouseEvent): void => { if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - if (pos) { + if (pos && pos.pos > 0) { let node = this._editorView!.state.doc.nodeAt(pos.pos); let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { @@ -833,24 +824,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); // }, 0); } - function timenow() { - var now = new Date(); - let ampm = 'am'; - let h = now.getHours(); - let m: any = now.getMinutes(); - let s: any = now.getSeconds(); - if (h >= 12) { - if (h > 12) h -= 12; - ampm = 'pm'; - } - - if (m < 10) m = '0' + m; - return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; - } - var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks()); - let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - this._editorView!.state.storedMarks = newMarks; - // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index a703f1cef..1462cdfad 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -308,7 +308,7 @@ export namespace Doc { // gets the document's prototype or returns the document if it is a prototype export function GetProto(doc: Doc) { - return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc); + return doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); } export function GetDataDoc(doc: Doc): Doc { let proto = Doc.GetProto(doc); -- cgit v1.2.3-70-g09d2 From 95f773b56b8ed4fa95b1fd308c19baee1744275a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 09:18:11 -0400 Subject: added note types. fixed user_mark --- src/client/util/ProsemirrorExampleTransfer.ts | 5 +++++ src/client/util/RichTextSchema.tsx | 3 +-- src/client/views/ContextMenu.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 ++++++++ src/client/views/nodes/FormattedTextBox.tsx | 22 ++++++++++------------ 5 files changed, 25 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 9f6da7ade..55e07cfb9 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -14,6 +14,7 @@ export type KeyMap = { [key: string]: any }; export default function buildKeymap>(schema: S, mapKeys?: KeyMap): KeyMap { let keys: { [key: string]: any } = {}, type; + keys["ACTIVE"] = false; function bind(key: string, cmd: any) { if (mapKeys) { let mapped = mapKeys[key]; @@ -143,6 +144,10 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { + if (!keys["ACTIVE"]) { + dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); + return true; + } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => { // marks && tx3.ensureMarks(marks); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 31a65dd3a..675c1d387 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -381,7 +381,6 @@ export const marks: { [index: string]: MarkSpec } = { modified: { default: "when?" } }, group: "inline", - inclusive: false, toDOM(node: any) { let hideUsers = node.attrs.hide_users; let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail); @@ -759,7 +758,7 @@ export class SummarizedView { 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, getPos() + 1); + 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(); diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 760736501..e27318429 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -246,7 +246,7 @@ export class ContextMenu extends React.Component { this.selectedIndex--; } e.preventDefault(); - } else if (e.key === "Enter") { + } else if (e.key === "Enter" || e.key === "Tab") { const item = this.flatItems[this.selectedIndex]; item.event(); this.closeMenu(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a7acd9e91..aadb7f6e9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -879,9 +879,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); + layoutItems.push({ description: "1: Note", event: () => this.createText("yellow"), icon: "eye" }); + layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" }); + layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" }); + layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } + createText = (color: string) => { + let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); + this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color })) + } private childViews = () => [ , diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0d530c9a1..944cc3211 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,6 +173,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } + this._keymap["ACTIVE"] = true; this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); @@ -307,14 +308,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); } + _keymap: any = undefined; @computed get config() { + this._keymap = buildKeymap(schema); + this._keymap["ACTIVE"] = this.extensionDoc.text; return { schema, inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ this.tooltipTextMenuPlugin(), history(), - keymap(buildKeymap(schema)), + keymap(this._keymap), keymap(baseKeymap), // this.tooltipLinkingMenuPlugin(), new Plugin({ @@ -325,7 +329,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe formattedTextBoxCommentPlugin ] : [ history(), - keymap(buildKeymap(schema)), + keymap(this._keymap), keymap(baseKeymap), ] }; @@ -626,8 +630,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } else if (this.props.isOverlay) this._editorView!.focus(); - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). - addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; } componentWillUnmount() { @@ -651,8 +654,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } - this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark). - addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; @@ -786,14 +787,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe SelectionManager.DeselectAll(); } e.stopPropagation(); - if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); + if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); - // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo - // setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - // SelectionManager.DeselectAll(); - // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - // }, 0); } + //this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; -- cgit v1.2.3-70-g09d2 From eb05b987d7a1b2ca2e50268a0c15f2de7d44c5bd Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 10:11:23 -0400 Subject: cleanup of prosemirror stuff. --- src/client/util/ProsemirrorExampleTransfer.ts | 11 ++----- src/client/util/RichTextSchema.tsx | 2 -- src/client/views/PreviewCursor.tsx | 6 ++-- src/client/views/nodes/FormattedTextBox.tsx | 47 +++++++++++---------------- 4 files changed, 24 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 55e07cfb9..e7566e3a4 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -116,8 +116,7 @@ export default function buildKeymap>(schema: S, mapKeys?: marks && tx2.setStoredMarks([...marks]); dispatch(tx2); })) { // couldn't sink into an existing list, so wrap in a new one - let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)); - let newstate = state.applyTransaction(sxf); + let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { updateBullets(tx2); // when promoting to a list, assume list will format things so don't copy the stored marks. @@ -144,16 +143,12 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!keys["ACTIVE"]) { + if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); return true; } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => { - // marks && tx3.ensureMarks(marks); - // marks && tx3.setStoredMarks(marks); - dispatch(tx3); - })) { + if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { marks && tx3.ensureMarks(marks); marks && tx3.setStoredMarks(marks); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 675c1d387..baa95acb0 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -103,7 +103,6 @@ export const nodes: { [index: string]: NodeSpec } = { textslice: { default: undefined }, }, group: "inline", - inclusive: false, toDOM(node) { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; @@ -333,7 +332,6 @@ export const marks: { [index: string]: MarkSpec } = { }, ], inclusive: false, - priority: 100, toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 1329dc02c..45a8556bf 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -93,9 +93,7 @@ export class PreviewCursor extends React.Component<{}> { @action onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. In FormattedTextBox, we set the - // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. 112- + // Mixing events between React and Native is finicky. //if not these keys, make a textbox if preview cursor is active! if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && @@ -103,7 +101,7 @@ export class PreviewCursor extends React.Component<{}> { e.key !== "NumLock" && (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && - !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { + !e.defaultPrevented) { if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 944cc3211..8d3286d71 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,19 +173,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - this._keymap["ACTIVE"] = true; + this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; + this.updateTitle(); let title = StrCast(this.dataDoc.title); - if (title && title.startsWith("-") && this._editorView && !this.Document.customTitle) { - let str = this._editorView.state.doc.textContent; - let titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } + } + } + + updateTitle = () => { + if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { + let str = this._editorView.state.doc.textContent; + let titlestr = str.substr(0, Math.min(40, str.length)); + this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); } } @@ -311,7 +315,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); - this._keymap["ACTIVE"] = this.extensionDoc.text; + this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -335,11 +339,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }; } - @action - rebuildEditor() { - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); - } - componentDidMount() { if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), @@ -583,11 +582,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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 }); - if (linkIndex !== -1) { - marks.splice(linkIndex, 1, link); - } else { - marks.push(link); - } + marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } } @@ -630,6 +625,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.props.select(false); } else if (this.props.isOverlay) 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: timenow() })]; } @@ -734,6 +730,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { + // 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. if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); if (pos && pos.pos > 0) { @@ -790,16 +787,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - //this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; - // stop propagation doesn't seem to stop propagation of native keyboard events. - // so we set a flag on the native event that marks that the event's been handled. - (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; - if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { - let str = this._editorView.state.doc.textContent; - let titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); - } + + this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + + this.updateTitle(); + if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } @@ -820,7 +812,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe render() { - let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground || -- cgit v1.2.3-70-g09d2 From 2707e0898d535cc143272b7bf3b80f829368c097 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 11:37:59 -0400 Subject: added metadata ui for text --- src/client/util/ProsemirrorExampleTransfer.ts | 27 ++++++++++++++++++++-- src/client/util/RichTextSchema.tsx | 6 +++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +++++----- src/client/views/nodes/FormattedTextBox.tsx | 11 ++++++++- 4 files changed, 47 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index e7566e3a4..da26da4f9 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -150,8 +150,8 @@ export default function buildKeymap>(schema: S, mapKeys?: var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - marks && tx3.ensureMarks(marks); - marks && tx3.setStoredMarks(marks); + marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { dispatch(tx3); } @@ -161,6 +161,29 @@ export default function buildKeymap>(schema: S, mapKeys?: } return true; }); + bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); + let tx = state.tr; + marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + dispatch(tx); + return false; + }); + bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { + let range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { + return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); + }); + let path = (state.doc.resolve(state.selection.from - 1) as any).path; + let spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; + let textsel = TextSelection.create(state.doc, range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator, range!.end); + let 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)); + } + return false; + }); return keys; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index baa95acb0..5ee445590 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -315,6 +315,12 @@ export const marks: { [index: string]: MarkSpec } = { } }, + metadata: { + toDOM() { + return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }]; + } + }, + highlight: { parseDOM: [ { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aadb7f6e9..074bc1822 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -879,16 +879,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - layoutItems.push({ description: "1: Note", event: () => this.createText("yellow"), icon: "eye" }); - layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" }); - layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" }); - layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" }); + layoutItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); + layoutItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); + layoutItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); + layoutItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (color: string) => { + createText = (noteStyle: string, color: string) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color })) + this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color })) } private childViews = () => [ diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8d3286d71..c09e88592 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -173,6 +173,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } + + let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to); + let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : ""; + let key = text.split("::")[0]; + let value = text.split("::")[text.split("::").length - 1]; + this.dataDoc[key] = value; + } + this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; @@ -787,7 +797,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); this.updateTitle(); -- cgit v1.2.3-70-g09d2 From 173863d85ee590c276bf22b1cfe91e0d00986720 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 13:45:04 -0400 Subject: added named target docs from rich text. --- src/client/documents/Documents.ts | 4 +-- src/client/util/ProsemirrorExampleTransfer.ts | 16 +++++---- src/client/util/RichTextSchema.tsx | 12 ++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 5 ++- src/client/views/nodes/FormattedTextBox.tsx | 40 ++++++++++++++++------ 6 files changed, 54 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ef8b68c2f..fbdfa8966 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -419,8 +419,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options }); } - export function FreeformDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }); + export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id); } export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array, options: DocumentOptions) { diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index da26da4f9..cc2ae7d38 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -142,6 +142,11 @@ export default function buildKeymap>(schema: S, mapKeys?: } }); + let 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; + } bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); @@ -150,8 +155,7 @@ export default function buildKeymap>(schema: S, mapKeys?: var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); + splitMetadata(marks, tx3); if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction>) => void))) { dispatch(tx3); } @@ -163,10 +167,7 @@ export default function buildKeymap>(schema: S, mapKeys?: }); bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - let tx = state.tr; - marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata)); - dispatch(tx); + dispatch(splitMetadata(marks, state.tr)); return false; }); bind(":", (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -180,7 +181,8 @@ export default function buildKeymap>(schema: S, mapKeys?: 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)); + 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; }); diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 5ee445590..6bae63174 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -317,7 +317,17 @@ export const marks: { [index: string]: MarkSpec } = { metadata: { toDOM() { - return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }]; + 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', { style: 'background:rgba(100, 100, 100, 0.1);' }]; } }, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 074bc1822..fac4d4970 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -888,7 +888,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { createText = (noteStyle: string, color: string) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color })) + this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) } private childViews = () => [ diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 27eafd769..5015ee39a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -93,9 +93,8 @@ export class MarqueeView extends React.Component } }); } else if (!e.ctrlKey) { - let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); - newBox.proto!.autoHeight = true; - this.props.addLiveTextDocument(newBox); + this.props.addLiveTextDocument( + Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); } e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c09e88592..1dd84a3db 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -167,6 +167,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe dispatchTransaction = (tx: Transaction) => { if (this._editorView) { + let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + if (metadata) { + let 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]; + let split = text.split("::"); + if (split.length > 1 && split[1]) { + let key = split[0]; + let value = split[split.length - 1]; + + DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value)); + const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, location: "onRight", title: value }); + const mval = this._editorView!.state.schema.marks.metadataVal.create(); + let 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); this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected @@ -174,15 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to); - let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : ""; - let key = text.split("::")[0]; - let value = text.split("::")[text.split("::").length - 1]; - this.dataDoc[key] = value; - } - this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu this._applyingChange = true; @@ -191,7 +203,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; this.updateTitle(); - let title = StrCast(this.dataDoc.title); } } @@ -670,6 +681,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; } + let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.pos); + if (node) { + let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); + href = link && link.attrs.href; + location = link && link.attrs.location; + } if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; @@ -690,7 +707,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else if (jumpToDoc) { DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); - + } else { + DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } } }); -- cgit v1.2.3-70-g09d2 From 0d9133bbd8417e68dfd62706369067c1e2e30c97 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 15:59:10 -0400 Subject: turned link lines back on... switched metadata links into real links --- src/client/documents/Documents.ts | 4 ++-- src/client/util/RichTextSchema.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormLinksView.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 15 ++++++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbdfa8966..ae65fde1e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -606,13 +606,13 @@ export namespace Docs { export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) { + export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string) { if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; if (target === CurrentUserUtils.UserDocument) return undefined; - let linkDocProto = new Doc(); + let linkDocProto = new Doc(id, true); UndoManager.RunInBatch(() => { linkDocProto.type = DocumentType.LINK; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6bae63174..8851839a2 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -327,7 +327,7 @@ export const marks: { [index: string]: MarkSpec } = { }, metadataVal: { toDOM() { - return ['span', { style: 'background:rgba(100, 100, 100, 0.1);' }]; + return ['span']; } }, @@ -347,7 +347,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, ], - inclusive: false, + inclusive: true, toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 2d94f1b8e..a593128be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -120,9 +120,9 @@ export class CollectionFreeFormLinksView extends React.Component - {/* + {this.uniqueConnections} - */} + {this.props.children}
); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1dd84a3db..5f185d8ae 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -180,8 +180,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let key = split[0]; let value = split[split.length - 1]; - DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value)); - const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, location: "onRight", title: value }); + let 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); + if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } + else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id); + }) + }); + const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); const mval = this._editorView!.state.schema.marks.metadataVal.create(); let 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); @@ -203,6 +210,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); this._applyingChange = false; this.updateTitle(); + this.tryUpdateHeight(); } } @@ -817,12 +825,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); - this.updateTitle(); - if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } - this.tryUpdateHeight(); } @action -- cgit v1.2.3-70-g09d2 From 3e94ae1fff76cf7190d3f95ff2b47e781c315af6 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 6 Sep 2019 16:20:53 -0400 Subject: index fix --- src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/index.ts b/src/server/index.ts index 17cd59ec7..d2c6584cc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport(1); + let viewport = page.getViewport({scale: 1}); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From ee8f7e6ff149defe919a5ac219ed700a4688b46c Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 6 Sep 2019 17:46:02 -0400 Subject: fixed image resizing within text --- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/views/nodes/WebBox.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 8851839a2..153aaa791 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -598,9 +598,9 @@ export class ImageResizeView { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); view.dispatch( - view.state.tr.setNodeMarkup(getPos(), null, + view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, { src: node.attrs.src, width: self._outer.style.width }) - .setSelection(view.state.selection)); + ); }; document.addEventListener("pointermove", onpointermove); diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 43220df71..fbe9bf063 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -30,6 +30,7 @@ width: 100%; height: 100%; position: absolute; + pointer-events: all; } .webBox-button { -- cgit v1.2.3-70-g09d2 From 4d4bebb5d2e7555fac4224a497b815c40365fe91 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 21:10:31 -0400 Subject: fixed clustering to be persistent --- src/client/views/ContextMenu.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 41 +++++++++++++++++++--- src/new_fields/Doc.ts | 9 +++-- 3 files changed, 44 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index e27318429..890bfdfb7 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -248,7 +248,7 @@ export class ContextMenu extends React.Component { e.preventDefault(); } else if (e.key === "Enter" || e.key === "Tab") { const item = this.flatItems[this.selectedIndex]; - item.event(); + item && item.event(); this.closeMenu(); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index fac4d4970..4f6055260 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -256,7 +256,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); this.bringToFront(newBox); - this.updateClusters(); + this.updateCluster(newBox); return true; } private selectDocuments = (docs: Doc[]) => { @@ -324,7 +324,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.bringToFront(d); }); - this.updateClusters(); + de.data.droppedDocuments.length == 1 && this.updateCluster(de.data.droppedDocuments[0]); } } else if (de.data instanceof DragManager.AnnotationDragData) { @@ -384,6 +384,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } @observable sets: (Doc[])[] = []; + + @undoBatch @action updateClusters() { this.sets.length = 0; @@ -412,12 +414,42 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.sets.map((set, i) => set.map(member => member.cluster = i)); } + @undoBatch + @action + updateCluster(doc: Doc) { + if (this.props.Document.useClusters) { + this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); + let preferredInd = NumCast(doc.cluster); + doc.cluster = -1; + this.sets.map((set, i) => set.map(member => { + if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + doc.cluster = i; + } + })); + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + doc.cluster = preferredInd; + } + this.sets.map((set, i) => { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + doc.cluster = i; + } + }); + if (doc.cluster === -1) { + doc.cluster = this.sets.length; + this.sets.push([doc]); + } else { + for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]); + this.sets[doc.cluster].push(doc); + } + } + } + getClusterColor = (doc: Doc) => { if (this.props.Document.useClusters) { let cluster = NumCast(doc.cluster); if (this.sets.length <= cluster) { - setTimeout(() => this.updateClusters(), 0); - return; + setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0); + return ""; } let set = this.sets.length > cluster ? this.sets[cluster] : undefined; let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; @@ -869,6 +901,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; this.props.Document.useClusters = !this.props.Document.useClusters; + this.updateClusters(); }, icon: !this.props.Document.useClusters ? "braille" : "braille" }); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index be4bf232b..e3d7cc9ed 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -300,9 +300,9 @@ export namespace Doc { export function AreProtosEqual(doc?: Doc, other?: Doc) { if (!doc || !other) return false; let r = (doc === other); - let r2 = (doc.proto === other); - let r3 = (other.proto === doc); - let r4 = (doc.proto === other.proto && other.proto !== undefined); + let r2 = (Doc.GetProto(doc) === other); + let r3 = (Doc.GetProto(other) === doc); + let r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); return r || r2 || r3 || r4; } @@ -327,6 +327,9 @@ export namespace Doc { return Array.from(results); } + export function IndexOf(toFind: Doc, list: Doc[]) { + return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)) + } export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { if (target[key] === undefined) { Doc.GetProto(target)[key] = new List(); -- cgit v1.2.3-70-g09d2 From 0fea8592b5bd790334d0557c3ef30eb03973c601 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 6 Sep 2019 23:19:21 -0400 Subject: changed sub-menu pop up location. added rotation jitter --- src/client/views/ContextMenuItem.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 14 ++++++++++---- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 90f7be33f..1a0839060 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -89,7 +89,7 @@ export class ContextMenuItem extends React.Component +
{this._items.map(prop => )}
; return ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4f6055260..2df2a3464 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -152,6 +152,7 @@ export namespace PivotView { y={pos.y} width={pos.width} height={pos.height} + jitterRotation={NumCast(target.props.Document.jitterRotation)} {...target.getChildDocumentViewProps(doc)} />, bounds: { @@ -797,6 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (pair.layout && !(pair.data instanceof Promise)) { prev.push({ ele: , bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } @@ -912,10 +914,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - layoutItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - layoutItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - layoutItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - layoutItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); + + let noteItems: ContextMenuProps[] = []; + noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); + noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); + noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); + noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c9c394960..f07584b4f 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -8,12 +8,14 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView" import "./DocumentView.scss"; import React = require("react"); import { Doc } from "../../../new_fields/Doc"; +import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { x?: number; y?: number; width?: number; height?: number; + jitterRotation: number; } const schema = createSchema({ @@ -27,7 +29,7 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(FreeformDocument) { - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}) `; } + @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; } @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; } @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; } @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } -- cgit v1.2.3-70-g09d2 From 8b7bfb4f9ecc9f85e9a413f57decd74e88179a6e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 7 Sep 2019 01:37:13 -0400 Subject: fixed image dragging within textboxes --- src/client/util/RichTextSchema.tsx | 17 +++++++++-------- src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 153aaa791..6ded78b4d 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -9,6 +9,7 @@ import { undo, redo } from "prosemirror-history"; import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands"; import { Domain } from "domain"; import { DOM } from "@fortawesome/fontawesome-svg-core"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -124,9 +125,10 @@ export const nodes: { [index: string]: NodeSpec } = { inline: true, attrs: { src: {}, - width: { default: "100px" }, + width: { default: 100 }, alt: { default: null }, - title: { default: null } + title: { default: null }, + float: { default: "left" } }, group: "inline", draggable: true, @@ -140,11 +142,6 @@ export const nodes: { [index: string]: NodeSpec } = { }; } }], - // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why? - toDOM(node) { - const attrs = { style: `width: ${node.attrs.width}` }; - return ["img", { ...node.attrs, ...attrs }]; - } }, video: { @@ -571,6 +568,7 @@ export class ImageResizeView { this._outer.style.width = node.attrs.width; this._outer.style.display = "inline-block"; this._outer.style.overflow = "hidden"; + (this._outer.style as any).float = node.attrs.float; this._img.setAttribute("src", node.attrs.src); this._img.style.width = "100%"; @@ -592,6 +590,8 @@ export class ImageResizeView { const currentX = e.pageX; const diffInPx = currentX - startX; self._outer.style.width = `${startWidth + diffInPx}`; + //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1"); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0"; }; const onpointerup = () => { @@ -600,7 +600,8 @@ export class ImageResizeView { view.dispatch( view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, { src: node.attrs.src, width: self._outer.style.width }) - ); + ); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; }; document.addEventListener("pointermove", onpointermove); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 5f185d8ae..794d3a573 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -261,7 +261,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // } } } - + setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { + let view = this._editorView!; + let mid = view.state.doc.resolve(Math.round((start + end) / 2)); + let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); + view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid))); + } protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; this.dropDisposer && this.dropDisposer(); @@ -276,7 +281,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // We're dealing with an internal document drop let url = de.data.urlField.url.href; let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; - this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url }))); + let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); + this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url }))); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; @@ -629,6 +635,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } if (this._proseRef) { + let self = this; this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), -- cgit v1.2.3-70-g09d2 From af34c087bf3226c09a657959cc14fc4ae291feb0 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 7 Sep 2019 13:51:11 -0400 Subject: added linking to images in text boxes --- src/client/util/RichTextSchema.tsx | 51 ++++++++++++++++++++++------- src/client/views/MainOverlayTextBox.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 +++-- 3 files changed, 46 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 6ded78b4d..05a37759f 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,15 +1,16 @@ -import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model"; +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 { TextSelection, EditorState } from "prosemirror-state"; -import { Doc } from "../../new_fields/Doc"; +import { EditorState, TextSelection } from "prosemirror-state"; import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; -import { keymap } from "prosemirror-keymap"; -import { undo, redo } from "prosemirror-history"; -import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands"; -import { Domain } from "domain"; -import { DOM } from "@fortawesome/fontawesome-svg-core"; +import { Doc } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { DocServer } from "../DocServer"; +import { Cast, NumCast } from "../../new_fields/Types"; +import { DocumentManager } from "./DocumentManager"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -128,7 +129,8 @@ export const nodes: { [index: string]: NodeSpec } = { width: { default: 100 }, alt: { default: null }, title: { default: null }, - float: { default: "left" } + float: { default: "left" }, + docid: { default: "" } }, group: "inline", draggable: true, @@ -560,7 +562,7 @@ export class ImageResizeView { _handle: HTMLElement; _img: HTMLElement; _outer: HTMLElement; - constructor(node: any, view: any, getPos: any) { + constructor(node: any, view: any, getPos: any, addDocTab: any) { this._handle = document.createElement("span"); this._img = document.createElement("img"); this._outer = document.createElement("span"); @@ -581,6 +583,33 @@ export class ImageResizeView { this._handle.style.bottom = "-10px"; this._handle.style.right = "-10px"; let self = this; + this._img.onpointerdown = function (e: any) { + if (!view.isOverlay || e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + DocServer.GetRefField(node.attrs.docid).then(async linkDoc => { + if (linkDoc instanceof Doc) { + let proto = Doc.GetProto(linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let jumpToDoc = await Cast(linkDoc.anchor2, Doc); + if (jumpToDoc) { + if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + return; + } + } + if (targetContext) { + DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + } else if (jumpToDoc) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + } else { + DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab")); + } e.ctrlKey + } + }); + } + } this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); @@ -599,7 +628,7 @@ export class ImageResizeView { document.removeEventListener("pointerup", onpointerup); view.dispatch( view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, - { src: node.attrs.src, width: self._outer.style.width }) + { ...node.attrs, width: self._outer.style.width }) ); FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; }; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 27e0d181f..c3a2cb214 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -25,7 +25,6 @@ export class MainOverlayTextBox extends React.Component private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject; private _textBottom: boolean | undefined; - private _textAutoHeight: boolean | undefined; private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); }; private _outerdiv: HTMLElement | null = null; private _textBox: FormattedTextBox | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 794d3a573..ae1393fc4 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -278,11 +278,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe drop = async (e: Event, de: DragManager.DropEvent) => { // We're dealing with a link to a document if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) { + let target = de.data.embeddableSourceDoc; // We're dealing with an internal document drop let url = de.data.urlField.url.href; let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url }))); + this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); + DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; @@ -641,7 +643,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, nodeViews: { - image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }, + image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } @@ -696,7 +698,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; } - let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.pos); + 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); href = link && link.attrs.href; -- cgit v1.2.3-70-g09d2 From 280675c94b482147bbc81fdd317de6184b33f4e3 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sat, 7 Sep 2019 15:58:34 -0400 Subject: pdf stuffs --- src/client/views/pdf/Annotation.tsx | 27 +++++++++++++-------------- src/client/views/pdf/PDFViewer.tsx | 29 ++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 6f77a0a5b..eeb2531a2 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; @@ -13,10 +13,7 @@ import { PresBox } from "../nodes/PresBox"; interface IAnnotationProps { anno: Doc; - index: number; - ParentIndex: () => number; fieldExtensionDoc: Doc; - scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; pinToPres: (document: Doc) => void; } @@ -33,10 +30,7 @@ interface IRegionAnnotationProps { y: number; width: number; height: number; - index: number; - ParentIndex: () => number; fieldExtensionDoc: Doc; - scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; pinToPres: (document: Doc) => void; document: Doc; @@ -45,9 +39,11 @@ interface IRegionAnnotationProps { @observer class RegionAnnotation extends React.Component { private _reactionDisposer?: IReactionDisposer; - private _scrollDisposer?: IReactionDisposer; + private _brushDisposer?: IReactionDisposer; private _mainCont: React.RefObject = React.createRef(); + @observable private _brushed: boolean = false; + componentDidMount() { this._reactionDisposer = reaction( () => this.props.document.delete, @@ -55,15 +51,18 @@ class RegionAnnotation extends React.Component { { fireImmediately: true } ); - this._scrollDisposer = reaction( - () => this.props.ParentIndex(), - (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale) - ); + this._brushDisposer = reaction( + () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.IsBrushed(FieldValue(Cast(this.props.document.group, Doc))!), + (brushed) => { + if (brushed !== undefined) { + runInAction(() => this._brushed = brushed); + } + } + ) } componentWillUnmount() { this._reactionDisposer && this._reactionDisposer(); - this._scrollDisposer && this._scrollDisposer(); } deleteAnnotation = () => { @@ -126,7 +125,7 @@ class RegionAnnotation extends React.Component { left: this.props.x, width: this.props.width, height: this.props.height, - backgroundColor: this.props.ParentIndex() === this.props.index ? "green" : StrCast(this.props.document.color) + backgroundColor: this._brushed ? "green" : StrCast(this.props.document.color) }} />); } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index e5917fefc..91fc755c8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -76,7 +76,15 @@ export class PDFViewer extends React.Component { return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer); } - @computed get filteredAnnotations() { + @computed get allAnnotations() { + let annotations = DocListCast(this.props.fieldExtensionDoc.annotations); + return annotations.filter(anno => { + let run = this._script.run({ this: anno }); + return run.success ? run.result : true; + }) + } + + @computed get nonDocAnnotations() { return this._annotations.filter(anno => { let run = this._script.run({ this: anno }); return run.success ? run.result : true; @@ -101,12 +109,15 @@ export class PDFViewer extends React.Component { this._filterReactionDisposer = reaction( () => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }), action(({ scriptField, annos }: { scriptField: FieldResult, annos: Doc[] }) => { + let oldScript = this._script.originalScript; this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; + if (this._script.originalScript !== oldScript) { + this.Index = -1; + } annos.forEach(d => { let run = this._script.run(d); d.opacity = !run.success || run.result ? 1 : 0; }); - this.Index = -1; }), { fireImmediately: true } ); @@ -295,12 +306,20 @@ export class PDFViewer extends React.Component { prevAnnotation = (e: React.MouseEvent) => { e.stopPropagation(); this.Index = Math.max(this.Index - 1, 0); + let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]; + this.allAnnotations.forEach(d => Doc.UnBrushDoc(d)); + Doc.BrushDoc(scrollToAnnotation); + this.props.scrollTo(NumCast(scrollToAnnotation.y)); } @action nextAnnotation = (e: React.MouseEvent) => { e.stopPropagation(); - this.Index = Math.min(this.Index + 1, this.filteredAnnotations.length - 1); + this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); + let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]; + this.allAnnotations.forEach(d => Doc.UnBrushDoc(d)); + Doc.BrushDoc(scrollToAnnotation); + this.props.scrollTo(NumCast(scrollToAnnotation.y)); } sendAnnotations = (page: number) => { @@ -413,8 +432,8 @@ export class PDFViewer extends React.Component {
- {this.filteredAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} + {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => + )}
e.stopPropagation()} style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}> -- cgit v1.2.3-70-g09d2 From 01f0c9b113b3956ae1cdaa3ee8dbc3c7145bf652 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 8 Sep 2019 17:32:04 -0400 Subject: i think pdfs should now not require scrolling to 'correct' the pan position --- src/client/views/pdf/PDFViewer.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 91fc755c8..7bc1d3507 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -181,6 +181,7 @@ export class PDFViewer extends React.Component { let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY)); this.props.setPanY && this.props.setPanY(startY); + this.props.scrollTo(startY); } } -- cgit v1.2.3-70-g09d2 From f43294e192279fd3205be54f891a97ea3ccb6e99 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 9 Sep 2019 11:26:09 -0400 Subject: server compiling tweaks --- src/server/apis/google/GooglePhotosUploadUtils.ts | 176 ++++++++++++++++++++++ src/server/index.ts | 6 +- 2 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 src/server/apis/google/GooglePhotosUploadUtils.ts (limited to 'src') diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts new file mode 100644 index 000000000..35f986250 --- /dev/null +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -0,0 +1,176 @@ +import request = require('request-promise'); +import { GoogleApiServerUtils } from './GoogleApiServerUtils'; +import * as fs from 'fs'; +import { Utils } from '../../../Utils'; +import * as path from 'path'; +import { Opt } from '../../../new_fields/Doc'; +import * as sharp from 'sharp'; + +const uploadDirectory = path.join(__dirname, "../../public/files/"); + +export namespace GooglePhotosUploadUtils { + + export interface Paths { + uploadDirectory: string; + credentialsPath: string; + tokenPath: string; + } + + export interface MediaInput { + url: string; + description: string; + } + + const prepend = (extension: string) => `https://photoslibrary.googleapis.com/v1/${extension}`; + const headers = (type: string) => ({ + 'Content-Type': `application/${type}`, + 'Authorization': Bearer, + }); + + let Bearer: string; + let Paths: Paths; + + export const initialize = async (paths: Paths) => { + Paths = paths; + const { tokenPath, credentialsPath } = paths; + const token = await GoogleApiServerUtils.RetrieveAccessToken({ tokenPath, credentialsPath }); + Bearer = `Bearer ${token}`; + }; + + export const DispatchGooglePhotosUpload = async (url: string) => { + const body = await request(url, { encoding: null }); + const parameters = { + method: 'POST', + headers: { + ...headers('octet-stream'), + 'X-Goog-Upload-File-Name': path.basename(url), + 'X-Goog-Upload-Protocol': 'raw' + }, + uri: prepend('uploads'), + body + }; + return new Promise(resolve => request(parameters, (error, _response, body) => resolve(error ? undefined : body))); + }; + + export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }) => { + return new Promise((resolve, reject) => { + const parameters = { + method: 'POST', + headers: headers('json'), + uri: prepend('mediaItems:batchCreate'), + body: { newMediaItems } as any, + json: true + }; + album && (parameters.body.albumId = album.id); + request(parameters, (error, _response, body) => { + if (error) { + reject(error); + } else { + resolve(body); + } + }); + }); + }; + +} + +export namespace DownloadUtils { + + export interface Size { + width: number; + suffix: string; + } + + export const Sizes: { [size: string]: Size } = { + SMALL: { width: 100, suffix: "_s" }, + MEDIUM: { width: 400, suffix: "_m" }, + LARGE: { width: 900, suffix: "_l" }, + }; + + const png = ".png"; + const pngs = [".png", ".PNG"]; + const jpgs = [".jpg", ".JPG", ".jpeg", ".JPEG"]; + const formats = [".jpg", ".png", ".gif"]; + const size = "content-length"; + const type = "content-type"; + + export interface UploadInformation { + mediaPaths: string[]; + fileNames: { [key: string]: string }; + contentSize?: number; + contentType?: string; + } + + const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; + const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); + + export const UploadImage = async (url: string, filename?: string, prefix = ""): Promise> => { + const resolved = filename ? sanitize(filename) : generate(prefix, url); + const extension = path.extname(url) || path.extname(resolved) || png; + let information: UploadInformation = { + mediaPaths: [], + fileNames: { clean: resolved } + }; + const { isLocal, stream, normalized } = classify(url); + url = normalized; + if (!isLocal) { + const metadata = (await new Promise((resolve, reject) => { + request.head(url, async (error, res) => { + if (error) { + return reject(error); + } + resolve(res); + }); + })).headers; + information.contentSize = parseInt(metadata[size]); + information.contentType = metadata[type]; + } + return new Promise(async (resolve, reject) => { + const resizers = [ + { resizer: sharp().rotate(), suffix: "_o" }, + ...Object.values(Sizes).map(size => ({ + resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), + suffix: size.suffix + })) + ]; + if (pngs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.png()); + } else if (jpgs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.jpeg()); + } else if (!formats.includes(extension.toLowerCase())) { + return reject(); + } + for (let resizer of resizers) { + const suffix = resizer.suffix; + let mediaPath: string; + await new Promise(resolve => { + const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; + information.mediaPaths.push(mediaPath = uploadDirectory + filename); + information.fileNames[suffix] = filename; + stream(url).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) + .on('close', resolve) + .on('error', reject); + }); + } + resolve(information); + }); + }; + + const classify = (url: string) => { + const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); + return { + isLocal, + stream: isLocal ? fs.createReadStream : request, + normalized: isLocal ? path.normalize(url) : url + }; + }; + + export const createIfNotExists = async (path: string) => { + if (await new Promise(resolve => fs.exists(path, resolve))) { + return true; + } + return new Promise(resolve => fs.mkdir(path, error => resolve(error === null))); + }; + + export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index d2c6584cc..082e9422d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport({scale: 1}); + let viewport = page.getViewport(1 as any); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector: any = req.params.sector; - let action: any = req.params.action; + let sector: GoogleApiServerUtils.Service = req.params.sector; + let action: GoogleApiServerUtils.Action = req.params.action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From 172fcca8e1269133c97e0915740865e36a77d7c6 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 9 Sep 2019 12:55:54 -0400 Subject: set default link following behavior to jump to link --- src/client/views/linking/LinkFollowBox.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 13a341543..d5ed01f53 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -245,22 +245,23 @@ export class LinkFollowBox extends React.Component { let proto = Doc.GetProto(LinkFollowBox.linkDoc); let targetContext = await Cast(proto.targetContext, Doc); let sourceContext = await Cast(proto.sourceContext, Doc); + const shouldZoom = options ? options.shouldZoom : false; let dockingFunc = (document: Doc) => { this._addDocTab && this._addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); } else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!)); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); } else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page))); } else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); } this.highlightDoc(); @@ -326,7 +327,7 @@ export class LinkFollowBox extends React.Component { } //set this to be the default link behavior, can be any of the above - public defaultLinkBehavior: (options?: any) => void = this.openLinkTab; + public defaultLinkBehavior: (options?: any) => void = this.jumpToLink; @action currentLinkBehavior = () => { -- cgit v1.2.3-70-g09d2 From 3865ccd688015d92c8c551bf78ee2a9b6ece5500 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 15:40:49 -0400 Subject: added local overlay for free form views. added start of minimap. --- src/client/views/OverlayView.tsx | 4 +++- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 ++++++++++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 23 ++++++++++++++++++---- src/client/views/nodes/DocumentView.tsx | 2 -- src/server/index.ts | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index fe06e4440..da4b71e5c 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -197,7 +197,9 @@ export class OverlayView extends React.Component { render() { return (
- {this._elements} +
+ {this._elements} +
{this.overlayDocs}
); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2df2a3464..2ec2b0671 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; @@ -225,8 +225,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return bounds; } + @computed get actualContentBounds() { + return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; + } + @computed get contentBounds() { - let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; + let bounds = this.actualContentBounds; let res = { panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, @@ -775,7 +779,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; - const docs = this.childDocs; + let docs = this.childDocs; + let overlayDocs = DocListCast(this.props.Document.localOverlays); + overlayDocs && docs.push(...overlayDocs); let elements: ViewDefResult[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); @@ -967,6 +973,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } render() { + this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x; + this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y; + this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x); + this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y); const easing = () => this.props.Document.panTransformType === "Ease"; Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); return ( diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index f07584b4f..c059ff50d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,7 +1,7 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"; @@ -77,6 +77,21 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; render() { + let txf = this.transform; + let w = this.width; + let h = this.height; + let renderScript = this.Document.renderScript; + if (renderScript) { + let someView = Cast(this.Document.someView, Doc); + let minimap = Cast(this.Document.minimap, Doc); + if (someView instanceof Doc && minimap instanceof Doc) { + let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; + let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; + w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + txf = `translate(${x}px,${y}px)`; + } + } const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return (
(Docu }); } let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith(" { console.log("reading " + page); - let viewport = page.getViewport({scale: 1}); + let viewport = page.getViewport(1 as any); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From 5941e6f9904b0285fa983248535bead284aa16eb Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 15:51:01 -0400 Subject: fixed portal navigation to restore collection pan/zoom --- src/client/views/nodes/DocumentView.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1763c664e..a65f16e1c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -375,8 +375,17 @@ export class DocumentView extends DocComponent(Docu let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => { // open up target if it's not already in view ... + let cv = this.props.ContainingCollectionView; // bcz: ugh --- maybe need to have a props.unfocus() method so that we leave things in the state we found them?? + let px = cv && cv.props.Document.panX; + let py = cv && cv.props.Document.panY; + let s = cv && cv.props.Document.scale; this.props.focus(this.props.Document, true, 1); // by zooming into the button document first - setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab + setTimeout(() => { + this.props.addDocTab(document, undefined, maxLocation); + cv && (cv.props.Document.panX = px); + cv && (cv.props.Document.panY = py); + cv && (cv.props.Document.scale = s); + }, 1000); // then after the 1sec animation, open up the target in a new tab }, linkedFwdPage[altKey ? 1 : 0], targetContext); } -- cgit v1.2.3-70-g09d2 From 2d21fff15510d6eeb8975cc2459f69ca28d86d1d Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 9 Sep 2019 17:26:27 -0400 Subject: added stand-in spatial parser. --- src/client/util/TooltipTextMenu.tsx | 11 +++++++++++ src/client/views/DocumentDecorations.tsx | 6 ++++++ src/client/views/InkingControl.tsx | 7 ++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 13 +++++++++++++ .../views/collections/collectionFreeForm/MarqueeView.tsx | 1 + src/client/views/nodes/FormattedTextBox.tsx | 10 ++++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 020c51c36..c376b6f86 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -18,6 +18,7 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; +import { Cast, NumCast } from '../../new_fields/Types'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -495,10 +496,20 @@ export class TooltipTextMenu { if (markType.name[0] === 'p') { let size = this.fontSizeToNum.get(markType); if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } } else { let fontName = this.fontStylesToName.get(markType); if (fontName) { this.updateFontStyleDropdown(fontName); } + let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } } //actually apply font return toggleMark(markType)(view.state, view.dispatch, view); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 700a4b49d..7cdb16f52 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -428,6 +428,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1])); SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). map(d => d.borderRounding = `${Math.min(100, dist)}%`); + SelectionManager.SelectedDocuments().map(dv => { + let cv = dv.props.ContainingCollectionView; + let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); + let heading = NumCast(dv.props.Document.heading); + cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding)); + }) e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 3f40642b5..eb6312e78 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -9,7 +9,7 @@ import { SelectionManager } from "../util/SelectionManager"; import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; -import { StrCast } from "../../new_fields/Types"; +import { StrCast, NumCast, Cast } from "../../new_fields/Types"; import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { MainOverlayTextBox } from "./MainOverlayTextBox"; @@ -50,6 +50,11 @@ export class InkingControl extends React.Component { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); targetDoc.backgroundColor = this._selectedColor; + if (view.props.Document.heading) { + let cv = view.props.ContainingCollectionView; + let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); + cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor); + } return { target: targetDoc, previous: oldColor diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2ec2b0671..f7c1bedbb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -256,6 +256,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed + newBox.heading = 1; + for (let i = 0; i < this.childDocs.length; i++) { + if (this.childDocs[i].heading == 1) { + newBox.heading = 2; + } + } + let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; + let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); + round && (newBox.borderRounding = round); + col && (newBox.backgroundColor = col); + newBox.ruleProvider = ruleProvider; this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5015ee39a..100e6d817 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -329,6 +329,7 @@ export class MarqueeView extends React.Component this.props.addLiveTextDocument(summary); } else { + newCollection.ruleProvider = this.props.container.props.Document; this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ae1393fc4..93c97fa23 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -665,6 +665,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe else if (this.props.isOverlay) 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: timenow() })]; + let heading = this.props.Document.heading; + if (heading) { + let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); + if (ruleProvider instanceof Doc) { + let font = StrCast(ruleProvider["ruleFont_" + heading]); + let size = NumCast(ruleProvider["ruleSize_" + heading]); + size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]); + font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]); + } + } } componentWillUnmount() { -- cgit v1.2.3-70-g09d2 From 82b0b08979f63b88b93bf1419cde659cb262e2a3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 18:16:12 -0400 Subject: need toDOM for images or you cant paste them --- src/client/util/RichTextSchema.tsx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 05a37759f..e71031a87 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -144,6 +144,11 @@ export const nodes: { [index: string]: NodeSpec } = { }; } }], + // 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 }]; + } }, video: { -- cgit v1.2.3-70-g09d2 From a8be7ef95403a1f4559aeff9695e67ad2030e3b9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 21:34:34 -0400 Subject: restored input parsing rules back to prosemirror and added some news --- src/client/util/RichTextRules.ts | 52 ++++++++++++++++++----------- src/client/util/prosemirrorPatches.js | 47 ++++++++++++++++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/server/index.ts | 4 +-- 4 files changed, 83 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 8c4c76027..979b76988 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -1,14 +1,6 @@ -import { - inputRules, - wrappingInputRule, - textblockTypeInputRule, - smartQuotes, - emDash, - ellipsis -} from "prosemirror-inputrules"; -import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"; - +import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules"; import { schema } from "./RichTextSchema"; +import { wrappingInputRule } from "./prosemirrorPatches"; export const inpRules = { rules: [ @@ -21,17 +13,29 @@ export const inpRules = { // 1. ordered list wrappingInputRule( - /^(\d+)\.\s$/, + /^1\.\s$/, schema.nodes.ordered_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + () => { + 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-z]+)\.\s$/, - schema.nodes.alphabet_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + /^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 @@ -42,9 +46,17 @@ export const inpRules = { // # heading textblockTypeInputRule( - new RegExp("^(#{1,6})\\s$"), + new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, - match => ({ level: match[1].length }) - ) + match => { + return ({ level: match[1].length }); + } + ), + + new InputRule( + new RegExp(/^#([0-9]+)\s$/), + (state, match, start, end) => { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) + }), ] }; diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js index 6bf4395ad..188e3e1c5 100644 --- a/src/client/util/prosemirrorPatches.js +++ b/src/client/util/prosemirrorPatches.js @@ -2,11 +2,13 @@ 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. @@ -89,4 +91,49 @@ function sinkListItem(itemType) { } 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/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 93c97fa23..6a6000dc5 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -38,6 +38,7 @@ import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; +import { inputRules } from 'prosemirror-inputrules'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -355,8 +356,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, - inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ + inputRules(inpRules), this.tooltipTextMenuPlugin(), history(), keymap(this._keymap), diff --git a/src/server/index.ts b/src/server/index.ts index 082e9422d..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector: GoogleApiServerUtils.Service = req.params.sector; - let action: GoogleApiServerUtils.Action = req.params.action; + let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service; + let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From a1e08c0be1a1067232ea71ed3344ce44d9522bf7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 23:17:50 -0400 Subject: added center alignment for text using ^^ --- src/client/util/ParagraphNodeSpec.ts | 133 ++++++++++++++++++++++++++ src/client/util/ProsemirrorExampleTransfer.ts | 24 ++--- src/client/util/RichTextRules.ts | 23 +++++ src/client/util/RichTextSchema.tsx | 20 ++-- src/client/util/clamp.js | 15 +++ src/client/util/convertToCSSPTValue.js | 43 +++++++++ src/client/util/toCSSLineSpacing.js | 64 +++++++++++++ 7 files changed, 302 insertions(+), 20 deletions(-) create mode 100644 src/client/util/ParagraphNodeSpec.ts create mode 100644 src/client/util/clamp.js create mode 100644 src/client/util/convertToCSSPTValue.js create mode 100644 src/client/util/toCSSLineSpacing.js (limited to 'src') diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts new file mode 100644 index 000000000..3a993e1ff --- /dev/null +++ b/src/client/util/ParagraphNodeSpec.ts @@ -0,0 +1,133 @@ +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 }, + 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, + 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};`; + } + + 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 index cc2ae7d38..bac0177ad 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -52,18 +52,18 @@ export default function buildKeymap>(schema: S, mapKeys?: 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; - }); + // 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; + // }); let cmd = chainCommands(exitCode, (state, dispatch) => { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 979b76988..5d1131410 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -1,6 +1,7 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules"; import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; +import { NodeSelection } from "prosemirror-state"; export const inpRules = { rules: [ @@ -58,5 +59,27 @@ export const inpRules = { (state, match, start, end) => { return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) }), + new InputRule( + new RegExp(/^\^\^\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), + new InputRule( + new RegExp(/\^f\s$/), + (state, match, start, end) => { + let newNode = schema.nodes.footnote.create({}); + let 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))); + }), + // let newNode = schema.nodes.footnote.create({}); + // if (dispatch && state.selection.from === state.selection.to) { + // return true; + // } ] }; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index e71031a87..5081c81d8 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -11,6 +11,7 @@ import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { DocServer } from "../DocServer"; import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; +import ParagraphNodeSpec from "./ParagraphNodeSpec"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -23,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = { content: "block+" }, + footnote: { group: "inline", content: "inline*", @@ -37,14 +39,16 @@ export const nodes: { [index: string]: NodeSpec } = { parseDOM: [{ tag: "footnote" }] }, - // :: NodeSpec A plain paragraph textblock. Represented in the DOM - // as a `

` element. - paragraph: { - content: "inline*", - group: "block", - parseDOM: [{ tag: "p" }], - toDOM() { return pDOM; } - }, + // // :: NodeSpec A plain paragraph textblock. Represented in the DOM + // // as a `

` element. + // paragraph: { + // content: "inline*", + // group: "block", + // parseDOM: [{ tag: "p" }], + // toDOM() { return pDOM; } + // }, + + paragraph: ParagraphNodeSpec, // :: NodeSpec A blockquote (`

`) wrapping one or more blocks. blockquote: { diff --git a/src/client/util/clamp.js b/src/client/util/clamp.js new file mode 100644 index 000000000..9c7fd78a4 --- /dev/null +++ b/src/client/util/clamp.js @@ -0,0 +1,15 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = clamp; +function clamp(min, val, max) { + if (val < min) { + return min; + } + if (val > max) { + return max; + } + return val; +} \ No newline at end of file diff --git a/src/client/util/convertToCSSPTValue.js b/src/client/util/convertToCSSPTValue.js new file mode 100644 index 000000000..179557953 --- /dev/null +++ b/src/client/util/convertToCSSPTValue.js @@ -0,0 +1,43 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.PT_TO_PX_RATIO = exports.PX_TO_PT_RATIO = undefined; +exports.default = convertToCSSPTValue; +exports.toClosestFontPtSize = toClosestFontPtSize; + +// var _FontSizeCommandMenuButton = require('./ui/FontSizeCommandMenuButton'); + +var SIZE_PATTERN = /([\d\.]+)(px|pt)/i; + +var PX_TO_PT_RATIO = exports.PX_TO_PT_RATIO = 0.7518796992481203; // 1 / 1.33. +var PT_TO_PX_RATIO = exports.PT_TO_PX_RATIO = 1.33; + +function convertToCSSPTValue(styleValue) { + var matches = styleValue.match(SIZE_PATTERN); + if (!matches) { + return 0; + } + var value = parseFloat(matches[1]); + var unit = matches[2]; + if (!value || !unit) { + return 0; + } + if (unit === 'px') { + value = PX_TO_PT_RATIO * value; + } + return value; +} + +function toClosestFontPtSize(styleValue) { + var originalPTValue = convertToCSSPTValue(styleValue); + + // if (_FontSizeCommandMenuButton.FONT_PT_SIZES.includes(originalPTValue)) { + // return originalPTValue; + // } + + return _FontSizeCommandMenuButton.FONT_PT_SIZES.reduce(function (prev, curr) { + return Math.abs(curr - originalPTValue) < Math.abs(prev - originalPTValue) ? curr : prev; + }, Number.NEGATIVE_INFINITY); +} \ No newline at end of file diff --git a/src/client/util/toCSSLineSpacing.js b/src/client/util/toCSSLineSpacing.js new file mode 100644 index 000000000..939d11a0e --- /dev/null +++ b/src/client/util/toCSSLineSpacing.js @@ -0,0 +1,64 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = toCSSLineSpacing; + + +// Line spacing names and their values. +var LINE_SPACING_100 = exports.LINE_SPACING_100 = '125%'; +var LINE_SPACING_115 = exports.LINE_SPACING_115 = '138%'; +var LINE_SPACING_150 = exports.LINE_SPACING_150 = '165%'; +var LINE_SPACING_200 = exports.LINE_SPACING_200 = '232%'; + +var SINGLE_LINE_SPACING = exports.SINGLE_LINE_SPACING = LINE_SPACING_100; +var DOUBLE_LINE_SPACING = exports.DOUBLE_LINE_SPACING = LINE_SPACING_200; + +var NUMBER_VALUE_PATTERN = /^\d+(.\d+)?$/; + +// Normalize the css line-height vlaue to percentage-based value if applicable. +// Also, it calibrates the incorrect line spacing value exported from Google +// Doc. +function toCSSLineSpacing(source) { + if (!source) { + return ''; + } + + var strValue = String(source); + + // e.g. line-height: 1.5; + if (NUMBER_VALUE_PATTERN.test(strValue)) { + var numValue = parseFloat(strValue); + strValue = String(Math.round(numValue * 100)) + '%'; + } + + // Google Doc exports line spacing with wrong values. For instance: + // - Single => 100% + // - 1.15 => 115% + // - Double => 200% + // But the actual CSS value measured in Google Doc is like this: + // - Single => 125% + // - 1.15 => 138% + // - Double => 232% + // The following `if` block will calibrate the value if applicable. + + if (strValue === '100%') { + return LINE_SPACING_100; + } + + if (strValue === '115%') { + return LINE_SPACING_115; + } + + if (strValue === '150%') { + return LINE_SPACING_150; + } + + if (strValue === '200%') { + return LINE_SPACING_200; + } + + // e.g. line-height: 15px; + return strValue; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 149fa3116cdbb58f2eed144cc0bb90c1b1cd2b2a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 23:49:13 -0400 Subject: fixed footnote saving --- src/client/util/RichTextSchema.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 5081c81d8..3def4a579 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -757,10 +757,11 @@ export class FootnoteView { this.innerView.updateState(state); if (!tr.getMeta("fromOutside")) { - let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1); - for (let steps of transactions) { - for (let step of steps) { - outerTr.step(step.map(offsetMap)); + let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1) + for (let i = 0; i < transactions.length; i++) { + let steps = transactions[i].steps; + for (let j = 0; j < steps.length; j++) { + outerTr.step(steps[j].map(offsetMap)); } } if (outerTr.docChanged) this.outerView.dispatch(outerTr); -- cgit v1.2.3-70-g09d2 From 5ea7e3318620865146318e0f3826b6f13aec0675 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Sep 2019 13:38:16 -0400 Subject: added more support for creating stylized layouts --- src/Utils.ts | 92 ++++++++++++++++++++++ src/client/util/ProsemirrorExampleTransfer.ts | 5 -- src/client/util/RichTextRules.ts | 40 ++++++++++ src/client/util/RichTextSchema.tsx | 19 +++++ src/client/views/ContextMenu.tsx | 1 + src/client/views/DocumentDecorations.tsx | 5 ++ src/client/views/InkingControl.tsx | 32 +++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 27 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++++- src/client/views/nodes/FormattedTextBox.tsx | 48 ++++++++--- .../authentication/models/current_user_utils.ts | 20 ++--- 11 files changed, 270 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index f805ae872..3921a49c3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -53,6 +53,98 @@ export class Utils { document.body.removeChild(textArea); } + public static fromRGBAstr(rgba: string) { + let rm = rgba.match(/rgb[a]?\(([0-9]+)/); + let r = rm ? Number(rm[1]) : 0; + let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/); + let g = gm ? Number(gm[1]) : 0; + let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/); + let b = bm ? Number(bm[1]) : 0; + let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/); + let a = am ? Number(am[1]) : 0; + return { r: r, g: g, b: b, a: a }; + } + public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { + return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; + } + + public static HSLtoRGB(h: number, s: number, l: number) { + // Must be fractions of 1 + // s /= 100; + // l /= 100; + + let c = (1 - Math.abs(2 * l - 1)) * s, + x = c * (1 - Math.abs((h / 60) % 2 - 1)), + m = l - c / 2, + r = 0, + g = 0, + b = 0; + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + return { r: r, g: g, b: b }; + } + + public static RGBToHSL(r: number, g: number, b: number) { + // Make r, g, and b fractions of 1 + r /= 255; + g /= 255; + b /= 255; + + // Find greatest and smallest channel values + let cmin = Math.min(r, g, b), + cmax = Math.max(r, g, b), + delta = cmax - cmin, + h = 0, + s = 0, + l = 0; + // Calculate hue + + // No difference + if (delta == 0) + h = 0; + // Red is max + else if (cmax == r) + h = ((g - b) / delta) % 6; + // Green is max + else if (cmax == g) + h = (b - r) / delta + 2; + // Blue is max + else + h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + // Make negative hues positive behind 360° + if (h < 0) + h += 360; // Calculate lightness + + l = (cmax + cmin) / 2; + + // Calculate saturation + s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + + // Multiply l and s by 100 + // s = +(s * 100).toFixed(1); + // l = +(l * 100).toFixed(1); + + return { h: h, s: s, l: l }; + } + + public static GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index bac0177ad..1d2d33800 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -14,7 +14,6 @@ export type KeyMap = { [key: string]: any }; export default function buildKeymap>(schema: S, mapKeys?: KeyMap): KeyMap { let keys: { [key: string]: any } = {}, type; - keys["ACTIVE"] = false; function bind(key: string, cmd: any) { if (mapKeys) { let mapped = mapKeys[key]; @@ -148,10 +147,6 @@ export default function buildKeymap>(schema: S, mapKeys?: return tx; } bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu - dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); - return true; - } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 5d1131410..00e671db9 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -2,6 +2,9 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; import { NodeSelection } from "prosemirror-state"; +import { NumCast, Cast } from "../../new_fields/Types"; +import { Doc } from "../../new_fields/Doc"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; export const inpRules = { rules: [ @@ -57,6 +60,12 @@ export const inpRules = { new InputRule( new RegExp(/^#([0-9]+)\s$/), (state, match, start, end) => { + let size = Number(match[1]); + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) }), new InputRule( @@ -64,9 +73,40 @@ export const inpRules = { (state, match, start, end) => { let node = (state.doc.resolve(start) as any).nodeAfter; let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "center"; + } return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), + new InputRule( + new RegExp(/^\[\[\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "left"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), + new InputRule( + new RegExp(/^\]\]\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "right"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), new InputRule( new RegExp(/\^f\s$/), (state, match, start, end) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3def4a579..f027a4bf7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -12,6 +12,7 @@ import { DocServer } from "../DocServer"; import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; +import { times } from "async"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -422,6 +423,24 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, + // pFontFamily: { + // attrs: { + // style: { default: 'font-family: "Times New Roman", Times, serif;' }, + // }, + // parseDOM: [{ + // tag: "span", getAttrs(dom: any) { + // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; + // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; + // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; + // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; + // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; + // } + // }], + // toDOM: (node: any) => ['span', { + // style: node.attrs.style + // }] + // }, + /* FONTS */ timesNewRoman: { diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 890bfdfb7..68b97f2b6 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -250,6 +250,7 @@ export class ContextMenu extends React.Component { const item = this.flatItems[this.selectedIndex]; item && item.event(); this.closeMenu(); + e.preventDefault(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7cdb16f52..94aab8b2f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -621,6 +621,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> doc.y = (doc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight); + if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) { + doc.ignoreAspect = false; + proto.nativeWidth = nwidth = doc.width || 0; + proto.nativeHeight = nheight = doc.height || 0; + } if (fixedAspect && (!nwidth || !nheight)) { proto.nativeWidth = nwidth = doc.width || 0; proto.nativeHeight = nheight = doc.height || 0; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index eb6312e78..519792308 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,8 +10,10 @@ import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { StrCast, NumCast, Cast } from "../../new_fields/Types"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { MainOverlayTextBox } from "./MainOverlayTextBox"; +import { listSpec } from "../../new_fields/Schema"; +import { List } from "../../new_fields/List"; +import { Utils } from "../../Utils"; library.add(faPen, faHighlighter, faEraser, faBan); @@ -49,11 +51,34 @@ export class InkingControl extends React.Component { let oldColors = selected.map(view => { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); - targetDoc.backgroundColor = this._selectedColor; + if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) { + let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; + let closest = 0; + let dist = 10000000; + let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); + for (let i = 0; i < cp.length; i++) { + let cpcol = Utils.fromRGBAstr(cp[i]); + let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g)); + if (d < dist) { + dist = d; + closest = i; + } + } + cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")"; + view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); + targetDoc.backgroundColor = cp[closest]; + } else + targetDoc.backgroundColor = this._selectedColor; if (view.props.Document.heading) { let cv = view.props.ContainingCollectionView; let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); - cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor); + let parback = cv && StrCast(cv.props.Document.backgroundColor); + cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); + // if (parback && cv && parback.indexOf("rgb") !== -1) { + // let parcol = Utils.fromRGBAstr(parback); + // let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b); + // cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s); + // } } return { target: targetDoc, @@ -67,7 +92,6 @@ export class InkingControl extends React.Component { }); } }); - @action switchWidth = (width: string): void => { this._selectedWidth = width; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f7c1bedbb..c9b2c30b1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs } from "../../../documents/Documents"; @@ -262,14 +262,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { newBox.heading = 2; } } - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (newBox.borderRounding = round); - col && (newBox.backgroundColor = col); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); + PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { + if (!ruleProvider) ruleProvider = this.props.Document; + // saturation shift + // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor)); + // let hsl = Utils.RGBToHSL(back.r, back.g, back.b); + // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l }; + // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); + // OR transparency set + let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + col && (Doc.GetProto(newBox).backgroundColor = col); + + let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); + round && (newBox.borderRounding = round); + newBox.ruleProvider = ruleProvider; + this.addDocument(newBox, false); + }); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 100e6d817..56d8127e2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,11 +1,11 @@ import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult } from "../../../../new_fields/Doc"; +import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; -import { Cast, NumCast } from "../../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; @@ -20,6 +20,8 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; +import { string } from "prop-types"; +import { listSpec } from "../../../../new_fields/Schema"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -272,14 +274,30 @@ export class MarqueeView extends React.Component return d; }); } + let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let usedPaletted = new Map(); + [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + let bg = StrCast(child.backgroundColor); + if (palette.indexOf(bg) !== -1) { + palette.splice(palette.indexOf(bg), 1); + if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); + else usedPaletted.set(bg, 1); + } + }); + let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); + let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, panY: 0, - backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", - defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", + backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 6a6000dc5..0ea36cdc2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; -import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Utils, numberRange, timenow } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; +import { select } from 'async'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -203,8 +204,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu - this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); @@ -353,7 +352,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); - this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, plugins: this.props.isOverlay ? [ @@ -659,7 +657,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) { + let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } @@ -667,15 +666,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // 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: timenow() })]; let heading = this.props.Document.heading; - if (heading) { - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (ruleProvider instanceof Doc) { - let font = StrCast(ruleProvider["ruleFont_" + heading]); - let size = NumCast(ruleProvider["ruleSize_" + heading]); - size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]); - font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]); - } + if (heading && selectOnLoad) { + PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { + if (ruleProvider) { + let align = StrCast(ruleProvider["ruleAlign_" + heading]); + let font = StrCast(ruleProvider["ruleFont_" + heading]); + let size = NumCast(ruleProvider["ruleSize_" + heading]); + if (align) { + let tr = this._editorView!.state.tr; + tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true). + setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); + this._editorView!.dispatch(tr); + } + let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; + size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]); + font && (sm = [...sm, this.getFont(font)]); + this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm)); + } + }); + } + } + 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() { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9866e22eb..9d35d36d3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -115,17 +115,17 @@ export class CurrentUserUtils { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } }); - try { - const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); - NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); - await Gateway.Instance.ClearCatalog(); - const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); - let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); - let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); - // if (catprom) await Promise.all(catprom); - } catch (e) { + // try { + // const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); + // NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); + // await Gateway.Instance.ClearCatalog(); + // const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); + // let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); + // let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); + // // if (catprom) await Promise.all(catprom); + // } catch (e) { - } + // } } /* Northstar catalog ... really just for testing so this should eventually go away */ -- cgit v1.2.3-70-g09d2 From 9608245db4ba8cca6054a0641f2eb2bd2032eba6 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Sep 2019 15:55:51 -0400 Subject: fixed up some stuff with portals and added a ButtonBox menu item for running scripts over a collection --- src/client/views/ContextMenuItem.tsx | 2 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/ScriptBox.tsx | 15 +++++++++++++-- src/client/views/collections/CollectionBaseView.tsx | 4 ++++ src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 11 +++++++---- src/client/views/nodes/DocumentView.tsx | 12 +++++++++--- src/new_fields/Doc.ts | 3 ++- 7 files changed, 37 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 1a0839060..0366a6a30 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -89,7 +89,7 @@ export class ContextMenuItem extends React.Component +
{this._items.map(prop => )}
; return ( diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 519792308..aa573f16b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -64,7 +64,7 @@ export class InkingControl extends React.Component { closest = i; } } - cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")"; + cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")"; view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); targetDoc.backgroundColor = cp[closest]; } else diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 2b862a81e..7afba5e01 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -66,13 +66,24 @@ export class ScriptBox extends React.Component {
); } - public static EditClickScript(doc: Doc, fieldKey: string) { + public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; - if (script) originalText = script.script.originalScript; + if (script) { + originalText = script.script.originalScript; + if (prewrapper && originalText.startsWith(prewrapper)) { + originalText = originalText.substr(prewrapper.length); + } + if (postwrapper && originalText.endsWith(postwrapper)) { + originalText = originalText.substr(0, originalText.length - postwrapper.length); + } + } // tslint:disable-next-line: no-unnecessary-callback-wrapper let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + if (prewrapper) { + text = prewrapper + text + (postwrapper ? postwrapper : ""); + } const script = CompileScript(text, { params: { this: Doc.name }, typecheck: false, diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index b6ed6aaa0..bd8d56851 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -12,6 +12,7 @@ import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; import { DateField } from '../../../new_fields/DateField'; +import { DocumentType } from '../../documents/DocumentTypes'; export enum CollectionViewType { Invalid, @@ -103,6 +104,9 @@ export class CollectionBaseView extends React.Component { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } + if (doc.type === DocumentType.BUTTON) { + doc.collectionContext = this.props.Document; // used by docList() function in Doc.ts so that buttons can iterate over the documents in their collection + } allowDuplicates = true; let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c059ff50d..9692dd8a9 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -99,10 +99,13 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); + makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); makes.push({ description: "Into Portal", event: () => { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - this.makeBtnClicked(); + if (!DocListCast(this.props.Document.links).find(doc => { + if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; + return false; + })) { + let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); + DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); + Doc.GetProto(this.props.Document).isButton = true; + } }, icon: "window-restore" }); makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e3d7cc9ed..ccb8f4aa2 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -685,4 +685,5 @@ export namespace Doc { Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; }); Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); -Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); \ No newline at end of file +Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); +Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From be9562ef9d2ad2675641e6215f1567f7e0b9c0e3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 18:34:34 -0400 Subject: write backgroundColor to data doc --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f7c1bedbb..3ebbe0046 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -266,8 +266,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (newBox.borderRounding = round); - col && (newBox.backgroundColor = col); + round && (Doc.GetProto(newBox).borderRounding = round); + col && (Doc.GetProto(newBox).backgroundColor = col); newBox.ruleProvider = ruleProvider; this.addDocument(newBox, false); } -- cgit v1.2.3-70-g09d2 From a709e21384cef80a85eac9220739c854a96d5313 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 20:15:40 -0400 Subject: fixed several search issues with text boxes and highlighting. --- .../collections/collectionFreeForm/MarqueeView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 16 ++++--- src/client/views/search/SearchBox.tsx | 6 +-- src/client/views/search/SearchItem.tsx | 56 ++-------------------- src/server/Search.ts | 3 ++ src/server/index.ts | 3 ++ 6 files changed, 27 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 56d8127e2..0c4860be1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -288,8 +288,11 @@ export class MarqueeView extends React.Component else usedPaletted.set(bg, 1); } }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 633455f63..1e755f121 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -644,12 +644,16 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ - description: "Download document", icon: "download", event: () => { - const a = document.createElement("a"); - const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - a.href = url; - a.download = `DocExport-${this.props.Document[Id]}.zip`; - a.click(); + description: "Download document", icon: "download", event: async () => { + let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { + qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } + })); + console.log(y); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); } }); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2ad69daca..2e29838e6 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -141,7 +141,7 @@ export class SearchBox extends React.Component { private get filterQuery() { const types = FilterBox.Instance.filterTypes; const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -304,14 +304,14 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 386b5fe74..0b722c086 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss"; export interface SearchItemProps { doc: Doc; - query?: string; + query: string; highlighting: string[]; } @@ -128,68 +128,26 @@ export class LinkContextMenu extends React.Component { export class SearchItem extends React.Component { @observable _selected: boolean = false; - private _previewDoc?: Doc; onClick = () => { // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like DocumentManager.Instance.jumpToDocument(this.props.doc, false); - if (this.props.doc.data instanceof RichTextField) { - this.highlightTextBox(this.props.doc); - } - // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - highlightTextBox = (doc: Doc) => { - if (this.props.query) { - const fieldkey = 'search_string'; - if (Object.keys(doc).indexOf(fieldkey) === -1) { - doc.search_string = this.props.query; - } - else { - doc.search_string = undefined; - } - - } - } - - fitToBox = () => { - let bounds = Doc.ComputeContentBounds([this.props.doc]); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; - } - componentWillUnmount() { - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } + this.props.doc.search_string = undefined; } - //@computed @action public DocumentIcon() { let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; - } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - this._previewDoc = newRenderDoc; + let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); const docview =
{ this._useIcons = !this._useIcons; @@ -219,15 +177,9 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; - newRenderDoc.search_string = this.props.query; + this.props.doc.search_string = this.props.query; return docview; } - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : diff --git a/src/server/Search.ts b/src/server/Search.ts index 723dc101b..4a408405a 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,10 +32,13 @@ export class Search { public async search(query: any) { try { + console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); + console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; + console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 50ce2b14e..149bacf0f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,12 +198,15 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; + console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } + console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); + console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From 4df85ecd5026127c27b147b34398822307715e54 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 21:36:42 -0400 Subject: fixed search highlighting --- .../views/collections/CollectionTreeView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 13 ++++++-- src/client/views/search/SearchBox.tsx | 38 +++++++++++----------- src/client/views/search/SearchItem.tsx | 12 ++++--- src/server/Search.ts | 3 -- src/server/index.ts | 3 -- 6 files changed, 38 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 50f03005c..e31fa0b40 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -373,6 +373,7 @@ class TreeView extends React.Component { style={{ color: this.props.document.isMinimized ? "red" : "black", background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0", + fontWeight: this.props.document.search_string ? "bold" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" }} > diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1e755f121..73426b3dc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -780,6 +780,10 @@ export class DocumentView extends DocComponent(Docu let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding); let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + let searchHighlight = (!this.props.Document.search_fields ? (null) : +
+ {StrCast(this.props.Document.search_fields)} +
); return (
(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - {!showTitle && !showCaption ? this.contents : + {!showTitle && !showCaption ? + this.props.Document.search_fields ?
+ {this.contents} + {searchHighlight} +
: + this.contents :
-
{this.contents}
@@ -828,6 +836,7 @@ export class DocumentView extends DocComponent(Docu
} + {searchHighlight}
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2e29838e6..b30fd83e8 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,25 +1,23 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action, runInAction, flow, computed } from 'mobx'; -import "./SearchBox.scss"; -import "./FilterBox.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { SetupDrag } from '../../util/DragManager'; -import { Docs } from '../../documents/Documents'; -import { NumCast, Cast } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { SearchItem } from './SearchItem'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { SearchUtil } from '../../util/SearchUtil'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { RouteStore } from '../../../server/RouteStore'; -import { FilterBox } from './FilterBox'; -import { ReadStream } from 'fs'; -import * as $ from 'jquery'; -import { MainView } from '../MainView'; import { Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { SearchUtil } from '../../util/SearchUtil'; +import { MainView } from '../MainView'; +import { FilterBox } from './FilterBox'; +import "./FilterBox.scss"; +import "./SearchBox.scss"; +import { SearchItem } from './SearchItem'; library.add(faTimes); @@ -304,14 +302,16 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0b722c086..30e0454f3 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -136,8 +136,13 @@ export class SearchItem extends React.Component { @observable _useIcons = true; @observable _displayDim = 50; + componentDidMount() { + this.props.doc.search_string = this.props.query; + this.props.doc.search_fields = this.props.highlighting.join(", "); + } componentWillUnmount() { this.props.doc.search_string = undefined; + this.props.doc.search_fields = undefined; } //@computed @@ -177,7 +182,6 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
; - this.props.doc.search_string = this.props.query; return docview; } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : @@ -231,8 +235,7 @@ export class SearchItem extends React.Component { Doc.BrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => - Doc.BrushDoc(element.props.Document)); + Doc.BrushDoc(this.props.doc); } } @@ -246,8 +249,7 @@ export class SearchItem extends React.Component { Doc.UnBrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc). - forEach(element => Doc.UnBrushDoc(element.props.Document)); + Doc.UnBrushDoc(this.props.doc); } } diff --git a/src/server/Search.ts b/src/server/Search.ts index 4a408405a..723dc101b 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,13 +32,10 @@ export class Search { public async search(query: any) { try { - console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); - console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; - console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 149bacf0f..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,15 +198,12 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; - console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } - console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); - console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From edec708b4396cd3b21ea22296812d5014b1359db Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 22:18:46 -0400 Subject: got rid of zoomBasis remnants. cleaned up (just a little bit) renderScript stuff in colelctionfreeformdocumentview --- src/client/views/DocumentDecorations.tsx | 3 +- .../views/collections/ParentDocumentSelector.tsx | 4 +- .../CollectionFreeFormLinkView.tsx | 8 ++-- .../CollectionFreeFormLinksView.tsx | 4 +- src/client/views/linking/LinkFollowBox.tsx | 12 +++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 50 +++++++++++----------- src/client/views/search/SearchBox.tsx | 1 - src/client/views/search/SearchItem.tsx | 4 +- src/scraping/buxton/scraper.py | 1 - 9 files changed, 41 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 94aab8b2f..773ab8b9f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -397,8 +397,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } moveIconDoc(iconDoc: Doc) { let selView = SelectionManager.SelectedDocuments()[0]; - let zoom = NumCast(selView.props.Document.zoomBasis, 1); - let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom). + let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()). transformPoint(this._minimizedX - 12, this._minimizedY - 12); iconDoc.x = where[0] + NumCast(selView.props.Document.x); iconDoc.y = where[1] + NumCast(selView.props.Document.y); diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 17111af58..d8475a467 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -38,8 +38,8 @@ export class SelectorContextMenu extends React.Component { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 6af87b138..790c6694b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -39,10 +39,10 @@ export class CollectionFreeFormLinkView extends React.Component)[]) => field.findIndex(brush => { diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index d5ed01f53..f8807641b 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -180,8 +180,8 @@ export class LinkFollowBox extends React.Component { openColFullScreen = (options: { context: Doc }) => { if (LinkFollowBox.destinationDoc) { if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } @@ -209,8 +209,8 @@ export class LinkFollowBox extends React.Component { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } @@ -286,8 +286,8 @@ export class LinkFollowBox extends React.Component { if (LinkFollowBox.destinationDoc) { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2; options.context.panX = newPanX; options.context.panY = newPanY; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 9692dd8a9..eb7ab64f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -19,7 +19,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { } const schema = createSchema({ - zoomBasis: "number", zIndex: "number", }); @@ -29,22 +28,36 @@ const FreeformDocument = makeInterface(schema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(FreeformDocument) { - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; } - @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } - @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } - @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); } + @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } + @computed get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } + @computed get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } + @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.Document.width || 0; } + @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.Document.height || 0; } @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); } @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); } @computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); } + @computed get renderScriptDim() { + if (this.Document.renderScript) { + let someView = Cast(this.Document.someView, Doc); + let minimap = Cast(this.Document.minimap, Doc); + if (someView instanceof Doc && minimap instanceof Doc) { + let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; + let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; + let w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + let h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + return { x: x, y: y, width: w, height: h }; + } + } + return undefined; + } + contentScaling = () => this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1; panelWidth = () => this.props.PanelWidth(); panelHeight = () => this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.zoom / this.scaleToOverridingWidth) + .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => { this.props.bringToFront(this.props.Document); @@ -77,21 +90,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; render() { - let txf = this.transform; - let w = this.width; - let h = this.height; - let renderScript = this.Document.renderScript; - if (renderScript) { - let someView = Cast(this.Document.someView, Doc); - let minimap = Cast(this.Document.minimap, Doc); - if (someView instanceof Doc && minimap instanceof Doc) { - let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; - let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; - w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); - h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); - txf = `translate(${x}px,${y}px)`; - } - } const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return (
1000) { x = 0; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 30e0454f3..c56d093fa 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -71,8 +71,8 @@ export class SelectorContextMenu extends React.Component { return () => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(target.x) + NumCast(target.width) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 807216ef1..a9256073b 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -88,7 +88,6 @@ def write_collection(parse_results, display_fields, storage_key, viewType=2): "height": 600, "panX": 0, "panY": 0, - "zoomBasis": 1, "zIndex": 2, "libraryBrush": False, "viewType": viewType -- cgit v1.2.3-70-g09d2 From f4df8cedd38dbf09e282315604ecaa6cde8185e5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Sep 2019 00:56:26 -0400 Subject: added start of parameterized button maker --- src/client/views/ContextMenuItem.tsx | 4 ++-- src/client/views/EditableView.tsx | 2 +- src/client/views/ScriptBox.tsx | 41 +++++++++++++++++++++++++++++++++ src/client/views/nodes/ButtonBox.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 1 + 5 files changed, 47 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 0366a6a30..ac055be5b 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -10,7 +10,7 @@ library.add(faAngleRight); export interface OriginalMenuProps { description: string; - event: () => void; + event: (stuff?: any) => void; undoable?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; @@ -44,7 +44,7 @@ export class ContextMenuItem extends React.Component { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return (
{this.props.contents}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 7afba5e01..375a5cc93 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -10,6 +10,8 @@ import { emptyFunction } from "../../Utils"; import { ScriptCast } from "../../new_fields/Types"; import { CompileScript } from "../util/Scripting"; import { ScriptField } from "../../new_fields/ScriptField"; +import { DragManager } from "../util/DragManager"; +import { EditableView } from "./EditableView"; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; @@ -66,6 +68,45 @@ export class ScriptBox extends React.Component {
); } + //let l = docList(this.source.data).length; if (l) { let ind = this.target.index !== undefined ? (this.target.index+1) % l : 0; this.target.index = ind; this.target.proto = getProto(docList(this.source.data)[ind]);} + public static EditButtonScript(doc: Doc, fieldKey: string, content: any, clientX: number, clientY: number) { + let overlayDisposer: () => void = emptyFunction; + const script = ScriptCast(doc[fieldKey]); + let originalText = script && script.script.originalScript; + // tslint:disable-next-line: no-unnecessary-callback-wrapper + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + const script = CompileScript(text, { + params: { this: Doc.name }, + typecheck: false, + editable: true, + transformer: DocumentIconContainer.getTransformer() + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } + + DragManager.StartButtonDrag([], text, "a script", + {}, this._params, (button: Doc) => { }, clientX, clientY); + + doc[fieldKey] = new ScriptField(script); + overlayDisposer(); + }} showDocumentIcons />; + let params = ""} + SetValue={(value: string) => (this._params = value.split(" ").filter(s => s !== " ")) ? true : true} + />; + let box =
+ {scriptingBox} + {params} +
+ overlayDisposer = OverlayView.Instance.addWindow(box, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); + } + static _params: string[] = []; public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 54848344b..db4bb7972 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -44,8 +44,8 @@ export class ButtonBox extends DocComponent(Butt @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - Doc.GetProto(this.dataDoc).source = new List(de.data.droppedDocuments); + if (de.data instanceof DragManager.DocumentDragData && e.target) { + Doc.GetProto(this.dataDoc)[(e.target as any).textContent] = new List(de.data.droppedDocuments); e.stopPropagation(); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 73426b3dc..84169cc93 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -601,6 +601,7 @@ export class DocumentView extends DocComponent(Docu let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); + makes.push({ description: "OnClick Button script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript(this.props.Document, "onClick", this._mainCont, obj.x, obj.y) }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); makes.push({ -- cgit v1.2.3-70-g09d2 From 71594c0e64d2559f2a72bcbc5faee1db78eecfb8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 11:19:21 -0400 Subject: added makeCustomView to promote documents to a new layout. --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/ScriptBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 57 +++++++++++++++++++++----------- src/new_fields/Doc.ts | 12 ++++--- 4 files changed, 47 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 773ab8b9f..fe409d9a6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -283,7 +283,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onCloseUp = async (e: PointerEvent) => { e.stopPropagation(); if (e.button === 0) { - const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc); + const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc; SelectionManager.SelectedDocuments().map(dv => { recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true); dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 375a5cc93..1f3673390 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -68,7 +68,7 @@ export class ScriptBox extends React.Component {
); } - //let l = docList(this.source.data).length; if (l) { let ind = this.target.index !== undefined ? (this.target.index+1) % l : 0; this.target.index = ind; this.target.proto = getProto(docList(this.source.data)[ind]);} + //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(doc: Doc, fieldKey: string, content: any, clientX: number, clientY: number) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 84169cc93..ca8fb573f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -444,6 +444,19 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(kvp, this.dataDoc, "onRight"); } + @undoBatch + makeCustomViewClicked = (): void => { + let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) }; + let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: NumCast(this.props.Document.height) + 20 }); + let metaKey = "data"; + let proto = Doc.GetProto(docTemplate); + Doc.MakeTemplate(fieldTemplate, metaKey, proto, true); + + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, true); + } + @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); @@ -552,17 +565,30 @@ export class DocumentView extends DocComponent(Docu proto.nativeHeight = this.props.PanelHeight(); } } + @undoBatch + @action + makeIntoPortal = (): void => { + if (!DocListCast(this.props.Document.links).find(doc => { + if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; + return false; + })) { + let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); + DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); + Doc.GetProto(this.props.Document).isButton = true; + } + } + @undoBatch @action makeBackground = (): void => { - this.props.Document.isBackground = !this.props.Document.isBackground; - this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true); + this.layoutDoc.isBackground = !this.layoutDoc.isBackground; + this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true); } @undoBatch @action toggleLockPosition = (): void => { - this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; + this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true; } listen = async () => { @@ -601,30 +627,21 @@ export class DocumentView extends DocComponent(Docu let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); + makes.push({ description: "Custom View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); + makes.push({ description: "Custom Field", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) makes.push({ description: "OnClick Button script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript(this.props.Document, "onClick", this._mainCont, obj.x, obj.y) }); makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); - makes.push({ - description: "Into Portal", event: () => { - if (!DocListCast(this.props.Document.links).find(doc => { - if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true; - return false; - })) { - let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); - Doc.GetProto(this.props.Document).isButton = true; - } - }, icon: "window-restore" - }); - makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" }); + makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); + makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - layoutItems.push({ description: `${this.props.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.props.Document.chromeStatus = (this.props.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.props.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.props.Document.autoHeight = !this.props.Document.autoHeight, icon: "plus" }); - layoutItems.push({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); - layoutItems.push({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); + layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); + layoutItems.push({ description: this.props.Document.ignoreAspect || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); + layoutItems.push({ description: this.layoutDoc.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.layoutDoc.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index ccb8f4aa2..d4b784cac 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -459,7 +459,7 @@ export namespace Doc { } if (expandedTemplateLayout === undefined) { setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined && - (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0); + (dataDoc[expandedLayoutFieldKey] = !BoolCast(templateLayoutDoc.suppressTemplateInstance) ? Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]") : templateLayoutDoc), 0); } return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending. } @@ -528,7 +528,7 @@ export namespace Doc { !templateDoc.nativeWidth && (otherdoc.ignoreAspect = true); return otherdoc; } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) { + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc, useTemplateDoc?: boolean) { if (!templateDoc) { target.layout = undefined; target.nativeWidth = undefined; @@ -537,7 +537,7 @@ export namespace Doc { target.type = undefined; return; } - let temp = Doc.MakeDelegate(templateDoc); + let temp = useTemplateDoc ? templateDoc : Doc.MakeDelegate(templateDoc); target.nativeWidth = Doc.GetProto(target).nativeWidth = undefined; target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined; !templateDoc.nativeWidth && (target.nativeWidth = 0); @@ -558,7 +558,7 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) { + export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc, suppressTemplateFlag?: boolean) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; @@ -576,6 +576,7 @@ export namespace Doc { fieldTemplate.templateField = metaKey; fieldTemplate.title = metaKey; fieldTemplate.isTemplate = true; + fieldTemplate.suppressTemplateInstance = suppressTemplateFlag; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid @@ -585,6 +586,9 @@ export namespace Doc { fieldTemplate.singleColumn = BoolCast(fieldTemplate.singleColumn); fieldTemplate.nativeWidth = Cast(fieldTemplate.nativeWidth, "number"); fieldTemplate.nativeHeight = Cast(fieldTemplate.nativeHeight, "number"); + fieldTemplate.panX = 0; + fieldTemplate.panY = 0; + fieldTemplate.scale = 1; fieldTemplate.showTitle = "title"; setTimeout(() => fieldTemplate.proto = templateDataDoc); } -- cgit v1.2.3-70-g09d2 From d7d73856ed405cef01b4cf727ca56f4d9a31e894 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 15:45:37 -0400 Subject: cleaned up ScriptBox EditScript's restructured menus for dealing with onClicks... exposed more layout paramters for EditableViews. --- src/client/views/ContextMenuItem.tsx | 4 +- src/client/views/EditableView.tsx | 5 +- src/client/views/ScriptBox.tsx | 67 +++++++--------------- .../views/collections/CollectionSchemaCells.tsx | 3 +- .../views/collections/CollectionStackingView.tsx | 6 +- .../views/collections/CollectionTreeView.tsx | 5 +- src/client/views/nodes/ButtonBox.tsx | 19 +++++- src/client/views/nodes/DocumentView.tsx | 42 ++++++++------ src/client/views/nodes/KeyValuePair.tsx | 3 +- 9 files changed, 82 insertions(+), 72 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index ac055be5b..5f673b3f3 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -93,13 +93,13 @@ export class ContextMenuItem extends React.Component )} ; return ( -
+
{this.props.icon ? ( ) : null} -
+
{this.props.description}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 1bdb26b3d..e9db4b048 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -28,7 +28,8 @@ export interface EditableProps { contents: any; fontStyle?: string; fontSize?: number; - height?: number; + height?: number | "auto"; + maxHeight?: number; display?: string; autosuggestProps?: { resetValue: () => void; @@ -145,7 +146,7 @@ export class EditableView extends React.Component { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return (
{this.props.contents}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 1f3673390..8f08224c8 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -18,6 +18,7 @@ export interface ScriptBoxProps { onCancel?: () => void; initialText?: string; showDocumentIcons?: boolean; + setParams?: (p: string[]) => void; } @observer @@ -58,56 +59,30 @@ export class ScriptBox extends React.Component { onFocus = this.onFocus; onBlur = this.onBlur; } + let params = ""} + SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true} + />; return (
+
+ +
{params}
+
-
); } //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(doc: Doc, fieldKey: string, content: any, clientX: number, clientY: number) { - let overlayDisposer: () => void = emptyFunction; - const script = ScriptCast(doc[fieldKey]); - let originalText = script && script.script.originalScript; - // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - const script = CompileScript(text, { - params: { this: Doc.name }, - typecheck: false, - editable: true, - transformer: DocumentIconContainer.getTransformer() - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } - - DragManager.StartButtonDrag([], text, "a script", - {}, this._params, (button: Doc) => { }, clientX, clientY); - - doc[fieldKey] = new ScriptField(script); - overlayDisposer(); - }} showDocumentIcons />; - let params = ""} - SetValue={(value: string) => (this._params = value.split(" ").filter(s => s !== " ")) ? true : true} - />; - let box =
- {scriptingBox} - {params} -
- overlayDisposer = OverlayView.Instance.addWindow(box, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); - } - static _params: string[] = []; - public static EditClickScript(doc: Doc, fieldKey: string, prewrapper?: string, postwrapper?: string) { + public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, prewrapper?: string, postwrapper?: string) { let overlayDisposer: () => void = emptyFunction; const script = ScriptCast(doc[fieldKey]); let originalText: string | undefined = undefined; @@ -121,10 +96,9 @@ export class ScriptBox extends React.Component { } } // tslint:disable-next-line: no-unnecessary-callback-wrapper - let scriptingBox = overlayDisposer()} onSave={(text, onError) => { - if (prewrapper) { - text = prewrapper + text + (postwrapper ? postwrapper : ""); - } + let params: string[] = []; + let setParams = (p: string[]) => params.splice(0, params.length, ...p); + let scriptingBox = overlayDisposer()} onSave={(text, onError) => { const script = CompileScript(text, { params: { this: Doc.name }, typecheck: false, @@ -135,9 +109,12 @@ export class ScriptBox extends React.Component { onError(script.errors.map(error => error.messageText).join("\n")); return; } + + params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY); + doc[fieldKey] = new ScriptField(script); overlayDisposer(); }} showDocumentIcons />; - overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` }); + overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title }); } } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 9c26a08f0..c59107b53 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -214,7 +214,8 @@ export class CollectionSchemaCell extends React.Component { isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents} - height={Number(MAX_ROW_HEIGHT)} + height={"auto"} + maxHeight={Number(MAX_ROW_HEIGHT)} GetValue={() => { let field = props.Document[props.fieldKey]; if (Field.IsField(field)) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 654ff2279..91e10b0ac 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -362,8 +362,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" }); subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" }); subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" }); - subItems.push({ description: "Edit onChildClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onChildClick") }); ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" }); + + let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e31fa0b40..8539b3fcc 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -592,13 +592,14 @@ export class CollectionTreeView extends CollectionSubView(Document) {
(e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()} + onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> StrCast(this.resolvedDataDoc.title)} SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} OnFillDown={undoBatch((value: string) => { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index db4bb7972..68d3b8ae1 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -13,6 +13,8 @@ import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; library.add(faEdit as any); @@ -41,11 +43,24 @@ export class ButtonBox extends DocComponent(Butt this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } + + specificContextMenu = (e: React.MouseEvent): void => { + let funcs: ContextMenuProps[] = []; + funcs.push({ + description: "Clear Script Params", event: () => { + let params = Cast(this.props.Document.buttonParams, listSpec("string")); + params && params.map(p => this.props.Document[p] = undefined) + }, icon: "trash" + }); + + ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" }); + } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData && e.target) { - Doc.GetProto(this.dataDoc)[(e.target as any).textContent] = new List(de.data.droppedDocuments); + this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments); e.stopPropagation(); } } @@ -55,7 +70,7 @@ export class ButtonBox extends DocComponent(Butt let missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(async p => await DocListCastAsync(this.props.Document[p])); // bcz: really hacky form of prefetching ... return ( -
+
{(this.Document.text || this.Document.title)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ca8fb573f..9c2cf5f01 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -446,10 +446,10 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeCustomViewClicked = (): void => { - let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) }; + let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) + 25, x: -NumCast(this.props.Document.width) / 2, y: -NumCast(this.props.Document.height) / 2, }; let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: NumCast(this.props.Document.height) + 20 }); + let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let metaKey = "data"; let proto = Doc.GetProto(docTemplate); Doc.MakeTemplate(fieldTemplate, metaKey, proto, true); @@ -460,15 +460,21 @@ export class DocumentView extends DocComponent(Docu @undoBatch makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); - doc.isButton = !BoolCast(doc.isButton); - if (doc.isButton) { - if (!doc.nativeWidth) { - doc.nativeWidth = this.props.Document[WidthSym](); - doc.nativeHeight = this.props.Document[HeightSym](); - } + if (doc.isButton || doc.onClick) { + doc.isButton = false; + doc.onClick = undefined; } else { - doc.nativeWidth = doc.nativeHeight = undefined; + doc.isButton = true; } + + // if (doc.isButton) { + // if (!doc.nativeWidth) { + // doc.nativeWidth = this.props.Document[WidthSym](); + // doc.nativeHeight = this.props.Document[HeightSym](); + // } + // } else { + // doc.nativeWidth = doc.nativeHeight = undefined; + // } } @undoBatch @@ -623,21 +629,25 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); + let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); - makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); - makes.push({ description: "Custom View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); - makes.push({ description: "Custom Field", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) - makes.push({ description: "OnClick Button script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript(this.props.Document, "onClick", this._mainCont, obj.x, obj.y) }); - makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); - makes.push({ description: "OnClick foreach doc", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick", "docList(this.collectionContext.data).map(d => {", "});\n") }); + makes.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); + makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); + + let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: this.props.Document.isButton || this.props.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" }); + onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); + onClicks.push({ description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n") }); + !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + let existing = ContextMenu.Instance.findByDescription("Layout..."); let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; - layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.props.Document.ignoreAspect || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5afd4d834..a27dbd83d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -112,7 +112,8 @@ export class KeyValuePair extends React.Component {
{ return Field.toKeyValueString(props.Document, props.fieldKey); }} -- cgit v1.2.3-70-g09d2 From 4722644d09a561e394bd72c92af5561a2020776e Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 16:28:14 -0400 Subject: fixed collection iteration onClick script --- src/client/views/ScriptBox.tsx | 3 +++ src/client/views/collections/CollectionBaseView.tsx | 3 --- src/client/views/nodes/DocumentView.tsx | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 8f08224c8..8f06cf770 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -99,6 +99,9 @@ export class ScriptBox extends React.Component { let params: string[] = []; let setParams = (p: string[]) => params.splice(0, params.length, ...p); let scriptingBox = overlayDisposer()} onSave={(text, onError) => { + if (prewrapper) { + text = prewrapper + text + (postwrapper ? postwrapper : ""); + } const script = CompileScript(text, { params: { this: Doc.name }, typecheck: false, diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index bd8d56851..5829f0626 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -104,9 +104,6 @@ export class CollectionBaseView extends React.Component { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } - if (doc.type === DocumentType.BUTTON) { - doc.collectionContext = this.props.Document; // used by docList() function in Doc.ts so that buttons can iterate over the documents in their collection - } allowDuplicates = true; let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9c2cf5f01..940a66b36 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -643,7 +643,12 @@ export class DocumentView extends DocComponent(Docu let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: this.props.Document.isButton || this.props.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" }); onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) }); - onClicks.push({ description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n") }); + onClicks.push({ + description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => { + this.props.Document.collectionContext = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; + ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n"); + } + }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); -- cgit v1.2.3-70-g09d2 From 68e554cafb6107bfde9526773b3e0e667d582c88 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 17:24:48 -0400 Subject: added default note type collection --- src/client/documents/Documents.ts | 1 + src/client/views/Main.tsx | 1 - src/client/views/PreviewCursor.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++++++++------- .../views/collections/collectionFreeForm/MarqueeView.tsx | 6 ++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 ++- src/server/authentication/models/current_user_utils.ts | 12 +++++++++++- 7 files changed, 30 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ae65fde1e..602a7f9ad 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -66,6 +66,7 @@ export interface DocumentOptions { page?: number; scale?: number; layout?: string; + isTemplate?: boolean; templates?: List; viewType?: number; backgroundColor?: string; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0e687737d..11ec6f0c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -37,7 +37,6 @@ let swapDocs = async () => { (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled"; - CurrentUserUtils.UserDocument.chromeStatus = "disabled"; await swapDocs(); document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 45a8556bf..1aed51e64 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> { (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && !e.defaultPrevented) { - if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { + if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 21f119d57..2591bdd8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { DocServer } from "../../../DocServer"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -272,7 +273,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); // OR transparency set let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - col && (Doc.GetProto(newBox).backgroundColor = col); + (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col); let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); round && (Doc.GetProto(newBox).borderRounding = round); @@ -945,17 +946,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); let noteItems: ContextMenuProps[] = []; - noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" })); layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (noteStyle: string, color: string) => { + createText = (noteStyle: number) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) }); + text.layout = notes[noteStyle % notes.length]; + this.addLiveTextBox(text); } private childViews = () => [ diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0c4860be1..fe48a3485 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,6 +22,7 @@ import React = require("react"); import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; import { string } from "prop-types"; import { listSpec } from "../../../../new_fields/Schema"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -97,6 +98,11 @@ export class MarqueeView extends React.Component } else if (!e.ctrlKey) { this.props.addLiveTextDocument( Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); + } else if (e.keyCode > 48 && e.keyCode <= 57) { + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }); + text.layout = notes[(e.keyCode - 49) % notes.length]; + this.props.addLiveTextDocument(text); } e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0ea36cdc2..194026a08 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -289,7 +289,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { - this.props.Document.layout = draggedDoc; + if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc; + else this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9d35d36d3..af5774ebe 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -7,7 +7,7 @@ import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/nort import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView"; import { CollectionView } from "../../../client/views/collections/CollectionView"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; @@ -50,6 +50,16 @@ export class CurrentUserUtils { doc.workspaces = workspaces; } PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); + if (doc.noteTypes === undefined) { + let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), + Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), + Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), + Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })]; + const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); + noteTypes.excludeFromLibrary = true; + doc.noteTypes = noteTypes; + } + PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals))); if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); recentlyClosed.excludeFromLibrary = true; -- cgit v1.2.3-70-g09d2 From 4869c5a6de14ca04b2159dc3a35aa26653501168 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Sep 2019 21:20:49 -0400 Subject: cycle prevention in treeview --- src/client/views/collections/CollectionTreeView.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8539b3fcc..f5bb76966 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -52,6 +52,7 @@ export interface TreeViewProps { active: () => boolean; showHeaderFields: () => boolean; preventTreeViewOpen: boolean; + renderedIds: string[]; } library.add(faTrashAlt); @@ -274,7 +275,9 @@ class TreeView extends React.Component { let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, + this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen, + [...this.props.renderedIds, doc[Id]]); } else { contentElement = { TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)} + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen, + [...this.props.renderedIds, this.props.document[Id]])} ; } else if (this.treeViewExpandedView === "fields") { return
    @@ -392,7 +396,7 @@ class TreeView extends React.Component { {this.renderTitle}
    - {!this.treeViewOpen ? (null) : this.renderContent} + {!this.treeViewOpen || this.props.renderedIds.indexOf(this.props.document[Id]) !== -1 ? (null) : this.renderContent}
; @@ -415,7 +419,8 @@ class TreeView extends React.Component { panelWidth: () => number, renderDepth: number, showHeaderFields: () => boolean, - preventTreeViewOpen: boolean + preventTreeViewOpen: boolean, + renderedIds: string[] ) { let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); @@ -502,7 +507,8 @@ class TreeView extends React.Component { parentKey={key} active={active} showHeaderFields={showHeaderFields} - preventTreeViewOpen={preventTreeViewOpen} />; + preventTreeViewOpen={preventTreeViewOpen} + renderedIds={renderedIds} />; }); } } @@ -616,7 +622,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled", - BoolCast(this.props.Document.preventTreeViewOpen)) + BoolCast(this.props.Document.preventTreeViewOpen), []) }
-- cgit v1.2.3-70-g09d2 From 186d7aed7b99b1373e99b51cfe0c88c8167c8290 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Sep 2019 22:52:07 -0400 Subject: fixed some template issues specifically for self-templates. --- src/client/views/DocumentDecorations.tsx | 22 +++++++++++++------- .../views/collections/CollectionBaseView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++++++++++-------- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/FormattedTextBox.tsx | 3 +-- src/new_fields/Doc.ts | 5 ++--- 6 files changed, 37 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index fe409d9a6..814d718be 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -30,6 +30,7 @@ import { MetadataEntryMenu } from './MetadataEntryMenu'; import { ImageBox } from './nodes/ImageBox'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; +import { ObjectField } from '../../new_fields/ObjectField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -145,13 +146,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let fieldTemplateView = SelectionManager.SelectedDocuments()[0]; SelectionManager.DeselectAll(); let fieldTemplate = fieldTemplateView.props.Document; - let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document; - let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); - let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto); - if (text.startsWith(">>")) { - proto.detailedLayout = proto.layout; - proto.miniLayout = ImageBox.LayoutString(metaKey); + let containerView = fieldTemplateView.props.ContainingCollectionView; + if (containerView) { + let docTemplate = containerView.props.Document; + let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length); + let proto = Doc.GetProto(docTemplate); + if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) { + const fd = fieldTemplate.data; + fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd)); + } + Doc.MakeTemplate(fieldTemplate, metaKey, proto); + if (text.startsWith(">>")) { + proto.detailedLayout = proto.layout; + proto.miniLayout = ImageBox.LayoutString(metaKey); + } } } else { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 5829f0626..b7036b3ff 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -127,7 +127,7 @@ export class CollectionBaseView extends React.Component { let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1); + let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index eb7ab64f8..07dd1cae7 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -74,10 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent { - let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding); + let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding); if (br.endsWith("%")) { let percent = Number(br.substr(0, br.length - 1)) / 100; - let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight)); + let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight())); return minDim; } @@ -89,6 +89,12 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; + get layoutDoc() { + // if this document's layout field contains a document (ie, a rendering template), then we will use that + // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. + return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } + render() { const hasPosition = this.props.x !== undefined || this.props.y !== undefined; return ( @@ -98,15 +104,15 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) }); let metaKey = "data"; let proto = Doc.GetProto(docTemplate); - Doc.MakeTemplate(fieldTemplate, metaKey, proto, true); + Doc.MakeTemplate(fieldTemplate, metaKey, proto); - Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, true); + Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false); } @undoBatch @@ -634,7 +634,7 @@ export class DocumentView extends DocComponent(Docu let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" }); makes.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" }); - makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" }) + makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" }) makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" }); makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 194026a08..0ea36cdc2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -289,8 +289,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { - if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc; - else this.props.Document.layout = draggedDoc; + this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d4b784cac..e94b9f1eb 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -459,7 +459,7 @@ export namespace Doc { } if (expandedTemplateLayout === undefined) { setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined && - (dataDoc[expandedLayoutFieldKey] = !BoolCast(templateLayoutDoc.suppressTemplateInstance) ? Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]") : templateLayoutDoc), 0); + (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0); } return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending. } @@ -558,7 +558,7 @@ export namespace Doc { } } - export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc, suppressTemplateFlag?: boolean) { + export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; @@ -576,7 +576,6 @@ export namespace Doc { fieldTemplate.templateField = metaKey; fieldTemplate.title = metaKey; fieldTemplate.isTemplate = true; - fieldTemplate.suppressTemplateInstance = suppressTemplateFlag; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid -- cgit v1.2.3-70-g09d2