aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/DocumentView.tsx153
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx141
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx7
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx118
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx27
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx7
-rw-r--r--src/client/views/nodes/formattedText/ImageResizeView.tsx138
-rw-r--r--src/client/views/nodes/formattedText/OrderedListView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts172
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts315
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx397
-rw-r--r--src/client/views/nodes/formattedText/SummaryView.tsx116
-rw-r--r--src/client/views/pdf/PDFViewer.tsx26
13 files changed, 611 insertions, 1014 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d1877c3a0..da5327f48 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,13 +43,13 @@ import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
-import { undo } from 'prosemirror-history';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
- fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
+ fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion);
export type DocFocusFunc = () => boolean;
+
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
@@ -138,7 +138,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
console.log(SelectionManager.SelectedDocuments());
- console.log("START");
if (RadialMenu.Instance._display === false) {
this.addHoldMoveListeners();
this.addHoldEndListeners();
@@ -179,8 +178,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- // console.log(InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true));
- // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
@@ -189,12 +186,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 });
- // if (SelectionManager.IsSelected(this, true)) {
- // SelectionManager.SelectDoc(this, false);
- // }
SelectionManager.DeselectAll();
-
-
}
@action
@@ -511,8 +503,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerDown = (e: React.PointerEvent): void => {
- // console.log(e.button)
- // console.log(e.nativeEvent)
// continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
@@ -739,7 +729,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.contextMenuItems?.().forEach(item =>
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
-
let options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
@@ -761,7 +750,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
-
const funcs: ContextMenuProps[] = [];
if (this.Document.onDragStart) {
funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
@@ -772,20 +760,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
- moreItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
- moreItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
- if (!ClientUtils.RELEASE) {
- // let copies: ContextMenuProps[] = [];
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
- // cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
- }
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
@@ -804,65 +783,83 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.download = `DocExport-${this.props.Document[Id]}.zip`;
// a.click();
});
-
- const recommender_subitems: ContextMenuProps[] = [];
-
- recommender_subitems.push({
- description: "Internal recommendations",
- event: () => this.recommender(),
- icon: "brain"
- });
-
- const ext_recommender_subitems: ContextMenuProps[] = [];
-
- ext_recommender_subitems.push({
- description: "arXiv",
- event: () => this.externalRecommendation("arxiv"),
- icon: "brain"
- });
- ext_recommender_subitems.push({
- description: "Bing",
- event: () => this.externalRecommendation("bing"),
- icon: "brain"
- });
-
- recommender_subitems.push({
- description: "External recommendations",
- subitems: ext_recommender_subitems,
- icon: "brain"
- });
-
moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
- moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
- moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+ moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
- runInAction(() => {
- const setWriteMode = (mode: DocServer.WriteMode) => {
- DocServer.AclsMode = mode;
- const mode1 = mode;
- const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- DocServer.setFieldWriteMode("x", mode1);
- DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("_width", mode1);
- DocServer.setFieldWriteMode("_height", mode1);
-
- DocServer.setFieldWriteMode("_panX", mode2);
- DocServer.setFieldWriteMode("_panY", mode2);
- DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("_viewType", mode2);
- };
- const aclsMenu: ContextMenuProps[] = [];
- aclsMenu.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
- aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
- aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
- aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
- cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
+ const help = cm.findByDescription("Help...");
+ const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+ helpItems.push({
+ description: "Keyboard Shortcuts Ctrl+/",
+ event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"),
+ icon: "keyboard"
});
+ cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+
+ const existingAcls = cm.findByDescription("Privacy...");
+ const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
+ aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+ aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+ !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+
+ // const recommender_subitems: ContextMenuProps[] = [];
+
+ // recommender_subitems.push({
+ // description: "Internal recommendations",
+ // event: () => this.recommender(),
+ // icon: "brain"
+ // });
+
+ // const ext_recommender_subitems: ContextMenuProps[] = [];
+
+ // ext_recommender_subitems.push({
+ // description: "arXiv",
+ // event: () => this.externalRecommendation("arxiv"),
+ // icon: "brain"
+ // });
+ // ext_recommender_subitems.push({
+ // description: "Bing",
+ // event: () => this.externalRecommendation("bing"),
+ // icon: "brain"
+ // });
+
+ // recommender_subitems.push({
+ // description: "External recommendations",
+ // subitems: ext_recommender_subitems,
+ // icon: "brain"
+ // });
+
+
+ //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
+ //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
+ //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+
+ // runInAction(() => {
+ // const setWriteMode = (mode: DocServer.WriteMode) => {
+ // DocServer.AclsMode = mode;
+ // const mode1 = mode;
+ // const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ // DocServer.setFieldWriteMode("x", mode1);
+ // DocServer.setFieldWriteMode("y", mode1);
+ // DocServer.setFieldWriteMode("_width", mode1);
+ // DocServer.setFieldWriteMode("_height", mode1);
+
+ // DocServer.setFieldWriteMode("_panX", mode2);
+ // DocServer.setFieldWriteMode("_panY", mode2);
+ // DocServer.setFieldWriteMode("scale", mode2);
+ // DocServer.setFieldWriteMode("_viewType", mode2);
+ // };
+ // const aclsMenu: ContextMenuProps[] = [];
+ // aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+ // aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+ // cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
+ // });
runInAction(() => {
if (!this.topMost && !(e instanceof Touch)) {
// DocumentViews should stop propagation of this event
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index d56b87ae5..ad204f3df 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,95 +1,112 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
+import { TextSelection } from "prosemirror-state";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
-import { ObjectField } from "../../../../fields/ObjectField";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { Doc } from "../../../../fields/Doc";
import { DocServer } from "../../../DocServer";
-
import React = require("react");
-import { schema } from "./schema_rts";
-interface IDashDocCommentView {
- node: any;
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+ _fieldWrapper: HTMLDivElement; // container for label and value
+
+ constructor(node: any, view: any, getPos: any) {
+ this._fieldWrapper = document.createElement("div");
+ this._fieldWrapper.style.width = node.attrs.width;
+ this._fieldWrapper.style.height = node.attrs.height;
+ this._fieldWrapper.style.fontWeight = "bold";
+ this._fieldWrapper.style.position = "relative";
+ this._fieldWrapper.style.display = "inline-block";
+ this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+ ReactDOM.render(<DashDocCommentViewInternal view={view} getPos={getPos} docid={node.attrs.docid} />, this._fieldWrapper);
+ (this as any).dom = this._fieldWrapper;
+ }
+
+ destroy() {
+ ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+ }
+
+ selectNode() { }
+}
+
+interface IDashDocCommentViewInternal {
+ docid: string;
view: any;
getPos: any;
}
-export class DashDocCommentView extends React.Component<IDashDocCommentView>{
- constructor(props: IDashDocCommentView) {
- super(props);
- }
+export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal>{
- targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
- const m = this.props.view.state.doc.nodeAt(i);
- if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
- }
- }
- const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
- this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
- setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
- return undefined;
+ constructor(props: IDashDocCommentViewInternal) {
+ super(props)
+ this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this)
+ this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this)
+ this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this)
+ this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this)
}
- onPointerDownCollapse = (e: any) => e.stopPropagation();
+ onPointerLeaveCollapsed(e: any) {
+ DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ e.preventDefault();
+ e.stopPropagation();
+ };
- onPointerUpCollapse = (e: any) => {
+ onPointerEnterCollapsed(e: any) {
+ DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
+ onPointerUpCollapsed(e: any) {
const target = this.targetNode();
+
if (target) {
const expand = target.hidden;
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
}, 0);
}
e.stopPropagation();
- }
+ };
- onPointerEnterCollapse = (e: any) => {
- DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
- e.preventDefault();
+ onPointerDownCollapsed(e: any) {
e.stopPropagation();
- }
-
- onPointerLeaveCollapse = (e: any) => {
- DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
- e.preventDefault();
- e.stopPropagation();
- }
+ };
- render() {
+ targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ const state = this.props.view.state;
+ for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
+ const m = state.doc.nodeAt(i);
+ if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) {
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ }
+ }
- const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
+ const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" });
+ this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc));
+ setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+ return undefined;
+ };
+ render() {
return (
<span
className="formattedTextBox-inlineComment"
- id={collapsedId}
- onPointerDown={this.onPointerDownCollapse}
- onPointerUp={this.onPointerUpCollapse}
- onPointerEnter={this.onPointerEnterCollapse}
- onPointerLeave={this.onPointerLeaveCollapse}
+ id={"DashDocCommentView-" + this.props.docid}
+ onPointerLeave={this.onPointerLeaveCollapsed}
+ onPointerEnter={this.onPointerEnterCollapsed}
+ onPointerUp={this.onPointerUpCollapsed}
+ onPointerDown={this.onPointerDownCollapsed}
>
-
- </span >
- );
+ </span>
+ )
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 60a9c2a27..8c16f4a1a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -14,7 +14,6 @@ import "./DashFieldView.scss";
import { observer } from "mobx-react";
import { DocUtils } from "../../../documents/Documents";
-
export class DashFieldView {
_fieldWrapper: HTMLDivElement; // container for label and value
@@ -40,12 +39,10 @@ export class DashFieldView {
/>, this._fieldWrapper);
(this as any).dom = this._fieldWrapper;
}
- destroy() {
- ReactDOM.unmountComponentAtNode(this._fieldWrapper);
- }
+ destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
selectNode() { }
-
}
+
interface IDashFieldViewInternal {
fieldKey: string;
docid: string;
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index ee21fb765..1683cc972 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -6,54 +6,50 @@ import { schema } from "./schema_rts";
import { redo, undo } from "prosemirror-history";
import { StepMap } from "prosemirror-transform";
-import React = require("react");
-
-interface IFootnoteView {
+export class FootnoteView {
innerView: any;
outerView: any;
node: any;
dom: any;
getPos: any;
-}
-export class FootnoteView extends React.Component<IFootnoteView> {
- _innerView: any;
- _node: any;
+ constructor(node: any, view: any, getPos: any) {
+ // We'll need these later
+ this.node = node;
+ this.outerView = view;
+ this.getPos = getPos;
+
+ // The node's representation in the editor (empty, for now)
+ this.dom = document.createElement("footnote");
- constructor(props: IFootnoteView) {
- super(props);
- const node = this.props.node;
- const outerView = this.props.outerView;
- const _innerView = this.props.innerView;
- const getPos = this.props.getPos;
+ this.dom.addEventListener("pointerup", this.toggle, true);
+ // These are used when the footnote is selected
+ this.innerView = null;
}
selectNode() {
- const attrs = { ...this.props.node.attrs };
- attrs.visibility = true;
this.dom.classList.add("ProseMirror-selectednode");
- if (!this.props.innerView) this.open();
+ if (!this.innerView) this.open();
}
deselectNode() {
- const attrs = { ...this.props.node.attrs };
- attrs.visibility = false;
this.dom.classList.remove("ProseMirror-selectednode");
- if (this.props.innerView) this.close();
+ if (this.innerView) this.close();
}
+
open() {
// Append a tooltip to the outer node
const tooltip = this.dom.appendChild(document.createElement("div"));
tooltip.className = "footnote-tooltip";
// And put a sub-ProseMirror into that
- this.props.innerView.defineProperty(new EditorView(tooltip, {
+ this.innerView = new EditorView(tooltip, {
// You can use any node as an editor document
state: EditorState.create({
- doc: this.props.node,
+ doc: this.node,
plugins: [keymap(baseKeymap),
keymap({
- "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
- "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
+ "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+ "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
"Mod-b": toggleMark(schema.marks.strong)
}),
// new Plugin({
@@ -74,11 +70,11 @@ export class FootnoteView extends React.Component<IFootnoteView> {
// the parent editor is focused.
e.stopPropagation();
document.addEventListener("pointerup", this.ignore, true);
- if (this.props.outerView.hasFocus()) this.props.innerView.focus();
+ if (this.outerView.hasFocus()) this.innerView.focus();
}) as any
}
- }));
- setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
+ });
+ setTimeout(() => this.innerView?.docView.setSelection(0, 0, this.innerView.root, true), 0);
}
ignore = (e: PointerEvent) => {
@@ -86,32 +82,43 @@ export class FootnoteView extends React.Component<IFootnoteView> {
document.removeEventListener("pointerup", this.ignore, true);
}
+ toggle = () => {
+ if (this.innerView) this.close();
+ else this.open();
+ }
+
+ close() {
+ this.innerView?.destroy();
+ this.innerView = null;
+ this.dom.textContent = "";
+ }
+
dispatchInner(tr: any) {
- const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
- this.props.innerView.updateState(state);
+ const { state, transactions } = this.innerView.state.applyTransaction(tr);
+ this.innerView.updateState(state);
if (!tr.getMeta("fromOutside")) {
- const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
+ const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
for (const transaction of transactions) {
- const steps = transaction.steps;
- for (const step of steps) {
+ for (const step of transaction.steps) {
outerTr.step(step.map(offsetMap));
}
}
- if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
+ if (outerTr.docChanged) this.outerView.dispatch(outerTr);
}
}
+
update(node: any) {
- if (!node.sameMarkup(this.props.node)) return false;
- this._node = node; //not sure
- if (this.props.innerView) {
- const state = this.props.innerView.state;
+ if (!node.sameMarkup(this.node)) return false;
+ this.node = node;
+ if (this.innerView) {
+ const state = this.innerView.state;
const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
const overlap = start - Math.min(endA, endB);
if (overlap > 0) { endA += overlap; endB += overlap; }
- this.props.innerView.dispatch(
+ this.innerView.dispatch(
state.tr
.replace(start, endB, node.slice(start, endA))
.setMeta("fromOutside", true));
@@ -119,44 +126,17 @@ export class FootnoteView extends React.Component<IFootnoteView> {
}
return true;
}
- onPointerUp = (e: any) => {
- this.toggle(e);
- }
-
- toggle = (e: any) => {
- e.preventDefault();
- if (this.props.innerView) this.close();
- else {
- this.open();
- }
- }
-
- close() {
- this.props.innerView && this.props.innerView.destroy();
- this._innerView = null;
- this.dom.textContent = "";
- }
destroy() {
- if (this.props.innerView) this.close();
+ if (this.innerView) this.close();
}
stopEvent(event: any) {
- return this.props.innerView && this.props.innerView.dom.contains(event.target);
+ return this.innerView?.dom.contains(event.target);
}
- ignoreMutation() { return true; }
-
-
- render() {
- return (
- <div
- className="footnote"
- onPointerUp={this.onPointerUp}>
- <div className="footnote-tooltip" >
-
- </div >
- </div>
- );
+ ignoreMutation() {
+ return true;
}
}
+
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 600671179..ba36a1618 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -34,15 +34,15 @@ import { makeTemplate } from '../../../util/DropConverter';
import buildKeymap from "./ProsemirrorExampleTransfer";
import RichTextMenu from './RichTextMenu';
import { RichTextRules } from "./RichTextRules";
-import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema";
-// import { OrderedListView } from "./RichTextSchema";
-// import { ImageResizeView } from "./ImageResizeView";
-// import { DashDocCommentView } from "./DashDocCommentView";
-// import { FootnoteView } from "./FootnoteView";
-// import { SummaryView } from "./SummaryView";
-// import { DashDocView } from "./DashDocView";
+
+//import { DashDocView } from "./DashDocView";
+import { DashDocView } from "./RichTextSchema";
+
+import { DashDocCommentView } from "./DashDocCommentView";
import { DashFieldView } from "./DashFieldView";
+import { SummaryView } from "./SummaryView";
+import { OrderedListView } from "./OrderedListView";
+import { FootnoteView } from "./FootnoteView";
import { schema } from "./schema_rts";
import { SelectionManager } from "../../../util/SelectionManager";
@@ -904,17 +904,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
- dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
- // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
-
- // image(node, view, getPos) {
- // //const addDocTab = this.props.addDocTab;
- // return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
- // },
- // // WAS :
- // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
-
+ dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
ordered_list(node, view, getPos) { return new OrderedListView(); },
footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 5ac173602..000b3c2e5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -50,7 +50,9 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
return after;
}
-
+// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
+// this will also display metadata information about text when the view is configured to display things like other people who authored text.
+//
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
@@ -235,9 +237,6 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
}
- // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
- // let text = ext && StrCast(ext.text);
- // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
}
});
}
diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx
deleted file mode 100644
index 401ecd7e6..000000000
--- a/src/client/views/nodes/formattedText/ImageResizeView.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { NodeSelection } from "prosemirror-state";
-import { Doc } from "../../../../fields/Doc";
-import { DocServer } from "../../../DocServer";
-import { DocumentManager } from "../../../util/DocumentManager";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IImageResizeView {
- node: any;
- view: any;
- getPos: any;
- addDocTab: any;
-}
-
-export class ImageResizeView extends React.Component<IImageResizeView> {
- constructor(props: IImageResizeView) {
- super(props);
- }
-
- onClickImg = (e: any) => {
- e.stopPropagation();
- e.preventDefault();
- if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
- this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
- }
- }
-
- onPointerDownImg = (e: any) => {
- if (e.ctrlKey) {
- e.preventDefault();
- e.stopPropagation();
- DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
- (linkDoc instanceof Doc) &&
- DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
- document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
- }
- }
-
- onPointerDownHandle = (e: any) => {
- e.preventDefault();
- e.stopPropagation();
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
- const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
- const startX = e.pageX;
- const startWidth = parseFloat(this.props.node.attrs.width);
-
- const onpointermove = (e: any) => {
- const elementOuter = document.getElementById("outerId") as HTMLElement;
-
- const currentX = e.pageX;
- const diffInPx = currentX - startX;
- elementOuter.style.width = `${startWidth + diffInPx}`;
- elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
- };
-
- const onpointerup = () => {
- document.removeEventListener("pointermove", onpointermove);
- document.removeEventListener("pointerup", onpointerup);
- const pos = this.props.view.state.selection.from;
- const elementOuter = document.getElementById("outerId") as HTMLElement;
- this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
- this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
- };
-
- document.addEventListener("pointermove", onpointermove);
- document.addEventListener("pointerup", onpointerup);
- }
-
- selectNode() {
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const elementHandle = document.getElementById("handleId") as HTMLElement;
-
- elementImage.classList.add("ProseMirror-selectednode");
- elementHandle.style.display = "";
- }
-
- deselectNode() {
- const elementImage = document.getElementById("imageId") as HTMLElement;
- const elementHandle = document.getElementById("handleId") as HTMLElement;
-
- elementImage.classList.remove("ProseMirror-selectednode");
- elementHandle.style.display = "none";
- }
-
-
- render() {
-
- const outerStyle = {
- width: this.props.node.attrs.width,
- height: this.props.node.attrs.height,
- display: "inline-block",
- overflow: "hidden",
- float: this.props.node.attrs.float
- };
-
- const imageStyle = {
- width: "100%",
- };
-
- const handleStyle = {
- position: "absolute",
- width: "20px",
- heiht: "20px",
- backgroundColor: "blue",
- borderRadius: "15px",
- display: "none",
- bottom: "-10px",
- right: "-10px"
-
- };
-
-
-
- return (
- <div id="outer"
- style={outerStyle}
- >
- <img
- id="imageId"
- style={imageStyle}
- src={this.props.node.src}
- onClick={this.onClickImg}
- onPointerDown={this.onPointerDownImg}
-
- >
- </img>
- <span
- id="handleId"
- onPointerDown={this.onPointerDownHandle}
- >
-
- </span>
- </div >
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
new file mode 100644
index 000000000..c3595e59b
--- /dev/null
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -0,0 +1,8 @@
+export class OrderedListView {
+
+ update(node: any) {
+ // 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
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 77b93b9d2..114288a9d 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -10,6 +10,7 @@ import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types";
import { Doc } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
+import { Docs } from "../../../documents/Documents";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -29,6 +30,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string)
});
return tx2;
};
+
export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
const keys: { [key: string]: any } = {};
@@ -41,77 +43,27 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
keys[key] = cmd;
}
+ //History commands
bind("Mod-z", undo);
- bind("Shift-Mod-z", redo);
bind("Backspace", undoInputRule);
-
+ bind("Shift-Mod-z", redo);
!mac && bind("Mod-y", redo);
- bind("Alt-ArrowUp", joinUp);
- bind("Alt-ArrowDown", joinDown);
- bind("Mod-BracketLeft", lift);
- bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
- (document.activeElement as any).blur?.();
- SelectionManager.DeselectAll();
- });
-
+ //Commands to modify Mark
bind("Mod-b", toggleMark(schema.marks.strong));
bind("Mod-B", toggleMark(schema.marks.strong));
bind("Mod-e", toggleMark(schema.marks.em));
bind("Mod-E", toggleMark(schema.marks.em));
+ bind("Mod-*", toggleMark(schema.marks.code));
+
bind("Mod-u", toggleMark(schema.marks.underline));
bind("Mod-U", toggleMark(schema.marks.underline));
- bind("Mod-`", toggleMark(schema.marks.code));
-
+ //Commands for lists
bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
-
- bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
-
- bind("Ctrl->", wrapIn(schema.nodes.blockquote));
-
- // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- // let newNode = schema.nodes.footnote.create({});
- // if (dispatch && state.selection.from === state.selection.to) {
- // let tr = state.tr;
- // tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
- // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
- // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
- // return true;
- // }
- // return false;
- // });
-
-
- const cmd = chainCommands(exitCode, (state, dispatch) => {
- if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
- return true;
- }
- return false;
- });
- bind("Mod-Enter", cmd);
- bind("Shift-Enter", cmd);
- mac && bind("Ctrl-Enter", cmd);
-
-
- bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
-
- bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
-
- for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
- }
-
- const hr = schema.nodes.horizontal_rule;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
+ bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const ref = state.selection;
@@ -148,10 +100,46 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
console.log("bullet demote fail");
}
});
- bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+
+ //Command to create a new Tab with a PDF of all the command shortcuts
+ bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ const newDoc = Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 });
+ props.addDocTab(newDoc, "onRight");
+ });
+
+ //Commands to modify BlockType
+ bind("Ctrl->", wrapIn(schema.nodes.blockquote));
+ bind("Alt-\\", setBlockType(schema.nodes.paragraph));
+ bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+
+ for (let i = 1; i <= 6; i++) {
+ bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+ }
+
+ //Command to create a horizontal break line
+ const hr = schema.nodes.horizontal_rule;
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ });
+
+ //Command to unselect all
+ bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+ (document.activeElement as any).blur?.();
+ SelectionManager.DeselectAll();
+ });
+
+ const splitMetadata = (marks: any, tx: Transaction) => {
+ marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+ marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+ return tx;
+ };
+
+ const addTextOnRight = (force: boolean) => {
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (originalDoc instanceof Doc) {
+ if (force || props.Document._singleLine) {
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
@@ -161,18 +149,21 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
+ return true;
}
+ return false;
+ };
+
+ //Command to create a text document to the right of the selected textbox
+ bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ return addTextOnRight(true);
});
- const splitMetadata = (marks: any, tx: Transaction) => {
- marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
- return tx;
- };
- const addTextOnRight = (force: boolean) => {
+ //Command to create a text document to the bottom of the selected textbox
+ bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const layoutDoc = props.Document;
const originalDoc = layoutDoc.rootDocument || layoutDoc;
- if (force || props.Document._singleLine) {
+ if (originalDoc instanceof Doc) {
const layoutKey = StrCast(originalDoc.layoutKey);
const newDoc = Doc.MakeCopy(originalDoc, true);
newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
@@ -182,13 +173,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
- return true;
}
- return false;
- };
- bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
- return addTextOnRight(true);
});
+
+ //command to break line
bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
if (addTextOnRight(false)) return true;
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -204,31 +192,73 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
}
return true;
});
+
+ //Command to create a blank space
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
+
+ bind("Alt-ArrowUp", joinUp);
+ bind("Alt-ArrowDown", joinDown);
+ bind("Mod-BracketLeft", lift);
+
+ const cmd = chainCommands(exitCode, (state, dispatch) => {
+ if (dispatch) {
+ dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
+
+ // mac && bind("Ctrl-Enter", cmd);
+ // bind("Mod-Enter", cmd);
+ bind("Shift-Enter", cmd);
+
+
bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
});
+
const path = (state.doc.resolve(state.selection.from - 1) as any).path;
+
const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+
const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
+
if (anchor >= 0) {
+
const textsel = TextSelection.create(state.doc, anchor, range!.end);
+
const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+
let whitespace = text.length - 1;
+
for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
if (text.endsWith(":")) {
dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
}
}
+
return false;
});
+ // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => 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;
+ // });
return keys;
}
+
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index fbd6c87bb..91187edf9 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -30,7 +30,7 @@ export class RichTextRules {
// > blockquote
wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
- // 1. ordered list
+ // 1. create numerical ordered list
wrappingInputRule(
/^1\.\s$/,
schema.nodes.ordered_list,
@@ -42,49 +42,29 @@ export class RichTextRules {
},
(type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })
),
- // a. alphabbetical list
+
+ // A. create alphabetical ordered list
wrappingInputRule(
- /^a\.\s$/,
+ /^A\.\s$/,
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "alpha", bulletStyle: 1 });
+ return ({ mapStyle: "multi", 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 } })
+ (type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })
),
- // * bullet list
+ // * + - create bullet list
wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
- // ``` code block
+ // ``` create code block
textblockTypeInputRule(/^```$/, schema.nodes.code_block),
- // create an inline view of a tag stored under the '#' field
- new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
- (state, match, start, end) => {
- const tag = match[1];
- if (!tag) return state.tr;
- const multiple = tag.split(";");
- this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
-
- // # heading
- textblockTypeInputRule(
- new RegExp(/^(#{1,6})\s$/),
- schema.nodes.heading,
- match => {
- return ({ level: match[1].length });
- }
- ),
-
- // set the font size using #<font-size>
+ // %<font-size> set the font size
new InputRule(
new RegExp(/%([0-9]+)\s$/),
(state, match, start, end) => {
@@ -92,51 +72,7 @@ export class RichTextRules {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
- // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc
- new InputRule(
- new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
- (state, match, start, end) => {
- const fieldKey = match[1];
- const docid = match[3]?.substring(1);
- const value = match[2]?.substring(1);
- if (!fieldKey) {
- if (docid) {
- DocServer.GetRefField(docid).then(docx => {
- const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
- DocUtils.Publish(target, docid, returnFalse, returnFalse);
- DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
- });
- const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
- }
- return state.tr;
- }
- if (value !== "" && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
- }
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
- return state.tr.deleteRange(start, end).insert(start, fieldView);
- }),
- // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc
- new InputRule(
- new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
- (state, match, start, end) => {
- const fieldKey = match[1] || "";
- const fieldParam = match[2]?.replace("…", "...") || "";
- const docid = match[3]?.substring(1);
- if (!fieldKey && !docid) return state.tr;
- docid && DocServer.GetRefField(docid).then(docx => {
- if (!(docx instanceof Doc && docx)) {
- const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
- DocUtils.Publish(docx, docid, returnFalse, returnFalse);
- }
- });
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
- const sm = state.storedMarks || undefined;
- return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
- }),
+ //Create annotation to a field on the text document
new InputRule(
new RegExp(/>>$/),
(state, match, start, end) => {
@@ -161,25 +97,7 @@ export class RichTextRules {
state.tr;
return replaced;
}),
- // stop using active style
- new InputRule(
- new RegExp(/%%$/),
- (state, match, start, end) => {
- const tr = state.tr.deleteRange(start, end);
- const marks = state.tr.selection.$anchor.nodeBefore?.marks;
- return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
- }),
- // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
- new InputRule(
- new RegExp(/[ti!x]$/),
- (state, match, start, end) => {
- if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
- const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
- const node = (state.doc.resolve(start) as any).nodeAfter;
- if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
// set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
@@ -214,6 +132,7 @@ export class RichTextRules {
}
return null;
}),
+
// set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
new RegExp(/(%q|q)$/),
@@ -235,56 +154,56 @@ export class RichTextRules {
return null;
}),
-
// center justify text
new InputRule(
- new RegExp(/%\^$/),
+ new RegExp(/%\^/),
(state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
// left justify text
new InputRule(
- new RegExp(/%\[$/),
+ new RegExp(/%\[/),
(state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
// right justify text
new InputRule(
- new RegExp(/%\]$/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || undefined;
- const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
- }),
- new InputRule(
- new RegExp(/%\(/),
- (state, match, start, end) => {
- const node = (state.doc.resolve(start) as any).nodeAfter;
- const sm = state.storedMarks || [];
- const mark = state.schema.marks.summarizeInclusive.create();
- sm.push(mark);
- const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- const content = selected.selection.content();
- const replaced = node ? selected.replaceRangeWith(start, end,
- schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
- state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
- }),
- new InputRule(
- new RegExp(/%\)/),
+ new RegExp(/%\]/),
(state, match, start, end) => {
- return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+ const resolved = state.doc.resolve(start) as any;
+ if (resolved?.parent.type.name === "paragraph") {
+ return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }
}),
+
+
+ // %f create footnote
new InputRule(
new RegExp(/%f$/),
(state, match, start, end) => {
@@ -296,26 +215,160 @@ export class RichTextRules {
tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
}),
- // activate a style by name using prefix '%'
+ // activate a style by name using prefix '%<color name>'
new InputRule(
new RegExp(/%[a-z]+$/),
(state, match, start, end) => {
+
const color = match[0].substring(1, match[0].length);
const marks = RichTextMenu.Instance._brushMap.get(color);
+
if (marks) {
const tr = state.tr.deleteRange(start, end);
return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
}
+
const isValidColor = (strColor: string) => {
const s = new Option().style;
s.color = strColor;
return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
};
+
if (isValidColor(color)) {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
}
+
return null;
}),
+
+ // stop using active style
+ new InputRule(
+ new RegExp(/%%$/),
+ (state, match, start, end) => {
+
+ const tr = state.tr.deleteRange(start, end);
+ const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+
+ return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
+ }),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // [[ <fieldKey> : <Doc>]]
+ // [[:Doc]] => hyperlink
+ // [[fieldKey]] => show field
+ // [[fieldKey:Doc]] => show field of doc
+ new InputRule(
+ new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docid = match[3]?.substring(1);
+ const value = match[2]?.substring(1);
+ if (!fieldKey) {
+ if (docid) {
+ DocServer.GetRefField(docid).then(docx => {
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+ DocUtils.Publish(target, docid, returnFalse, returnFalse);
+ DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
+ });
+ const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
+ }
+ return state.tr;
+ }
+ if (value !== "" && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
+ }
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+
+ // create an inline view of a document {{ <layoutKey> : <Doc> }}
+ // {{:Doc}} => show default view of document
+ // {{<layout>}} => show layout for this doc
+ // {{<layout> : Doc}} => show layout for another doc
+ new InputRule(
+ new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1] || "";
+ const fieldParam = match[2]?.replace("…", "...") || "";
+ const docid = match[3]?.substring(1);
+ if (!fieldKey && !docid) return state.tr;
+ docid && DocServer.GetRefField(docid).then(docx => {
+ if (!(docx instanceof Doc && docx)) {
+ const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+ DocUtils.Publish(docx, docid, returnFalse, returnFalse);
+ }
+ });
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
+ const sm = state.storedMarks || undefined;
+ return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ }),
+
+
+
+ // create an inline view of a tag stored under the '#' field
+ new InputRule(
+ new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
+ (state, match, start, end) => {
+ const tag = match[1];
+ if (!tag) return state.tr;
+ const multiple = tag.split(";");
+ this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+ return state.tr.deleteRange(start, end).insert(start, fieldView);
+ }),
+
+
+ // # heading
+ textblockTypeInputRule(
+ new RegExp(/^(#{1,6})\s$/),
+ schema.nodes.heading,
+ match => {
+ return ({ level: match[1].length });
+ }
+ ),
+
+ // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
+ new InputRule(
+ new RegExp(/[ti!x]$/),
+ (state, match, start, end) => {
+
+ if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
+
+ const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+
+ new InputRule(
+ new RegExp(/%\(/),
+ (state, match, start, end) => {
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks || [];
+ const mark = state.schema.marks.summarizeInclusive.create();
+
+ sm.push(mark);
+ const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ const content = selected.selection.content();
+ const replaced = node ? selected.replaceRangeWith(start, end,
+ schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
+ state.tr;
+
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ }),
+
+ new InputRule(
+ new RegExp(/%\)/),
+ (state, match, start, end) => {
+
+ return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+ }),
+
]
};
}
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index 1cc7ec8bf..05557e22a 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -1,188 +1,19 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
+import { IReactionDisposer, reaction } from "mobx";
+import { NodeSelection } from "prosemirror-state";
import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
-import { listSpec } from "../../../../fields/Schema";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
import { ComputedField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnZero, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
-import { CollectionViewType } from "../../collections/CollectionView";
+import { Transform } from "../../../util/Transform";
import { DocumentView } from "../DocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { Transform } from "../../../util/Transform";
import React = require("react");
-import { schema } from "./schema_rts";
-
-export class OrderedListView {
- update(node: any) {
- return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
- }
-}
-
-export class ImageResizeView {
- _handle: HTMLElement;
- _img: HTMLElement;
- _outer: HTMLElement;
- constructor(node: any, view: any, getPos: any, addDocTab: any) {
- //moved
- this._handle = document.createElement("span");
- this._img = document.createElement("img");
- this._outer = document.createElement("span");
- this._outer.style.position = "relative";
- this._outer.style.width = node.attrs.width;
- this._outer.style.height = node.attrs.height;
- this._outer.style.display = "inline-block";
- this._outer.style.overflow = "hidden";
- (this._outer.style as any).float = node.attrs.float;
- //moved
- this._img.setAttribute("src", node.attrs.src);
- this._img.style.width = "100%";
- this._handle.style.position = "absolute";
- this._handle.style.width = "20px";
- this._handle.style.height = "20px";
- this._handle.style.backgroundColor = "blue";
- this._handle.style.borderRadius = "15px";
- this._handle.style.display = "none";
- this._handle.style.bottom = "-10px";
- this._handle.style.right = "-10px";
- const self = this;
- //moved
- this._img.onclick = function (e: any) {
- e.stopPropagation();
- e.preventDefault();
- if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
- view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
- }
- };
- //moved
- this._img.onpointerdown = function (e: any) {
- if (e.ctrlKey) {
- e.preventDefault();
- e.stopPropagation();
- DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
- (linkDoc instanceof Doc) &&
- DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
- document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
- }
- };
- //moved
- this._handle.onpointerdown = function (e: any) {
- e.preventDefault();
- e.stopPropagation();
- const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
- const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
- const startX = e.pageX;
- const startWidth = parseFloat(node.attrs.width);
- const onpointermove = (e: any) => {
- const currentX = e.pageX;
- const diffInPx = currentX - startX;
- self._outer.style.width = `${startWidth + diffInPx}`;
- self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
- };
-
- const onpointerup = () => {
- document.removeEventListener("pointermove", onpointermove);
- document.removeEventListener("pointerup", onpointerup);
- const pos = view.state.selection.from;
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
- view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
- };
-
- document.addEventListener("pointermove", onpointermove);
- document.addEventListener("pointerup", onpointerup);
- };
- //Moved
- this._outer.appendChild(this._img);
- this._outer.appendChild(this._handle);
- (this as any).dom = this._outer;
- }
-
- selectNode() {
- this._img.classList.add("ProseMirror-selectednode");
-
- this._handle.style.display = "";
- }
-
- deselectNode() {
- this._img.classList.remove("ProseMirror-selectednode");
-
- this._handle.style.display = "none";
- }
-}
-
-export class DashDocCommentView {
- _collapsed: HTMLElement;
- _view: any;
- constructor(node: any, view: any, getPos: any) {
- //moved
- this._collapsed = document.createElement("span");
- this._collapsed.className = "formattedTextBox-inlineComment";
- this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
- this._view = view;
- //moved
- const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
- for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
- const m = view.state.doc.nodeAt(i);
- if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
- return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
- }
- }
- const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
- view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
- setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
- return undefined;
- };
- //moved
- this._collapsed.onpointerdown = (e: any) => {
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerup = (e: any) => {
- const target = targetNode();
- if (target) {
- const expand = target.hidden;
- const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
- view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
- setTimeout(() => {
- expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
- try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
- }, 0);
- }
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerenter = (e: any) => {
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
- e.preventDefault();
- e.stopPropagation();
- };
- //moved
- this._collapsed.onpointerleave = (e: any) => {
- DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
- e.preventDefault();
- e.stopPropagation();
- };
-
- (this as any).dom = this._collapsed;
- }
- //moved
- selectNode() { }
-}
export class DashDocView {
_dashSpan: HTMLDivElement;
@@ -192,16 +23,22 @@ export class DashDocView {
_renderDisposer: IReactionDisposer | undefined;
_textBox: FormattedTextBox;
+ //moved
getDocTransform = () => {
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
}
+
+ //moved
contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+ //moved
outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ //moved
this._textBox = tbox;
+
this._dashSpan = document.createElement("div");
this._outer = document.createElement("span");
this._outer.style.position = "relative";
@@ -218,28 +55,35 @@ export class DashDocView {
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
this._dashSpan.style.whiteSpace = "normal";
+
this._dashSpan.onpointerleave = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
(ele as HTMLDivElement).style.backgroundColor = "";
}
};
+
this._dashSpan.onpointerenter = () => {
const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
if (ele) {
(ele as HTMLDivElement).style.backgroundColor = "orange";
}
};
+
const removeDoc = () => {
+ console.log("DashDocView.removeDoc"); // SMM
const pos = getPos();
const ns = new NodeSelection(view.state.doc.resolve(pos));
view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
return true;
};
- const alias = node.attrs.alias;
+ const alias = node.attrs.alias;
+ const self = this;
const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
+
DocServer.GetRefField(docid + alias).then(async dashDoc => {
+
if (!(dashDoc instanceof Doc)) {
alias && DocServer.GetRefField(docid).then(async dashDocBase => {
if (dashDocBase instanceof Doc) {
@@ -253,7 +97,8 @@ export class DashDocView {
self.doRender(dashDoc, removeDoc, node, view, getPos);
}
});
- const self = this;
+
+
this._dashSpan.onkeydown = function (e: any) {
e.stopPropagation();
if (e.key === "Tab" || e.key === "Enter") {
@@ -281,6 +126,7 @@ export class DashDocView {
this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
}, { fireImmediately: true });
+
const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
ReactDOM.unmountComponentAtNode(this._dashSpan);
@@ -310,6 +156,7 @@ export class DashDocView {
ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
ContentScaling={this.contentScaling}
/>, this._dashSpan);
+
if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
@@ -318,6 +165,7 @@ export class DashDocView {
}
}
};
+
this._renderDisposer?.();
this._renderDisposer = reaction(() => {
// if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
@@ -337,202 +185,9 @@ export class DashDocView {
{ fireImmediately: true });
}
}
+
destroy() {
ReactDOM.unmountComponentAtNode(this._dashSpan);
this._reactionDisposer?.();
}
}
-
-export class FootnoteView {
- innerView: any;
- outerView: any;
- node: any;
- dom: any;
- getPos: any;
-
- constructor(node: any, view: any, getPos: any) {
- // We'll need these later
- this.node = node;
- this.outerView = view;
- this.getPos = getPos;
-
- // The node's representation in the editor (empty, for now)
- this.dom = document.createElement("footnote");
- this.dom.addEventListener("pointerup", this.toggle, true);
- // These are used when the footnote is selected
- this.innerView = null;
- }
- selectNode() {
- const attrs = { ...this.node.attrs };
- attrs.visibility = true;
- this.dom.classList.add("ProseMirror-selectednode");
- if (!this.innerView) this.open();
- }
-
- deselectNode() {
- const attrs = { ...this.node.attrs };
- attrs.visibility = false;
- this.dom.classList.remove("ProseMirror-selectednode");
- if (this.innerView) this.close();
- }
- open() {
- // Append a tooltip to the outer node
- const tooltip = this.dom.appendChild(document.createElement("div"));
- tooltip.className = "footnote-tooltip";
- // And put a sub-ProseMirror into that
- this.innerView = new EditorView(tooltip, {
- // You can use any node as an editor document
- state: EditorState.create({
- doc: this.node,
- plugins: [keymap(baseKeymap),
- keymap({
- "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
- "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
- "Mod-b": toggleMark(schema.marks.strong)
- }),
- // new Plugin({
- // view(newView) {
- // // TODO -- make this work with RichTextMenu
- // // return FormattedTextBox.getToolTip(newView);
- // }
- // })
- ],
-
- }),
- // This is the magic part
- dispatchTransaction: this.dispatchInner.bind(this),
- handleDOMEvents: {
- pointerdown: ((view: any, e: PointerEvent) => {
- // Kludge to prevent issues due to the fact that the whole
- // footnote is node-selected (and thus DOM-selected) when
- // the parent editor is focused.
- e.stopPropagation();
- document.addEventListener("pointerup", this.ignore, true);
- if (this.outerView.hasFocus()) this.innerView.focus();
- }) as any
- }
-
- });
- setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
- }
-
- ignore = (e: PointerEvent) => {
- e.stopPropagation();
- document.removeEventListener("pointerup", this.ignore, true);
- }
-
- toggle = () => {
- if (this.innerView) this.close();
- else {
- this.open();
- }
- }
- close() {
- this.innerView && this.innerView.destroy();
- this.innerView = null;
- this.dom.textContent = "";
- }
-
- dispatchInner(tr: any) {
- const { state, transactions } = this.innerView.state.applyTransaction(tr);
- this.innerView.updateState(state);
-
- if (!tr.getMeta("fromOutside")) {
- const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
- for (const transaction of transactions) {
- const steps = transaction.steps;
- for (const step of steps) {
- outerTr.step(step.map(offsetMap));
- }
- }
- if (outerTr.docChanged) this.outerView.dispatch(outerTr);
- }
- }
- update(node: any) {
- if (!node.sameMarkup(this.node)) return false;
- this.node = node;
- if (this.innerView) {
- const state = this.innerView.state;
- const start = node.content.findDiffStart(state.doc.content);
- if (start !== null) {
- let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
- const overlap = start - Math.min(endA, endB);
- if (overlap > 0) { endA += overlap; endB += overlap; }
- this.innerView.dispatch(
- state.tr
- .replace(start, endB, node.slice(start, endA))
- .setMeta("fromOutside", true));
- }
- }
- return true;
- }
-
- destroy() {
- if (this.innerView) this.close();
- }
-
- stopEvent(event: any) {
- return this.innerView && this.innerView.dom.contains(event.target);
- }
-
- ignoreMutation() { return true; }
-}
-
-export class SummaryView {
- _collapsed: HTMLElement;
- _view: any;
- constructor(node: any, view: any, getPos: any) {
- this._collapsed = document.createElement("span");
- this._collapsed.className = this.className(node.attrs.visibility);
- this._view = view;
- const js = node.toJSON;
- node.toJSON = function () {
- return js.apply(this, arguments);
- };
-
- this._collapsed.onpointerdown = (e: any) => {
- const visible = !node.attrs.visibility;
- const attrs = { ...node.attrs, visibility: visible };
- let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
- textSelection = this.updateSummarizedText(getPos() + 1);
- attrs.text = textSelection.content();
- attrs.textslice = attrs.text.toJSON();
- }
- view.dispatch(view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
- setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
- e.preventDefault();
- e.stopPropagation();
- this._collapsed.className = this.className(visible);
- };
- (this as any).dom = this._collapsed;
- }
- selectNode() { }
-
- deselectNode() { }
-
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
- updateSummarizedText(start?: any) {
- const mtype = this._view.state.schema.marks.summarize;
- const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
- let endPos = start;
-
- const visited = new Set();
- for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
- let skip = false;
- this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
- if (node.isLeaf && !visited.has(node) && !skip) {
- if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
- visited.add(node);
- endPos = i + node.nodeSize - 1;
- }
- else skip = true;
- }
- });
- }
- return TextSelection.create(this._view.state.doc, start, endPos);
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 89908d8ee..922285bd0 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,81 +1,81 @@
import { TextSelection } from "prosemirror-state";
import { Fragment, Node, Slice } from "prosemirror-model";
-
+import * as ReactDOM from 'react-dom';
import React = require("react");
-interface ISummaryView {
- node: any;
- view: any;
- getPos: any;
- self: any;
-}
-export class SummaryView extends React.Component<ISummaryView> {
+// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
+// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
+// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
+// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
+export class SummaryView {
+ _fieldWrapper: HTMLSpanElement; // container for label and value
- onPointerDown = (e: any) => {
- const visible = !this.props.node.attrs.visibility;
- const attrs = { ...this.props.node.attrs, visibility: visible };
- let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
- if (!visible) { // update summarized text and save in attrs
- textSelection = this.updateSummarizedText(this.props.getPos() + 1);
- attrs.text = textSelection.content();
- attrs.textslice = attrs.text.toJSON();
- }
- this.props.view.dispatch(this.props.view.state.tr.
- setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
- replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
- setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
- e.preventDefault();
- e.stopPropagation();
- const _collapsed = document.getElementById('collapse') as HTMLElement;
- _collapsed.className = this.className(visible);
+ constructor(node: any, view: any, getPos: any) {
+ const self = this;
+ this._fieldWrapper = document.createElement("span");
+ this._fieldWrapper.className = this.className(node.attrs.visibility);
+ this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); }
+ this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+ const js = node.toJSON;
+ node.toJSON = function () { return js.apply(this, arguments); };
+
+ ReactDOM.render(<SummaryViewInternal />, this._fieldWrapper);
+ (this as any).dom = this._fieldWrapper;
}
- updateSummarizedText(start?: any) {
- const mtype = this.props.view.state.schema.marks.summarize;
- const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
+ className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+ destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); }
+ selectNode() { }
+
+ updateSummarizedText(start: any, view: any) {
+ const mtype = view.state.schema.marks.summarize;
+ const mtypeInc = view.state.schema.marks.summarizeInclusive;
let endPos = start;
const visited = new Set();
- for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
+ for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
- this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
- if (this.props.node.isLeaf && !visited.has(node) && !skip) {
- if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+ view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ if (node.isLeaf && !visited.has(node) && !skip) {
+ if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
- endPos = i + this.props.node.nodeSize - 1;
+ endPos = i + node.nodeSize - 1;
}
else skip = true;
}
});
}
- return TextSelection.create(this.props.view.state.doc, start, endPos);
+ return TextSelection.create(view.state.doc, start, endPos);
}
- className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
- selectNode() { }
-
- deselectNode() { }
+ onPointerDown = (e: any, node: any, view: any, getPos: any) => {
+ const visible = !node.attrs.visibility;
+ const attrs = { ...node.attrs, visibility: visible };
+ let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
+ if (!visible) { // update summarized text and save in attrs
+ textSelection = this.updateSummarizedText(getPos() + 1, view);
+ 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._fieldWrapper.className = this.className(visible);
+ }
+}
+interface ISummaryView {
+}
+// currently nothing needs to be rendered for the internal view of a summary.
+export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
- const _view = this.props.node.view;
- const js = this.props.node.toJSon;
-
- this.props.node.toJSON = function () {
- return js.apply(this, arguments);
- };
-
- const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
-
- return (
- <span
- className={spanCollapsedClassName}
- id='collapse'
- onPointerDown={this.onPointerDown}
- >
-
- </span>
- );
-
+ return <> </>;
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e39e96607..c02dd6786 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -126,16 +126,24 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
// file address of the pdf
const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
const { url: relative } = this.props;
- const pathComponents = relative.split("/pdfs/")[1].split("/");
- const coreFilename = pathComponents.pop()!.split(".")[0];
- const params: any = {
- coreFilename,
- pageNum: this.Document.curPage || 1,
- };
- if (pathComponents.length) {
- params.subtree = `${pathComponents.join("/")}/`;
+ if (relative.includes("/pdfs/")) {
+ const pathComponents = relative.split("/pdfs/")[1].split("/");
+ const coreFilename = pathComponents.pop()!.split(".")[0];
+ const params: any = {
+ coreFilename,
+ pageNum: this.Document.curPage || 1,
+ };
+ if (pathComponents.length) {
+ params.subtree = `${pathComponents.join("/")}/`;
+ }
+ this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
+ } else {
+ const params: any = {
+ coreFilename: relative.split("/")[relative.split("/").length - 1],
+ pageNum: this.Document.curPage || 1,
+ };
+ this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
}
- this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0;