aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DictationManager.ts75
-rw-r--r--src/client/util/DocumentManager.ts74
-rw-r--r--src/client/util/DragManager.ts459
-rw-r--r--src/client/util/DropConverter.ts10
-rw-r--r--src/client/util/History.ts9
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx96
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts9
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx6
-rw-r--r--src/client/util/InteractionUtils.ts51
-rw-r--r--src/client/util/LinkManager.ts68
-rw-r--r--src/client/util/ParagraphNodeSpec.ts10
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts60
-rw-r--r--src/client/util/RichTextRules.ts228
-rw-r--r--src/client/util/RichTextSchema.tsx304
-rw-r--r--src/client/util/Scripting.ts38
-rw-r--r--src/client/util/SearchUtil.ts32
-rw-r--r--src/client/util/SelectionManager.ts5
-rw-r--r--src/client/util/SerializationHelper.ts9
-rw-r--r--src/client/util/SharingManager.tsx14
-rw-r--r--src/client/util/TooltipLinkingMenu.tsx22
-rw-r--r--src/client/util/TooltipTextMenu.scss4
-rw-r--r--src/client/util/TooltipTextMenu.tsx1335
-rw-r--r--src/client/util/TypedEvent.ts62
-rw-r--r--src/client/util/UndoManager.ts16
24 files changed, 1301 insertions, 1695 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 6bbd3d0ed..3d8f2d234 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,7 +11,6 @@ import { Cast, CastCtor } from "../../new_fields/Types";
import { listSpec } from "../../new_fields/Schema";
import { AudioField, ImageField } from "../../new_fields/URLField";
import { HistogramField } from "../northstar/dash-fields/HistogramField";
-import { MainView } from "../views/MainView";
import { Utils } from "../../Utils";
import { RichTextField } from "../../new_fields/RichTextField";
import { DictationOverlay } from "../views/DictationOverlay";
@@ -48,7 +47,7 @@ export namespace DictationManager {
export const Infringed = "unable to process: dictation manager still involved in previous session";
const browser = (() => {
- let identifier = navigator.userAgent.toLowerCase();
+ const identifier = navigator.userAgent.toLowerCase();
if (identifier.indexOf("safari") >= 0) {
return "Safari";
}
@@ -90,7 +89,7 @@ export namespace DictationManager {
export const listen = async (options?: Partial<ListeningOptions>) => {
let results: string | undefined;
- let overlay = options !== undefined && options.useOverlay;
+ const overlay = options !== undefined && options.useOverlay;
if (overlay) {
DictationOverlay.Instance.dictationOverlayVisible = true;
DictationOverlay.Instance.isListening = { interim: false };
@@ -102,7 +101,7 @@ export namespace DictationManager {
Utils.CopyText(results);
if (overlay) {
DictationOverlay.Instance.isListening = false;
- let execute = options && options.tryExecute;
+ const execute = options && options.tryExecute;
DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
}
@@ -131,12 +130,12 @@ export namespace DictationManager {
}
isListening = true;
- let handler = options ? options.interimHandler : undefined;
- let continuous = options ? options.continuous : undefined;
- let indefinite = continuous && continuous.indefinite;
- let language = options ? options.language : undefined;
- let intra = options && options.delimiters ? options.delimiters.intra : undefined;
- let inter = options && options.delimiters ? options.delimiters.inter : undefined;
+ const handler = options ? options.interimHandler : undefined;
+ const continuous = options ? options.continuous : undefined;
+ const indefinite = continuous && continuous.indefinite;
+ const language = options ? options.language : undefined;
+ const intra = options && options.delimiters ? options.delimiters.intra : undefined;
+ const inter = options && options.delimiters ? options.delimiters.inter : undefined;
recognizer.onstart = () => console.log("initiating speech recognition session...");
recognizer.interimResults = handler !== undefined;
@@ -177,7 +176,7 @@ export namespace DictationManager {
recognizer.start();
};
- let complete = () => {
+ const complete = () => {
if (indefinite) {
current && sessionResults.push(current);
sessionResults.length && resolve(sessionResults.join(inter || interSession));
@@ -213,8 +212,8 @@ export namespace DictationManager {
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
- let results = e.results;
- let transcripts: string[] = [];
+ const results = e.results;
+ const transcripts: string[] = [];
for (let i = 0; i < results.length; i++) {
transcripts.push(results.item(i).item(0).transcript.trim());
}
@@ -238,18 +237,18 @@ export namespace DictationManager {
export const execute = async (phrase: string) => {
return UndoManager.RunInBatch(async () => {
- let targets = SelectionManager.SelectedDocuments();
+ const targets = SelectionManager.SelectedDocuments();
if (!targets || !targets.length) {
return;
}
phrase = phrase.toLowerCase();
- let entry = Independent.get(phrase);
+ const entry = Independent.get(phrase);
if (entry) {
let success = false;
- let restrictTo = entry.restrictTo;
- for (let target of targets) {
+ const restrictTo = entry.restrictTo;
+ for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
await entry.action(target);
success = true;
@@ -258,14 +257,14 @@ export namespace DictationManager {
return success;
}
- for (let entry of Dependent) {
- let regex = entry.expression;
- let matches = regex.exec(phrase);
+ for (const entry of Dependent) {
+ const regex = entry.expression;
+ const matches = regex.exec(phrase);
regex.lastIndex = 0;
if (matches !== null) {
let success = false;
- let restrictTo = entry.restrictTo;
- for (let target of targets) {
+ const restrictTo = entry.restrictTo;
+ for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
await entry.action(target, matches);
success = true;
@@ -289,7 +288,7 @@ export namespace DictationManager {
]);
const tryCast = (view: DocumentView, type: DocumentType) => {
- let ctor = ConstructorMap.get(type);
+ const ctor = ConstructorMap.get(type);
if (!ctor) {
return false;
}
@@ -297,7 +296,7 @@ export namespace DictationManager {
};
const validate = (target: DocumentView, types: DocumentType[]) => {
- for (let type of types) {
+ for (const type of types) {
if (tryCast(target, type)) {
return true;
}
@@ -306,11 +305,11 @@ export namespace DictationManager {
};
const interpretNumber = (number: string) => {
- let initial = parseInt(number);
+ const initial = parseInt(number);
if (!isNaN(initial)) {
return initial;
}
- let converted = interpreter.wordsToNumbers(number, { fuzzy: true });
+ const converted = interpreter.wordsToNumbers(number, { fuzzy: true });
if (converted === null) {
return NaN;
}
@@ -326,20 +325,20 @@ export namespace DictationManager {
["open fields", {
action: (target: DocumentView) => {
- let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+ const kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
target.props.addDocTab(kvp, target.props.DataDoc, "onRight");
}
}],
["new outline", {
action: (target: DocumentView) => {
- let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
+ const newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
newBox.autoHeight = true;
- let proto = newBox.proto!;
- let prompt = "Press alt + r to start dictating here...";
- let head = 3;
- let anchor = head + prompt.length;
- let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
+ const proto = newBox.proto!;
+ const prompt = "Press alt + r to start dictating here...";
+ const head = 3;
+ const anchor = head + prompt.length;
+ const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
proto.data = new RichTextField(proseMirrorState);
proto.backgroundColor = "#eeffff";
target.props.addDocTab(newBox, proto, "onRight");
@@ -353,10 +352,10 @@ export namespace DictationManager {
{
expression: /create (\w+) documents of type (image|nested collection)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
- let count = interpretNumber(matches[1]);
- let what = matches[2];
- let dataDoc = Doc.GetProto(target.props.Document);
- let fieldKey = "data";
+ const count = interpretNumber(matches[1]);
+ const what = matches[2];
+ const dataDoc = Doc.GetProto(target.props.Document);
+ const fieldKey = "data";
if (isNaN(count)) {
return;
}
@@ -379,7 +378,7 @@ export namespace DictationManager {
{
expression: /view as (freeform|stacking|masonry|schema|tree)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
- let mode = CollectionViewType.valueOf(matches[1]);
+ const mode = CollectionViewType.valueOf(matches[1]);
mode && (target.props.Document.viewType = mode);
},
restrictTo: [DocumentType.COL]
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 346e88f40..fb4c2155a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -33,7 +33,7 @@ export class DocumentManager {
//gets all views
public getDocumentViewsById(id: string) {
- let toReturn: DocumentView[] = [];
+ const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.map(view => {
if (view.props.Document[Id] === id) {
toReturn.push(view);
@@ -41,7 +41,7 @@ export class DocumentManager {
});
if (toReturn.length === 0) {
DocumentManager.Instance.DocumentViews.map(view => {
- let doc = view.props.Document.proto;
+ const doc = view.props.Document.proto;
if (doc && doc[Id] && doc[Id] === id) {
toReturn.push(view);
}
@@ -57,9 +57,9 @@ export class DocumentManager {
public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined {
let toReturn: DocumentView | undefined;
- let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
+ const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
- for (let pass of passes) {
+ for (const pass of passes) {
DocumentManager.Instance.DocumentViews.map(view => {
if (view.props.Document[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
@@ -68,7 +68,7 @@ export class DocumentManager {
});
if (!toReturn) {
DocumentManager.Instance.DocumentViews.map(view => {
- let doc = view.props.Document.proto;
+ const doc = view.props.Document.proto;
if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
}
@@ -90,51 +90,57 @@ export class DocumentManager {
return views.length ? views[0] : undefined;
}
public getDocumentViews(toFind: Doc): DocumentView[] {
- let toReturn: DocumentView[] = [];
+ const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.map(view =>
- Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+ view.props.Document === toFind && toReturn.push(view));
+ DocumentManager.Instance.DocumentViews.map(view =>
+ view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
return toReturn;
}
@computed
public get LinkedDocumentViews() {
- let pairs = DocumentManager.Instance.DocumentViews.filter(dv =>
- (dv.isSelected() || Doc.IsBrushed(dv.props.Document)) // draw links from DocumentViews that are selected or brushed OR
- || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which
- let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors
- let init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed
- return init && rest;
- })
- ).reduce((pairs, dv) => {
- let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
- pairs.push(...linksList.reduce((pairs, link) => {
- let linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
- linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
- if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) {
- pairs.push({ a: dv, b: docView1, l: link });
- }
- });
+ const pairs = DocumentManager.Instance.DocumentViews
+ //.filter(dv => (dv.isSelected() || Doc.IsBrushed(dv.props.Document))) // draw links from DocumentViews that are selected or brushed OR
+ // || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which
+ // const rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors
+ // const init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed
+ // return init && rest;
+ // }
+ // )
+ .reduce((pairs, dv) => {
+ const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
+ pairs.push(...linksList.reduce((pairs, link) => {
+ const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
+ linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+ if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) {
+ pairs.push({ a: dv, b: docView1, l: link });
+ }
+ });
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
return pairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
- return pairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
return pairs;
}
public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise<void> => {
- let highlight = () => {
+ const highlight = () => {
const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
finalDocView && (finalDocView.Document.scrollToLinkID = linkId);
finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
};
const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
- const annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
+ let annotatedDoc = await Cast(docView?.props.Document.annotationOn, Doc);
+ if (annotatedDoc) {
+ const first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc);
+ if (first) annotatedDoc = first.props.Document;
+ }
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
- annotatedDoc && docView.props.focus(annotatedDoc, false);
- docView.props.focus(docView.props.Document, willZoom);
+ docView.props.focus(docView.props.Document, false);
highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
@@ -176,7 +182,7 @@ export class DocumentManager {
}
public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
- const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc);
+ const linkDocs = link ? [link] : DocListCast(doc.links);
SelectionManager.DeselectAll();
const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc));
const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc));
@@ -194,17 +200,19 @@ export class DocumentManager {
const target = linkFollowDocs[reverse ? 1 : 0];
target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
+ } else if (link) {
+ DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined);
}
}
@action
zoomIntoScale = (docDelegate: Doc, scale: number) => {
- let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
+ const docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
docView && docView.props.zoomToScale(scale);
}
getScaleOfDocView = (docDelegate: Doc) => {
- let doc = Doc.GetProto(docDelegate);
+ const doc = Doc.GetProto(docDelegate);
const docView = DocumentManager.Instance.getDocumentView(doc);
if (docView) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index bbc29585c..df2f5fe3c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,7 +1,5 @@
-import { action, runInAction } from "mobx";
-import { Doc, Field } from "../../new_fields/Doc";
-import { Cast, StrCast, ScriptCast } from "../../new_fields/Types";
-import { URLField } from "../../new_fields/URLField";
+import { Doc, Field, DocListCast } from "../../new_fields/Doc";
+import { Cast, ScriptCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import * as globalCssVariables from "../views/globalCssVariables.scss";
@@ -20,43 +18,46 @@ import { convertDropDataToButtons } from "./DropConverter";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(
_reference: React.RefObject<HTMLElement>,
- docFunc: () => Doc | Promise<Doc>,
+ docFunc: () => Doc | Promise<Doc> | undefined,
moveFunc?: DragManager.MoveFunction,
dropAction?: dropActionType,
- options?: any,
+ treeViewId?: string,
dontHideOnDrop?: boolean,
dragStarted?: () => void
) {
- let onRowMove = async (e: PointerEvent) => {
+ const onRowMove = async (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- let doc = await docFunc();
- var dragData = new DragManager.DocumentDragData([doc]);
- dragData.dropAction = dropAction;
- dragData.moveDocument = moveFunc;
- dragData.options = options;
- dragData.dontHideOnDrop = dontHideOnDrop;
- DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
- dragStarted && dragStarted();
+ const doc = await docFunc();
+ if (doc) {
+ const dragData = new DragManager.DocumentDragData([doc]);
+ dragData.dropAction = dropAction;
+ dragData.moveDocument = moveFunc;
+ dragData.treeViewId = treeViewId;
+ dragData.dontHideOnDrop = dontHideOnDrop;
+ DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
+ dragStarted && dragStarted();
+ }
};
- let onRowUp = (): void => {
+ const onRowUp = (): void => {
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
- let onItemDown = async (e: React.PointerEvent) => {
+ const onItemDown = async (e: React.PointerEvent) => {
if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey && CollectionDockingView.Instance) {
e.persist();
- CollectionDockingView.Instance.StartOtherDrag({
+ const dragDoc = await docFunc();
+ dragDoc && CollectionDockingView.Instance.StartOtherDrag({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
button: 0
- }, [await docFunc()]);
+ }, [dragDoc]);
} else {
document.addEventListener("pointermove", onRowMove);
document.addEventListener("pointerup", onRowUp);
@@ -66,62 +67,9 @@ export function SetupDrag(
return onItemDown;
}
-function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
- const document = SelectionManager.SelectedDocuments()[0];
- document && document.props.removeDocument && document.props.removeDocument(doc);
- addDocument(doc);
- return true;
-}
-
-export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) {
- let draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
- if (draggeddoc) {
- let moddrag = await Cast(draggeddoc.annotationOn, Doc);
- let dragdocs = moddrag ? [moddrag] : [draggeddoc];
- let dragData = new DragManager.DocumentDragData(dragdocs);
- dragData.moveDocument = moveLinkedDocument;
- DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
-}
-
-export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) {
- let srcTarg = sourceDoc.proto;
- let draggedDocs: Doc[] = [];
-
- if (srcTarg) {
- let linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg);
- if (linkDocs) {
- draggedDocs = linkDocs.map(link => {
- let opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
- if (opp) return opp;
- }) as Doc[];
- }
- }
- if (draggedDocs.length) {
- let moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
- let doc = await Cast(draggedDoc.annotationOn, Doc);
- if (doc) moddrag.push(doc);
- }
- let dragdocs = moddrag.length ? moddrag : draggedDocs;
- let dragData = new DragManager.DocumentDragData(dragdocs);
- dragData.moveDocument = moveLinkedDocument;
- DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
-}
-
-
export namespace DragManager {
+ let dragDiv: HTMLDivElement;
+
export function Root() {
const root = document.getElementById("root");
if (!root) {
@@ -129,79 +77,45 @@ export namespace DragManager {
}
return root;
}
+ export let AbortDrag: () => void = emptyFunction;
+ export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
- let dragDiv: HTMLDivElement;
-
- export enum DragButtons {
- Left = 1,
- Right = 2,
- Both = Left | Right
- }
-
- interface DragOptions {
- handlers: DragHandlers;
-
- hideSource: boolean | (() => boolean);
-
- dragHasStarted?: () => void;
-
- withoutShiftDrag?: boolean;
-
- finishDrag?: (dropData: { [id: string]: any }) => void;
-
- offsetX?: number;
-
+ export interface DragDropDisposer { (): void; }
+ export interface DragOptions {
+ dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed
+ hideSource?: boolean; // hide source document during drag
+ offsetX?: number; // offset of top left of source drag visual from cursor
offsetY?: number;
}
- export interface DragDropDisposer {
- (): void;
- }
-
- export class DragCompleteEvent { }
-
- export interface DragHandlers {
- dragComplete: (e: DragCompleteEvent) => void;
- }
-
- export interface DropOptions {
- handlers: DropHandlers;
- }
+ // event called when the drag operation results in a drop action
export class DropEvent {
constructor(
readonly x: number,
readonly y: number,
- readonly data: { [id: string]: any },
- readonly mods: string
+ readonly complete: DragCompleteEvent,
+ readonly altKey: boolean,
+ readonly metaKey: boolean,
+ readonly ctrlKey: boolean
) { }
}
- export interface DropHandlers {
- drop: (e: Event, de: DropEvent) => void;
- }
-
- export function MakeDropTarget(
- element: HTMLElement,
- options: DropOptions
- ): DragDropDisposer {
- if ("canDrop" in element.dataset) {
- throw new Error(
- "Element is already droppable, can't make it droppable again"
- );
+ // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
+ export class DragCompleteEvent {
+ constructor(aborted: boolean, dragData: { [id: string]: any }) {
+ this.aborted = aborted;
+ this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined;
+ this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData : undefined;
+ this.linkDragData = dragData instanceof LinkDragData ? dragData : undefined;
+ this.columnDragData = dragData instanceof ColumnDragData ? dragData : undefined;
}
- element.dataset.canDrop = "true";
- const handler = (e: Event) => {
- const ce = e as CustomEvent<DropEvent>;
- options.handlers.drop(e, ce.detail);
- };
- element.addEventListener("dashOnDrop", handler);
- return () => {
- element.removeEventListener("dashOnDrop", handler);
- delete element.dataset.canDrop;
- };
+ aborted: boolean;
+ docDragData?: DocumentDragData;
+ annoDragData?: PdfAnnoDragData;
+ linkDragData?: LinkDragData;
+ columnDragData?: ColumnDragData;
}
- export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
export class DocumentDragData {
constructor(dragDoc: Doc[]) {
this.draggedDocuments = dragDoc;
@@ -210,6 +124,9 @@ export namespace DragManager {
}
draggedDocuments: Doc[];
droppedDocuments: Doc[];
+ dragDivName?: string;
+ treeViewId?: string;
+ dontHideOnDrop?: boolean;
offset: number[];
dropAction: dropActionType;
userDropAction: dropActionType;
@@ -217,16 +134,32 @@ export namespace DragManager {
moveDocument?: MoveFunction;
isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts
applyAsTemplate?: boolean;
- [id: string]: any;
}
-
- export class AnnotationDragData {
+ export class LinkDragData {
+ constructor(linkSourceDoc: Doc) {
+ this.linkSourceDocument = linkSourceDoc;
+ }
+ droppedDocuments: Doc[] = [];
+ linkSourceDocument: Doc;
+ dontClearTextBox?: boolean;
+ linkDocument?: Doc;
+ }
+ export class ColumnDragData {
+ constructor(colKey: SchemaHeaderField) {
+ this.colKey = colKey;
+ }
+ colKey: SchemaHeaderField;
+ }
+ // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected.
+ // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag
+ export class PdfAnnoDragData {
constructor(dragDoc: Doc, annotationDoc: Doc, dropDoc: Doc) {
this.dragDocument = dragDoc;
this.dropDocument = dropDoc;
this.annotationDocument = annotationDoc;
this.offset = [0, 0];
}
+ linkedToDoc?: boolean;
targetContext: Doc | undefined;
dragDocument: Doc;
annotationDocument: Doc;
@@ -236,98 +169,103 @@ export namespace DragManager {
userDropAction: dropActionType;
}
- export let StartDragFunctions: (() => void)[] = [];
+ export function MakeDropTarget(
+ element: HTMLElement,
+ dropFunc: (e: Event, de: DropEvent) => void
+ ): DragDropDisposer {
+ if ("canDrop" in element.dataset) {
+ throw new Error(
+ "Element is already droppable, can't make it droppable again"
+ );
+ }
+ element.dataset.canDrop = "true";
+ const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+ element.addEventListener("dashOnDrop", handler);
+ return () => {
+ element.removeEventListener("dashOnDrop", handler);
+ delete element.dataset.canDrop;
+ };
+ }
+ // drag a document and drop it (or make an alias/copy on drop)
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
- runInAction(() => StartDragFunctions.map(func => func()));
+ const finishDrag = (e: DragCompleteEvent) => {
+ e.docDragData && (e.docDragData.droppedDocuments =
+ dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
+ dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
+ dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
+ );
+ e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
+ Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
+ };
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
- StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
- (dropData: { [id: string]: any }) => {
- (dropData.droppedDocuments =
- dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
- dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
- dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
- );
- dropData.droppedDocuments.forEach((drop: Doc, i: number) =>
- Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
- });
+ StartDrag(eles, dragData, downX, downY, options, finishDrag);
}
+ // drag a button template and drop a new button
export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
- let dragData = new DragManager.DocumentDragData([]);
- runInAction(() => StartDragFunctions.map(func => func()));
- StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
- (dropData: { [id: string]: any }) => {
- let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
- bd.onClick = ScriptField.MakeScript(script);
- params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
- initialize && initialize(bd);
- bd.buttonParams = new List<string>(params);
- dropData.droppedDocuments = [bd];
- });
+ const finishDrag = (e: DragCompleteEvent) => {
+ const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
+ bd.onClick = ScriptField.MakeScript(script);
+ params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
+ initialize && initialize(bd);
+ bd.buttonParams = new List<string>(params);
+ e.docDragData && (e.docDragData.droppedDocuments = [bd]);
+ };
+ StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
- export function StartLinkedDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
- dragData.moveDocument = moveLinkedDocument;
+ // drag links and drop link targets (aliasing them if needed)
+ export async function StartLinkTargetsDrag(dragEle: HTMLElement, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
+ const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
- runInAction(() => StartDragFunctions.map(func => func()));
- StartDrag(eles, dragData, downX, downY, options,
- (dropData: { [id: string]: any }) => {
- let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => {
- let dvs = DocumentManager.Instance.getDocumentViews(d);
- if (dvs.length) {
- let containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
- let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView);
- if (inContext.length) {
- inContext.forEach(dv => droppedDocs.push(dv.props.Document));
+ if (draggedDocs.length) {
+ const moddrag: Doc[] = [];
+ for (const draggedDoc of draggedDocs) {
+ const doc = await Cast(draggedDoc.annotationOn, Doc);
+ if (doc) moddrag.push(doc);
+ }
+
+ const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+ dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
+ const document = SelectionManager.SelectedDocuments()[0];
+ document && document.props.removeDocument && document.props.removeDocument(doc);
+ addDocument(doc);
+ return true;
+ };
+ const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
+ const finishDrag = (e: DragCompleteEvent) =>
+ e.docDragData && (e.docDragData.droppedDocuments =
+ dragData.draggedDocuments.reduce((droppedDocs, d) => {
+ const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
+ if (dvs.length) {
+ dvs.forEach(dv => droppedDocs.push(dv.props.Document));
} else {
droppedDocs.push(Doc.MakeAlias(d));
}
- } else {
- droppedDocs.push(Doc.MakeAlias(d));
- }
- return droppedDocs;
- }, []);
- dropData.droppedDocuments = droppedDocuments;
- });
- }
+ return droppedDocs;
+ }, [] as Doc[]));
- export function StartAnnotationDrag(eles: HTMLElement[], dragData: AnnotationDragData, downX: number, downY: number, options?: DragOptions) {
- StartDrag(eles, dragData, downX, downY, options);
- }
-
- export class LinkDragData {
- constructor(linkSourceDoc: Doc, blacklist: Doc[] = []) {
- this.linkSourceDocument = linkSourceDoc;
- this.blacklist = blacklist;
+ StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
}
- droppedDocuments: Doc[] = [];
- linkSourceDocument: Doc;
- blacklist: Doc[];
- dontClearTextBox?: boolean;
- [id: string]: any;
}
- // for column dragging in schema view
- export class ColumnDragData {
- constructor(colKey: SchemaHeaderField) {
- this.colKey = colKey;
- }
- colKey: SchemaHeaderField;
- [id: string]: any;
+ // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption
+ export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag(eles, dragData, downX, downY, options);
}
- export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
- StartDrag([ele], dragData, downX, downY, options);
+ // drags a linker button and creates a link on drop
+ export function StartLinkDrag(ele: HTMLElement, sourceDoc: Doc, downX: number, downY: number, options?: DragOptions) {
+ StartDrag([ele], new DragManager.LinkDragData(sourceDoc), downX, downY, options);
}
+ // drags a column from a schema view
export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag([ele], dragData, downX, downY, options);
}
- export let AbortDrag: () => void = emptyFunction;
-
- function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
+ function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
eles = eles.filter(e => e);
if (!dragDiv) {
dragDiv = document.createElement("div");
@@ -336,80 +274,64 @@ export namespace DragManager {
DragManager.Root().appendChild(dragDiv);
}
SelectionManager.SetIsDragging(true);
- let scaleXs: number[] = [];
- let scaleYs: number[] = [];
- let xs: number[] = [];
- let ys: number[] = [];
-
- const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments :
- dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
- let dragElements = eles.map(ele => {
- const w = ele.offsetWidth,
- h = ele.offsetHeight;
+ const scaleXs: number[] = [];
+ const scaleYs: number[] = [];
+ const xs: number[] = [];
+ const ys: number[] = [];
+
+ const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
+ const dragElements = eles.map(ele => {
+ if (!ele.parentNode) dragDiv.appendChild(ele);
+ const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / w,
- scaleY = rect.height / h;
- let x = rect.left,
- y = rect.top;
- xs.push(x);
- ys.push(y);
+ const scaleX = rect.width / ele.offsetWidth,
+ scaleY = rect.height / ele.offsetHeight;
+ xs.push(rect.left);
+ ys.push(rect.top);
scaleXs.push(scaleX);
scaleYs.push(scaleY);
- let dragElement = ele.cloneNode(true) as HTMLElement;
dragElement.style.opacity = "0.7";
- dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
dragElement.style.position = "absolute";
dragElement.style.margin = "0";
dragElement.style.top = "0";
dragElement.style.bottom = "";
dragElement.style.left = "0";
- dragElement.style.transition = "none";
dragElement.style.color = "black";
+ dragElement.style.transition = "none";
dragElement.style.transformOrigin = "0 0";
+ dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
- dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`;
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
if (docs.length) {
- var pdfBox = dragElement.getElementsByTagName("canvas");
- var pdfBoxSrc = ele.getElementsByTagName("canvas");
+ const pdfBox = dragElement.getElementsByTagName("canvas");
+ const pdfBoxSrc = ele.getElementsByTagName("canvas");
Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
- var pdfView = dragElement.getElementsByClassName("pdfViewer-viewer");
- var pdfViewSrc = ele.getElementsByClassName("pdfViewer-viewer");
- let tops = Array.from(pdfViewSrc).map(p => p.scrollTop);
- let oldopacity = dragElement.style.opacity;
+ const pdfView = dragElement.getElementsByClassName("pdfViewer-viewer");
+ const pdfViewSrc = ele.getElementsByClassName("pdfViewer-viewer");
+ const tops = Array.from(pdfViewSrc).map(p => p.scrollTop);
+ const oldopacity = dragElement.style.opacity;
dragElement.style.opacity = "0";
setTimeout(() => {
dragElement.style.opacity = oldopacity;
Array.from(pdfView).map((v, i) => v.scrollTo({ top: tops[i] }));
}, 0);
}
- let set = dragElement.getElementsByTagName('*');
if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none";
+ const set = dragElement.getElementsByTagName('*');
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < set.length; i++) {
- if (set[i].hasAttribute("style")) {
- let s = set[i];
- (s as any).style.pointerEvents = "none";
- }
+ set[i].hasAttribute("style") && ((set[i] as any).style.pointerEvents = "none");
}
-
dragDiv.appendChild(dragElement);
return dragElement;
});
- let hideSource = false;
- if (options) {
- if (typeof options.hideSource === "boolean") {
- hideSource = options.hideSource;
- } else {
- hideSource = options.hideSource();
- }
- }
-
- eles.map(ele => ele.hidden = hideSource);
+ const hideSource = options?.hideSource ? true : false;
+ eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource));
let lastX = downX;
let lastY = downY;
@@ -418,9 +340,9 @@ export namespace DragManager {
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey ? "alias" : undefined;
}
- if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
+ if (e.shiftKey && CollectionDockingView.Instance) {
AbortDrag();
- finishDrag && finishDrag(dragData);
+ finishDrag?.(new DragCompleteEvent(true, dragData));
CollectionDockingView.Instance.StartOtherDrag({
pageX: e.pageX,
pageY: e.pageY,
@@ -429,61 +351,56 @@ export namespace DragManager {
}, dragData.droppedDocuments);
}
//TODO: Why can't we use e.movementX and e.movementY?
- let moveX = e.pageX - lastX;
- let moveY = e.pageY - lastY;
+ const moveX = e.pageX - lastX;
+ const moveY = e.pageY - lastY;
lastX = e.pageX;
lastY = e.pageY;
dragElements.map((dragElement, i) => (dragElement.style.transform =
- `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ `translate(${(xs[i] += moveX) + (options?.offsetX || 0)}px, ${(ys[i] += moveY) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
};
- let hideDragShowOriginalElements = () => {
+ const hideDragShowOriginalElements = () => {
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- eles.map(ele => ele.hidden = false);
+ eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false));
};
- let endDrag = () => {
+ const endDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- if (options) {
- options.handlers.dragComplete({});
- }
};
AbortDrag = () => {
hideDragShowOriginalElements();
SelectionManager.SetIsDragging(false);
+ options?.dragComplete?.(new DragCompleteEvent(true, dragData));
endDrag();
};
const upHandler = (e: PointerEvent) => {
hideDragShowOriginalElements();
dispatchDrag(eles, e, dragData, options, finishDrag);
SelectionManager.SetIsDragging(false);
+ options?.dragComplete?.(new DragCompleteEvent(false, dragData));
endDrag();
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
- let removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
- // let parent = dragEle.parentElement;
- // if (parent) parent.removeChild(dragEle);
- let ret = [dragEle, dragEle.style.width, dragEle.style.height];
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
+ const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
+ const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height };
dragEle.style.width = "0";
dragEle.style.height = "0";
return ret;
});
const target = document.elementFromPoint(e.x, e.y);
removed.map(r => {
- let dragEle = r[0] as HTMLElement;
- dragEle.style.width = r[1] as string;
- dragEle.style.height = r[2] as string;
- // let parent = r[1];
- // if (parent && dragEle) parent.appendChild(dragEle);
+ r.ele.style.width = r.w;
+ r.ele.style.height = r.h;
});
if (target) {
- finishDrag && finishDrag(dragData);
+ const complete = new DragCompleteEvent(false, dragData);
+ finishDrag?.(complete);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashOnDrop", {
@@ -491,8 +408,10 @@ export namespace DragManager {
detail: {
x: e.x,
y: e.y,
- data: dragData,
- mods: e.altKey ? "AltKey" : e.ctrlKey ? "CtrlKey" : e.metaKey ? "MetaKey" : ""
+ complete: complete,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey
}
})
);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 6b53333d7..9e036d6c2 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -9,10 +9,10 @@ import { ScriptField } from "../../new_fields/ScriptField";
function makeTemplate(doc: Doc): boolean {
- let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
- let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0];
- let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, "");
- let docs = DocListCast(layoutDoc[fieldKey]);
+ const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+ const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0];
+ const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, "");
+ const docs = DocListCast(layoutDoc[fieldKey]);
let any = false;
docs.map(d => {
if (!StrCast(d.title).startsWith("-")) {
@@ -28,7 +28,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
data && data.draggedDocuments.map((doc, i) => {
let dbox = doc;
if (!doc.onDragStart && !doc.onClick && doc.viewType !== CollectionViewType.Linear) {
- let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
+ const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc;
if (layoutDoc.type === DocumentType.COL) {
layoutDoc.isTemplateDoc = makeTemplate(layoutDoc);
} else {
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 899abbe40..545e8acb4 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,6 +1,5 @@
-import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { Doc } from "../../new_fields/Doc";
import { DocServer } from "../DocServer";
-import { RouteStore } from "../../server/RouteStore";
import { MainView } from "../views/MainView";
import * as qs from 'query-string';
import { Utils, OmitKeys } from "../../Utils";
@@ -26,7 +25,7 @@ export namespace HistoryUtil {
// const handlers: ((state: ParsedUrl | null) => void)[] = [];
function onHistory(e: PopStateEvent) {
- if (window.location.pathname !== RouteStore.home) {
+ if (window.location.pathname !== "/home") {
const url = e.state as ParsedUrl || parseUrl(window.location);
if (url) {
switch (url.type) {
@@ -54,7 +53,7 @@ export namespace HistoryUtil {
}
export function getState(): ParsedUrl {
- let state = copyState(history.state);
+ const state = copyState(history.state);
state.initializers = state.initializers || {};
return state;
}
@@ -161,7 +160,7 @@ export namespace HistoryUtil {
const pathname = location.pathname.substring(1);
const search = location.search;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
- let pathnameSplit = pathname.split("/");
+ const pathnameSplit = pathname.split("/");
const type = pathnameSplit[0];
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 5904088fc..5b5bffd8c 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,8 +1,7 @@
import "fs";
import React = require("react");
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { RouteStore } from "../../../server/RouteStore";
-import { action, observable, autorun, runInAction, computed, reaction, IReactionDisposer } from "mobx";
+import { action, observable, runInAction, computed, reaction, IReactionDisposer } from "mobx";
import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
import Measure, { ContentRect } from "react-measure";
import { library } from '@fortawesome/fontawesome-svg-core';
@@ -20,19 +19,13 @@ import { listSpec } from "../../../new_fields/Schema";
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import "./DirectoryImportBox.scss";
-import { Identified } from "../../Network";
+import { Networking } from "../../Network";
import { BatchedArray } from "array-batcher";
-import { ExifData } from "exif";
+import * as path from 'path';
+import { AcceptibleMedia } from "../../../server/SharedMediaTypes";
const unsupported = ["text/html", "text/plain"];
-interface ImageUploadResponse {
- name: string;
- path: string;
- type: string;
- exif: any;
-}
-
@observer
export default class DirectoryImportBox extends React.Component<FieldViewProps> {
private selector = React.createRef<HTMLInputElement>();
@@ -55,7 +48,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
constructor(props: FieldViewProps) {
super(props);
library.add(faTag, faPlus);
- let doc = this.props.Document;
+ const doc = this.props.Document;
this.editingMetadata = this.editingMetadata || false;
this.persistent = this.persistent || false;
!Cast(doc.data, listSpec(Doc)) && (doc.data = new List<Doc>());
@@ -85,17 +78,22 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
this.phase = "Initializing download...";
});
- let docs: Doc[] = [];
+ const docs: Doc[] = [];
- let files = e.target.files;
+ const files = e.target.files;
if (!files || files.length === 0) return;
- let directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0];
+ const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0];
- let validated: File[] = [];
+ const validated: File[] = [];
for (let i = 0; i < files.length; i++) {
- let file = files.item(i);
- file && !unsupported.includes(file.type) && validated.push(file);
+ const file = files.item(i);
+ if (file && !unsupported.includes(file.type)) {
+ const ext = path.extname(file.name).toLowerCase();
+ if (AcceptibleMedia.imageFormats.includes(ext)) {
+ validated.push(file);
+ }
+ }
}
runInAction(() => {
@@ -103,13 +101,13 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
this.completed = 0;
});
- let sizes: number[] = [];
- let modifiedDates: number[] = [];
+ const sizes: number[] = [];
+ const modifiedDates: number[] = [];
runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`);
const batched = BatchedArray.from(validated, { batchSize: 15 });
- const uploads = await batched.batchedMapAsync<ImageUploadResponse>(async (batch, collector) => {
+ const uploads = await batched.batchedMapAsync<any>(async (batch, collector) => {
const formData = new FormData();
batch.forEach(file => {
@@ -118,20 +116,14 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
formData.append(Utils.GenerateGuid(), file);
});
- collector.push(...(await Identified.PostFormDataToServer(RouteStore.upload, formData)));
+ collector.push(...(await Networking.PostFormDataToServer("/upload", formData)));
runInAction(() => this.completed += batch.length);
});
- await Promise.all(uploads.map(async upload => {
- const type = upload.type;
- const path = Utils.prepend(upload.path);
- const options = {
- nativeWidth: 300,
- width: 300,
- title: upload.name
- };
- const document = await Docs.Get.DocumentFromType(type, path, options);
- const { data, error } = upload.exif;
+ await Promise.all(uploads.map(async ({ name, type, clientAccessPath, exifData }) => {
+ const path = Utils.prepend(clientAccessPath);
+ const document = await Docs.Get.DocumentFromType(type, path, { width: 300, title: name });
+ const { data, error } = exifData;
if (document) {
Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data);
docs.push(document);
@@ -139,26 +131,26 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
}));
for (let i = 0; i < docs.length; i++) {
- let doc = docs[i];
+ const doc = docs[i];
doc.size = sizes[i];
doc.modified = modifiedDates[i];
this.entries.forEach(entry => {
- let target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
+ const target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
target[entry.key] = entry.value;
});
}
- let doc = this.props.Document;
- let height: number = NumCast(doc.height) || 0;
- let offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
- let options: DocumentOptions = {
+ const doc = this.props.Document;
+ const height: number = NumCast(doc.height) || 0;
+ const offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
+ const options: DocumentOptions = {
title: `Import of ${directory}`,
width: 1105,
height: 500,
x: NumCast(doc.x),
y: NumCast(doc.y) + offset
};
- let parent = this.props.ContainingCollectionView;
+ const parent = this.props.ContainingCollectionView;
if (parent) {
let importContainer: Doc;
if (docs.length < 50) {
@@ -197,18 +189,18 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
@action
preserveCentering = (rect: ContentRect) => {
- let bounds = rect.offset!;
+ const bounds = rect.offset!;
if (bounds.width === 0 || bounds.height === 0) {
return;
}
- let offset = this.dimensions / 2;
+ const offset = this.dimensions / 2;
this.left = bounds.width / 2 - offset;
this.top = bounds.height / 2 - offset;
}
@action
addMetadataEntry = async () => {
- let entryDoc = new Doc();
+ const entryDoc = new Doc();
entryDoc.checked = false;
entryDoc.key = keyPlaceholder;
entryDoc.value = valuePlaceholder;
@@ -217,7 +209,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
@action
remove = async (entry: ImportMetadataEntry) => {
- let metadata = await DocListCastAsync(this.props.Document.data);
+ const metadata = await DocListCastAsync(this.props.Document.data);
if (metadata) {
let index = this.entries.indexOf(entry);
if (index !== -1) {
@@ -231,18 +223,18 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
}
render() {
- let dimensions = 50;
- let entries = DocListCast(this.props.Document.data);
- let isEditing = this.editingMetadata;
- let completed = this.completed;
- let quota = this.quota;
- let uploading = this.uploading;
- let showRemoveLabel = this.removeHover;
- let persistent = this.persistent;
+ const dimensions = 50;
+ const entries = DocListCast(this.props.Document.data);
+ const isEditing = this.editingMetadata;
+ const completed = this.completed;
+ const quota = this.quota;
+ const uploading = this.uploading;
+ const showRemoveLabel = this.removeHover;
+ const persistent = this.persistent;
let percent = `${completed / quota * 100}`;
percent = percent.split(".")[0];
percent = percent.startsWith("100") ? "99" : percent;
- let marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
+ const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
const message = <span className={"phase"}>{this.phase}</span>;
const centerPiece = this.phase.includes("Google Photos") ?
<img src={"/assets/google_photos.png"} style={{
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index c9abf38fa..6a9486f83 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,9 +1,8 @@
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
import { ImageField } from "../../../new_fields/URLField";
import { Cast, StrCast } from "../../../new_fields/Types";
-import { RouteStore } from "../../../server/RouteStore";
import { Docs } from "../../documents/Documents";
-import { Identified } from "../../Network";
+import { Networking } from "../../Network";
import { Id } from "../../../new_fields/FieldSymbols";
import { Utils } from "../../../Utils";
@@ -15,7 +14,7 @@ export namespace ImageUtils {
return false;
}
const source = field.url.href;
- const response = await Identified.PostToServer(RouteStore.inspectImage, { source });
+ const response = await Networking.PostToServer("/inspectImage", { source });
const { error, data } = response.exifData;
document.exif = error || Docs.Get.DocumentHierarchyFromJson(data);
return data !== undefined;
@@ -23,7 +22,7 @@ export namespace ImageUtils {
export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
const a = document.createElement("a");
- a.href = Utils.prepend(`${RouteStore.imageHierarchyExport}/${collection[Id]}`);
+ a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
a.download = `Dash Export [${StrCast(collection.title)}].zip`;
a.click();
};
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index f5198c39b..8e1c50bea 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -1,11 +1,11 @@
import React = require("react");
import { observer } from "mobx-react";
import { EditableView } from "../../views/EditableView";
-import { observable, action, computed } from "mobx";
+import { action, computed } from "mobx";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { library } from '@fortawesome/fontawesome-svg-core';
-import { Opt, Doc } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
import { StrCast, BoolCast } from "../../../new_fields/Types";
interface KeyValueProps {
@@ -85,7 +85,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
}
render() {
- let keyValueStyle: React.CSSProperties = {
+ const keyValueStyle: React.CSSProperties = {
paddingLeft: 10,
width: "50%",
opacity: this.valid ? 1 : 0.5,
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index 2d3671041..2e4e8c7ca 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -9,9 +9,9 @@ export namespace InteractionUtils {
const ERASER_BUTTON = 5;
export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
- let myTouches = new Array<React.Touch>();
+ const myTouches = new Array<React.Touch>();
for (let i = 0; i < e.targetTouches.length; i++) {
- let pt = e.targetTouches.item(i);
+ const pt = e.targetTouches.item(i);
if (pt && prevPoints.has(pt.identifier)) {
myTouches.push(pt);
}
@@ -40,8 +40,8 @@ export namespace InteractionUtils {
* @param pts - n-arbitrary long list of points
*/
export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
- let centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
- let centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
+ const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
+ const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
return { X: centerX, Y: centerY };
}
@@ -53,9 +53,9 @@ export namespace InteractionUtils {
* @param oldPoint2 - previous point 2
*/
export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
- let threshold = window.devicePixelRatio;
- let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
- let newDist = TwoPointEuclidist(pt1, pt2);
+ const threshold = 4;
+ const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
+ const newDist = TwoPointEuclidist(pt1, pt2);
/** if they have the same sign, then we are either pinching in or out.
* threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
@@ -75,12 +75,12 @@ export namespace InteractionUtils {
* @param oldPoint2 - previous point 2
*/
export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
- let threshold = 4;
+ const threshold = 4;
- let pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
- let pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
+ const pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
+ const pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
- let pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
+ const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
if (pinching !== 0) {
if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) {
@@ -90,6 +90,20 @@ export namespace InteractionUtils {
return 0;
}
+ export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
+ for (const touch of newTouches) {
+ if (touch) {
+ const oldTouch = oldTouches.get(touch.identifier);
+ if (oldTouch) {
+ if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
// These might not be very useful anymore, but I'll leave them here for now -syip2
{
@@ -145,20 +159,5 @@ export namespace InteractionUtils {
// return { type: undefined };
// }
// }
-
- // export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: TouchList, leniency: number): boolean {
- // for (let i = 0; i < newTouches.length; i++) {
- // let touch = newTouches.item(i);
- // if (touch) {
- // let oldTouch = oldTouches.get(touch.identifier);
- // if (oldTouch) {
- // if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
- // return true;
- // }
- // }
- // }
- // }
- // return false;
- // }
}
} \ No newline at end of file
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index eedc4967d..5f3667acc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -38,16 +38,16 @@ export class LinkManager {
}
public getAllLinks(): Doc[] {
- let ldoc = LinkManager.Instance.LinkManagerDoc;
+ const ldoc = LinkManager.Instance.LinkManagerDoc;
if (ldoc) {
- let docs = DocListCast(ldoc.allLinks);
+ const docs = DocListCast(ldoc.allLinks);
return docs;
}
return [];
}
public addLink(linkDoc: Doc): boolean {
- let linkList = LinkManager.Instance.getAllLinks();
+ const linkList = LinkManager.Instance.getAllLinks();
linkList.push(linkDoc);
if (LinkManager.Instance.LinkManagerDoc) {
LinkManager.Instance.LinkManagerDoc.allLinks = new List<Doc>(linkList);
@@ -57,8 +57,8 @@ export class LinkManager {
}
public deleteLink(linkDoc: Doc): boolean {
- let linkList = LinkManager.Instance.getAllLinks();
- let index = LinkManager.Instance.getAllLinks().indexOf(linkDoc);
+ const linkList = LinkManager.Instance.getAllLinks();
+ const index = LinkManager.Instance.getAllLinks().indexOf(linkDoc);
if (index > -1) {
linkList.splice(index, 1);
if (LinkManager.Instance.LinkManagerDoc) {
@@ -70,24 +70,24 @@ export class LinkManager {
}
// finds all links that contain the given anchor
- public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
- let related = LinkManager.Instance.getAllLinks().filter(link => {
- let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
- let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
+ public getAllRelatedLinks(anchor: Doc): Doc[] {
+ const related = LinkManager.Instance.getAllLinks().filter(link => {
+ const protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
+ const protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
return protomatch1 || protomatch2 || Doc.AreProtosEqual(link, anchor);
});
return related;
}
public deleteAllLinksOnAnchor(anchor: Doc) {
- let related = LinkManager.Instance.getAllRelatedLinks(anchor);
+ const related = LinkManager.Instance.getAllRelatedLinks(anchor);
related.forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
}
public addGroupType(groupType: string): boolean {
if (LinkManager.Instance.LinkManagerDoc) {
LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>([]);
- let groupTypes = LinkManager.Instance.getAllGroupTypes();
+ const groupTypes = LinkManager.Instance.getAllGroupTypes();
groupTypes.push(groupType);
LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
return true;
@@ -99,8 +99,8 @@ export class LinkManager {
public deleteGroupType(groupType: string): boolean {
if (LinkManager.Instance.LinkManagerDoc) {
if (LinkManager.Instance.LinkManagerDoc[groupType]) {
- let groupTypes = LinkManager.Instance.getAllGroupTypes();
- let index = groupTypes.findIndex(type => type.toUpperCase() === groupType.toUpperCase());
+ const groupTypes = LinkManager.Instance.getAllGroupTypes();
+ const index = groupTypes.findIndex(type => type.toUpperCase() === groupType.toUpperCase());
if (index > -1) groupTypes.splice(index, 1);
LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
@@ -146,8 +146,8 @@ export class LinkManager {
}
public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) {
- let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
- let index = groups.findIndex(gDoc => {
+ const groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+ const index = groups.findIndex(gDoc => {
return StrCast(groupDoc.type).toUpperCase() === StrCast(gDoc.type).toUpperCase();
});
if (index > -1 && replace) {
@@ -161,32 +161,32 @@ export class LinkManager {
// removes group doc of given group type only from given anchor on given link
public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
- let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
- let newGroups = groups.filter(groupDoc => StrCast(groupDoc.type).toUpperCase() !== groupType.toUpperCase());
+ const groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+ const newGroups = groups.filter(groupDoc => StrCast(groupDoc.type).toUpperCase() !== groupType.toUpperCase());
LinkManager.Instance.setAnchorGroups(linkDoc, anchor, newGroups);
}
// returns map of group type to anchor's links in that group type
public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
- let related = this.getAllRelatedLinks(anchor);
- let anchorGroups = new Map<string, Array<Doc>>();
+ const related = this.getAllRelatedLinks(anchor);
+ const anchorGroups = new Map<string, Array<Doc>>();
related.forEach(link => {
- let groups = LinkManager.Instance.getAnchorGroups(link, anchor);
+ const groups = LinkManager.Instance.getAnchorGroups(link, anchor);
if (groups.length > 0) {
groups.forEach(groupDoc => {
- let groupType = StrCast(groupDoc.type);
+ const groupType = StrCast(groupDoc.type);
if (groupType === "") {
- let group = anchorGroups.get("*");
+ const group = anchorGroups.get("*");
anchorGroups.set("*", group ? [...group, link] : [link]);
} else {
- let group = anchorGroups.get(groupType);
+ const group = anchorGroups.get(groupType);
anchorGroups.set(groupType, group ? [...group, link] : [link]);
}
});
} else {
// if link is in no groups then put it in default group
- let group = anchorGroups.get("*");
+ const group = anchorGroups.get("*");
anchorGroups.set("*", group ? [...group, link] : [link]);
}
@@ -212,11 +212,11 @@ export class LinkManager {
// returns a list of all metadata docs associated with the given group type
public getAllMetadataDocsInGroup(groupType: string): Array<Doc> {
- let md: Doc[] = [];
- let allLinks = LinkManager.Instance.getAllLinks();
+ const md: Doc[] = [];
+ const allLinks = LinkManager.Instance.getAllLinks();
allLinks.forEach(linkDoc => {
- let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, null));
- let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, null));
+ const anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, null));
+ const anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, null));
anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
});
@@ -225,8 +225,8 @@ export class LinkManager {
// checks if a link with the given anchors exists
public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
- let allLinks = LinkManager.Instance.getAllLinks();
- let index = allLinks.findIndex(linkDoc => {
+ const allLinks = LinkManager.Instance.getAllLinks();
+ const index = allLinks.findIndex(linkDoc => {
return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor2)) ||
(Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor1));
});
@@ -237,14 +237,12 @@ export class LinkManager {
//TODO This should probably return undefined if there isn't an opposite anchor
//TODO This should also await the return value of the anchor so we don't filter out promises
public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
- let a1 = Cast(linkDoc.anchor1, Doc, null);
- let a2 = Cast(linkDoc.anchor2, Doc, null);
+ const a1 = Cast(linkDoc.anchor1, Doc, null);
+ const a2 = Cast(linkDoc.anchor2, Doc, null);
if (Doc.AreProtosEqual(anchor, a1)) return a2;
if (Doc.AreProtosEqual(anchor, a2)) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
}
-Scripting.addGlobal(function links(doc: any) {
- return new List(LinkManager.Instance.getAllRelatedLinks(doc));
-});
+Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }); \ No newline at end of file
diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
index 3a993e1ff..0a3b68217 100644
--- a/src/client/util/ParagraphNodeSpec.ts
+++ b/src/client/util/ParagraphNodeSpec.ts
@@ -34,6 +34,7 @@ const ParagraphNodeSpec: NodeSpec = {
color: { default: null },
id: { default: null },
indent: { default: null },
+ inset: { default: null },
lineSpacing: { default: null },
// TODO: Add UI to let user edit / clear padding.
paddingBottom: { default: null },
@@ -76,6 +77,7 @@ function toDOM(node: Node): DOMOutputSpec {
const {
align,
indent,
+ inset,
lineSpacing,
paddingTop,
paddingBottom,
@@ -105,6 +107,14 @@ function toDOM(node: Node): DOMOutputSpec {
style += `padding-bottom: ${paddingBottom};`;
}
+ if (indent) {
+ style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
+ }
+
+ if (inset) {
+ style += `margin-left: ${inset}; margin-right: ${inset};`;
+ }
+
style && (attrs.style = style);
if (indent) {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 003ff6272..c028dbf8b 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -4,8 +4,10 @@ import { undoInputRule } from "prosemirror-inputrules";
import { Schema } from "prosemirror-model";
import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
import { splitListItem, wrapInList, } from "prosemirror-schema-list";
-import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
+import { EditorState, Transaction, TextSelection } from "prosemirror-state";
import { TooltipTextMenu } from "./TooltipTextMenu";
+import { SelectionManager } from "./SelectionManager";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -15,22 +17,22 @@ export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string)
let fontSize: number | undefined = undefined;
tx2.doc.descendants((node: any, offset: any, index: any) => {
if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
- let path = (tx2.doc.resolve(offset) as any).path;
+ const path = (tx2.doc.resolve(offset) as any).path;
let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) depth++;
fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
- let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
+ const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
}
});
return tx2;
};
export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
- let keys: { [key: string]: any } = {}, type;
+ const keys: { [key: string]: any } = {};
function bind(key: string, cmd: any) {
if (mapKeys) {
- let mapped = mapKeys[key];
+ const mapped = mapKeys[key];
if (mapped === false) return;
if (mapped) key = mapped;
}
@@ -46,7 +48,11 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Alt-ArrowUp", joinUp);
bind("Alt-ArrowDown", joinDown);
bind("Mod-BracketLeft", lift);
- bind("Escape", selectParentNode);
+ 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();
+ });
bind("Mod-b", toggleMark(schema.marks.strong));
bind("Mod-B", toggleMark(schema.marks.strong));
@@ -79,7 +85,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
// });
- let cmd = chainCommands(exitCode, (state, dispatch) => {
+ const cmd = chainCommands(exitCode, (state, dispatch) => {
if (dispatch) {
dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
return true;
@@ -99,27 +105,25 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
}
- let hr = schema.nodes.horizontal_rule;
+ 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("Mod-s", TooltipTextMenu.insertStar);
-
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- var ref = state.selection;
- var range = ref.$from.blockRange(ref.$to);
- var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ const ref = state.selection;
+ const range = ref.$from.blockRange(ref.$to);
+ const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
- let tx3 = updateBullets(tx2, schema);
+ const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
})) { // couldn't sink into an existing list, so wrap in a new one
- let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+ const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
- let tx3 = updateBullets(tx2, schema);
+ const tx3 = updateBullets(tx2, schema);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -131,10 +135,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
});
bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- let tx3 = updateBullets(tx2, schema);
+ const tx3 = updateBullets(tx2, schema);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
@@ -143,14 +147,14 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
}
});
- let splitMetadata = (marks: any, tx: Transaction) => {
+ 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;
};
- bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
- if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
+ bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+ const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ if (!splitListItem(schema.nodes.list_item)(state, dispatch)) {
if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
splitMetadata(marks, tx3);
if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
@@ -163,18 +167,18 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
return true;
});
bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
dispatch(splitMetadata(marks, state.tr));
return false;
});
bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- let range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
+ const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
});
- let path = (state.doc.resolve(state.selection.from - 1) as any).path;
- let spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
- let textsel = TextSelection.create(state.doc, range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator, range!.end);
- let text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+ const path = (state.doc.resolve(state.selection.from - 1) as any).path;
+ const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+ const textsel = TextSelection.create(state.doc, range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator, 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(":")) {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index ebb9bda8a..29b378299 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -5,8 +5,11 @@ import { NodeSelection, TextSelection } from "prosemirror-state";
import { NumCast, Cast } from "../../new_fields/Types";
import { Doc } from "../../new_fields/Doc";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Docs } from "../documents/Documents";
+import { TooltipTextMenuManager } from "../util/TooltipTextMenu";
+import { Docs, DocUtils } from "../documents/Documents";
import { Id } from "../../new_fields/FieldSymbols";
+import { DocServer } from "../DocServer";
+import { returnFalse, Utils } from "../../Utils";
export const inpRules = {
rules: [
@@ -59,137 +62,222 @@ export const inpRules = {
}
),
+ // set the font size using #<font-size>
new InputRule(
- new RegExp(/^#([0-9]+)\s$/),
+ new RegExp(/^%([0-9]+)\s$/),
(state, match, start, end) => {
- let size = Number(match[1]);
- let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const size = Number(match[1]);
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
- (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]);
+ (Cast(FormattedTextBox.FocusedBox!.props.Document, Doc) as Doc).heading = size;
return state.tr.deleteRange(start, end);
}
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }));
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
}),
+
+ // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode)
+ new InputRule(
+ new RegExp(/@$/),
+ (state, match, start, end) => {
+ if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
+
+ const value = state.doc.textBetween(start, end);
+ if (value) {
+ DocServer.GetRefField(value).then(docx => {
+ const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value);
+ DocUtils.Publish(target, value, returnFalse, returnFalse);
+ DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
+ });
+ const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value, targetId: value });
+ return state.tr.addMark(start, end, link);
+ }
+ return state.tr;
+ }),
+ // stop using active style
new InputRule(
- new RegExp(/t/),
+ new RegExp(/%%$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
- let node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ 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 || !(schema as any).EnteringStyle) return null;
+ const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+
+ // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
- new RegExp(/i/),
+ new RegExp(/(%d|d)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
- let node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
+ const pos = (state.doc.resolve(start) as any);
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+ }
+ }
+ return null;
}),
+
+ // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
- new RegExp(/\!/),
+ new RegExp(/(%h|h)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
- let node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
+ const pos = (state.doc.resolve(start) as any);
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+ }
+ }
+ return null;
}),
+ // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
new InputRule(
- new RegExp(/\x/),
+ new RegExp(/(%q|q)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
- let node = (state.doc.resolve(start) as any).nodeAfter;
- return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
+ const pos = (state.doc.resolve(start) as any);
+ if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+ const node = state.selection.node;
+ return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
+ }
+ for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+ const node = pos.node(depth);
+ if (node.type === schema.nodes.paragraph) {
+ const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+ const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+ return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+ }
+ }
+ return null;
}),
+
+
+ // center justify text
new InputRule(
- new RegExp(/^\^\^\s$/),
+ new RegExp(/%\^$/),
(state, match, start, end) => {
- let node = (state.doc.resolve(start) as any).nodeAfter;
- let sm = state.storedMarks || undefined;
- let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "center";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ 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(/^\[\[\s$/),
+ new RegExp(/%\[$/),
(state, match, start, end) => {
- let node = (state.doc.resolve(start) as any).nodeAfter;
- let sm = state.storedMarks || undefined;
- let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "left";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ 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(/^\]\]\s$/),
+ new RegExp(/%\]$/),
(state, match, start, end) => {
- let node = (state.doc.resolve(start) as any).nodeAfter;
- let sm = state.storedMarks || undefined;
- let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
- let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const sm = state.storedMarks || undefined;
+ const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
+ const heading = NumCast(FormattedTextBox.FocusedBox!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "right";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ 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(/##\s$/),
+ new RegExp(/%#$/),
(state, match, start, end) => {
- let node = (state.doc.resolve(start) as any).nodeAfter;
- let sm = state.storedMarks || undefined;
- let target = Docs.Create.TextDocument({ width: 75, height: 35, autoHeight: true, fontSize: 9, title: "inline comment" });
- let replaced = node ? state.tr.insertText("←", start).replaceRangeWith(start + 1, end + 1, schema.nodes.dashDoc.create({
- width: 75, height: 35,
- title: "dashDoc", docid: target[Id],
- float: "right"
- })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", annotationOn: FormattedTextBox.FocusedBox!.dataDoc, autoHeight: true, fontSize: 9, title: "inline comment" });
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docid: target[Id] });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: target[Id], float: "right" });
+ const sm = state.storedMarks || undefined;
+ const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 1)));
+ return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
}),
new InputRule(
- new RegExp(/\(\(/),
+ new RegExp(/%\(/),
(state, match, start, end) => {
- let node = (state.doc.resolve(start) as any).nodeAfter;
- let sm = state.storedMarks || undefined;
- let mark = state.schema.marks.highlight.create();
- let selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
- let content = selected.selection.content();
- let replaced = node ? selected.replaceRangeWith(start, start,
- schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ 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)));
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
}),
new InputRule(
- new RegExp(/\)\)/),
+ new RegExp(/%\)/),
(state, match, start, end) => {
- let mark = state.schema.marks.highlight.create();
- return state.tr.removeStoredMark(mark);
+ return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
}),
new InputRule(
- new RegExp(/\^f\s$/),
+ new RegExp(/%f$/),
(state, match, start, end) => {
- let newNode = schema.nodes.footnote.create({});
- let tr = state.tr;
+ const newNode = schema.nodes.footnote.create({});
+ const tr = state.tr;
tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
return tr.setSelection(new NodeSelection( // select the footnote node to open its display
tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
}),
- // let newNode = schema.nodes.footnote.create({});
- // if (dispatch && state.selection.from === state.selection.to) {
- // return true;
- // }
+
+ // activate a style by name using prefix '%'
+ new InputRule(
+ new RegExp(/%[a-z]+$/),
+ (state, match, start, end) => {
+ const color = match[0].substring(1, match[0].length);
+ const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+ if (marks) {
+ const tr = state.tr.deleteRange(start, end);
+ return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+ }
+ const isValidColor = (strColor: string) => {
+ const s = new Option().style;
+ s.color = strColor;
+ return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+ };
+ if (isValidColor(color)) {
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+ }
+ return null;
+ }),
]
};
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 0a717dff1..ef90a7294 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction, reaction, IReactionDisposer } from "mobx";
+import { reaction, IReactionDisposer } from "mobx";
import { baseKeymap, toggleMark } from "prosemirror-commands";
import { redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
@@ -16,10 +16,10 @@ import { DocumentManager } from "./DocumentManager";
import ParagraphNodeSpec from "./ParagraphNodeSpec";
import { Transform } from "./Transform";
import React = require("react");
-import { BoolCast, NumCast } from "../../new_fields/Types";
+import { BoolCast, NumCast, Cast } from "../../new_fields/Types";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
+const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
// :: Object
@@ -30,7 +30,6 @@ export const nodes: { [index: string]: NodeSpec } = {
content: "block+"
},
-
footnote: {
group: "inline",
content: "inline*",
@@ -45,15 +44,6 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [{ tag: "footnote" }]
},
- // // :: NodeSpec A plain paragraph textblock. Represented in the DOM
- // // as a `<p>` element.
- // paragraph: {
- // content: "inline*",
- // group: "block",
- // parseDOM: [{ tag: "p" }],
- // toDOM() { return pDOM; }
- // },
-
paragraph: ParagraphNodeSpec,
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
@@ -107,7 +97,19 @@ export const nodes: { [index: string]: NodeSpec } = {
group: "inline"
},
- star: {
+ dashComment: {
+ attrs: {
+ docid: { default: "" },
+ },
+ inline: true,
+ group: "inline",
+ toDOM(node) {
+ const attrs = { style: `width: 40px` };
+ return ["span", { ...node.attrs, ...attrs }, "←"];
+ },
+ },
+
+ summary: {
inline: true,
attrs: {
visibility: { default: false },
@@ -119,15 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = {
const attrs = { style: `width: 40px` };
return ["span", { ...node.attrs, ...attrs }];
},
- // parseDOM: [{
- // tag: "star", getAttrs(dom: any) {
- // return {
- // visibility: dom.getAttribute("visibility"),
- // oldtext: dom.getAttribute("oldtext"),
- // oldtextlen: dom.getAttribute("oldtextlen"),
- // }
- // }
- // }]
},
// :: NodeSpec An inline image (`<img>`) node. Supports `src`,
@@ -171,21 +164,11 @@ export const nodes: { [index: string]: NodeSpec } = {
title: { default: null },
float: { default: "right" },
location: { default: "onRight" },
- docid: { default: "" }
+ hidden: { default: false },
+ docid: { default: "" },
},
group: "inline",
- draggable: true,
- // parseDOM: [{
- // tag: "img[src]", getAttrs(dom: any) {
- // return {
- // src: dom.getAttribute("src"),
- // title: dom.getAttribute("title"),
- // alt: dom.getAttribute("alt"),
- // width: Math.min(100, Number(dom.getAttribute("width"))),
- // };
- // }
- // }],
- // TODO if we don't define toDom, dragging the image crashes. Why?
+ draggable: false,
toDOM(node) {
const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
return ["div", { ...node.attrs, ...attrs }];
@@ -235,20 +218,21 @@ export const nodes: { [index: string]: NodeSpec } = {
bulletStyle: { default: 0 },
mapStyle: { default: "decimal" },
setFontSize: { default: undefined },
- setFontFamily: { default: undefined },
+ setFontFamily: { default: "inherit" },
+ setFontColor: { default: "inherit" },
inheritedFontSize: { default: undefined },
- visibility: { default: true }
+ visibility: { default: true },
+ indent: { default: undefined }
},
toDOM(node: Node<any>) {
- const bs = node.attrs.bulletStyle;
if (node.attrs.mapStyle === "bullet") return ['ul', 0];
- const decMap = bs ? "decimal" + bs : "";
- const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
- let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
- let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
- let ffam = node.attrs.setFontFamily;
- return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
+ const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
+ const ffam = node.attrs.setFontFamily;
+ const color = node.attrs.setFontColor;
+ return node.attrs.visibility ?
+ ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+ ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
}
},
@@ -271,10 +255,7 @@ export const nodes: { [index: string]: NodeSpec } = {
...listItem,
content: 'paragraph block*',
toDOM(node: any) {
- const bs = node.attrs.bulletStyle;
- const decMap = bs ? "decimal" + bs : "";
- const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
- let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : "";
+ const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
//return ["li", { class: `${map}` }, 0];
}
@@ -293,6 +274,8 @@ export const marks: { [index: string]: MarkSpec } = {
link: {
attrs: {
href: {},
+ targetId: { default: "" },
+ showPreview: { default: true },
location: { default: null },
title: { default: null },
docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
@@ -300,22 +283,23 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") };
+ return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
}
}],
toDOM(node: any) {
return node.attrs.docref && node.attrs.title ?
["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
- ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0];
+ ["a", { ...node.attrs, id: node.attrs.targetId, title: `${node.attrs.title}` }, 0];
}
},
+
// :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
- color: {
+ pFontColor: {
attrs: {
color: { default: "#000" }
},
- inclusive: false,
+ inclusive: true,
parseDOM: [{
tag: "span", getAttrs(dom: any) {
return { color: dom.getAttribute("color") };
@@ -330,7 +314,7 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
highlight: { default: "transparent" }
},
- inclusive: false,
+ inclusive: true,
parseDOM: [{
tag: "span", getAttrs(dom: any) {
return { highlight: dom.getAttribute("backgroundColor") };
@@ -413,16 +397,16 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
- highlight: {
+ summarizeInclusive: {
parseDOM: [
{
tag: "span",
getAttrs: (p: any) => {
if (typeof (p) !== "string") {
- let style = getComputedStyle(p);
+ const style = getComputedStyle(p);
if (style.textDecoration === "underline") return null;
if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+ p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
return null;
}
}
@@ -433,6 +417,31 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: true,
toDOM() {
return ['span', {
+ style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
+ }];
+ }
+ },
+
+ summarize: {
+ inclusive: false,
+ parseDOM: [
+ {
+ tag: "span",
+ getAttrs: (p: any) => {
+ if (typeof (p) !== "string") {
+ const style = getComputedStyle(p);
+ if (style.textDecoration === "underline") return null;
+ if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+ p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+ return null;
+ }
+ }
+ return false;
+ }
+ },
+ ],
+ toDOM() {
+ return ['span', {
style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
}];
}
@@ -444,7 +453,7 @@ export const marks: { [index: string]: MarkSpec } = {
tag: "span",
getAttrs: (p: any) => {
if (typeof (p) !== "string") {
- let style = getComputedStyle(p);
+ const style = getComputedStyle(p);
if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
return null;
}
@@ -475,35 +484,30 @@ export const marks: { [index: string]: MarkSpec } = {
user_mark: {
attrs: {
userid: { default: "" },
- opened: { default: true },
modified: { default: "when?" }, // 5 second intervals since 1970
},
group: "inline",
toDOM(node: any) {
- let uid = node.attrs.userid.replace(".", "").replace("@", "");
- let min = Math.round(node.attrs.modified / 12);
- let hr = Math.round(min / 60);
- let day = Math.round(hr / 60 / 24);
- let remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
- return node.attrs.opened ?
- ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
- ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
+ const uid = node.attrs.userid.replace(".", "").replace("@", "");
+ const min = Math.round(node.attrs.modified / 12);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 60 / 24);
+ const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
+ return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
}
},
// the id of the user who entered the text
user_tag: {
attrs: {
userid: { default: "" },
- opened: { default: true },
modified: { default: "when?" }, // 5 second intervals since 1970
tag: { default: "" }
},
group: "inline",
+ inclusive: false,
toDOM(node: any) {
- let uid = node.attrs.userid.replace(".", "").replace("@", "");
- return node.attrs.opened ?
- ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] :
- ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]];
+ const uid = node.attrs.userid.replace(".", "").replace("@", "");
+ return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
}
},
@@ -521,7 +525,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
parseDOM: [{
tag: "span", getAttrs(dom: any) {
- let cstyle = getComputedStyle(dom);
+ const cstyle = getComputedStyle(dom);
if (cstyle.font) {
if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
@@ -537,18 +541,6 @@ export const marks: { [index: string]: MarkSpec } = {
}]
},
- pFontColor: {
- attrs: {
- color: { default: "yellow" }
- },
- parseDOM: [{ style: 'background: #d9dbdd' }],
- toDOM: (node) => {
- return ['span', {
- style: `color: ${node.attrs.color}`
- }];
- }
- },
-
/** FONT SIZES */
pFontSize: {
attrs: {
@@ -586,7 +578,7 @@ export class ImageResizeView {
this._handle.style.display = "none";
this._handle.style.bottom = "-10px";
this._handle.style.right = "-10px";
- let self = this;
+ const self = this;
this._img.onclick = function (e: any) {
e.stopPropagation();
e.preventDefault();
@@ -607,8 +599,8 @@ export class ImageResizeView {
this._handle.onpointerdown = function (e: any) {
e.preventDefault();
e.stopPropagation();
- let wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
- let hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
+ const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
+ const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
const startX = e.pageX;
const startWidth = parseFloat(node.attrs.width);
const onpointermove = (e: any) => {
@@ -621,7 +613,7 @@ export class ImageResizeView {
const onpointerup = () => {
document.removeEventListener("pointermove", onpointermove);
document.removeEventListener("pointerup", onpointerup);
- let pos = view.state.selection.from;
+ const pos = view.state.selection.from;
view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
};
@@ -648,6 +640,58 @@ export class ImageResizeView {
}
}
+
+export class DashDocCommentView {
+ _collapsed: HTMLElement;
+ _view: any;
+ constructor(node: any, view: any, getPos: any) {
+ this._collapsed = document.createElement("span");
+ this._collapsed.className = "formattedTextBox-inlineComment";
+ this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
+ this._view = view;
+ const targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+ for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
+ const m = view.state.doc.nodeAt(i);
+ if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
+ return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+ }
+ }
+ const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
+ view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
+ setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
+ return undefined;
+ };
+ this._collapsed.onpointerdown = (e: any) => {
+ e.stopPropagation();
+ };
+ this._collapsed.onpointerup = (e: any) => {
+ const target = targetNode();
+ if (target) {
+ const expand = target.hidden;
+ const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+ view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
+ setTimeout(() => {
+ expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
+ }, 0);
+ }
+ e.stopPropagation();
+ };
+ this._collapsed.onpointerenter = (e: any) => {
+ DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ e.preventDefault();
+ e.stopPropagation();
+ };
+ this._collapsed.onpointerleave = (e: any) => {
+ DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ e.preventDefault();
+ e.stopPropagation();
+ };
+ (this as any).dom = this._collapsed;
+ }
+ selectNode() { }
+}
+
export class DashDocView {
_dashSpan: HTMLDivElement;
_outer: HTMLElement;
@@ -656,36 +700,55 @@ export class DashDocView {
_textBox: FormattedTextBox;
getDocTransform = () => {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
}
contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
+ outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this._textBox = tbox;
this._dashSpan = document.createElement("div");
this._outer = document.createElement("span");
this._outer.style.position = "relative";
+ this._outer.style.textIndent = "0";
this._outer.style.width = node.attrs.width;
this._outer.style.height = node.attrs.height;
- this._outer.style.display = "inline-block";
- this._outer.style.overflow = "hidden";
+ this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
+ // this._outer.style.overflow = "hidden"; // bcz: not sure if this is needed. if it's used, then the doc doesn't highlight when you hover over a docComment
(this._outer.style as any).float = node.attrs.float;
this._dashSpan.style.width = node.attrs.width;
this._dashSpan.style.height = node.attrs.height;
this._dashSpan.style.position = "absolute";
this._dashSpan.style.display = "inline-block";
- let removeDoc = () => {
- let pos = getPos();
- let ns = new NodeSelection(view.state.doc.resolve(pos));
+ const removeDoc = () => {
+ const pos = getPos();
+ const ns = new NodeSelection(view.state.doc.resolve(pos));
view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
return true;
};
+ 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";
+ }
+ };
DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
if (dashDoc instanceof Doc) {
self._dashDoc = dashDoc;
+ dashDoc.hideSidebar = true;
if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
- view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+ try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+ } catch (e) {
+ console.log(e);
+ }
}
this._reactionDisposer && this._reactionDisposer();
this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => {
@@ -693,8 +756,9 @@ export class DashDocView {
this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px";
});
ReactDOM.render(<DocumentView
- fitToBox={BoolCast(dashDoc.fitToBox)}
Document={dashDoc}
+ LibraryPath={tbox.props.LibraryPath}
+ fitToBox={BoolCast(dashDoc.fitToBox)}
addDocument={returnFalse}
removeDocument={removeDoc}
ruleProvider={undefined}
@@ -704,22 +768,27 @@ export class DashDocView {
renderDepth={1}
PanelWidth={self._dashDoc[WidthSym]}
PanelHeight={self._dashDoc[HeightSym]}
- focus={emptyFunction}
+ focus={self.outerFocus}
backgroundColor={returnEmptyString}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
zoomToScale={emptyFunction}
getScale={returnOne}
- dontRegisterView={true}
+ dontRegisterView={false}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={this.contentScaling}
/>, this._dashSpan);
}
});
- let self = this;
- this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
+ const self = this;
+ this._dashSpan.onkeydown = function (e: any) {
+ e.stopPropagation();
+ if (e.key === "Tab" || e.key === "Enter") {
+ e.preventDefault();
+ }
+ };
this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
@@ -771,7 +840,7 @@ export class FootnoteView {
}
open() {
// Append a tooltip to the outer node
- let tooltip = this.dom.appendChild(document.createElement("div"));
+ const tooltip = this.dom.appendChild(document.createElement("div"));
tooltip.className = "footnote-tooltip";
// And put a sub-ProseMirror into that
this.innerView = new EditorView(tooltip, {
@@ -826,14 +895,14 @@ export class FootnoteView {
this.dom.textContent = "";
}
dispatchInner(tr: any) {
- let { state, transactions } = this.innerView.state.applyTransaction(tr);
+ const { state, transactions } = this.innerView.state.applyTransaction(tr);
this.innerView.updateState(state);
if (!tr.getMeta("fromOutside")) {
- let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
- for (let transaction of transactions) {
- let steps = transaction.steps;
- for (let step of steps) {
+ const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+ for (const transaction of transactions) {
+ const steps = transaction.steps;
+ for (const step of steps) {
outerTr.step(step.map(offsetMap));
}
}
@@ -844,11 +913,11 @@ export class FootnoteView {
if (!node.sameMarkup(this.node)) return false;
this.node = node;
if (this.innerView) {
- let state = this.innerView.state;
- let start = node.content.findDiffStart(state.doc.content);
+ const state = this.innerView.state;
+ const start = node.content.findDiffStart(state.doc.content);
if (start !== null) {
let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
- let overlap = start - Math.min(endA, endB);
+ const overlap = start - Math.min(endA, endB);
if (overlap > 0) { endA += overlap; endB += overlap; }
this.innerView.dispatch(
state.tr
@@ -870,7 +939,7 @@ export class FootnoteView {
ignoreMutation() { return true; }
}
-export class SummarizedView {
+export class SummaryView {
_collapsed: HTMLElement;
_view: any;
constructor(node: any, view: any, getPos: any) {
@@ -908,15 +977,16 @@ export class SummarizedView {
className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
updateSummarizedText(start?: any) {
- let mark = this._view.state.schema.marks.highlight.create();
+ const mtype = this._view.state.schema.marks.summarize;
+ const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
let endPos = start;
- let visited = new Set();
+ const visited = new Set();
for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
let skip = false;
this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
if (node.isLeaf && !visited.has(node) && !skip) {
- if (node.marks.find((m: any) => m.type === mark.type)) {
+ if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
}
@@ -940,8 +1010,8 @@ export const schema = new Schema({ nodes, marks });
const fromJson = schema.nodeFromJSON;
schema.nodeFromJSON = (json: any) => {
- let node = fromJson(json);
- if (json.type === "star") {
+ const node = fromJson(json);
+ if (json.type === schema.marks.summarize.name) {
node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
}
return node;
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index ff4451824..0fa96963e 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -94,16 +94,16 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
return { compiled: false, errors: diagnostics };
}
- let paramNames = Object.keys(scriptingGlobals);
- let params = paramNames.map(key => scriptingGlobals[key]);
+ const paramNames = Object.keys(scriptingGlobals);
+ const params = paramNames.map(key => scriptingGlobals[key]);
// let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript];
// let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)];
// let params: any[] = [Docs, ...fieldTypes];
- let compiledFunction = new Function(...paramNames, `return ${script}`);
- let { capturedVariables = {} } = options;
- let run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
- let argsArray: any[] = [];
- for (let name of customParams) {
+ const compiledFunction = new Function(...paramNames, `return ${script}`);
+ const { capturedVariables = {} } = options;
+ const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
+ const argsArray: any[] = [];
+ for (const name of customParams) {
if (name === "this") {
continue;
}
@@ -113,7 +113,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
argsArray.push(capturedVariables[name]);
}
}
- let thisParam = args.this || capturedVariables.this;
+ const thisParam = args.this || capturedVariables.this;
let batch: { end(): void } | undefined = undefined;
try {
if (!options.editable) {
@@ -146,7 +146,7 @@ class ScriptingCompilerHost {
// getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
- let contents = this.readFile(fileName);
+ const contents = this.readFile(fileName);
if (contents !== undefined) {
return ts.createSourceFile(fileName, contents, languageVersion, true);
}
@@ -180,7 +180,7 @@ class ScriptingCompilerHost {
return this.files.some(file => file.fileName === fileName);
}
readFile(fileName: string): string | undefined {
- let file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(file => file.fileName === fileName);
if (file) {
return file.content;
}
@@ -218,7 +218,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if (options.globals) {
Scripting.setScriptingGlobals(options.globals);
}
- let host = new ScriptingCompilerHost;
+ const host = new ScriptingCompilerHost;
if (options.traverser) {
const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
@@ -240,7 +240,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
script = printer.printFile(transformed[0]);
result.dispose();
}
- let paramNames: string[] = [];
+ const paramNames: string[] = [];
if ("this" in params || "this" in capturedVariables) {
paramNames.push("this");
}
@@ -248,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if (key === "this") continue;
paramNames.push(key);
}
- let paramList = paramNames.map(key => {
+ const paramList = paramNames.map(key => {
const val = params[key];
return `${key}: ${val}`;
});
@@ -258,18 +258,18 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
paramNames.push(key);
paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
}
- let paramString = paramList.join(", ");
- let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
+ const paramString = paramList.join(", ");
+ const funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
})`;
host.writeFile("file.ts", funcScript);
if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
- let program = ts.createProgram(["file.ts"], {}, host);
- let testResult = program.emit();
- let outputText = host.readFile("file.js");
+ const program = ts.createProgram(["file.ts"], {}, host);
+ const testResult = program.emit();
+ const outputText = host.readFile("file.js");
- let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
+ const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
const result = Run(outputText, paramNames, diagnostics, script, options);
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 2cf13680a..8ff54d052 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -34,37 +34,37 @@ export namespace SearchUtil {
export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
query = query || "*"; //If we just have a filter query, search for * as the query
- let result: IdSearchResult = JSON.parse(await rp.get(Utils.prepend("/search"), {
- qs: { ...options, q: query },
- }));
+ const rpquery = Utils.prepend("/search");
+ const gotten = await rp.get(rpquery, { qs: { ...options, q: query } });
+ const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten);
if (!returnDocs) {
return result;
}
- let { ids, numFound, highlighting } = result;
+ const { ids, highlighting } = result;
- let txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), {
+ const txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), {
qs: { ...options, q: query },
}));
- let fileids = txtresult ? txtresult.ids : [];
- let newIds: string[] = [];
- let newLines: string[][] = [];
+ const fileids = txtresult ? txtresult.ids : [];
+ const newIds: string[] = [];
+ const newLines: string[][] = [];
await Promise.all(fileids.map(async (tr: string, i: number) => {
- let docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query
- let docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } }));
+ const docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query
+ const docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } }));
newIds.push(...docResult.ids);
newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
}));
- let theDocs: Doc[] = [];
- let theLines: string[][] = [];
+ const theDocs: Doc[] = [];
+ const theLines: string[][] = [];
const textDocMap = await DocServer.GetRefFields(newIds);
const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc);
for (let i = 0; i < textDocs.length; i++) {
- let testDoc = textDocs[i];
- if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
+ const testDoc = textDocs[i];
+ if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
theDocs.push(Doc.GetProto(testDoc));
theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase())));
}
@@ -73,8 +73,8 @@ export namespace SearchUtil {
const docMap = await DocServer.GetRefFields(ids);
const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
for (let i = 0; i < ids.length; i++) {
- let testDoc = docs[i];
- if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
+ const testDoc = docs[i];
+ if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && testDoc.type !== DocumentType.EXTENSION && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
theDocs.push(testDoc);
theLines.push([]);
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index e01216e0f..4612f10f4 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,6 +2,7 @@ import { observable, action, runInAction, ObservableMap } from "mobx";
import { Doc } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { computedFn } from "mobx-utils";
+import { List } from "../../new_fields/List";
export namespace SelectionManager {
@@ -27,18 +28,21 @@ export namespace SelectionManager {
manager.SelectedDocuments.clear();
manager.SelectedDocuments.set(docView, true);
}
+ Doc.UserDoc().SelectedDocs = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
}
@action
DeselectDoc(docView: DocumentView): void {
if (manager.SelectedDocuments.get(docView)) {
manager.SelectedDocuments.delete(docView);
docView.props.whenActiveChanged(false);
+ Doc.UserDoc().SelectedDocs = new List(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
}
}
@action
DeselectAll(): void {
Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments.clear();
+ Doc.UserDoc().SelectedDocs = new List<Doc>([]);
}
}
@@ -78,3 +82,4 @@ export namespace SelectionManager {
return Array.from(manager.SelectedDocuments.keys());
}
}
+
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index ff048f647..1f6b939d3 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,7 +1,6 @@
-import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
-import { Field, Doc } from "../../new_fields/Doc";
+import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from "serializr";
+import { Field } from "../../new_fields/Doc";
import { ClientUtils } from "./ClientUtils";
-import { emptyFunction } from "../../Utils";
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -65,8 +64,8 @@ export namespace SerializationHelper {
}
}
-let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
-let reverseMap: { [ctor: string]: string } = {};
+const serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
+const reverseMap: { [ctor: string]: string } = {};
export interface DeserializableOpts {
(constructor: { new(...args: any[]): any }): void;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 2082d6324..7496ac73c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -4,13 +4,11 @@ import MainViewModal from "../views/MainViewModal";
import { Doc, Opt, DocCastAsync } from "../../new_fields/Doc";
import { DocServer } from "../DocServer";
import { Cast, StrCast } from "../../new_fields/Types";
-import { RouteStore } from "../../server/RouteStore";
import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
import "./SharingManager.scss";
import { Id } from "../../new_fields/FieldSymbols";
import { observer } from "mobx-react";
-import { MainView } from "../views/MainView";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
@@ -104,10 +102,10 @@ export default class SharingManager extends React.Component<{}> {
}
populateUsers = async () => {
- let userList = await RequestPromise.get(Utils.prepend(RouteStore.getUsers));
+ const userList = await RequestPromise.get(Utils.prepend("/getUsers"));
const raw = JSON.parse(userList) as User[];
const evaluating = raw.map(async user => {
- let isCandidate = user.email !== Doc.CurrentUserEmail;
+ const isCandidate = user.email !== Doc.CurrentUserEmail;
if (isCandidate) {
const userDocument = await DocServer.GetRefField(user.userDocumentId);
if (userDocument instanceof Doc) {
@@ -131,7 +129,7 @@ export default class SharingManager extends React.Component<{}> {
if (state === SharingPermissions.None) {
const metadata = (await DocCastAsync(manager[key]));
if (metadata) {
- let sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
+ const sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
manager[key] = undefined;
}
@@ -146,7 +144,7 @@ export default class SharingManager extends React.Component<{}> {
}
private setExternalSharing = (state: string) => {
- let sharingDoc = this.sharingDoc;
+ const sharingDoc = this.sharingDoc;
if (!sharingDoc) {
return;
}
@@ -157,7 +155,7 @@ export default class SharingManager extends React.Component<{}> {
if (!this.targetDoc) {
return undefined;
}
- let baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
+ const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
return `${baseUrl}?sharing=true`;
}
@@ -179,7 +177,7 @@ export default class SharingManager extends React.Component<{}> {
}
private focusOn = (contents: string) => {
- let title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
return (
<span
className={"focus-span"}
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
index e6d6c471f..b46675a04 100644
--- a/src/client/util/TooltipLinkingMenu.tsx
+++ b/src/client/util/TooltipLinkingMenu.tsx
@@ -2,10 +2,6 @@ import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { FieldViewProps } from "../views/nodes/FieldView";
import "./TooltipTextMenu.scss";
-import React = require("react");
-const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
-
-const SVG = "http://www.w3.org/2000/svg";
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipLinkingMenu {
@@ -23,9 +19,9 @@ export class TooltipLinkingMenu {
//add the div which is the tooltip
view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
- let target = "https://www.google.com";
+ const target = "https://www.google.com";
- let link = document.createElement("a");
+ const link = document.createElement("a");
link.href = target;
link.textContent = target;
link.target = "_blank";
@@ -37,7 +33,7 @@ export class TooltipLinkingMenu {
//updates the tooltip menu when the selection changes
update(view: EditorView, lastState: EditorState | undefined) {
- let state = view.state;
+ const state = view.state;
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return;
@@ -53,16 +49,16 @@ export class TooltipLinkingMenu {
// Otherwise, reposition it and update its content
this.tooltip.style.display = "";
- let { from, to } = state.selection;
- let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
+ const { from, to } = state.selection;
+ const start = view.coordsAtPos(from), end = view.coordsAtPos(to);
// The box in which the tooltip is positioned, to use as base
- let box = this.tooltip.offsetParent!.getBoundingClientRect();
+ const box = this.tooltip.offsetParent!.getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when
// crossing lines, end may be more to the left)
- let left = Math.max((start.left + end.left) / 2, start.left + 3);
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
- let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
- let mid = Math.min(start.left, end.left) + width;
+ const width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
+ const mid = Math.min(start.left, end.left) + width;
this.tooltip.style.width = "auto";
this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 8310a0da6..2a38fe118 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -149,7 +149,7 @@
}
svg {
- fill: white;
+ fill: inherit;
width: 18px;
height: 18px;
}
@@ -181,7 +181,7 @@
}
}
-#colorPicker {
+.colorPicker {
position: relative;
svg {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 5136089b3..8aa304fad 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,17 +1,13 @@
-import { action } from "mobx";
import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
import { wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc, Field, Opt } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { FieldViewProps } from "../views/nodes/FieldView";
import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { DocumentManager } from "./DocumentManager";
-import { DragManager } from "./DragManager";
import { LinkManager } from "./LinkManager";
import { schema } from "./RichTextSchema";
import "./TooltipTextMenu.scss";
@@ -20,12 +16,10 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
import { DocumentDecorations } from '../views/DocumentDecorations';
import { SelectionManager } from './SelectionManager';
import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-const { toggleMark, setBlockType } = require("prosemirror-commands");
-const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
+const { toggleMark } = require("prosemirror-commands");
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
-
public static Toolbar: HTMLDivElement | undefined;
// editor state properties
@@ -34,10 +28,7 @@ export class TooltipTextMenu {
private fontStyles: Mark[] = [];
private fontSizes: Mark[] = [];
- private listTypes: (NodeType | any)[] = [];
- private listTypeToIcon: Map<NodeType | any, string> = new Map();
- private _activeMarks: Mark[] = [];
- private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+ private _marksToDoms: Map<MarkType, HTMLSpanElement> = new Map();
private _collapsed: boolean = false;
// editor doms
@@ -47,20 +38,18 @@ export class TooltipTextMenu {
// editor button doms
private colorDom?: Node;
private colorDropdownDom?: Node;
- private highlightDom?: Node;
- private highlightDropdownDom?: Node;
- private linkEditor?: HTMLDivElement;
- private linkText?: HTMLDivElement;
- private linkDrag?: HTMLImageElement;
- private _linkDropdownDom?: Node;
+ private linkDom?: Node;
+ private highighterDom?: Node;
+ private highlighterDropdownDom?: Node;
+ private linkDropdownDom?: Node;
private _brushdom?: Node;
private _brushDropdownDom?: Node;
private fontSizeDom?: Node;
private fontStyleDom?: Node;
- private listTypeBtnDom?: Node;
private basicTools?: HTMLElement;
-
+ static createDiv(className: string) { const div = document.createElement("div"); div.className = className; return div; }
+ static createSpan(className: string) { const div = document.createElement("span"); div.className = className; return div; }
constructor(view: EditorView) {
this.view = view;
@@ -68,74 +57,64 @@ export class TooltipTextMenu {
this.initTooltip(view);
// initialize the wrapper
- this.wrapper = document.createElement("div");
- this.wrapper.className = "wrapper";
+ this.wrapper = TooltipTextMenu.createDiv("wrapper");
this.wrapper.appendChild(this.tooltip);
- // initialize the dragger -- appends it to the wrapper
- this.createDragger();
-
TooltipTextMenu.Toolbar = this.wrapper;
}
private async initTooltip(view: EditorView) {
- // initialize tooltip dom
- this.tooltip = document.createElement("div");
- this.tooltip.className = "tooltipMenu";
- this.basicTools = document.createElement("div");
- this.basicTools.className = "basic-tools";
-
- // init buttons to the tooltip -- paths to svgs are obtained from fontawesome
- let items = [
- { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
- { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
- { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
- { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
- { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
- { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
- // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
+ const self = this;
+ this.tooltip = TooltipTextMenu.createDiv("tooltipMenu");
+ this.basicTools = TooltipTextMenu.createDiv("basic-tools");
+
+ const svgIcon = (name: string, title: string = name, dpath: string) => {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "-100 -100 650 650");
+ const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ path.setAttributeNS(null, "d", dpath);
+ svg.appendChild(path);
+
+ const span = TooltipTextMenu.createSpan(name + " menuicon");
+ span.title = title;
+ span.appendChild(svg);
+
+ return span;
+ }
+
+ const basicItems = [ // init basicItems in minimized toolbar -- paths to svgs are obtained from fontawesome
+ { mark: schema.marks.strong, dom: svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
+ { mark: schema.marks.em, dom: svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
+ { mark: schema.marks.underline, dom: svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
+ ];
+ const items = [ // init items in full size toolbar
+ { mark: schema.marks.strikethrough, dom: svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
+ { mark: schema.marks.superscript, dom: svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
+ { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
];
- // add menu items
- this._marksToDoms = new Map();
- items.forEach(({ dom, command }) => {
+ basicItems.map(({ dom, mark }) => this.basicTools?.appendChild(dom.cloneNode(true)));
+ basicItems.concat(items).forEach(({ dom, mark }) => {
this.tooltip.appendChild(dom);
- switch (dom.title) {
- case "Bold":
- this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
- this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
- break;
- case "Italic":
- this._marksToDoms.set(schema.mark(schema.marks.em), dom);
- this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
- break;
- case "Underline":
- this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
- this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
- break;
- }
+ this._marksToDoms.set(mark, dom);
//pointer down handler to activate button effects
dom.addEventListener("pointerdown", e => {
- e.preventDefault();
this.view.focus();
if (dom.contains(e.target as Node)) {
+ e.preventDefault();
e.stopPropagation();
- command(this.view.state, this.view.dispatch, this.view);
- // if (this.view.state.selection.empty) {
- // if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
- // else { dom.style.color = "white"; }
- // }
+ toggleMark(mark)(this.view.state, this.view.dispatch, this.view);
+ this.updateHighlightStateOfButtons();
}
});
-
});
- // highlight menu
- this.highlightDom = this.createHighlightTool().render(this.view).dom;
- this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom;
- this.tooltip.appendChild(this.highlightDom);
- this.tooltip.appendChild(this.highlightDropdownDom);
+ // summarize menu
+ this.highighterDom = this.createHighlightTool().render(this.view).dom;
+ this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom;
+ this.tooltip.appendChild(this.highighterDom);
+ this.tooltip.appendChild(this.highlighterDropdownDom);
// color menu
this.colorDom = this.createColorTool().render(this.view).dom;
@@ -144,46 +123,15 @@ export class TooltipTextMenu {
this.tooltip.appendChild(this.colorDropdownDom);
// link menu
- this.updateLinkMenu();
- let dropdown = await this.createLinkDropdown();
- this._linkDropdownDom = dropdown.render(this.view).dom;
- this.tooltip.appendChild(this._linkDropdownDom);
+ this.linkDom = this.createLinkTool().render(this.view).dom;
+ this.linkDropdownDom = this.createLinkDropdown("").render(this.view).dom;
+ this.tooltip.appendChild(this.linkDom);
+ this.tooltip.appendChild(this.linkDropdownDom);
// list of font styles
- this.initFontStyles();
-
- // font sizes
- this.initFontSizes();
-
- // list types
- this.initListTypes();
-
- // init brush tool
- this._brushdom = this.createBrush().render(this.view).dom;
- this.tooltip.appendChild(this._brushdom);
- this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
- this.tooltip.appendChild(this._brushDropdownDom);
-
- // star
- this.tooltip.appendChild(this.createStar().render(this.view).dom);
-
- // list types dropdown
- this.updateListItemDropdown(":", this.listTypeBtnDom);
-
- await this.updateFromDash(view, undefined, undefined);
- }
-
- initFontStyles() {
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
- this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
- }
-
- initFontSizes() {
+ this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 7 }));
+ this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 8 }));
+ this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 9 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 }));
@@ -194,56 +142,89 @@ export class TooltipTextMenu {
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 }));
- }
- initListTypes() {
- this.listTypeToIcon = new Map();
- //this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
- this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":");
- this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1)");
- this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)");
- // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
- this.listTypes = Array.from(this.listTypeToIcon.keys());
- }
+ // font sizes
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
+ this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
- // creates dragger element that allows dragging and collapsing (on double click)
- // of editor and appends it to the wrapper
- createDragger() {
- let draggerWrapper = document.createElement("div");
- draggerWrapper.className = "dragger-wrapper";
- let dragger = document.createElement("div");
- dragger.className = "dragger";
+ // init brush tool
+ this._brushdom = this.createBrushTool().render(this.view).dom;
+ this.tooltip.appendChild(this._brushdom);
+ this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
+ this.tooltip.appendChild(this._brushDropdownDom);
- let line1 = document.createElement("span");
- line1.className = "dragger-line";
- let line2 = document.createElement("span");
- line2.className = "dragger-line";
- let line3 = document.createElement("span");
- line3.className = "dragger-line";
+ // summarizer tool
+ const summarizer = new MenuItem({
+ title: "Summarize",
+ label: "Summarize",
+ icon: icons.join,
+ css: "fill:white;",
+ class: "menuicon",
+ execEvent: "",
+ run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
+ });
+ this.tooltip.appendChild(summarizer.render(this.view).dom);
- dragger.appendChild(line1);
- dragger.appendChild(line2);
- dragger.appendChild(line3);
+ // list types dropdown
+ const listDropdownTypes = [{ mapStyle: "bullet", label: ":" }, { mapStyle: "decimal", label: "1.1" }, { mapStyle: "multi", label: "A.1" }, { label: "X" }];
+ const listTypes = new Dropdown(listDropdownTypes.map(({ mapStyle, label }) =>
+ new MenuItem({
+ title: "Set Bullet Style",
+ label: label,
+ execEvent: "",
+ class: "dropdown-item",
+ css: "color: black; width: 40px;",
+ enable() { return true; },
+ run() {
+ const marks = self.view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
+ if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
+ const tx3 = updateBullets(tx2, schema, mapStyle);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+
+ view.dispatch(tx2);
+ })) {
+ const tx2 = view.state.tr;
+ const tx3 = updateBullets(tx2, schema, mapStyle);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+
+ view.dispatch(tx3);
+ }
+ }
+ })), { label: ":", css: "color:black; width: 40px;" });
+ this.tooltip.appendChild(listTypes.render(this.view).dom);
- draggerWrapper.appendChild(dragger);
+ await this.updateFromDash(view, undefined, undefined);
+ const draggerWrapper = TooltipTextMenu.createDiv("dragger-wrapper");
+ const dragger = TooltipTextMenu.createDiv("dragger");
+ dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
+ dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
+ dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
+ draggerWrapper.appendChild(dragger);
this.wrapper.appendChild(draggerWrapper);
- this.dragElement(draggerWrapper);
+ this.setupDragElementInteractions(draggerWrapper);
}
- dragElement(elmnt: HTMLElement) {
+ setupDragElementInteractions(elmnt: HTMLElement) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (elmnt) {
// if present, the header is where you move the DIV from:
- elmnt.onpointerdown = dragMouseDown;
+ elmnt.onpointerdown = dragPointerDown;
elmnt.ondblclick = onClick;
}
const self = this;
- function dragMouseDown(e: PointerEvent) {
+ function dragPointerDown(e: PointerEvent) {
e = e || window.event;
- //e.preventDefault();
+ e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
@@ -285,24 +266,40 @@ export class TooltipTextMenu {
// stop moving when mouse button is released:
document.onpointerup = null;
document.onpointermove = null;
- //self.highlightSearchTerms(self.state, ["hello"]);
- //FormattedTextBox.Instance.unhighlightSearchTerms();
}
}
//label of dropdown will change to given label
updateFontSizeDropdown(label: string) {
//font SIZES
- let fontSizeBtns: MenuItem[] = [];
- this.fontSizes.forEach(mark => {
- fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
- });
+ const fontSizeBtns: MenuItem[] = [];
+ const self = this;
+ this.fontSizes.forEach(mark =>
+ fontSizeBtns.push(new MenuItem({
+ title: "Set Font Size",
+ label: String(mark.attrs.fontSize),
+ execEvent: "",
+ class: "dropdown-item",
+ css: "color: black; width: 50px;",
+ enable() { return true; },
+ run() {
+ const size = mark.attrs.fontSize;
+ if (size) { self.updateFontSizeDropdown(String(size) + " pt"); }
+ if (self.editorProps) {
+ const ruleProvider = self.editorProps.ruleProvider;
+ const heading = NumCast(self.editorProps.Document.heading);
+ if (ruleProvider && heading) {
+ ruleProvider["ruleSize_" + heading] = size;
+ }
+ }
+ TooltipTextMenu.setMark(self.view.state.schema.marks.pFontSize.create({ fontSize: size }), self.view.state, self.view.dispatch);
+ }
+ })));
- let newfontSizeDom = (new Dropdown(fontSizeBtns, {
- label: label,
- css: "color:black; min-width: 60px;"
- }) as MenuItem).render(this.view).dom;
- if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); }
+ const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom;
+ if (this.fontSizeDom) {
+ this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom);
+ }
else {
this.tooltip.appendChild(newfontSizeDom);
}
@@ -312,127 +309,53 @@ export class TooltipTextMenu {
//label of dropdown will change to given label
updateFontStyleDropdown(label: string) {
//font STYLES
- let fontBtns: MenuItem[] = [];
- this.fontStyles.forEach((mark) => {
- fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
- });
+ const fontBtns: MenuItem[] = [];
+ const self = this;
+ this.fontStyles.forEach(mark =>
+ fontBtns.push(new MenuItem({
+ title: "Set Font Family",
+ label: mark.attrs.family,
+ execEvent: "",
+ class: "dropdown-item",
+ css: "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;",
+ enable() { return true; },
+ run() {
+ const fontName = mark.attrs.family;
+ if (fontName) { self.updateFontStyleDropdown(fontName); }
+ if (self.editorProps) {
+ const ruleProvider = self.editorProps.ruleProvider;
+ const heading = NumCast(self.editorProps.Document.heading);
+ if (ruleProvider && heading) {
+ ruleProvider["ruleFont_" + heading] = fontName;
+ }
+ }
+ TooltipTextMenu.setMark(self.view.state.schema.marks.pFontFamily.create({ family: fontName }), self.view.state, self.view.dispatch);
+ }
+ })));
- let newfontStyleDom = (new Dropdown(fontBtns, {
- label: label,
- css: "color:black; width: 125px;"
- }) as MenuItem).render(this.view).dom;
- if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); }
+ const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom;
+ if (this.fontStyleDom) {
+ this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom);
+ }
else {
this.tooltip.appendChild(newfontStyleDom);
}
this.fontStyleDom = newfontStyleDom;
}
-
- updateLinkMenu() {
- if (!this.linkEditor || !this.linkText) {
- this.linkEditor = document.createElement("div");
- this.linkEditor.className = "ProseMirror-icon menuicon";
- this.linkText = document.createElement("div");
- this.linkText.setAttribute("contenteditable", "true");
- this.linkText.style.whiteSpace = "nowrap";
- this.linkText.style.width = "150px";
- this.linkText.style.overflow = "hidden";
- this.linkText.style.color = "white";
- this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
- let linkBtn = document.createElement("div");
- linkBtn.textContent = ">>";
- linkBtn.style.width = "10px";
- linkBtn.style.height = "10px";
- linkBtn.style.color = "white";
- linkBtn.style.cssFloat = "left";
- linkBtn.onpointerdown = (e: PointerEvent) => {
- let node = this.view.state.selection.$from.nodeAfter;
- let link = node && node.marks.find(m => m.type.name === "link");
- if (link) {
- let href: string = link.attrs.href;
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- let docid = href.replace(Utils.prepend("/doc/"), "");
- DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
- if (f instanceof Doc) {
- if (DocumentManager.Instance.getDocumentView(f)) {
- DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
- }
- else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight");
- }
- }));
- }
- // TODO This should have an else to handle external links
- e.stopPropagation();
- e.preventDefault();
- }
- };
- this.linkDrag = document.createElement("img");
- this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
- this.linkDrag.style.width = "15px";
- this.linkDrag.style.height = "15px";
- this.linkDrag.title = "Drag to create link";
- this.linkDrag.id = "link-drag";
- this.linkDrag.onpointerdown = (e: PointerEvent) => {
- if (!this.editorProps) return;
- let dragData = new DragManager.LinkDragData(this.editorProps.Document);
- dragData.dontClearTextBox = true;
- // hack to get source context -sy
- let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document);
- e.stopPropagation();
- let ctrlKey = e.ctrlKey;
- DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
- {
- handlers: {
- dragComplete: action(() => {
- if (dragData.linkDocument) {
- let linkDoc = dragData.linkDocument;
- let proto = Doc.GetProto(linkDoc);
- if (proto && docView) {
- proto.sourceContext = docView.props.ContainingCollectionDoc;
- }
- let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab");
- if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
- proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
- }
- }
- }),
- },
- hideSource: false
- });
- e.stopPropagation();
- e.preventDefault();
- };
- this.linkEditor.appendChild(this.linkDrag);
- this.tooltip.appendChild(this.linkEditor);
- }
-
- let node = this.view.state.selection.$from.nodeAfter;
- let link = node && node.marks.find(m => m.type.name === "link");
- this.linkText.textContent = link ? link.attrs.href : "-empty-";
-
- this.linkText.onkeydown = (e: KeyboardEvent) => {
- if (e.key === "Enter") {
- // this.makeLink(this.linkText!.textContent!);
- e.stopPropagation();
- e.preventDefault();
- }
- };
- }
-
async getTextLinkTargetTitle() {
- let node = this.view.state.selection.$from.nodeAfter;
- let link = node && node.marks.find(m => m.type.name === "link");
+ const node = this.view.state.selection.$from.nodeAfter;
+ const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- let href = link.attrs.href;
+ const href = link.attrs.href;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
if (linkclicked) {
- let linkDoc = await DocServer.GetRefField(linkclicked);
+ const linkDoc = await DocServer.GetRefField(linkclicked);
if (linkDoc instanceof Doc) {
- let anchor1 = await Cast(linkDoc.anchor1, Doc);
- let anchor2 = await Cast(linkDoc.anchor2, Doc);
- let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+ const anchor1 = await Cast(linkDoc.anchor1, Doc);
+ const anchor2 = await Cast(linkDoc.anchor2, Doc);
+ const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
if (currentDoc && anchor1 && anchor2) {
if (Doc.AreProtosEqual(currentDoc, anchor1)) {
return StrCast(anchor2.title);
@@ -452,19 +375,32 @@ export class TooltipTextMenu {
}
}
- async createLinkDropdown() {
- let targetTitle = await this.getTextLinkTargetTitle();
- let input = document.createElement("input");
+ // LINK TOOL
+ createLinkTool(active: boolean = false) {
+ return new MenuItem({
+ title: "Link tool",
+ label: "Link tool",
+ icon: icons.link,
+ css: "fill:white;",
+ class: active ? "menuicon-active" : "menuicon",
+ execEvent: "",
+ run: async (state, dispatch) => { },
+ active: (state) => true
+ });
+ }
+
+ createLinkDropdown(targetTitle: string) {
+ const input = document.createElement("input");
// menu item for input for hyperlink url
// TODO: integrate search to allow users to search for a doc to link to
- let linkInfo = new MenuItem({
+ const linkInfo = new MenuItem({
title: "",
execEvent: "",
class: "button-setting-disabled",
css: "",
render() {
- let p = document.createElement("p");
+ const p = document.createElement("p");
p.textContent = "Linked to:";
input.type = "text";
@@ -475,286 +411,156 @@ export class TooltipTextMenu {
input.focus();
};
- let div = document.createElement("div");
+ const div = document.createElement("div");
div.appendChild(p);
div.appendChild(input);
return div;
},
enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
+ run(p1, p2, p3, event) { event.stopPropagation(); }
});
// menu item to update/apply the hyperlink to the selected text
- let linkApply = new MenuItem({
+ const linkApply = new MenuItem({
title: "",
execEvent: "",
class: "",
css: "",
render() {
- let button = document.createElement("button");
+ const button = document.createElement("button");
button.className = "link-url-button";
button.textContent = "Apply hyperlink";
return button;
},
enable() { return false; },
- run: (state, dispatch, view, event) => {
+ run: async (state, dispatch, view, event) => {
event.stopPropagation();
- this.makeLinkToURL(input.value, "onRight");
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: input.value, location: "onRight" });
+ this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+ this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+ node = this.view.state.selection.$from.nodeAfter;
+ link = node && node.marks.find(m => m.type.name === "link");
+
+ // update link menu
+ const linkDom = self.createLinkTool(true).render(self.view).dom;
+ const linkDropdownDom = self.createLinkDropdown(await self.getTextLinkTargetTitle()).render(self.view).dom;
+ self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
+ self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
+ self.linkDom = linkDom;
+ self.linkDropdownDom = linkDropdownDom;
}
});
// menu item to remove the link
// TODO: allow this to be undoable
- let self = this;
- let deleteLink = new MenuItem({
+ const self = this;
+ const deleteLink = new MenuItem({
title: "Delete link",
execEvent: "",
class: "separated-button",
css: "",
render() {
- let button = document.createElement("button");
+ const button = document.createElement("button");
button.textContent = "Remove link";
- let wrapper = document.createElement("div");
+ const wrapper = document.createElement("div");
wrapper.appendChild(button);
return wrapper;
},
enable() { return true; },
async run() {
- self.deleteLink();
- // update link dropdown
- let dropdown = await self.createLinkDropdown();
- let newLinkDropdowndom = dropdown.render(self.view).dom;
- self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom);
- self._linkDropdownDom = newLinkDropdowndom;
- }
- });
-
-
- let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
- return linkDropdown;
- }
-
- // makeLinkWithState = (state: EditorState, target: string, location: string) => {
- // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
- // }
-
- makeLink = (targetDoc: Doc, title: string, location: string): string => {
- let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location });
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
- addMark(this.view.state.selection.from, this.view.state.selection.to, link));
- let node = this.view.state.selection.$from.nodeAfter;
- if (node && node.text) {
- return node.text;
- }
- return "";
- }
-
- makeLinkToURL = (target: String, lcoation: string) => {
- let node = this.view.state.selection.$from.nodeAfter;
- let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
- this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
- node = this.view.state.selection.$from.nodeAfter;
- link = node && node.marks.find(m => m.type.name === "link");
- }
-
- deleteLink = () => {
- let node = this.view.state.selection.$from.nodeAfter;
- let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link);
- let href = link!.attrs.href;
- if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (linkclicked) {
- DocServer.GetRefField(linkclicked).then(async linkDoc => {
+ // delete the link
+ const node = self.view.state.selection.$from.nodeAfter;
+ const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
+ const href = link!.attrs.href;
+ if (href?.indexOf(Utils.prepend("/doc/")) === 0) {
+ const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
if (linkDoc instanceof Doc) {
LinkManager.Instance.deleteLink(linkDoc);
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+ self.view.dispatch(self.view.state.tr.removeMark(self.view.state.selection.from, self.view.state.selection.to, self.view.state.schema.marks.link));
}
});
}
- }
- }
- }
-
- deleteLinkItem() {
- const icon = {
- height: 16, width: 16,
- path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
- };
- return new MenuItem({
- title: "Delete Link",
- label: "X",
- icon: icon,
- css: "color: red",
- class: "summarize",
- execEvent: "",
- run: (state, dispatch) => {
- this.deleteLink();
- }
- });
- }
-
- createLink() {
- let markType = schema.marks.link;
- return new MenuItem({
- title: "Add or remove link",
- label: "Add or remove link",
- execEvent: "",
- icon: icons.link,
- css: "color:white;",
- class: "menuicon",
- enable(state) { return !state.selection.empty; },
- run: (state, dispatch, view) => {
- // to remove link
- let curLink = "";
- if (this.markActive(state, markType)) {
-
- let { from, $from, to, empty } = state.selection;
- let node = state.doc.nodeAt(from);
- node && node.marks.map(m => {
- m.type === markType && (curLink = m.attrs.href);
- });
- //toggleMark(markType)(state, dispatch);
- //return true;
- }
- // to create link
- openPrompt({
- title: "Create a link",
- fields: {
- href: new TextField({
- value: curLink,
- label: "Link Target",
- required: true
- }),
- title: new TextField({ label: "Title" })
- },
- callback(attrs: any) {
- toggleMark(markType, attrs)(view.state, view.dispatch);
- view.focus();
- },
- flyout_top: 0,
- flyout_left: 0
- });
+ // update link menu
+ const linkDom = self.createLinkTool(false).render(self.view).dom;
+ const linkDropdownDom = self.createLinkDropdown("").render(self.view).dom;
+ self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
+ self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
+ self.linkDom = linkDom;
+ self.linkDropdownDom = linkDropdownDom;
}
});
- }
- //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
- updateListItemDropdown(label: string, listTypeBtn: any) {
- //remove old btn
- if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
-
- //Make a dropdown of all list types
- let toAdd: MenuItem[] = [];
- this.listTypeToIcon.forEach((icon, type) => {
- toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
- });
- //option to remove the list formatting
- toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
-
- listTypeBtn = (new Dropdown(toAdd, {
- label: label,
- css: "color:black; width: 40px;"
- }) as MenuItem).render(this.view).dom;
-
- //add this new button and return it
- this.tooltip.appendChild(listTypeBtn);
- return listTypeBtn;
- }
-
- createStar() {
- return new MenuItem({
- title: "Summarize",
- label: "Summarize",
- icon: icons.join,
- css: "color:white;",
- class: "menuicon",
- execEvent: "",
- run: (state, dispatch) => {
- TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
- }
-
- });
+ return new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
}
- public static insertStar(state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- let mark = state.schema.marks.highlight.create();
- let tr = state.tr;
- tr.addMark(state.selection.from, state.selection.to, mark);
- let content = tr.selection.content();
- let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
- dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
- return true;
+ public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+ const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
+ this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+ addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+ return this.view.state.selection.$from.nodeAfter?.text || "";
}
- public static insertComment(state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- let mark = state.schema.marks.highlight.create();
- let tr = state.tr;
- tr.addMark(state.selection.from, state.selection.to, mark);
- let content = tr.selection.content();
- let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
- dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
- return true;
+ // SUMMARIZER TOOL
+ static insertSummarizer(state: EditorState<any>, dispatch: any) {
+ if (!state.selection.empty) {
+ const mark = state.schema.marks.summarize.create();
+ const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
+ const content = tr.selection.content();
+ const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+ dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+ }
}
+ // HIGHLIGHTER TOOL
createHighlightTool() {
return new MenuItem({
title: "Highlight",
- css: "color:white;",
+ css: "fill:white;",
class: "menuicon",
execEvent: "",
render() {
- let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "-100 -100 650 650");
- let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z");
svg.appendChild(path);
- let color = document.createElement("div");
- color.className = "buttonColor";
- color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString();
+ const color = TooltipTextMenu.createDiv("buttonColor");
+ color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
- let wrapper = document.createElement("div");
- wrapper.id = "colorPicker";
+ const wrapper = TooltipTextMenu.createDiv("colorPicker");
wrapper.appendChild(svg);
wrapper.appendChild(color);
return wrapper;
},
- run: (state, dispatch) => {
- TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch);
- }
+ run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch)
});
}
- public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
-
- let highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color });
- dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark));
+ static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+ if (!state.selection.empty) {
+ toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+ }
}
createHighlightDropdown() {
// menu item for color picker
- let self = this;
- let colors = new MenuItem({
+ const self = this;
+ const colors = new MenuItem({
title: "",
execEvent: "",
class: "button-setting-disabled",
css: "",
render() {
- let p = document.createElement("p");
+ const p = document.createElement("p");
p.textContent = "Change highlight:";
- let colorsWrapper = document.createElement("div");
- colorsWrapper.className = "colorPicker-wrapper";
+ const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
- let colors = [
+ const colors = [
PastelSchemaPalette.get("pink2"),
PastelSchemaPalette.get("purple4"),
PastelSchemaPalette.get("bluegreen1"),
@@ -768,29 +574,29 @@ export class TooltipTextMenu {
];
colors.forEach(color => {
- let button = document.createElement("button");
- button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker";
+ const button = document.createElement("button");
+ button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker";
if (color) {
button.style.backgroundColor = color;
button.textContent = color === "transparent" ? "X" : "";
button.onclick = e => {
- TooltipTextMenuManager.Instance.highlight = color;
+ TooltipTextMenuManager.Instance.highlighter = color;
- TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch);
+ TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch);
// update color menu
- let highlightDom = self.createHighlightTool().render(self.view).dom;
- let highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
- self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom);
- self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom);
- self.highlightDom = highlightDom;
- self.highlightDropdownDom = highlightDropdownDom;
+ const highlightDom = self.createHighlightTool().render(self.view).dom;
+ const highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
+ self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom);
+ self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom);
+ self.highighterDom = highlightDom;
+ self.highlighterDropdownDom = highlightDropdownDom;
};
}
colorsWrapper.appendChild(button);
});
- let div = document.createElement("div");
+ const div = document.createElement("div");
div.appendChild(p);
div.appendChild(colorsWrapper);
return div;
@@ -801,62 +607,59 @@ export class TooltipTextMenu {
}
});
- let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
- return colorDropdown;
+ return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
}
+ // COLOR TOOL
createColorTool() {
return new MenuItem({
title: "Color",
- css: "color:white;",
+ css: "fill:white;",
class: "menuicon",
execEvent: "",
render() {
- let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "-100 -100 650 650");
- let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z");
svg.appendChild(path);
- let color = document.createElement("div");
- color.className = "buttonColor";
+ const color = TooltipTextMenu.createDiv("buttonColor");
color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
- let wrapper = document.createElement("div");
- wrapper.id = "colorPicker";
+ const wrapper = TooltipTextMenu.createDiv("colorPicker");
wrapper.appendChild(svg);
wrapper.appendChild(color);
return wrapper;
},
- run: (state, dispatch) => {
- TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch);
- }
+ run: (state, dispatch) => TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
});
}
- public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
-
- let colorMark = state.schema.mark(state.schema.marks.color, { color: color });
- dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
+ static insertColor(color: String, state: EditorState<any>, dispatch: any) {
+ const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+ if (state.selection.empty) {
+ dispatch(state.tr.addStoredMark(colorMark));
+ } else {
+ this.setMark(colorMark, state, dispatch);
+ }
}
createColorDropdown() {
// menu item for color picker
- let self = this;
- let colors = new MenuItem({
+ const self = this;
+ const colors = new MenuItem({
title: "",
execEvent: "",
class: "button-setting-disabled",
css: "",
render() {
- let p = document.createElement("p");
+ const p = document.createElement("p");
p.textContent = "Change color:";
- let colorsWrapper = document.createElement("div");
- colorsWrapper.className = "colorPicker-wrapper";
+ const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
- let colors = [
+ const colors = [
DarkPastelSchemaPalette.get("pink2"),
DarkPastelSchemaPalette.get("purple4"),
DarkPastelSchemaPalette.get("bluegreen1"),
@@ -870,7 +673,7 @@ export class TooltipTextMenu {
];
colors.forEach(color => {
- let button = document.createElement("button");
+ const button = document.createElement("button");
button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker";
if (color) {
button.style.backgroundColor = color;
@@ -880,8 +683,8 @@ export class TooltipTextMenu {
TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
// update color menu
- let colorDom = self.createColorTool().render(self.view).dom;
- let colorDropdownDom = self.createColorDropdown().render(self.view).dom;
+ const colorDom = self.createColorTool().render(self.view).dom;
+ const colorDropdownDom = self.createColorDropdown().render(self.view).dom;
self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom);
self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom);
self.colorDom = colorDom;
@@ -891,75 +694,72 @@ export class TooltipTextMenu {
colorsWrapper.appendChild(button);
});
- let div = document.createElement("div");
+ const div = document.createElement("div");
div.appendChild(p);
div.appendChild(colorsWrapper);
return div;
},
enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
+ run(p1, p2, p3, event) { event.stopPropagation(); }
});
- let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
- return colorDropdown;
+ return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
}
- createBrush(active: boolean = false) {
+ // BRUSH TOOL
+ createBrushTool(active: boolean = false) {
const icon = {
height: 32, width: 32,
path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
};
- let self = this;
+ const self = this;
return new MenuItem({
title: "Brush tool",
label: "Brush tool",
icon: icon,
- css: "color:white;",
+ css: "fill:white;",
class: active ? "menuicon-active" : "menuicon",
execEvent: "",
run: (state, dispatch) => {
this.brush_function(state, dispatch);
// update dropdown with marks
- let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
+ const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
self._brushDropdownDom = newBrushDropdowndom;
},
- active: (state) => {
- return true;
- }
+ active: (state) => true
});
}
brush_function(state: EditorState<any>, dispatch: any) {
if (TooltipTextMenuManager.Instance._brushIsEmpty) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (this._brushdom) {
- if (selected_marks.size >= 0) {
- TooltipTextMenuManager.Instance._brushMarks = selected_marks;
- const newbrush = this.createBrush(true).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
- }
+ // get marks in the selection
+ const selected_marks = new Set<Mark>();
+ const { from, to } = state.selection as TextSelection;
+ state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => selected_marks.add(m)));
+
+ if (this._brushdom && selected_marks.size >= 0) {
+ TooltipTextMenuManager.Instance._brushMarks = selected_marks;
+ const newbrush = this.createBrushTool(true).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
}
}
else {
- let { from, to, $from } = this.view.state.selection;
+ const { from, to, $from } = this.view.state.selection;
if (this._brushdom) {
if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
this.view.dispatch(this.view.state.tr.removeMark(from, to));
Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- const markType = mark.type;
- this.changeToMarkInGroup(markType, this.view, []);
+ TooltipTextMenu.setMark(mark, this.view.state, this.view.dispatch);
});
}
}
else {
- const newbrush = this.createBrush(false).render(this.view).dom;
+ const newbrush = this.createBrushTool(false).render(this.view).dom;
this.tooltip.replaceChild(newbrush, this._brushdom);
this._brushdom = newbrush;
TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
@@ -971,40 +771,58 @@ export class TooltipTextMenu {
createBrushDropdown(active: boolean = false) {
let label = "Stored marks: ";
if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
- TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => {
- const markType = mark.type;
- label += markType.name;
- label += ", ";
- });
+ TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => label += mark.type.name + ", ");
label = label.substring(0, label.length - 2);
} else {
label = "No marks are currently stored";
}
-
- let brushInfo = new MenuItem({
+ const brushInfo = new MenuItem({
title: "",
label: label,
execEvent: "",
class: "button-setting-disabled",
css: "",
enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
+ run(p1, p2, p3, event) { event.stopPropagation(); }
});
- let self = this;
- let clearBrush = new MenuItem({
+ const self = this;
+ const input = document.createElement("input");
+ const clearBrush = new MenuItem({
title: "Clear brush",
execEvent: "",
class: "separated-button",
css: "",
render() {
- let button = document.createElement("button");
+ const button = document.createElement("button");
button.textContent = "Clear brush";
- let wrapper = document.createElement("div");
+ input.textContent = "editme";
+ input.style.width = "75px";
+ input.style.height = "30px";
+ input.style.background = "white";
+ input.setAttribute("contenteditable", "true");
+ input.style.whiteSpace = "nowrap";
+ input.type = "text";
+ input.placeholder = "Enter URL";
+ input.onpointerdown = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ };
+ input.onclick = (e: MouseEvent) => {
+ input.select();
+ input.focus();
+ };
+ input.onkeypress = (e: KeyboardEvent) => {
+ if (e.key === "Enter") {
+ TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
+ input.style.background = "lightGray";
+ }
+ };
+
+ const wrapper = document.createElement("div");
+ wrapper.appendChild(input);
wrapper.appendChild(button);
return wrapper;
},
@@ -1015,305 +833,41 @@ export class TooltipTextMenu {
// update brush tool
// TODO: this probably isn't very clean
- let newBrushdom = self.createBrush().render(self.view).dom;
+ const newBrushdom = self.createBrushTool().render(self.view).dom;
self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom);
self._brushdom = newBrushdom;
- let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
+ const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
self._brushDropdownDom = newBrushDropdowndom;
}
});
- let hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
- let brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
- return brushDom;
+ const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
+ return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
}
- //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
- changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
- let { $cursor, ranges } = view.state.selection as TextSelection;
- let state = view.state;
- let dispatch = view.dispatch;
-
- //remove all other active font marks
- fontMarks.forEach((type) => {
- if (dispatch) {
- if ($cursor) {
- if (type.isInSet(state.storedMarks || $cursor.marks())) {
- dispatch(state.tr.removeStoredMark(type));
- }
- } else {
- let has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, type);
- }
- for (let i of ranges) {
- if (has) {
- toggleMark(type)(view.state, view.dispatch, view);
- }
- }
- }
- }
- });
-
- if (markType) {
- //actually apply font
- if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
- let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
- { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema);
- view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
- }
- else toggleMark(markType)(view.state, view.dispatch, view);
- }
- }
-
- changeToFontFamily = (mark: Mark, view: EditorView) => {
- let { $cursor, ranges } = view.state.selection as TextSelection;
- let state = view.state;
- let dispatch = view.dispatch;
-
- //remove all other active font marks
- if ($cursor) {
- if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) {
- dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily));
- }
- } else {
- let has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily);
- }
- for (let i of ranges) {
- if (has) {
- toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view);
- }
- }
- }
- let fontName = mark.attrs.family;
- if (fontName) { this.updateFontStyleDropdown(fontName); }
- if (this.editorProps) {
- let ruleProvider = this.editorProps.ruleProvider;
- let heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleFont_" + heading] = fontName;
- }
- }
- //actually apply font
- if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
- let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
- { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema);
- view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
- }
- else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName })));
- view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })];
- }
-
- changeToFontSize = (mark: Mark, view: EditorView) => {
- let { $cursor, ranges } = view.state.selection as TextSelection;
- let state = view.state;
- let dispatch = view.dispatch;
-
- //remove all other active font marks
- if ($cursor) {
- if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) {
- dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize));
- }
- } else {
- let has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize);
- }
- for (let i of ranges) {
- if (has) {
- toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view);
- }
- }
- }
-
- let size = mark.attrs.fontSize;
- if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
- if (this.editorProps) {
- let ruleProvider = this.editorProps.ruleProvider;
- let heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleSize_" + heading] = size;
- }
- }
- //actually apply font
- if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
- let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
- { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema);
- view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
- }
- else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size })));
- view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })];
- }
-
- //remove all node typeand apply the passed-in one to the selected text
- changeToNodeType = (nodeType: NodeType | undefined) => {
- //remove oldif (nodeType) { //add new
- let view = this.view;
- if (nodeType === schema.nodes.bullet_list) {
- wrapInList(nodeType)(view.state, view.dispatch);
- } else {
- var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
- if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
- let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- view.dispatch(tx2);
- })) {
- let tx2 = view.state.tr;
- let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
- marks && tx3.ensureMarks([...marks]);
- marks && tx3.setStoredMarks([...marks]);
-
- view.dispatch(tx3);
- }
- }
- }
-
- //makes a button for the drop down FOR MARKS
- //css is the style you want applied to the button
- dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
- return new MenuItem({
- title: "",
- label: label,
- execEvent: "",
- class: "dropdown-item",
- css: css,
- enable() { return true; },
- run() {
- changeFontFamily(mark, view);
- }
- });
- }
- //makes a button for the drop down FOR MARKS
- //css is the style you want applied to the button
- dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark<any>, view: EditorView) => any) {
- return new MenuItem({
- title: "",
- label: label,
- execEvent: "",
- class: "dropdown-item",
- css: css,
- enable() { return true; },
- run() {
- changeFontSize(mark, view);
- }
- });
- }
-
- //makes a button for the drop down FOR NODE TYPES
- //css is the style you want applied to the button
- dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
- return new MenuItem({
- title: "",
- label: label,
- execEvent: "",
- class: "dropdown-item",
- css: css,
- enable() { return true; },
- run() {
- changeToNodeInGroup(nodeType, view, groupNodes);
- }
- });
- }
-
- markActive = function(state: EditorState<any>, type: MarkType<Schema<string, string>>) {
- let { from, $from, to, empty } = state.selection;
- if (empty) return type.isInSet(state.storedMarks || $from.marks());
- else return state.doc.rangeHasMark(from, to, type);
- };
-
- // Helper function to create menu icons
- icon(text: string, name: string, title: string = name) {
- let span = document.createElement("span");
- span.className = name + " menuicon";
- span.title = title;
- span.textContent = text;
- span.style.color = "white";
- return span;
- }
-
- svgIcon(name: string, title: string = name, dpath: string) {
- let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("viewBox", "-100 -100 650 650");
- let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
- path.setAttributeNS(null, "d", dpath);
- svg.appendChild(path);
-
- let span = document.createElement("span");
- span.className = name + " menuicon";
- span.title = title;
- span.appendChild(svg);
-
- return span;
- }
-
- //method for checking whether node can be inserted
- canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
- let $from = state.selection.$from;
- for (let d = $from.depth; d >= 0; d--) {
- let index = $from.index(d);
- if ($from.node(d).canReplaceWith(index, index, nodeType)) return true;
- }
- return false;
- }
-
-
- //adapted this method - use it to check if block has a tag (ie bulleting)
- blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
- let attrs = {};
-
- if (state.selection instanceof NodeSelection) {
- const sel: NodeSelection = state.selection;
- let $from = sel.$from;
- let to = sel.to;
- let node = sel.node;
-
- if (node) {
- return node.hasMarkup(type, attrs);
- }
-
- return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
- }
- }
-
- // Create an icon for a heading at the given level
- heading(level: number) {
- return {
- command: setBlockType(schema.nodes.heading, { level }),
- dom: this.icon("H" + level, "heading")
- };
- }
-
- getMarksInSelection(state: EditorState<any>) {
- let found = new Set<Mark>();
- let { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => {
- let marks = node.marks;
- if (marks) {
- marks.forEach(m => {
- found.add(m);
+ static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+ if (mark) {
+ const node = (state.selection as NodeSelection).node;
+ if (node?.type === schema.nodes.ordered_list) {
+ let attrs = node.attrs;
+ if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+ if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+ if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+ const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+ dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+ } else {
+ toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+ const { from, $from, to, empty } = tx.selection;
+ if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+ toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+ } else dispatch(tx);
});
}
- });
- return found;
- }
-
- reset_mark_doms() {
- let iterator = this._marksToDoms.values();
- let next = iterator.next();
- while (!next.done) {
- next.value.style.color = "white";
- next = iterator.next();
}
}
+ // called by Prosemirror
update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
//updates the tooltip menu when the selection changes
public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
@@ -1322,71 +876,52 @@ export class TooltipTextMenu {
return;
}
this.view = view;
- let state = view.state;
DocumentDecorations.Instance.showTextBar();
props && (this.editorProps = props);
- // Don't do anything if the document/selection didn't change
- if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return;
-
- this.reset_mark_doms();
-
- // Hide the tooltip if the selection is empty
- if (state.selection.empty) {
- //this.tooltip.style.display = "none";
- //return;
- }
- // update link dropdown
- let linkDropdown = await this.createLinkDropdown();
- let newLinkDropdowndom = linkDropdown.render(this.view).dom;
- this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
- this._linkDropdownDom = newLinkDropdowndom;
-
- //UPDATE FONT STYLE DROPDOWN
- let activeStyles = this.activeFontFamilyOnSelection();
- if (activeStyles !== undefined) {
- if (activeStyles.length === 1) {
- console.log("updating font style dropdown", activeStyles[0]);
- activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]);
- } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default");
- }
-
- //UPDATE FONT SIZE DROPDOWN
- let activeSizes = this.activeFontSizeOnSelection();
- if (activeSizes !== undefined) {
- if (activeSizes.length === 1) { //if there's only one active font size
- activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt");
- } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default");
+ // Don't do anything if the document/selection didn't change
+ if (!lastState || !lastState.doc.eq(view.state.doc) || !lastState.selection.eq(view.state.selection)) {
+
+ // UPDATE LINK DROPDOWN
+ const linkTarget = await this.getTextLinkTargetTitle()
+ const linkDom = this.createLinkTool(linkTarget ? true : false).render(this.view).dom;
+ const linkDropdownDom = this.createLinkDropdown(linkTarget).render(this.view).dom;
+ this.linkDom && this.tooltip.replaceChild(linkDom, this.linkDom);
+ this.linkDropdownDom && this.tooltip.replaceChild(linkDropdownDom, this.linkDropdownDom);
+ this.linkDom = linkDom;
+ this.linkDropdownDom = linkDropdownDom;
+
+ //UPDATE FONT STYLE DROPDOWN
+ const activeStyles = this.activeFontFamilyOnSelection();
+ this.updateFontStyleDropdown(activeStyles.length === 1 ? activeStyles[0] : activeStyles.length ? "various" : "default");
+
+ //UPDATE FONT SIZE DROPDOWN
+ const activeSizes = this.activeFontSizeOnSelection();
+ this.updateFontSizeDropdown(activeSizes.length === 1 ? String(activeSizes[0]) + " pt" : activeSizes.length ? "various" : "default");
+
+ //UPDATE ALL OTHER BUTTONS
+ this.updateHighlightStateOfButtons();
}
-
- this.update_mark_doms();
}
- update_mark_doms() {
- this.reset_mark_doms();
- this._activeMarks.forEach((mark) => {
- if (this._marksToDoms.has(mark)) {
- let dom = this._marksToDoms.get(mark);
- if (dom) dom.style.color = "greenyellow";
- }
- });
+
+ updateHighlightStateOfButtons() {
+ Array.from(this._marksToDoms.values()).forEach(val => val.style.fill = "white");
+ this.activeMarksOnSelection().filter(mark => this._marksToDoms.has(mark)).forEach(mark =>
+ this._marksToDoms.get(mark)!.style.fill = "greenyellow");
// keeps brush tool highlighted if active when switching between textboxes
- if (!TooltipTextMenuManager.Instance._brushIsEmpty) {
- if (this._brushdom) {
- const newbrush = this.createBrush(true).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- }
+ if (!TooltipTextMenuManager.Instance._brushIsEmpty && this._brushdom) {
+ const newbrush = this.createBrushTool(true).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
}
-
}
//finds fontSize at start of selection
activeFontSizeOnSelection() {
//current selection
- let state = this.view.state;
- let activeSizes: number[] = [];
+ const state = this.view.state;
+ const activeSizes: number[] = [];
const pos = this.view.state.selection.$from;
const ref_node: ProsNode = this.reference_node(pos);
if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
@@ -1397,8 +932,8 @@ export class TooltipTextMenu {
//finds fontSize at start of selection
activeFontFamilyOnSelection() {
//current selection
- let state = this.view.state;
- let activeFamilies: string[] = [];
+ const state = this.view.state;
+ const activeFamilies: string[] = [];
const pos = this.view.state.selection.$from;
const ref_node: ProsNode = this.reference_node(pos);
if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
@@ -1407,24 +942,21 @@ export class TooltipTextMenu {
return activeFamilies;
}
//finds all active marks on selection in given group
- activeMarksOnSelection(markGroup: MarkType[]) {
+ activeMarksOnSelection() {
+ const markGroup = Array.from(this._marksToDoms.keys());
+ if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
//current selection
- let { empty, ranges, $to } = this.view.state.selection as TextSelection;
- let state = this.view.state;
- let dispatch = this.view.dispatch;
- let activeMarks: MarkType[];
+ const { empty, ranges, $to } = this.view.state.selection as TextSelection;
+ const state = this.view.state;
+ let activeMarks: MarkType[] = [];
if (!empty) {
activeMarks = markGroup.filter(mark => {
- let has = false;
+ const has = false;
for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+ return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
}
return false;
});
-
- const refnode = this.reference_node($to);
- this._activeMarks = refnode.marks;
}
else {
const pos = this.view.state.selection.$from;
@@ -1435,20 +967,14 @@ export class TooltipTextMenu {
else {
return [];
}
- this._activeMarks = ref_node.marks;
activeMarks = markGroup.filter(mark_type => {
if (mark_type === state.schema.marks.pFontSize) {
return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
}
- let mark = state.schema.mark(mark_type);
+ const mark = state.schema.mark(mark_type);
return ref_node.marks.includes(mark);
- return false;
});
}
- else {
- return [];
- }
-
}
return activeMarks;
}
@@ -1485,20 +1011,21 @@ export class TooltipTextMenu {
}
-class TooltipTextMenuManager {
+export class TooltipTextMenuManager {
private static _instance: TooltipTextMenuManager;
+ private _isPinned: boolean = false;
public pinnedX: number = 0;
public pinnedY: number = 0;
public unpinnedX: number = 0;
public unpinnedY: number = 0;
- private _isPinned: boolean = false;
public _brushMarks: Set<Mark> | undefined;
+ public _brushMap: Map<string, Set<Mark>> = new Map();
public _brushIsEmpty: boolean = true;
public color: String = "#000";
- public highlight: String = "transparent";
+ public highlighter: String = "transparent";
public activeMenu: TooltipTextMenu | undefined;
@@ -1509,11 +1036,7 @@ class TooltipTextMenuManager {
return TooltipTextMenuManager._instance;
}
- public get isPinned() {
- return this._isPinned;
- }
+ public get isPinned() { return this._isPinned; }
- public toggleIsPinned() {
- this._isPinned = !this._isPinned;
- }
+ public toggleIsPinned() { this._isPinned = !this._isPinned; }
}
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 532ba78eb..90fd299c1 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -1,40 +1,40 @@
export interface Listener<T> {
- (event: T): any;
+ (event: T): any;
}
export interface Disposable {
- dispose(): void;
+ dispose(): void;
}
/** passes through events as they happen. You will not get events from before you start listening */
export class TypedEvent<T> {
- private listeners: Listener<T>[] = [];
- private listenersOncer: Listener<T>[] = [];
-
- on = (listener: Listener<T>): Disposable => {
- this.listeners.push(listener);
- return {
- dispose: () => this.off(listener)
- };
- }
-
- once = (listener: Listener<T>): void => {
- this.listenersOncer.push(listener);
- }
-
- off = (listener: Listener<T>) => {
- var callbackIndex = this.listeners.indexOf(listener);
- if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
- }
-
- emit = (event: T) => {
- /** Update any general listeners */
- this.listeners.forEach((listener) => listener(event));
-
- /** Clear the `once` queue */
- this.listenersOncer.forEach((listener) => listener(event));
- this.listenersOncer = [];
- }
-
- pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
+ private listeners: Listener<T>[] = [];
+ private listenersOncer: Listener<T>[] = [];
+
+ on = (listener: Listener<T>): Disposable => {
+ this.listeners.push(listener);
+ return {
+ dispose: () => this.off(listener)
+ };
+ }
+
+ once = (listener: Listener<T>): void => {
+ this.listenersOncer.push(listener);
+ }
+
+ off = (listener: Listener<T>) => {
+ const callbackIndex = this.listeners.indexOf(listener);
+ if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
+ }
+
+ emit = (event: T) => {
+ /** Update any general listeners */
+ this.listeners.forEach((listener) => listener(event));
+
+ /** Clear the `once` queue */
+ this.listenersOncer.forEach((listener) => listener(event));
+ this.listenersOncer = [];
+ }
+
+ pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
} \ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 472afac1d..314b52bf3 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -3,7 +3,7 @@ import 'source-map-support/register';
import { Without } from "../../Utils";
function getBatchName(target: any, key: string | symbol): string {
- let keyName = key.toString();
+ const keyName = key.toString();
if (target && target.constructor && target.constructor.name) {
return `${target.constructor.name}.${keyName}`;
}
@@ -23,7 +23,7 @@ function propertyDecorator(target: any, key: string | symbol) {
writable: true,
configurable: true,
value: function (...args: any[]) {
- let batch = UndoManager.StartBatch(getBatchName(target, key));
+ const batch = UndoManager.StartBatch(getBatchName(target, key));
try {
return value.apply(this, args);
} finally {
@@ -40,7 +40,7 @@ export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
if (!key) {
return function () {
- let batch = UndoManager.StartBatch("");
+ const batch = UndoManager.StartBatch("");
try {
return target.apply(undefined, arguments);
} finally {
@@ -55,7 +55,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
const oldFunction = descriptor.value;
descriptor.value = function (...args: any[]) {
- let batch = UndoManager.StartBatch(getBatchName(target, key));
+ const batch = UndoManager.StartBatch(getBatchName(target, key));
try {
return oldFunction.apply(this, args);
} finally {
@@ -98,7 +98,7 @@ export namespace UndoManager {
GetOpenBatches().forEach(batch => console.log(batch.batchName));
}
- let openBatches: Batch[] = [];
+ const openBatches: Batch[] = [];
export function GetOpenBatches(): Without<Batch, 'end'>[] {
return openBatches;
}
@@ -146,7 +146,7 @@ export namespace UndoManager {
//TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
- let batch = StartBatch(batchName);
+ const batch = StartBatch(batchName);
try {
return runInAction(fn);
} finally {
@@ -159,7 +159,7 @@ export namespace UndoManager {
return;
}
- let commands = undoStack.pop();
+ const commands = undoStack.pop();
if (!commands) {
return;
}
@@ -178,7 +178,7 @@ export namespace UndoManager {
return;
}
- let commands = redoStack.pop();
+ const commands = redoStack.pop();
if (!commands) {
return;
}