aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx12
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts393
-rw-r--r--src/client/util/DropConverter.ts4
-rw-r--r--src/client/util/LinkManager.ts6
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts2
-rw-r--r--src/client/util/RichTextRules.ts161
-rw-r--r--src/client/util/RichTextSchema.tsx81
-rw-r--r--src/client/util/TooltipTextMenu.tsx414
-rw-r--r--src/client/views/CollectionLinearView.tsx2
-rw-r--r--src/client/views/DocComponent.tsx2
-rw-r--r--src/client/views/DocumentButtonBar.tsx37
-rw-r--r--src/client/views/DocumentDecorations.tsx6
-rw-r--r--src/client/views/EditableView.tsx2
-rw-r--r--src/client/views/MainView.tsx4
-rw-r--r--src/client/views/MetadataEntryMenu.tsx7
-rw-r--r--src/client/views/OverlayView.tsx2
-rw-r--r--src/client/views/PreviewCursor.tsx98
-rw-r--r--src/client/views/ScriptBox.tsx10
-rw-r--r--src/client/views/TemplateMenu.scss8
-rw-r--r--src/client/views/TemplateMenu.tsx52
-rw-r--r--src/client/views/collections/CollectionDockingView.scss2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx20
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx14
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx21
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx33
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx24
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx8
-rw-r--r--src/client/views/collections/CollectionSubView.tsx31
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx30
-rw-r--r--src/client/views/collections/CollectionView.tsx10
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx6
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss25
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx54
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx34
-rw-r--r--src/client/views/linking/LinkMenu.scss85
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx28
-rw-r--r--src/client/views/linking/LinkMenuItem.scss87
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx24
-rw-r--r--src/client/views/nodes/ButtonBox.tsx9
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx1
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx7
-rw-r--r--src/client/views/nodes/DocuLinkBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx40
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx83
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx19
-rw-r--r--src/client/views/nodes/ImageBox.tsx10
-rw-r--r--src/client/views/pdf/PDFViewer.tsx22
-rw-r--r--src/client/views/search/FilterBox.tsx9
-rw-r--r--src/client/views/search/SearchBox.tsx21
-rw-r--r--src/client/views/search/SearchItem.scss1
-rw-r--r--src/client/views/search/SearchItem.tsx48
-rw-r--r--src/new_fields/Doc.ts3
-rw-r--r--src/server/ActionUtilities.ts29
-rw-r--r--src/server/ApiManagers/SearchManager.ts2
-rw-r--r--src/server/ApiManagers/UtilManager.ts2
-rw-r--r--src/server/ChildProcessUtilities/ProcessFactory.ts67
-rw-r--r--src/server/ChildProcessUtilities/daemon/session.ts190
-rw-r--r--src/server/Websocket/Websocket.ts1
-rw-r--r--src/server/index.ts29
-rw-r--r--src/server/persistence_daemon.ts79
-rw-r--r--src/server/updateSearch.ts13
-rw-r--r--src/typings/index.d.ts1
66 files changed, 1176 insertions, 1362 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5d5bdfcbd..7df08c7e6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -112,6 +112,7 @@ export interface DocumentOptions {
dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
strokeWidth?: number;
color?: string;
+ limitHeight?:number; // maximum height for newly created (eg, from pasting) text documents
// [key: string]: Opt<Field>;
}
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 854135648..8fee53fb9 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -46,8 +46,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
@action
dropX = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+ if (de.complete.docDragData) {
+ let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
if (h) {
this.HistoOp.X = h.HistoOp.X;
}
@@ -57,8 +57,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
}
@action
dropY = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+ if (de.complete.docDragData) {
+ let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
if (h) {
this.HistoOp.Y = h.HistoOp.X;
}
@@ -78,10 +78,10 @@ export class HistogramBox extends React.Component<FieldViewProps> {
componentDidMount() {
if (this._dropXRef.current) {
- this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
+ this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, this.dropX.bind(this));
}
if (this._dropYRef.current) {
- this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } });
+ this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, this.dropY.bind(this));
}
reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d491cd1b1..1cb8b55d3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -176,7 +176,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,6 +194,8 @@ 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);
}
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index b681387d1..326262895 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,5 +1,4 @@
-import { action, runInAction } from "mobx";
-import { Doc, Field } from "../../new_fields/Doc";
+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";
@@ -22,7 +21,7 @@ export function SetupDrag(
docFunc: () => Doc | Promise<Doc>,
moveFunc?: DragManager.MoveFunction,
dropAction?: dropActionType,
- options?: any,
+ treeViewId?: string,
dontHideOnDrop?: boolean,
dragStarted?: () => void
) {
@@ -36,7 +35,7 @@ export function SetupDrag(
const dragData = new DragManager.DocumentDragData([doc]);
dragData.dropAction = dropAction;
dragData.moveDocument = moveFunc;
- dragData.options = options;
+ dragData.treeViewId = treeViewId;
dragData.dontHideOnDrop = dontHideOnDrop;
DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
dragStarted && dragStarted();
@@ -65,62 +64,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) {
- const draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
- if (draggeddoc) {
- const moddrag = await Cast(draggeddoc.annotationOn, Doc);
- const dragdocs = moddrag ? [moddrag] : [draggeddoc];
- const 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) {
- const srcTarg = sourceDoc.proto;
- let draggedDocs: Doc[] = [];
-
- if (srcTarg) {
- const linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg);
- if (linkDocs) {
- draggedDocs = linkDocs.map(link => {
- const opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
- if (opp) return opp;
- }) as Doc[];
- }
- }
- if (draggedDocs.length) {
- const moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
- const doc = await Cast(draggedDoc.annotationOn, Doc);
- if (doc) moddrag.push(doc);
- }
- const dragdocs = moddrag.length ? moddrag : draggedDocs;
- const 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) {
@@ -128,79 +74,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;
@@ -209,6 +121,9 @@ export namespace DragManager {
}
draggedDocuments: Doc[];
droppedDocuments: Doc[];
+ dragDivName?: string;
+ treeViewId?: string;
+ dontHideOnDrop?: boolean;
offset: number[];
dropAction: dropActionType;
userDropAction: dropActionType;
@@ -216,16 +131,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;
@@ -235,98 +166,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) {
- const dragData = new DragManager.DocumentDragData([]);
- runInAction(() => StartDragFunctions.map(func => func()));
- StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
- (dropData: { [id: string]: any }) => {
- 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);
- 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[];
+
+ if (draggedDocs.length) {
+ const moddrag: Doc[] = [];
+ for (const draggedDoc of draggedDocs) {
+ const doc = await Cast(draggedDoc.annotationOn, Doc);
+ if (doc) moddrag.push(doc);
+ }
- runInAction(() => StartDragFunctions.map(func => func()));
- StartDrag(eles, dragData, downX, downY, options,
- (dropData: { [id: string]: any }) => {
- const droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => {
- const dvs = DocumentManager.Instance.getDocumentViews(d);
- if (dvs.length) {
- const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
- const inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView);
- if (inContext.length) {
- inContext.forEach(dv => droppedDocs.push(dv.props.Document));
+ 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;
- });
- }
-
- export function StartAnnotationDrag(eles: HTMLElement[], dragData: AnnotationDragData, downX: number, downY: number, options?: DragOptions) {
- StartDrag(eles, dragData, downX, downY, options);
- }
+ return droppedDocs;
+ }, [] as Doc[]));
- 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");
@@ -340,33 +276,29 @@ export namespace DragManager {
const xs: number[] = [];
const ys: number[] = [];
- const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments :
- dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
+ const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
const dragElements = eles.map(ele => {
- const w = ele.offsetWidth,
- h = ele.offsetHeight;
+ 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;
- const 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);
- const 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`;
@@ -384,31 +316,19 @@ export namespace DragManager {
Array.from(pdfView).map((v, i) => v.scrollTo({ top: tops[i] }));
}, 0);
}
- const 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")) {
- const 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;
@@ -417,9 +337,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,
@@ -433,56 +353,51 @@ export namespace DragManager {
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]})`)
);
};
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));
};
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) {
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
- // let parent = dragEle.parentElement;
- // if (parent) parent.removeChild(dragEle);
- const ret = [dragEle, dragEle.style.width, dragEle.style.height];
+ 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 => {
- const 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", {
@@ -490,8 +405,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 b2c720d5d..349d5d7e3 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -10,8 +10,8 @@ import { ScriptField } from "../../new_fields/ScriptField";
function makeTemplate(doc: Doc): boolean {
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 layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^"]*'}/)![0];
+ const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, "");
const docs = DocListCast(layoutDoc[fieldKey]);
let any = false;
docs.map(d => {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index fb6f27478..5f3667acc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -70,7 +70,7 @@ export class LinkManager {
}
// finds all links that contain the given anchor
- public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
+ 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));
@@ -244,7 +244,5 @@ export class LinkManager {
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/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index f1fa6f11d..3324d8abe 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -105,7 +105,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
return true;
});
- bind("Mod-s", TooltipTextMenu.insertStar);
+ bind("Mod-s", TooltipTextMenu.insertSummarizer);
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
const ref = state.selection;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 94bfc5ef2..364c85165 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -6,8 +6,10 @@ import { NumCast, Cast } from "../../new_fields/Types";
import { Doc } from "../../new_fields/Doc";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { TooltipTextMenuManager } from "../util/TooltipTextMenu";
-import { Docs } from "../documents/Documents";
+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: [
@@ -60,8 +62,9 @@ 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) => {
const size = Number(match[1]);
const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
@@ -72,108 +75,123 @@ export const inpRules = {
}
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 doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+ DocUtils.Publish(doc, value, returnFalse, returnFalse);
+ });
+ const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+ return state.tr.addMark(start, end, link);
+ }
+ return state.tr;
+ }),
+
+ // 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);
- let marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+ const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
if (marks) {
- let tr = state.tr.deleteRange(start, end);
+ const tr = state.tr.deleteRange(start, end);
return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
}
- let isValidColor = (strColor: string) => {
- var s = new Option().style;
+ const isValidColor = (strColor: string) => {
+ const s = new Option().style;
s.color = strColor;
- return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned
- }
+ return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+ };
if (isValidColor(color)) {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
}
return null;
}),
+ // stop using active style
new InputRule(
new RegExp(/%%$/),
(state, match, start, end) => {
- let tr = state.tr.deleteRange(start, end);
- let marks = state.tr.selection.$anchor.nodeBefore?.marks;
+ 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(/t$/),
- (state, match, start, end) => {
- if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
- 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: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
- new InputRule(
- new RegExp(/i$/),
+ new RegExp(/[ti!x]$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
+ 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: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ 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(/d$/),
+ new RegExp(/(%d|d)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
const pos = (state.doc.resolve(start) as any);
- let depth = pos.path.length / 3 - 1;
- for (; depth >= 0; depth--) {
- if (pos.node(depth).type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: 25 });
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ 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(/h$/),
+ new RegExp(/(%h|h)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
const pos = (state.doc.resolve(start) as any);
- let depth = pos.path.length / 3 - 1;
- for (; depth >= 0; depth--) {
- if (pos.node(depth).type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: -25 });
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ 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(/q$/),
+ new RegExp(/(%q|q)$/),
(state, match, start, end) => {
- if (state.selection.to === state.selection.from) return null;
+ if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
const pos = (state.doc.resolve(start) as any);
- let depth = pos.path.length / 3 - 1;
- for (; depth >= 0; depth--) {
- if (pos.node(depth).type === schema.nodes.paragraph) {
- const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, inset: 30 });
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ 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(/!$/),
- (state, match, start, end) => {
- if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
- 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: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
- new InputRule(
- new RegExp(/x$/),
- (state, match, start, end) => {
- if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
- 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: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
- }),
- new InputRule(
- new RegExp(/^\^\^\s$/),
+ new RegExp(/%\^$/),
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
@@ -187,8 +205,9 @@ export const inpRules = {
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) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
@@ -202,8 +221,9 @@ export const inpRules = {
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) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
@@ -218,7 +238,7 @@ export const inpRules = {
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
new InputRule(
- new RegExp(/##\s$/),
+ new RegExp(/%#$/),
(state, match, start, end) => {
const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" });
const node = (state.doc.resolve(start) as any).nodeAfter;
@@ -230,26 +250,25 @@ export const inpRules = {
return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
}),
new InputRule(
- new RegExp(/\(\(/),
+ new RegExp(/%\(/),
(state, match, start, end) => {
const node = (state.doc.resolve(start) as any).nodeAfter;
const sm = state.storedMarks || undefined;
- const mark = state.schema.marks.highlight.create();
+ const mark = state.schema.marks.summarizeInclusive.create();
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, start,
- schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
}),
new InputRule(
- new RegExp(/\)\)/),
+ new RegExp(/%\)/),
(state, match, start, end) => {
- const mark = state.schema.marks.highlight.create();
- return state.tr.removeStoredMark(mark);
+ return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create());
}),
new InputRule(
- new RegExp(/\^f\s$/),
+ new RegExp(/%f\$/),
(state, match, start, end) => {
const newNode = schema.nodes.footnote.create({});
const tr = state.tr;
@@ -258,9 +277,5 @@ export const inpRules = {
tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
}),
- // let newNode = schema.nodes.footnote.create({});
- // if (dispatch && state.selection.from === state.selection.to) {
- // return true;
- // }
]
};
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index fac8f4027..543f45731 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -109,7 +109,7 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
- star: {
+ summary: {
inline: true,
attrs: {
visibility: { default: false },
@@ -121,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`,
@@ -228,16 +219,20 @@ export const nodes: { [index: string]: NodeSpec } = {
mapStyle: { default: "decimal" },
setFontSize: { default: undefined },
setFontFamily: { default: "inherit" },
+ setFontColor: { default: "inherit" },
inheritedFontSize: { default: undefined },
- visibility: { default: true }
+ visibility: { default: true },
+ indent: { default: undefined }
},
toDOM(node: Node<any>) {
if (node.attrs.mapStyle === "bullet") return ['ul', 0];
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;
- 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 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;` }];
}
},
@@ -317,7 +312,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") };
@@ -400,7 +395,7 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
- highlight: {
+ summarizeInclusive: {
parseDOM: [
{
tag: "span",
@@ -409,7 +404,7 @@ export const marks: { [index: string]: MarkSpec } = {
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;
}
}
@@ -420,6 +415,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)'
}];
}
@@ -462,7 +482,6 @@ export const marks: { [index: string]: MarkSpec } = {
user_mark: {
attrs: {
userid: { default: "" },
- opened: { default: true },
modified: { default: "when?" }, // 5 second intervals since 1970
},
group: "inline",
@@ -472,16 +491,13 @@ export const marks: { [index: string]: MarkSpec } = {
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
const 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]];
+ 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: "" }
},
@@ -489,9 +505,7 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
toDOM(node: any) {
const 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]];
+ return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
}
},
@@ -642,7 +656,7 @@ export class DashDocCommentView {
}
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);
+ 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) => {
@@ -651,16 +665,16 @@ export class DashDocCommentView {
this._collapsed.onpointerup = (e: any) => {
const target = targetNode();
if (target) {
- let expand = target.hidden;
- let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+ 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) { }
+ 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();
@@ -913,7 +927,7 @@ export class FootnoteView {
ignoreMutation() { return true; }
}
-export class SummarizedView {
+export class SummaryView {
_collapsed: HTMLElement;
_view: any;
constructor(node: any, view: any, getPos: any) {
@@ -951,7 +965,8 @@ export class SummarizedView {
className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
updateSummarizedText(start?: any) {
- const 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;
const visited = new Set();
@@ -959,7 +974,7 @@ export class SummarizedView {
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;
}
@@ -984,7 +999,7 @@ const fromJson = schema.nodeFromJSON;
schema.nodeFromJSON = (json: any) => {
const node = fromJson(json);
- if (json.type === "star") {
+ if (json.type === schema.marks.summarize.name) {
node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
}
return node;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f29dbf2e4..483ab40a7 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,4 +1,3 @@
-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';
@@ -10,8 +9,6 @@ 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,13 +17,11 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
import { DocumentDecorations } from '../views/DocumentDecorations';
import { SelectionManager } from './SelectionManager';
import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-import { Keys } from "../views/search/FilterBox";
const { toggleMark, setBlockType } = require("prosemirror-commands");
const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
//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
@@ -48,10 +43,9 @@ export class TooltipTextMenu {
// editor button doms
private colorDom?: Node;
private colorDropdownDom?: Node;
- private highlightDom?: Node;
- private highlightDropdownDom?: Node;
+ private highighterDom?: Node;
+ private highlighterDropdownDom?: Node;
private linkEditor?: HTMLDivElement;
- private linkText?: HTMLDivElement;
private linkDrag?: HTMLImageElement;
private _linkDropdownDom?: Node;
private _brushdom?: Node;
@@ -94,7 +88,6 @@ export class TooltipTextMenu {
{ 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') }
];
// add menu items
@@ -123,20 +116,15 @@ export class TooltipTextMenu {
if (dom.contains(e.target as Node)) {
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"; }
- // }
}
});
-
});
- // 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;
@@ -166,7 +154,7 @@ export class TooltipTextMenu {
this.tooltip.appendChild(this._brushDropdownDom);
// star
- this.tooltip.appendChild(this.createStar().render(this.view).dom);
+ this.tooltip.appendChild(this.createSummarizer().render(this.view).dom);
// list types dropdown
this.updateListItemDropdown(":", this.listTypeBtnDom);
@@ -289,8 +277,6 @@ export class TooltipTextMenu {
// stop moving when mouse button is released:
document.onpointerup = null;
document.onpointermove = null;
- //self.highlightSearchTerms(self.state, ["hello"]);
- //FormattedTextBox.Instance.unhighlightSearchTerms();
}
}
@@ -302,11 +288,10 @@ export class TooltipTextMenu {
fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
});
- 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); }
+ 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);
}
@@ -321,11 +306,10 @@ export class TooltipTextMenu {
fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
});
- 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); }
+ 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);
}
@@ -333,94 +317,16 @@ export class TooltipTextMenu {
}
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(); };
- const 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) => {
- const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type.name === "link");
- if (link) {
- const href: string = link.attrs.href;
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const 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;
- const dragData = new DragManager.LinkDragData(this.editorProps.Document);
- dragData.dontClearTextBox = true;
- // hack to get source context -sy
- const docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document);
- e.stopPropagation();
- const ctrlKey = e.ctrlKey;
- DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
- {
- handlers: {
- dragComplete: action(() => {
- if (dragData.linkDocument) {
- const linkDoc = dragData.linkDocument;
- const proto = Doc.GetProto(linkDoc);
- if (proto && docView) {
- proto.sourceContext = docView.props.ContainingCollectionDoc;
- }
- const 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);
- }
-
- const node = this.view.state.selection.$from.nodeAfter;
- const 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();
- }
- };
+ this.linkEditor = document.createElement("div");
+ this.linkEditor.className = "ProseMirror-icon menuicon";
+ 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 = "Click to set link target";
+ this.linkDrag.id = "link-btn";
+ this.linkEditor.appendChild(this.linkDrag);
+ this.tooltip.appendChild(this.linkEditor);
}
async getTextLinkTargetTitle() {
@@ -485,9 +391,7 @@ export class TooltipTextMenu {
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
@@ -584,24 +488,6 @@ export class TooltipTextMenu {
}
}
- 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() {
const markType = schema.marks.link;
return new MenuItem({
@@ -655,22 +541,19 @@ export class TooltipTextMenu {
//Make a dropdown of all list types
const toAdd: MenuItem[] = [];
this.listTypeToIcon.forEach((icon, type) => {
- toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType));
});
//option to remove the list formatting
- toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType));
- listTypeBtn = (new Dropdown(toAdd, {
- label: label,
- css: "color:black; width: 40px;"
- }) as MenuItem).render(this.view).dom;
+ 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() {
+ createSummarizer() {
return new MenuItem({
title: "Summarize",
label: "Summarize",
@@ -678,31 +561,17 @@ export class TooltipTextMenu {
css: "color:white;",
class: "menuicon",
execEvent: "",
- run: (state, dispatch) => {
- TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
- }
-
+ run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
});
}
- public static insertStar(state: EditorState<any>, dispatch: any) {
+ public static insertSummarizer(state: EditorState<any>, dispatch: any) {
if (state.selection.empty) return false;
- const mark = state.schema.marks.highlight.create();
+ const mark = state.schema.marks.summarize.create();
const tr = state.tr;
tr.addMark(state.selection.from, state.selection.to, mark);
const content = tr.selection.content();
- const 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 static insertComment(state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- const mark = state.schema.marks.highlight.create();
- const tr = state.tr;
- tr.addMark(state.selection.from, state.selection.to, mark);
- const content = tr.selection.content();
- const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+ const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
return true;
}
@@ -722,7 +591,7 @@ export class TooltipTextMenu {
const color = document.createElement("div");
color.className = "buttonColor";
- color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString();
+ color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
const wrapper = document.createElement("div");
wrapper.id = "colorPicker";
@@ -730,17 +599,14 @@ export class TooltipTextMenu {
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;
- const highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color });
- dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark));
+ toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
}
createHighlightDropdown() {
@@ -773,22 +639,22 @@ export class TooltipTextMenu {
colors.forEach(color => {
const button = document.createElement("button");
- button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker";
+ 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
const highlightDom = self.createHighlightTool().render(self.view).dom;
const 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;
+ 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);
@@ -832,19 +698,18 @@ export class TooltipTextMenu {
wrapper.appendChild(color);
return wrapper;
},
- run: (state, dispatch) => {
- TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch);
- }
+ run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
});
}
- public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
+ public 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));
return false;
}
- dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
+ this.setMark(colorMark, state, dispatch);
+ toggleMark(colorMark.type, { color: color })(state, dispatch);
}
createColorDropdown() {
@@ -883,7 +748,7 @@ export class TooltipTextMenu {
button.onclick = e => {
TooltipTextMenuManager.Instance.color = color;
- TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+ self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
// update color menu
const colorDom = self.createColorTool().render(self.view).dom;
@@ -903,13 +768,10 @@ export class TooltipTextMenu {
return div;
},
enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
+ run(p1, p2, p3, event) { event.stopPropagation(); }
});
- const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
- return colorDropdown;
+ return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
}
createBrush(active: boolean = false) {
@@ -933,9 +795,7 @@ export class TooltipTextMenu {
self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
self._brushDropdownDom = newBrushDropdowndom;
},
- active: (state) => {
- return true;
- }
+ active: (state) => true
});
}
@@ -959,8 +819,7 @@ export class TooltipTextMenu {
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, []);
+ this.setMark(mark, this.view.state, this.view.dispatch);
});
}
}
@@ -995,9 +854,7 @@ export class TooltipTextMenu {
class: "button-setting-disabled",
css: "",
enable() { return false; },
- run(p1, p2, p3, event) {
- event.stopPropagation();
- }
+ run(p1, p2, p3, event) { event.stopPropagation(); }
});
const self = this;
@@ -1056,71 +913,26 @@ export class TooltipTextMenu {
});
const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
- const brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
- return brushDom;
+ 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[]) => {
- const { $cursor, ranges } = view.state.selection as TextSelection;
- const state = view.state;
- const 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++) {
- const { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, type);
- }
- for (const 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) {
- const 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))));
+ 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, dispatch);
}
- else toggleMark(markType)(view.state, view.dispatch, view);
}
}
changeToFontFamily = (mark: Mark, view: EditorView) => {
- const { $cursor, ranges } = view.state.selection as TextSelection;
- const state = view.state;
- const 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++) {
- const { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily);
- }
- for (const i of ranges) {
- if (has) {
- toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view);
- }
- }
- }
-
const fontName = mark.attrs.family;
if (fontName) { this.updateFontStyleDropdown(fontName); }
if (this.editorProps) {
@@ -1130,39 +942,10 @@ export class TooltipTextMenu {
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) {
- const 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 })];
+ this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
}
changeToFontSize = (mark: Mark, view: EditorView) => {
- const { $cursor, ranges } = view.state.selection as TextSelection;
- const state = view.state;
- const 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++) {
- const { $from, $to } = ranges[i];
- has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize);
- }
- for (const i of ranges) {
- if (has) {
- toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view);
- }
- }
- }
-
const size = mark.attrs.fontSize;
if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
if (this.editorProps) {
@@ -1172,18 +955,11 @@ export class TooltipTextMenu {
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) {
- const 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 })];
+ this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
}
//remove all node typeand apply the passed-in one to the selected text
- changeToNodeType = (nodeType: NodeType | undefined) => {
+ changeBulletType = (nodeType: NodeType | undefined) => {
//remove oldif (nodeType) { //add new
const view = this.view;
if (nodeType === schema.nodes.bullet_list) {
@@ -1211,46 +987,40 @@ export class TooltipTextMenu {
//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: "",
+ title: "Set Font Family",
label: label,
execEvent: "",
class: "dropdown-item",
css: css,
enable() { return true; },
- run() {
- changeFontFamily(mark, view);
- }
+ 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: "",
+ title: "Set Font Size",
label: label,
execEvent: "",
class: "dropdown-item",
css: css,
enable() { return true; },
- run() {
- changeFontSize(mark, view);
- }
+ 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) {
+ dropdownBulletBtn(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: "",
+ title: "Set Bullet Style",
label: label,
execEvent: "",
class: "dropdown-item",
css: css,
enable() { return true; },
- run() {
- changeToNodeInGroup(nodeType, view, groupNodes);
- }
+ run() { changeToNodeInGroup(nodeType, view, groupNodes); }
});
}
@@ -1325,14 +1095,7 @@ export class TooltipTextMenu {
getMarksInSelection(state: EditorState<any>) {
const found = new Set<Mark>();
const { from, to } = state.selection as TextSelection;
- state.doc.nodesBetween(from, to, (node) => {
- const marks = node.marks;
- if (marks) {
- marks.forEach(m => {
- found.add(m);
- });
- }
- });
+ state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
return found;
}
@@ -1362,12 +1125,6 @@ export class TooltipTextMenu {
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
const linkDropdown = await this.createLinkDropdown();
const newLinkDropdowndom = linkDropdown.render(this.view).dom;
@@ -1473,7 +1230,6 @@ export class TooltipTextMenu {
}
const mark = state.schema.mark(mark_type);
return ref_node.marks.includes(mark);
- return false;
});
}
else {
@@ -1518,19 +1274,19 @@ export class TooltipTextMenu {
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;
@@ -1541,11 +1297,7 @@ export 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/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 293a4feea..5ca861f71 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -39,7 +39,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 27ee9f122..c7ddee7ea 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -71,7 +71,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
// if the moved document is already in this overlay collection nothing needs to be done.
// otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection.
@action.bound
- moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+ moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
}
@action.bound
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 7f125dd34..58728ab7f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -3,13 +3,12 @@ import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadA
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, observable, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast } from "../../new_fields/Doc";
import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast, StrCast } from "../../new_fields/Types";
+import { NumCast, StrCast, Cast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
import { DragManager } from "../util/DragManager";
-import { LinkManager } from '../util/LinkManager';
import { UndoManager } from "../util/UndoManager";
import './DocumentButtonBar.scss';
import './collections/ParentDocumentSelector.scss';
@@ -107,27 +106,21 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
- const docView = this.props.views[0];
- const container = docView.props.ContainingCollectionDoc?.proto;
- const dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []);
const linkDrag = UndoManager.StartBatch("Drag Link");
- DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, {
- handlers: {
- dragComplete: () => {
- const tooltipmenu = FormattedTextBox.ToolTipTextMenu;
- const linkDoc = dragData.linkDocument;
- if (linkDoc && tooltipmenu) {
- const proto = Doc.GetProto(linkDoc);
- if (proto && docView) {
- proto.sourceContext = docView.props.ContainingCollectionDoc;
- }
- const text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.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
- }
+ DragManager.StartLinkDrag(this._linkButton.current, this.props.views[0].props.Document, e.pageX, e.pageY, {
+ dragComplete: dropEv => {
+ const linkDoc = dropEv.linkDragData?.linkDocument; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+ if (linkDoc && FormattedTextBox.ToolTipTextMenu) {
+ const proto = Doc.GetProto(linkDoc);
+ proto.sourceContext = this.props.views[0].props.ContainingCollectionDoc;
+
+ const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
+ const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab");
+ if (linkDoc.anchor2 instanceof Doc) {
+ proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
}
- linkDrag && linkDrag.end();
}
+ linkDrag?.end();
},
hideSource: false
});
@@ -200,7 +193,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
@computed
get linkButton() {
- const linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length;
+ const linkCount = DocListCast(this.props.views[0].props.Document.links).length;
return <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
<Flyout anchorPoint={anchorPoints.RIGHT_TOP}
content={<LinkMenu docView={this.props.views[0]} addDocTab={this.props.views[0].props.addDocTab} changeFlyout={emptyFunction} />}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dd3b740fb..fdaca87a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -85,14 +85,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith("#")) {
- let selectionTitleFieldKey = this._titleControlString.substring(1);
+ const selectionTitleFieldKey = this._titleControlString.substring(1);
selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-"));
selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d =>
Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle, true)
);
}
}
- }))
+ }));
@action titleEntered = (e: any) => {
const key = e.keyCode || e.which;
@@ -199,7 +199,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointermove", this.onTitleMove);
document.removeEventListener("pointerup", this.onTitleUp);
DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, {
- handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
+ dragComplete: action(e => this._hidden = this.Interacting = false),
hideSource: true
});
e.stopPropagation();
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d0cecf03d..54def38b5 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -120,7 +120,7 @@ export class EditableView extends React.Component<EditableProps> {
@action
setIsFocused = (value: boolean) => {
- let wasFocused = this._editing;
+ const wasFocused = this._editing;
this._editing = value;
return wasFocused !== this._editing;
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 01cd7957c..db2a3c298 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -286,7 +286,7 @@ export class MainView extends React.Component {
ContainingCollectionDoc={undefined}
zoomToScale={emptyFunction}
getScale={returnOne}
- />
+ />;
}
@computed get dockingContent() {
TraceMobx();
@@ -460,7 +460,7 @@ export class MainView extends React.Component {
addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
- moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
+ moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
buttonBarXf = () => {
if (!this._docBtnRef.current) return Transform.Identity();
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 8859c14cb..243cdb8f6 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -6,7 +6,7 @@ import { KeyValueBox } from './nodes/KeyValueBox';
import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc';
import * as Autosuggest from 'react-autosuggest';
import { undoBatch } from '../util/UndoManager';
-import { emptyFunction } from '../../Utils';
+import { emptyFunction, emptyPath } from '../../Utils';
export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
export interface MetadataEntryProps {
@@ -194,6 +194,7 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
);
}
+ _ref = React.createRef<HTMLInputElement>();
render() {
return (
<div className="metadataEntry-outerDiv">
@@ -201,14 +202,14 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
Key:
<Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
getSuggestionValue={this.getSuggestionValue}
- suggestions={[]}
+ suggestions={emptyPath}
alwaysRenderSuggestions={false}
renderSuggestion={this.renderSuggestion}
onSuggestionsFetchRequested={emptyFunction}
onSuggestionsClearRequested={emptyFunction}
ref={this.autosuggestRef} />
Value:
- <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+ <input className="metadataEntry-input" ref={this._ref} value={this._currentValue} onClick={e => this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
{this.considerChildOptions}
</div>
<div className="metadataEntry-keys" >
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index cd330d492..350a75d29 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -9,8 +9,6 @@ import { DocListCast, Doc } from "../../new_fields/Doc";
import { Id } from "../../new_fields/FieldSymbols";
import { DocumentView } from "./nodes/DocumentView";
import { Transform } from "../util/Transform";
-import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "./nodes/DocumentContentsView";
import { NumCast } from "../../new_fields/Types";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index fd0287b6c..208bc2b70 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,10 +1,9 @@
-import { action, observable, runInAction, trace } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import "./PreviewCursor.scss";
import { Docs } from '../documents/Documents';
-// import { Transform } from 'prosemirror-transform';
import { Doc } from '../../new_fields/Doc';
import { Transform } from "../util/Transform";
import { TraceMobx } from '../../new_fields/util';
@@ -24,64 +23,53 @@ export class PreviewCursor extends React.Component<{}> {
}
paste = (e: ClipboardEvent) => {
- if (PreviewCursor.Visible) {
- if (e.clipboardData) {
- const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
- runInAction(() => { PreviewCursor.Visible = false; });
+ if (PreviewCursor.Visible && e.clipboardData) {
+ const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
+ PreviewCursor.Visible = false;
-
- if (e.clipboardData.getData("text/plain") !== "") {
-
- // tests for youtube and makes video document
- if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
- const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
- PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
- title: url, width: 400, height: 315,
- nativeWidth: 600, nativeHeight: 472.5,
- x: newPoint[0], y: newPoint[1]
- }));
- return;
- }
-
- // tests for URL and makes web document
- const re: any = /^https?:\/\//g;
- if (re.test(e.clipboardData.getData("text/plain"))) {
- const url = e.clipboardData.getData("text/plain");
- PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
- title: url, width: 300, height: 300,
- // nativeWidth: 300, nativeHeight: 472.5,
- x: newPoint[0], y: newPoint[1]
- }));
- return;
- }
-
- // creates text document
- const newBox = Docs.Create.TextDocument({
- width: 200, height: 100,
- x: newPoint[0],
- y: newPoint[1],
- title: "-pasted text-"
- });
-
- newBox.proto!.autoHeight = true;
- PreviewCursor._addLiveTextDoc(newBox);
- return;
+ if (e.clipboardData.getData("text/plain") !== "") {
+ // tests for youtube and makes video document
+ if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
+ const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
+ return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
+ title: url, width: 400, height: 315,
+ nativeWidth: 600, nativeHeight: 472.5,
+ x: newPoint[0], y: newPoint[1]
+ }));
}
- //pasting in images
- if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
- const re: any = /<img src="(.*?)"/g;
- const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
- const img: Doc = Docs.Create.ImageDocument(
- arr[1], {
- width: 300, title: arr[1],
- x: newPoint[0],
- y: newPoint[1],
- });
- PreviewCursor._addDocument(img);
- return;
+ // tests for URL and makes web document
+ const re: any = /^https?:\/\//g;
+ if (re.test(e.clipboardData.getData("text/plain"))) {
+ const url = e.clipboardData.getData("text/plain");
+ return PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
+ title: url, width: 500, height: 300,
+ // nativeWidth: 300, nativeHeight: 472.5,
+ x: newPoint[0], y: newPoint[1]
+ }));
}
+ // creates text document
+ return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({
+ width: 500,
+ limitHeight: 400,
+ autoHeight: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ title: "-pasted text-"
+ }));
+ }
+ //pasting in images
+ if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
+ const re: any = /<img src="(.*?)"/g;
+ const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
+
+ return PreviewCursor._addDocument(Docs.Create.ImageDocument(
+ arr[1], {
+ width: 300, title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ }));
}
}
}
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 65d96f364..ded2329b4 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -113,7 +113,15 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
return;
}
- params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY);
+ const div = document.createElement("div");
+ div.style.width = "90";
+ div.style.height = "20";
+ div.style.background = "gray";
+ div.style.position = "absolute";
+ div.style.display = "inline-block";
+ div.style.transform = `translate(${clientX}px, ${clientY}px)`;
+ div.innerHTML = "button";
+ params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY);
doc[fieldKey] = new ScriptField(script);
overlayDisposer();
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 186d3ab0d..69bebe0e9 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -30,15 +30,15 @@
}
.template-list {
- position: absolute;
- top: 25px;
- left: 0px;
- width: max-content;
font-family: $sans-serif;
font-size: 12px;
background-color: $light-color-secondary;
padding: 2px 12px;
list-style: none;
+ position: relative;
+ display: inline-block;
+ height: 100%;
+ width: 100%;
.templateToggle, .chromeToggle {
text-align: left;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e6116ca09..598bcd56d 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -63,31 +63,20 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
SelectionManager.DeselectAll();
const topDocView = this.props.docs[0];
const topDoc = topDocView.props.Document;
- const xf = topDocView.props.ScreenToLocalTransform();
- const ex = e.target.clientLeft;
- const ey = e.target.clientTop;
+ const ex = e.target.getBoundingClientRect().left;
+ const ey = e.target.getBoundingClientRect().top;
+ const de = new DragManager.DocumentDragData([topDoc]);
+ de.dragDivName = topDocView.props.dragDivName;
+ de.moveDocument = topDocView.props.moveDocument;
undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
- if (e.target.checked) {
- setTimeout(() => {
- const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
- if (newDocView) {
- const de = new DragManager.DocumentDragData([topDoc]);
- de.moveDocument = topDocView.props.moveDocument;
- const xf = newDocView.ContentDiv!.getBoundingClientRect();
- DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
- offsetX: (ex - xf.left), offsetY: (ey - xf.top),
- handlers: { dragComplete: () => { }, },
- hideSource: false
- });
- }
- }, 10);
- } else if (topDocView.props.ContainingCollectionView) {
- const collView = topDocView.props.ContainingCollectionView;
- const [sx, sy] = xf.inverse().transformPoint(0, 0);
- const [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
- topDoc.x = x;
- topDoc.y = y;
- }
+ setTimeout(() => {
+ const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+ if (newDocView) {
+ const contentDiv = newDocView.ContentDiv!;
+ const xf = contentDiv.getBoundingClientRect();
+ DragManager.StartDocumentDrag([contentDiv], de, ex, ey, { offsetX: ex - xf.left, offsetY: ey - xf.top, hideSource: true });
+ }
+ }, 0);
}
@undoBatch
@@ -155,9 +144,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
offsetX: dragData.offset[0],
offsetY: dragData.offset[1],
- handlers: {
- dragComplete: action(emptyFunction),
- },
hideSource: false
});
}
@@ -173,13 +159,15 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
return (
- <div className="templating-menu" onPointerDown={this.onAliasButtonDown}>
- <div title="Drag:(create alias). Tap:(modify layout)." className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
- <ul className="template-list" ref={this._dragRef} style={{ display: this._hidden ? "none" : "block" }}>
+ <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+ content={<ul className="template-list" ref={this._dragRef} style={{ display: this._hidden ? "none" : "block" }}>
{templateMenu}
{<button onClick={this.clearTemplates}>Restore Defaults</button>}
- </ul>
- </div>
+ </ul>}>
+ <div className="templating-menu" onPointerDown={this.onAliasButtonDown}>
+ <div title="Drag:(create alias). Tap:(modify layout)." className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+ </div>
+ </Flyout>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index bcdc9c97e..f518ef8fb 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -25,7 +25,7 @@
position: absolute;
top: 0;
left: 0;
- overflow: hidden;
+ // overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu)
.collectionDockingView-dragAsDocument {
touch-action: none;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ffcb3e9ce..08b9fd216 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -20,7 +20,7 @@ import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, U
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from "../../util/UndoManager";
@@ -346,7 +346,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const docid = (e.target as any).DashDocId;
const tab = (e.target as any).parentElement as HTMLElement;
DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
- (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+ (sourceDoc instanceof Doc) && DragManager.StartLinkTargetsDrag(tab, x, y, sourceDoc)));
}
if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
@@ -419,15 +419,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
};
ReactDOM.render(<span title="Drag as document"
className="collectionDockingView-dragAsDocument"
- onPointerDown={
- e => {
- e.preventDefault();
- e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false
- });
- }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ onPointerDown={e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+ }}>
+ <FontAwesomeIcon icon="file" size="lg" />
+ </span>, dragSpan);
ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan);
tab.reactComponents = [dragSpan, gearSpan, upDiv];
tab.element.append(dragSpan);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index d697e721b..80752303c 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -56,7 +56,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
@@ -74,12 +74,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createAliasSelected = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
(this.props.parent.Document.dropConverter instanceof ScriptField) &&
- this.props.parent.Document.dropConverter.script.run({ dragData: de.data });
+ this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
const key = StrCast(this.props.parent.props.Document.sectionFilter);
const castedValue = this.getValue(this._heading);
- de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
this.props.parent.drop(e, de);
e.stopPropagation();
}
@@ -171,10 +171,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
const script = `return doc.${key} === ${value}`;
const compiled = CompileScript(script, { params: { doc: Doc.name } });
if (compiled.compiled) {
- const scriptField = new ScriptField(compiled);
- alias.viewSpecScript = scriptField;
- const dragData = new DragManager.DocumentDragData([alias]);
- DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+ alias.viewSpecScript = new ScriptField(compiled);
+ DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
}
e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 171dc4606..79a34bc00 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -37,7 +37,7 @@ export interface CellProps {
renderDepth: number;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
isFocused: boolean;
changeFocusedCellByIndex: (row: number, col: number) => void;
setIsEditing: (isEditing: boolean) => void;
@@ -105,13 +105,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
private drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
const fieldKey = this.props.rowProps.column.id as string;
- if (de.data.draggedDocuments.length === 1) {
- this._document[fieldKey] = de.data.draggedDocuments[0];
+ if (de.complete.docDragData.draggedDocuments.length === 1) {
+ this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0];
}
else {
- const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.data.draggedDocuments, {});
+ const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
this._document[fieldKey] = coll;
}
e.stopPropagation();
@@ -121,7 +121,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
private dropRef = (ele: HTMLElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@@ -167,11 +167,10 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
const onItemDown = (e: React.PointerEvent) => {
- if (fieldIsDoc) {
- SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
- this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
- this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
- }
+ fieldIsDoc && SetupDrag(this._focusRef,
+ () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
+ this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+ this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
};
const onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 90320df82..153bbd410 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -56,7 +56,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
createColDropTarget = (ele: HTMLDivElement) => {
this._colDropDisposer && this._colDropDisposer();
if (ele) {
- this._colDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.colDrop.bind(this) } });
+ this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
}
}
@@ -66,8 +66,8 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
const rect = this._header!.current!.getBoundingClientRect();
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
const before = x[0] < bounds[0];
- if (de.data instanceof DragManager.ColumnDragData) {
- this.props.reorderColumns(de.data.colKey, this.props.columnValue, before, this.props.allColumns);
+ if (de.complete.columnDragData) {
+ this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns);
return true;
}
return false;
@@ -165,7 +165,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
createRowDropTarget = (ele: HTMLDivElement) => {
this._rowDropDisposer && this._rowDropDisposer();
if (ele) {
- this._rowDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+ this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
}
}
@@ -178,16 +178,17 @@ export class MovableRow extends React.Component<MovableRowProps> {
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
const before = x[1] < bounds[1];
- if (de.data instanceof DragManager.DocumentDragData) {
+ const docDragData = de.complete.docDragData;
+ if (docDragData) {
e.stopPropagation();
- if (de.data.draggedDocuments[0] === rowDoc) return true;
+ if (docDragData.draggedDocuments[0] === rowDoc) return true;
const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
- const movedDocs = de.data.draggedDocuments;
- return (de.data.dropAction || de.data.userDropAction) ?
- de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
- : (de.data.moveDocument) ?
- movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, rowDoc, addDocument) || added, false)
- : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+ const movedDocs = docDragData.draggedDocuments;
+ return (docDragData.dropAction || docDragData.userDropAction) ?
+ docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+ : (docDragData.moveDocument) ?
+ movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+ : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
}
return false;
}
@@ -199,12 +200,12 @@ export class MovableRow extends React.Component<MovableRowProps> {
@undoBatch
@action
- move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
- const targetView = DocumentManager.Instance.getDocumentView(target);
+ move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => {
+ const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
if (targetView && targetView.props.ContainingCollectionDoc) {
- return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
}
- return doc !== target && this.props.removeDoc(doc) && addDoc(doc);
+ return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc);
}
render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 8169e9e2b..bb706e528 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -224,7 +224,7 @@ export interface SchemaTableProps {
renderDepth: number;
deleteDocument: (document: Doc) => boolean;
addDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: (outsideReaction: boolean) => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ff3417b77..6a23920f3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -147,7 +147,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@action
- moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
+ moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
return this.props.removeDocument(doc) && addDocument(doc);
}
createRef = (ele: HTMLDivElement | null) => {
@@ -243,7 +243,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
const where = [de.x, de.y];
let targInd = -1;
let plusOne = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
this._docXfs.map((cd, i) => {
const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
@@ -252,16 +252,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
plusOne = (where[1] > (pos[1] + pos1[1]) / 2 ? 1 : 0) ? true : false;
}
});
- }
- if (super.drop(e, de)) {
- const newDoc = de.data.droppedDocuments[0];
- const docs = this.childDocList;
- if (docs) {
- if (targInd === -1) targInd = docs.length;
- else targInd = docs.indexOf(this.filteredChildren[targInd]);
- const srcInd = docs.indexOf(newDoc);
- docs.splice(srcInd, 1);
- docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc);
+ if (super.drop(e, de)) {
+ const newDoc = de.complete.docDragData.droppedDocuments[0];
+ const docs = this.childDocList;
+ if (docs) {
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(this.filteredChildren[targInd]);
+ const srcInd = docs.indexOf(newDoc);
+ docs.splice(srcInd, 1);
+ docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc);
+ }
}
}
return false;
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index ca3b93bf8..39b4e4e1d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -51,21 +51,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
this._dropRef = ele;
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.columnDrop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
}
}
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
this._createAliasSelected = false;
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
const key = StrCast(this.props.parent.props.Document.sectionFilter);
const castedValue = this.getValue(this._heading);
if (castedValue) {
- de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
}
else {
- de.data.droppedDocuments.forEach(d => d[key] = undefined);
+ de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
}
this.props.parent.drop(e, de);
e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9fa244546..5c7794cc0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -27,7 +27,7 @@ import { Networking } from "../../Network";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc) => boolean;
removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
PanelWidth: () => number;
PanelHeight: () => number;
VisibleHeight?: () => number;
@@ -51,7 +51,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -132,32 +132,33 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
+ const docDragData = de.complete.docDragData;
(this.props.Document.dropConverter instanceof ScriptField) &&
- this.props.Document.dropConverter.script.run({ dragData: de.data });
- if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
- if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
+ this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this
+ if (docDragData && !docDragData.applyAsTemplate) {
+ if (de.altKey && docDragData.draggedDocuments.length) {
this.childDocs.map(doc =>
- Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, "layoutFromParent"));
+ Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent"));
e.stopPropagation();
return true;
}
let added = false;
- if (de.data.dropAction || de.data.userDropAction) {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
- } else if (de.data.moveDocument) {
- const movedDocs = de.data.draggedDocuments;
+ if (docDragData.dropAction || docDragData.userDropAction) {
+ added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ } else if (docDragData.moveDocument) {
+ const movedDocs = docDragData.draggedDocuments;
added = movedDocs.reduce((added: boolean, d, i) =>
- de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) :
- de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
+ docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
+ docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
} else {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+ added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
}
e.stopPropagation();
return added;
}
- else if (de.data instanceof DragManager.AnnotationDragData) {
+ else if (de.complete.annoDragData) {
e.stopPropagation();
- return this.props.addDocument(de.data.dropDocument);
+ return this.props.addDocument(de.complete.annoDragData.dropDocument);
}
return false;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a0ddc8884..0a22a897c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -123,7 +123,7 @@ class TreeView extends React.Component<TreeViewProps> {
@undoBatch delete = () => this.props.deleteDoc(this.props.document);
@undoBatch openRight = () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight", this.props.libraryPath);
@undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
- @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => {
+ @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
@undoBatch @action remove = (document: Document, key: string) => {
@@ -132,7 +132,7 @@ class TreeView extends React.Component<TreeViewProps> {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer && this._treedropDisposer();
- ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }));
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
}
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
@@ -220,25 +220,25 @@ class TreeView extends React.Component<TreeViewProps> {
const rect = this._header!.current!.getBoundingClientRect();
const before = pt[1] < rect.top + rect.height / 2;
const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
- if (de.data instanceof DragManager.LinkDragData) {
- const sourceDoc = de.data.linkSourceDocument;
+ if (de.complete.linkDragData) {
+ const sourceDoc = de.complete.linkDragData.linkSourceDocument;
const destDoc = this.props.document;
DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc });
e.stopPropagation();
}
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.complete.docDragData) {
e.stopPropagation();
- if (de.data.draggedDocuments[0] === this.props.document) return true;
+ if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before);
if (inside) {
addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
}
- const movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
- return ((de.data.dropAction && (de.data.options !== this.props.treeViewId)) || de.data.userDropAction) ?
- de.data.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
- : de.data.moveDocument ?
- movedDocs.reduce((added, d) => de.data.moveDocument(d, undefined, addDoc) || added, false)
- : de.data.droppedDocuments.reduce((added, d) => addDoc(d), false);
+ const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
+ return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ?
+ de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
+ : de.complete.docDragData.moveDocument ?
+ movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
+ : de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d), false);
}
return false;
}
@@ -401,7 +401,7 @@ class TreeView extends React.Component<TreeViewProps> {
style={{
color: this.props.document.isMinimized ? "red" : "black",
background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
- fontWeight: this.props.document.search_string ? "bold" : undefined,
+ fontWeight: this.props.document.searchMatch ? "bold" : undefined,
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
@@ -571,7 +571,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer && this.treedropDisposer();
if (this._mainEle = ele) {
- this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@@ -624,7 +624,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
render() {
const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
- const moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+ const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
<div className="collectionTreeView-dropTarget" id="body"
style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 411040332..9bd07966c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -68,7 +68,7 @@ export namespace CollectionViewType {
export interface CollectionRenderProps {
addDocument: (document: Doc) => boolean;
removeDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
}
@@ -150,7 +150,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
// otherwise, the document being moved must be able to be removed from its container before
// moving it into the target.
@action.bound
- moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+ moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
return true;
}
@@ -240,9 +240,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
const mainPath = path.extname(images[this._curLightboxImg]);
const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]);
const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]);
- let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
- let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
- let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
+ const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
+ const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
+ const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
mainSrc={main}
nextSrc={next}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 4161e5d6e..a870b6043 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -288,15 +288,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer && this.dropDisposer();
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments));
+ if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
e.stopPropagation();
}
return true;
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index aa25a900c..20e7cb994 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -1,14 +1,25 @@
-.PDS-flyout {
- position: absolute;
+.parentDocumentSelector-linkFlyout {
+ div {
+ overflow: visible !important;
+ }
+ .metadataEntry-outerDiv {
+ overflow: hidden !important;
+ pointer-events: all;
+ }
+}
+.parentDocumentSelector-flyout {
+ position: relative;
z-index: 9999;
background-color: #eeeeee;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
- min-width: 150px;
color: black;
- top: 12px;
padding: 10px;
border-radius: 3px;
+ display: inline-block;
+ height: 100%;
+ width: 100%;
+ border-radius: 3px;
hr {
height: 1px;
@@ -21,7 +32,11 @@
}
}
.parentDocumentSelector-button {
- pointer-events: all;
+ pointer-events: all;
+ position: relative;
+ display: inline-block;
+ padding-left: 5px;
+ padding-right: 5px;
}
.parentDocumentSelector-metadata {
pointer-events: auto;
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 75ee8de32..5208f65c4 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -80,35 +80,20 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
@observer
export class ParentDocSelector extends React.Component<SelectorProps> {
- @observable hover = false;
-
- @action
- onMouseLeave = () => {
- this.hover = false;
- }
-
- @action
- onMouseEnter = () => {
- this.hover = true;
- }
-
render() {
- let flyout;
- if (this.hover) {
- flyout = (
- <div className="PDS-flyout" title=" ">
- <SelectorContextMenu {...this.props} />
- </div>
- );
- }
- return (
- <span className="parentDocumentSelector-button" style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }}
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}>
- <p>^</p>
- {flyout}
- </span>
+ let flyout = (
+ <div className="parentDocumentSelector-flyout" style={{}} title=" ">
+ <SelectorContextMenu {...this.props} />
+ </div>
);
+ return <div title="Drag(create link) Tap(view links)" onPointerDown={e => e.stopPropagation()} className="parentDocumentSelector-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+ content={flyout}>
+ <span className="parentDocumentSelector-button" >
+ <p>^</p>
+ </span>
+ </Flyout>
+ </div>;
}
}
@@ -117,13 +102,9 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any
@observable hover = false;
@action
- onMouseLeave = () => {
- this.hover = false;
- }
-
- @action
- onMouseEnter = () => {
- this.hover = true;
+ onPointerDown = (e: React.PointerEvent) => {
+ this.hover = !this.hover;
+ e.stopPropagation();
}
render() {
@@ -131,15 +112,14 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any
if (this.hover) {
const view = DocumentManager.Instance.getDocumentView(this.props.Document);
flyout = !view ? (null) : (
- <div className="PDS-flyout" title=" " onMouseLeave={this.onMouseLeave}>
+ <div className="ParentDocumentSelector-flyout" title=" ">
<DocumentButtonBar views={[view]} stack={this.props.Stack} />
</div>
);
}
return (
<span className="buttonSelector"
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}>
+ onPointerDown={this.onPointerDown}>
{this.hover ? (null) : <FontAwesomeIcon icon={faEdit} size={"sm"} />}
{flyout}
</span>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 81d8d467b..89c1df598 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -139,15 +139,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
if (super.drop(e, de)) {
- if (de.data instanceof DragManager.DocumentDragData) {
- if (de.data.droppedDocuments.length) {
- const firstDoc = de.data.droppedDocuments[0];
+ if (de.complete.docDragData) {
+ if (de.complete.docDragData.droppedDocuments.length) {
+ const firstDoc = de.complete.docDragData.droppedDocuments[0];
const z = NumCast(firstDoc.z);
- const x = (z ? xpo : xp) - de.data.offset[0];
- const y = (z ? ypo : yp) - de.data.offset[1];
+ const x = (z ? xpo : xp) - de.complete.docDragData.offset[0];
+ const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
const dropX = NumCast(firstDoc.x);
const dropY = NumCast(firstDoc.y);
- de.data.droppedDocuments.forEach(action((d: Doc) => {
+ de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
const layoutDoc = Doc.Layout(d);
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
@@ -162,19 +162,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.bringToFront(d);
}));
- de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
+ de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]);
}
}
- else if (de.data instanceof DragManager.AnnotationDragData) {
- if (de.data.dropDocument) {
- const dragDoc = de.data.dropDocument;
- const x = xp - de.data.offset[0];
- const y = yp - de.data.offset[1];
+ else if (de.complete.annoDragData) {
+ if (de.complete.annoDragData.dropDocument) {
+ const dragDoc = de.complete.annoDragData.dropDocument;
+ const x = xp - de.complete.annoDragData.offset[0];
+ const y = yp - de.complete.annoDragData.offset[1];
const dropX = NumCast(dragDoc.x);
const dropY = NumCast(dragDoc.y);
dragDoc.x = x + NumCast(dragDoc.x) - dropX;
dragDoc.y = y + NumCast(dragDoc.y) - dropY;
- de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
+ de.complete.annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
this.bringToFront(dragDoc);
}
}
@@ -205,10 +205,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
- DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, {
- handlers: { dragComplete: action(emptyFunction) },
- hideSource: !de.dropAction
- });
+ DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
return true;
}
}
@@ -723,6 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
componentDidMount() {
+ super.componentDidMount();
this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; },
action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)),
{ fireImmediately: true, name: "doLayout" });
@@ -895,7 +893,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
return !this.extensionDoc ? (null) :
<div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- style={{ height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
+ style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
<MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a4018bd2d..7dee22f66 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -48,90 +48,5 @@
}
}
-.linkMenu-item {
- // border-top: 0.5px solid $main-accent;
- position: relative;
- display: flex;
- font-size: 12px;
-
-
- .link-name {
- position: relative;
-
- p {
- padding: 4px 6px;
- line-height: 12px;
- border-radius: 5px;
- overflow-wrap: break-word;
- }
- }
-
- .linkMenu-item-content {
- width: 100%;
- }
-
- .link-metadata {
- padding: 0 10px 0 16px;
- margin-bottom: 4px;
- color: $main-accent;
- font-style: italic;
- font-size: 10.5px;
- }
-
- &:hover {
- .linkMenu-item-buttons {
- display: flex;
- }
- .linkMenu-item-content {
- &.expand-two p {
- width: calc(100% - 52px);
- background-color: lightgray;
- }
- &.expand-three p {
- width: calc(100% - 84px);
- background-color: lightgray;
- }
- }
- }
-}
-
-.linkMenu-item-buttons {
- display: none;
- position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
-
- .button {
- width: 20px;
- height: 20px;
- margin: 0;
- margin-right: 6px;
- border-radius: 50%;
- cursor: pointer;
- pointer-events: auto;
- background-color: $dark-color;
- color: $light-color;
- font-size: 65%;
- transition: transform 0.2s;
- text-align: center;
- position: relative;
-
- .fa-icon {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- }
-
- &:last-child {
- margin-right: 0;
- }
- &:hover {
- background: $main-accent;
- }
- }
-}
-
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 15aacbbc9..abd17ec4d 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -4,11 +4,9 @@ import { observer } from "mobx-react";
import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { emptyFunction } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
-import { UndoManager } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
import { LinkMenuItem } from "./LinkMenuItem";
@@ -21,7 +19,6 @@ interface LinkMenuGroupProps {
showEditor: (linkDoc: Doc) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
docView: DocumentView;
-
}
@observer
@@ -44,27 +41,14 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
e.stopPropagation();
}
-
onLinkButtonMoved = async (e: PointerEvent) => {
- UndoManager.RunInBatch(() => {
- if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
+ if (this._drag.current && (e.movementX > 1 || e.movementY > 1)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
- const draggedDocs = this.props.group.map(linkDoc => {
- const opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
- if (opp) return opp;
- }) as Doc[];
- const dragData = new DragManager.DocumentDragData(draggedDocs);
-
- DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
- }, "drag links");
+ const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
+ DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets);
+ }
e.stopPropagation();
}
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
new file mode 100644
index 000000000..fd0954f65
--- /dev/null
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -0,0 +1,87 @@
+@import "../globalCssVariables";
+
+.linkMenu-item {
+ // border-top: 0.5px solid $main-accent;
+ position: relative;
+ display: flex;
+ font-size: 12px;
+
+
+ .linkMenu-name {
+ position: relative;
+
+ p {
+ padding: 4px 6px;
+ line-height: 12px;
+ border-radius: 5px;
+ overflow-wrap: break-word;
+ user-select: none;
+ }
+ }
+
+ .linkMenu-item-content {
+ width: 100%;
+ }
+
+ .link-metadata {
+ padding: 0 10px 0 16px;
+ margin-bottom: 4px;
+ color: $main-accent;
+ font-style: italic;
+ font-size: 10.5px;
+ }
+
+ &:hover {
+ .linkMenu-item-buttons {
+ display: flex;
+ }
+ .linkMenu-item-content {
+ &.expand-two p {
+ width: calc(100% - 52px);
+ background-color: lightgray;
+ }
+ &.expand-three p {
+ width: calc(100% - 84px);
+ background-color: lightgray;
+ }
+ }
+ }
+}
+
+.linkMenu-item-buttons {
+ display: none;
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+
+ .button {
+ width: 20px;
+ height: 20px;
+ margin: 0;
+ margin-right: 6px;
+ border-radius: 50%;
+ cursor: pointer;
+ pointer-events: auto;
+ background-color: $dark-color;
+ color: $light-color;
+ font-size: 65%;
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+
+ .fa-icon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ &:hover {
+ background: $main-accent;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index edf5e9c26..b7d27ee30 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -5,11 +5,11 @@ import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc } from '../../../new_fields/Doc';
import { Cast, StrCast } from '../../../new_fields/Types';
-import { DragLinkAsDocument } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
import { LinkManager } from '../../util/LinkManager';
import { ContextMenu } from '../ContextMenu';
import { LinkFollowBox } from './LinkFollowBox';
-import './LinkMenu.scss';
+import './LinkMenuItem.scss';
import React = require("react");
library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
@@ -26,6 +26,9 @@ interface LinkMenuItemProps {
@observer
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
+ private _downX = 0;
+ private _downY = 0;
+ private _eleClone: any;
@observable private _showMore: boolean = false;
@action toggleShowMore() { this._showMore = !this._showMore; }
@@ -55,6 +58,9 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
onLinkButtonDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ this._eleClone = this._drag.current!.cloneNode(true);
e.stopPropagation();
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.addEventListener("pointermove", this.onLinkButtonMoved);
@@ -75,11 +81,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
+ if (this._drag.current !== null && Math.abs((e.clientX - this._downX) * (e.clientX - this._downX) + (e.clientY - this._downY) * (e.clientY - this._downY)) > 5) {
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
- DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc);
+ this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
+ DragManager.StartLinkTargetsDrag(this._eleClone, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
}
e.stopPropagation();
}
@@ -109,20 +116,21 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
render() {
-
const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
const canExpand = keys ? keys.length > 0 : false;
return (
<div className="linkMenu-item">
<div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
- <div className="link-name">
- <p ref={this._drag} onPointerDown={this.onLinkButtonDown}>{StrCast(this.props.destinationDoc.title)}</p>
+ <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." onPointerDown={this.onLinkButtonDown}>
+ <p >{StrCast(this.props.destinationDoc.title)}</p>
<div className="linkMenu-item-buttons">
{canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
<div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
- <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
+ <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+ <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
+ </div>
</div>
</div>
{this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index 34151a311..d1272c266 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -46,7 +46,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
this.dropDisposer();
}
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
@@ -65,9 +65,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData && e.target) {
- this.props.Document[(e.target as any).textContent] = new List<Doc>(de.data.droppedDocuments.map((d, i) =>
- d.onDragStart ? de.data.draggedDocuments[i] : d));
+ const docDragData = de.complete.docDragData;
+ if (docDragData && e.target) {
+ this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+ d.onDragStart ? docDragData.draggedDocuments[i] : d));
e.stopPropagation();
}
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d599de56e..261a88deb 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -104,6 +104,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
zIndex: this.Document.zIndex || 0,
}} >
<DocumentView {...this.props}
+ dragDivName={"collectionFreeFormDocumentView-container"}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
backgroundColor={this.clusterColorFunc}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 6d0ea33fa..5e3306a11 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -32,7 +32,7 @@ interface ContentFittingDocumentViewProps {
onClick?: ScriptField;
getTransform: () => Transform;
addDocument: (document: Doc) => boolean;
- moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
+ moveDocument: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
removeDocument: (document: Doc) => boolean;
active: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
@@ -60,11 +60,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
+ const docDragData = de.complete.docDragData;
+ if (docDragData) {
this.props.childDocs && this.props.childDocs.map(otherdoc => {
const target = Doc.GetProto(otherdoc);
target.layout = ComputedField.MakeFunction("this.image_data[0]");
- target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]);
+ target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
});
e.stopPropagation();
}
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index a22472e9e..3e2e74c67 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -5,7 +5,7 @@ import { makeInterface } from "../../../new_fields/Schema";
import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
import { Utils } from '../../../Utils';
import { DocumentManager } from "../../util/DocumentManager";
-import { DragLinksAsDocuments } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
import { DocComponent } from "../DocComponent";
import "./DocuLinkBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
@@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
const dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy));
if (separation > 100) {
- DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack.
+ DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
} else if (dragdist > separation) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 727d631c4..b096d68fc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -19,7 +19,6 @@ import { DocumentType } from '../../documents/DocumentTypes';
import { ClientUtils } from '../../util/ClientUtils';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
-import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from "../../util/SelectionManager";
import SharingManager from '../../util/SharingManager';
@@ -57,9 +56,10 @@ export interface DocumentViewProps {
LibraryPath: Doc[];
fitToBox?: boolean;
onClick?: ScriptField;
+ dragDivName?: string;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
- moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
renderDepth: number;
showOverlays?: (doc: Doc) => { title?: string, caption?: string };
@@ -104,7 +104,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidMount() {
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
+ this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
!this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
}
@@ -112,7 +112,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidUpdate() {
this._dropDisposer && this._dropDisposer();
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
+ this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
}
@action
@@ -130,12 +130,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.dropAction = dropAction;
dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument;
dragData.applyAsTemplate = applyAsTemplate;
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
- handlers: {
- dragComplete: action((emptyFunction))
- },
- hideSource: !dropAction && !this.Document.onDragStart
- });
+ dragData.dragDivName = this.props.dragDivName;
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
}
}
@@ -143,12 +139,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (e.altKey && (e.key === "†" || e.key === "t") && !(e.nativeEvent as any).StopPropagationForReal) {
(e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work...
e.stopPropagation();
+ e.preventDefault();
if (!StrCast(this.Document.showTitle)) this.Document.showTitle = "title";
if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
{
this._titleRef.current?.setIsFocused(false);
- let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
+ const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
any.keeplocation = true;
any?.focus();
}
@@ -187,7 +184,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs);
const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs);
- const linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
+ const linkDocs = DocListCast(this.props.Document.links);
let expandedDocs: Doc[] = [];
expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
@@ -321,23 +318,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.AnnotationDragData) {
+ if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
- (de.data as any).linkedToDoc = true;
+ de.complete.annoDragData.linkedToDoc = true;
- DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
+ DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc },
+ `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
}
- if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
- Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, "layoutCustom");
+ if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
+ Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom");
e.stopPropagation();
}
- if (de.data instanceof DragManager.LinkDragData) {
+ if (de.complete.linkDragData) {
e.stopPropagation();
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
- de.data.linkSourceDocument !== this.props.Document &&
- (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
+ de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+ (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
}
}
@@ -644,7 +642,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
contents={this.Document[showTitle]}
display={"block"} height={72} fontSize={12}
GetValue={() => StrCast(this.Document[showTitle])}
- SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true)}
/>
</div>);
return <>
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ce1c468ad..c56fde186 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -39,7 +39,7 @@ export interface FieldViewProps {
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
removeDocument?: (document: Doc) => boolean;
- moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: (outsideReaction?: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 70fa4974d..a298fd6af 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -27,7 +27,7 @@ import { DictationManager } from '../../util/DictationManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
+import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
+ const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -219,7 +219,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if (this._editorView && (this._editorView as any).docView) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- const res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+ const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
let tr = this._editorView.state.tr;
const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
@@ -238,30 +238,30 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
}
}
- setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
+ adoptAnnotation = (start: number, end: number, mark: Mark) => {
const view = this._editorView!;
- const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+ const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
}
protected createDropTarget = (ele: HTMLDivElement) => {
this._proseRef = ele;
this.dropDisposer && this.dropDisposer();
- ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+ ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
+ if (de.complete.docDragData) {
+ const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
// replace text contents whend dragging with Alt
- if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") {
+ if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
if (draggedDoc.data instanceof RichTextField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
}
// apply as template when dragging with Meta
- } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
+ } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
draggedDoc.isTemplateDoc = true;
let newLayout = Doc.Layout(draggedDoc);
if (typeof (draggedDoc.layout) === "string") {
@@ -272,8 +272,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.Document.layoutKey = "layoutCustom";
e.stopPropagation();
// embed document when dragging with a userDropAction or an embedDoc flag set
- } else if (de.data.userDropAction || de.data.embedDoc) {
- const target = de.data.droppedDocuments[0];
+ } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
+ const target = de.complete.docDragData.droppedDocuments[0];
// const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
// if (link) {
target.fitToBox = true;
@@ -550,16 +550,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
- this._searchReactionDisposer = reaction(() => {
- return StrCast(this.layoutDoc.search_string);
- }, searchString => {
- if (searchString) {
- this.highlightSearchTerms([searchString]);
- }
- else {
- this.unhighlightSearchTerms();
- }
- }, { fireImmediately: true });
+ this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch,
+ search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+ { fireImmediately: true });
this._rulesReactionDisposer = reaction(() => {
const ruleProvider = this.props.ruleProvider;
@@ -637,7 +630,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
{ fireImmediately: true }
);
- setTimeout(() => this.tryUpdateHeight(), 0);
+ setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
}
pushToGoogleDoc = async () => {
@@ -822,7 +815,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
- star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
+ summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
ordered_list(node, view, getPos) { return new OrderedListView(); },
footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
},
@@ -873,6 +866,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
static _downEvent: any;
onPointerDown = (e: React.PointerEvent): void => {
+ this.doLinkOnDeselect();
FormattedTextBox._downEvent = true;
FormattedTextBoxComment.textBox = this;
if (this.props.onClick && e.button === 0) {
@@ -906,10 +900,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.tryUpdateHeight();
// see if we need to preserve the insertion point
- let prosediv = this._proseRef?.children?.[0] as any;
- let keeplocation = prosediv?.keeplocation;
+ const prosediv = this._proseRef?.children?.[0] as any;
+ const keeplocation = prosediv?.keeplocation;
prosediv && (prosediv.keeplocation = undefined);
- let pos = this._editorView?.state.selection.$from.pos || 1;
+ const pos = this._editorView?.state.selection.$from.pos || 1;
keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
}
onPointerWheel = (e: React.WheelEvent): void => {
@@ -924,14 +918,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
onClick = (e: React.MouseEvent): void => {
if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
- let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
- let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+ const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+ const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
e.preventDefault();
}
if (!node && this._proseRef) {
- let lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+ const lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
@@ -972,7 +966,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
// }
// }
- this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false)
+ this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
}
@@ -983,7 +977,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if (pos && this.props.isSelected(true)) {
// let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
//const node = this._editorView!.state.doc.nodeAt(pos.pos);
- let $pos = this._editorView!.state.doc.resolve(pos.pos);
+ const $pos = this._editorView!.state.doc.resolve(pos.pos);
let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
if ($pos.node().type === schema.nodes.ordered_list) {
for (let off = 1; off < 100; off++) {
@@ -997,7 +991,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
if (select) {
- let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+ const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
if (!highlightOnly) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
}
@@ -1053,23 +1047,24 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this._undoTyping.end();
this._undoTyping = undefined;
}
- this.doLinkOnDeselect(); 6
+ this.doLinkOnDeselect();
}
+ _lastTimedMark: Mark | undefined = undefined;
onKeyPress = (e: React.KeyboardEvent) => {
if (e.altKey) {
e.preventDefault();
return;
}
if (!this._editorView!.state.selection.empty && e.key === "%") {
- (this._editorView!.state as any).EnteringStyle = true;
+ this._editorView!.state.schema.EnteringStyle = true;
e.preventDefault();
e.stopPropagation();
return;
}
- if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) {
- (this._editorView!.state as any).EnteringStyle = false;
+ if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) {
+ this._editorView!.state.schema.EnteringStyle = false;
}
if (e.key === "Escape") {
SelectionManager.DeselectAll();
@@ -1078,7 +1073,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
}
- const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+ const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+ this._lastTimedMark = mark;
this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
@@ -1091,13 +1087,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
@action
- tryUpdateHeight() {
- const scrollHeight = this._ref.current?.scrollHeight;
+ tryUpdateHeight(limitHeight?: number) {
+ let scrollHeight = this._ref.current?.scrollHeight;
if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ if (limitHeight && scrollHeight > limitHeight) {
+ scrollHeight = limitHeight;
+ this.layoutDoc.limitHeight = undefined;
+ this.layoutDoc.autoHeight = false;
+ }
const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
const dh = NumCast(this.layoutDoc.height, 0);
- let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+ const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
this.layoutDoc.height = newHeight;
this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 409229c1a..1755cb99c 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -57,7 +57,6 @@ export class FormattedTextBoxComment {
static start: number;
static end: number;
static mark: Mark;
- static opened: boolean;
static textBox: FormattedTextBox | undefined;
static linkDoc: Doc | undefined;
constructor(view: any) {
@@ -89,10 +88,8 @@ export class FormattedTextBoxComment {
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
}
- FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
- textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation(
- FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
- FormattedTextBoxComment.opened, keep);
+ keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+ FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
e.stopPropagation();
e.preventDefault();
};
@@ -104,12 +101,11 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.textBox = undefined;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
}
- public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
+ public static SetState(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.start = start;
FormattedTextBoxComment.end = end;
FormattedTextBoxComment.mark = mark;
- FormattedTextBoxComment.opened = opened;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
@@ -143,7 +139,7 @@ export class FormattedTextBoxComment {
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
- FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+ FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
@@ -160,19 +156,22 @@ export class FormattedTextBoxComment {
const mark = child && findLinkMark(child.marks);
if (mark && child && nbef && naft) {
FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+ (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
} else {
FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
}
- (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+ FormattedTextBoxComment.tooltipText.textContent = "target not found...";
+ (FormattedTextBoxComment.tooltipText as any).href = "";
const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
if (linkDoc instanceof Doc) {
+ (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
FormattedTextBoxComment.linkDoc = linkDoc;
- const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
+ const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
try {
ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
} catch (e) { }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4b3da3dae..f6aa47f15 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -67,18 +67,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer && this._dropDisposer();
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url);
+ if (de.complete.docDragData) {
+ if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url);
e.stopPropagation();
}
- de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => {
+ de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
e.stopPropagation();
}));
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d16fe2edd..a1f710617 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -39,7 +39,7 @@ export const pageSchema = createSchema({
rotation: "number",
scrollY: "number",
scrollHeight: "number",
- search_string: "string"
+ serachMatch: "boolean"
});
pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -129,10 +129,10 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
this._coverPath = JSON.parse(await rp.get(path));
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
- this._searchReactionDisposer = reaction(() => this.Document.search_string, searchString => {
- if (searchString) {
- this.search(searchString, true);
- this._lastSearch = searchString;
+ this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
+ if (search) {
+ this.search(Doc.SearchQuery(), true);
+ this._lastSearch = Doc.SearchQuery();
}
else {
setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights?
@@ -409,6 +409,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
if ((this.Document.scale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this.active(true)) {
this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+ e.stopPropagation();
}
this._marqueeing = false;
if (!e.altKey && e.button === 0 && this.active(true)) {
@@ -559,14 +560,9 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
const targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
if (annotationDoc) {
- const dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
- DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
- handlers: {
- dragComplete: () => !(dragData as any).linkedToDoc &&
- DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF")
-
- },
- hideSource: false
+ DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
+ dragComplete: e => !e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc &&
+ DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument, ctx: e.annoDragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF")
});
}
}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 12e1bc265..684f50766 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -62,15 +62,6 @@ export class FilterBox extends React.Component {
super(props);
FilterBox.Instance = this;
}
-
- componentDidMount = () => {
- document.addEventListener("pointerdown", (e) => {
- if (!e.defaultPrevented && e.timeStamp !== this._pointerTime) {
- SearchBox.Instance.closeSearch();
- }
- });
- }
-
setupAccordion() {
$('document').ready(function () {
const acc = document.getElementsByClassName('filter-header');
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index c45fbe210..b80c3bb54 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -12,13 +12,11 @@ import { Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { SetupDrag } from '../../util/DragManager';
import { SearchUtil } from '../../util/SearchUtil';
-import { MainView } from '../MainView';
import { FilterBox } from './FilterBox';
import "./FilterBox.scss";
import "./SearchBox.scss";
import { SearchItem } from './SearchItem';
import { IconBar } from './IconBar';
-import { string } from 'prop-types';
library.add(faTimes);
@@ -85,7 +83,11 @@ export class SearchBox extends React.Component {
this._maxSearchIndex = 0;
}
- enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } };
+ enter = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ this.submitSearch();
+ }
+ }
public static async convertDataUri(imageUri: string, returnedFilename: string) {
try {
@@ -307,7 +309,7 @@ export class SearchBox extends React.Component {
this.getResults(this._searchString);
if (i < this._results.length) result = this._results[i];
if (result) {
- const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+ const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
this._isSearch[i] = "search";
}
@@ -315,7 +317,7 @@ export class SearchBox extends React.Component {
else {
result = this._results[i];
if (result) {
- const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+ const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
this._isSearch[i] = "search";
}
@@ -337,7 +339,7 @@ export class SearchBox extends React.Component {
render() {
return (
- <div className="searchBox-container">
+ <div className="searchBox-container" onPointerDown={e => { e.stopPropagation(); e.preventDefault(); }}>
<div className="searchBox-bar">
<span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef} title="Drag Results as Collection">
<FontAwesomeIcon icon="object-group" size="lg" />
@@ -347,10 +349,9 @@ export class SearchBox extends React.Component {
style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
<button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button>
</div>
- {(this._numTotalResults > 0 || !this._searchbarOpen) ? (null) :
- (<div className="searchBox-quickFilter" onPointerDown={this.openSearch}>
- <div className="filter-panel"><IconBar /></div>
- </div>)}
+ <div className="searchBox-quickFilter" onPointerDown={this.openSearch}>
+ <div className="filter-panel"><IconBar /></div>
+ </div>
<div className="searchBox-results" onScroll={this.resultsScrolled} style={{
display: this._resultsOpen ? "flex" : "none",
height: this.resFull ? "auto" : this.resultHeight, overflow: this.resFull ? "auto" : "visible"
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
index 9f12994c3..82ff96700 100644
--- a/src/client/views/search/SearchItem.scss
+++ b/src/client/views/search/SearchItem.scss
@@ -203,6 +203,7 @@
.searchBox-placeholder {
min-height: 50px;
margin-left: 150px;
+ width: calc(100% - 150px);
text-transform: uppercase;
text-align: left;
font-weight: bold;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 1007102f6..673cb7937 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -4,14 +4,13 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, emptyPath } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
-import { LinkManager } from "../../util/LinkManager";
import { SearchUtil } from "../../util/SearchUtil";
import { Transform } from "../../util/Transform";
import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss";
@@ -22,6 +21,7 @@ import { DocumentView } from "../nodes/DocumentView";
import { SearchBox } from "./SearchBox";
import "./SearchItem.scss";
import "./SelectorContextMenu.scss";
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
export interface SearchItemProps {
doc: Doc;
@@ -135,12 +135,11 @@ export class SearchItem extends React.Component<SearchItemProps> {
@observable _displayDim = 50;
componentDidMount() {
- this.props.doc.search_string = this.props.query;
- this.props.doc.search_fields = this.props.highlighting.join(", ");
+ Doc.SetSearchQuery(this.props.query);
+ this.props.doc.searchMatch = true;
}
componentWillUnmount() {
- this.props.doc.search_string = undefined;
- this.props.doc.search_fields = undefined;
+ this.props.doc.searchMatch = undefined;
}
//@computed
@@ -150,37 +149,31 @@ export class SearchItem extends React.Component<SearchItemProps> {
if (!this._useIcons) {
const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
const returnYDimension = () => this._displayDim;
- const scale = () => returnXDimension() / NumCast(Doc.Layout(this.props.doc).nativeWidth, returnXDimension());
const docview = <div
onPointerDown={action(() => {
this._useIcons = !this._useIcons;
this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
})}
- onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
- onPointerLeave={action(() => this._displayDim = 50)} >
- <DocumentView
+ onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} >
+ <ContentFittingDocumentView
Document={this.props.doc}
LibraryPath={emptyPath}
fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
addDocument={returnFalse}
removeDocument={returnFalse}
ruleProvider={undefined}
- ScreenToLocalTransform={Transform.Identity}
addDocTab={returnFalse}
pinToPres={returnFalse}
+ getTransform={Transform.Identity}
renderDepth={1}
PanelWidth={returnXDimension}
PanelHeight={returnYDimension}
focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnFalse}
+ moveDocument={returnFalse}
+ active={returnFalse}
whenActiveChanged={returnFalse}
- bringToFront={emptyFunction}
- zoomToScale={emptyFunction}
- getScale={returnOne}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContentScaling={scale}
+ setPreviewScript={emptyFunction}
+ previewScript={undefined}
/>
</div>;
return docview;
@@ -212,16 +205,16 @@ export class SearchItem extends React.Component<SearchItemProps> {
}
@computed
- get linkCount() { return LinkManager.Instance.getAllRelatedLinks(this.props.doc).length; }
+ get linkCount() { return DocListCast(this.props.doc.links).length; }
@action
pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
nextHighlight = (e: React.PointerEvent) => {
- e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e);
- const sstring = StrCast(this.props.doc.search_string);
- this.props.doc.search_string = "";
- setTimeout(() => this.props.doc.search_string = sstring, 0);
+ e.preventDefault();
+ e.button === 0 && SearchBox.Instance.openSearch(e);
+ this.props.doc.searchMatch = false;
+ setTimeout(() => this.props.doc.searchMatch = true, 0);
}
highlightDoc = (e: React.PointerEvent) => {
if (this.props.doc.type === DocumentType.LINK) {
@@ -268,10 +261,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
e.stopPropagation();
const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
- DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false,
- });
+ DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
}
render() {
@@ -292,7 +282,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
<div className="search-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div>
{this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="search-highlighting">`${l}`</div>)}
</div>
- <div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
+ <div className="search-info" style={{ width: this._useIcons ? "15%" : "100%" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
<div className="search-type" title="Click to Preview">{this.DocumentIcon()}</div>
<div className="search-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6f55775fe..17c1b7e16 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -644,6 +644,7 @@ export namespace Doc {
export class DocData {
@observable _user_doc: Doc = undefined!;
+ @observable _searchQuery: string = "";
}
// the document containing the view layout information - will be the Document itself unless the Document has
@@ -651,6 +652,8 @@ export namespace Doc {
export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; }
export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
const manager = new DocData();
+ export function SearchQuery(): string { return manager._searchQuery; }
+ export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
export function UserDoc(): Doc { return manager._user_doc; }
export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
export function IsBrushed(doc: Doc) {
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 53ddea2fc..053576a92 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -1,4 +1,4 @@
-import * as fs from 'fs';
+import { readFile, writeFile, exists, mkdir, unlink, createWriteStream } from 'fs';
import { ExecOptions } from 'shelljs';
import { exec } from 'child_process';
import * as path from 'path';
@@ -6,6 +6,17 @@ import * as rimraf from "rimraf";
import { yellow, Color } from 'colors';
const projectRoot = path.resolve(__dirname, "../../");
+export function pathFromRoot(relative?: string) {
+ if (!relative) {
+ return projectRoot;
+ }
+ return path.resolve(projectRoot, relative);
+}
+
+export async function fileDescriptorFromStream(path: string) {
+ const logStream = createWriteStream(path);
+ return new Promise<number>(resolve => logStream.on("open", resolve));
+}
export const command_line = (command: string, fromDirectory?: string) => {
return new Promise<string>((resolve, reject) => {
@@ -20,14 +31,14 @@ export const command_line = (command: string, fromDirectory?: string) => {
export const read_text_file = (relativePath: string) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<string>((resolve, reject) => {
- fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
+ readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
});
};
export const write_text_file = (relativePath: string, contents: any) => {
const target = path.resolve(__dirname, relativePath);
return new Promise<void>((resolve, reject) => {
- fs.writeFile(target, contents, (err) => err ? reject(err) : resolve());
+ writeFile(target, contents, (err) => err ? reject(err) : resolve());
});
};
@@ -51,11 +62,7 @@ export async function log_execution<T>({ startMessage, endMessage, action, color
} catch (e) {
error = e;
} finally {
- if (typeof endMessage === "string") {
- log_helper(`${endMessage}.`, resolvedColor);
- } else {
- log_helper(`${endMessage({ result, error })}.`, resolvedColor);
- }
+ log_helper(typeof endMessage === "string" ? endMessage : endMessage({ result, error }), resolvedColor);
}
return result;
}
@@ -86,10 +93,10 @@ export function msToTime(duration: number) {
}
export const createIfNotExists = async (path: string) => {
- if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) {
+ if (await new Promise<boolean>(resolve => exists(path, resolve))) {
return true;
}
- return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null)));
+ return new Promise<boolean>(resolve => mkdir(path, error => resolve(error === null)));
};
export async function Prune(rootDirectory: string): Promise<boolean> {
@@ -97,4 +104,4 @@ export async function Prune(rootDirectory: string): Promise<boolean> {
return error === null;
}
-export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null)));
+export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null)));
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index ccfd570b8..37d66666b 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -72,7 +72,7 @@ export namespace SolrManager {
const args = status ? "start" : "stop -p 8983";
try {
console.log(`Solr management: trying to ${args}`);
- console.log(await command_line(`solr.cmd ${args}`, "../../solr-8.1.1/bin"));
+ console.log(await command_line(`solr.cmd ${args}`, "./solr-8.3.1/bin"));
return true;
} catch (e) {
console.log(red(`Solr management error: unable to ${args}`));
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e959645e0..2f1bd956f 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -43,7 +43,7 @@ export default class UtilManager extends ApiManager {
method: Method.GET,
subscription: "/buxton",
onValidation: async ({ res }) => {
- const cwd = '../scraping/buxton';
+ const cwd = './src/scraping/buxton';
const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); };
const onRejected = (err: any) => { console.error(err.message); res.send(err); };
diff --git a/src/server/ChildProcessUtilities/ProcessFactory.ts b/src/server/ChildProcessUtilities/ProcessFactory.ts
new file mode 100644
index 000000000..745b1479a
--- /dev/null
+++ b/src/server/ChildProcessUtilities/ProcessFactory.ts
@@ -0,0 +1,67 @@
+import { existsSync, mkdirSync } from "fs";
+import { pathFromRoot, log_execution, fileDescriptorFromStream } from '../ActionUtilities';
+import { red, green } from "colors";
+import rimraf = require("rimraf");
+import { ChildProcess, spawn, StdioOptions } from "child_process";
+import { Stream } from "stream";
+import { resolve } from "path";
+
+export namespace ProcessFactory {
+
+ export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined;
+
+ export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise<ChildProcess> {
+ if (stdio === "logfile") {
+ const log_fd = await Logger.create(command, args);
+ stdio = ["ignore", log_fd, log_fd];
+ }
+ const child = spawn(command, args, { detached, stdio });
+ child.unref();
+ return child;
+ }
+
+ export namespace NamedAgents {
+
+ export async function persistenceDaemon() {
+ await log_execution({
+ startMessage: "\ninitializing persistence daemon",
+ endMessage: ({ result, error }) => {
+ const success = error === null && result !== undefined;
+ if (!success) {
+ console.log(red("failed to initialize the persistance daemon"));
+ console.log(error);
+ process.exit(0);
+ }
+ return "failsafe daemon process successfully spawned";
+ },
+ action: () => createWorker('npx', ['ts-node', resolve(__dirname, "./daemon/persistence_daemon.ts")], ["ignore", "inherit", "inherit"]),
+ color: green
+ });
+ console.log();
+ }
+ }
+
+}
+
+export namespace Logger {
+
+ const logPath = pathFromRoot("./logs");
+
+ export async function initialize() {
+ if (existsSync(logPath)) {
+ if (!process.env.SPAWNED) {
+ await new Promise<any>(resolve => rimraf(logPath, resolve));
+ }
+ }
+ mkdirSync(logPath);
+ }
+
+ export async function create(command: string, args?: readonly string[]): Promise<number> {
+ return fileDescriptorFromStream(generate_log_path(command, args));
+ }
+
+ function generate_log_path(command: string, args?: readonly string[]) {
+ return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
+ }
+
+} \ No newline at end of file
diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts
new file mode 100644
index 000000000..fb2b3551e
--- /dev/null
+++ b/src/server/ChildProcessUtilities/daemon/session.ts
@@ -0,0 +1,190 @@
+import * as request from "request-promise";
+import { log_execution, pathFromRoot } from "../../ActionUtilities";
+import { red, yellow, cyan, green, Color } from "colors";
+import * as nodemailer from "nodemailer";
+import { MailOptions } from "nodemailer/lib/json-transport";
+import { writeFileSync, existsSync, mkdirSync } from "fs";
+import { resolve } from 'path';
+import { ChildProcess, exec, execSync } from "child_process";
+import { createInterface } from "readline";
+const killport = require("kill-port");
+
+process.on('SIGINT', endPrevious);
+const identifier = yellow("__session_manager__:");
+
+let manualRestartActive = false;
+createInterface(process.stdin, process.stdout).on('line', async line => {
+ const prompt = line.trim().toLowerCase();
+ switch (prompt) {
+ case "restart":
+ manualRestartActive = true;
+ identifiedLog(cyan("Initializing manual restart..."));
+ await endPrevious();
+ break;
+ case "exit":
+ identifiedLog(cyan("Initializing session end"));
+ await endPrevious();
+ identifiedLog("Cleanup complete. Exiting session...\n");
+ execSync(killAllCommand());
+ break;
+ default:
+ identifiedLog(red("commands: { exit, restart }"));
+ return;
+ }
+});
+
+const logPath = resolve(__dirname, "./logs");
+const crashPath = resolve(logPath, "./crashes");
+if (!existsSync(logPath)) {
+ mkdirSync(logPath);
+}
+if (!existsSync(crashPath)) {
+ mkdirSync(crashPath);
+}
+
+const crashLogPath = resolve(crashPath, `./session_crashes_${new Date().toISOString()}.log`);
+function addLogEntry(message: string, color: Color) {
+ const formatted = color(`${message} ${timestamp()}.`);
+ identifiedLog(formatted);
+ // appendFileSync(crashLogPath, `${formatted}\n`);
+}
+
+function identifiedLog(message?: any, ...optionalParams: any[]) {
+ console.log(identifier, message, ...optionalParams);
+}
+
+if (!["win32", "darwin"].includes(process.platform)) {
+ identifiedLog(red("Invalid operating system: this script is supported only on Mac and Windows."));
+ process.exit(1);
+}
+
+const latency = 10;
+const ports = [1050, 4321];
+const onWindows = process.platform === "win32";
+const LOCATION = "http://localhost";
+const heartbeat = `${LOCATION}:1050/serverHeartbeat`;
+const recipient = "samuel_wilkins@brown.edu";
+const { pid } = process;
+let restarting = false;
+let count = 0;
+
+function startServerCommand() {
+ if (onWindows) {
+ return '"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"';
+ }
+ return `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && npm run start-release"\nend tell'`;
+}
+
+function killAllCommand() {
+ if (onWindows) {
+ return "taskkill /f /im node.exe";
+ }
+ return "killall -9 node";
+}
+
+identifiedLog("Initializing session...");
+
+writeLocalPidLog("session_manager", pid);
+
+function writeLocalPidLog(filename: string, contents: any) {
+ const path = `./logs/current_${filename}_pid.log`;
+ identifiedLog(cyan(`${contents} written to ${path}`));
+ writeFileSync(resolve(__dirname, path), `${contents}\n`);
+}
+
+function timestamp() {
+ return `@ ${new Date().toISOString()}`;
+}
+
+async function endPrevious() {
+ identifiedLog(yellow("Cleaning up previous connections..."));
+ current_backup?.kill("SIGTERM");
+ await Promise.all(ports.map(port => {
+ const task = killport(port, 'tcp');
+ return task.catch((error: any) => identifiedLog(red(error)));
+ }));
+ identifiedLog(yellow("Done. Any failures will be printed in red immediately above."));
+}
+
+let current_backup: ChildProcess | undefined = undefined;
+
+async function checkHeartbeat() {
+ let error: any;
+ try {
+ count && !restarting && process.stdout.write(`${identifier} 👂 `);
+ await request.get(heartbeat);
+ count && !restarting && console.log('⇠ 💚');
+ if (restarting || manualRestartActive) {
+ addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green);
+ restarting = false;
+ }
+ } catch (e) {
+ count && !restarting && console.log("⇠ 💔");
+ error = e;
+ } finally {
+ if (error) {
+ if (!restarting || manualRestartActive) {
+ restarting = true;
+ if (count && !manualRestartActive) {
+ console.log();
+ addLogEntry("Detected a server crash", red);
+ identifiedLog(red(error.message));
+ await endPrevious();
+ await log_execution({
+ startMessage: identifier + " Sending crash notification email",
+ endMessage: ({ error, result }) => {
+ const success = error === null && result === true;
+ return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`;
+ },
+ action: async () => notify(error || "Hmm, no error to report..."),
+ color: cyan
+ });
+ identifiedLog(green("Initiating server restart..."));
+ }
+ manualRestartActive = false;
+ current_backup = exec(startServerCommand(), err => identifiedLog(err?.message || count ? "Previous server process exited." : "Spawned initial server."));
+ writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`);
+ }
+ }
+ setTimeout(checkHeartbeat, 1000 * latency);
+ }
+}
+
+async function startListening() {
+ identifiedLog(yellow(`After initialization, will poll server heartbeat repeatedly...\n`));
+ if (!LOCATION) {
+ identifiedLog(red("No location specified for session manager. Please include as a command line environment variable or in a .env file."));
+ process.exit(0);
+ }
+ await checkHeartbeat();
+}
+
+function emailText(error: any) {
+ return [
+ `Hey ${recipient.split("@")[0]},`,
+ "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:",
+ `Location: ${LOCATION}\nError: ${error}`,
+ "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress."
+ ].join("\n\n");
+}
+
+async function notify(error: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ const mailOptions = {
+ to: recipient,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Dash Server Crash',
+ text: emailText(error)
+ } as MailOptions;
+ return new Promise<boolean>(resolve => {
+ smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null));
+ });
+}
+
+startListening(); \ No newline at end of file
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 76e02122b..e1e157fc4 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -133,6 +133,7 @@ export namespace WebSocket {
"pdf": ["_t", "url"],
"audio": ["_t", "url"],
"web": ["_t", "url"],
+ "RichTextField": ["_t", value => value.Text],
"date": ["_d", value => new Date(value.date).toISOString()],
"proxy": ["_i", "fieldId"],
"list": ["_l", list => {
diff --git a/src/server/index.ts b/src/server/index.ts
index 3764eaabb..bc481e579 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -18,11 +18,12 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader';
import DeleteManager from "./ApiManagers/DeleteManager";
import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
-import { log_execution, command_line } from "./ActionUtilities";
+import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
import { yellow, red } from "colors";
import { disconnect } from "../server/Initialization";
+import { ProcessFactory, Logger } from "./ChildProcessUtilities/ProcessFactory";
export const publicDirectory = path.resolve(__dirname, "public");
export const filesDirectory = path.resolve(publicDirectory, "files");
@@ -35,6 +36,7 @@ export const ExitHandlers = new Array<() => void>();
* before clients can access the server should be run or awaited here.
*/
async function preliminaryFunctions() {
+ await Logger.initialize();
await GoogleCredentialsLoader.loadCredentials();
GoogleApiServerUtils.processProjectCredentials();
await DashUploadUtils.buildFileDirectories();
@@ -119,31 +121,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
}
});
- let daemonInitialized = false;
- addSupervisedRoute({
- method: Method.GET,
- subscription: "/persist",
- onValidation: async ({ res }) => {
- if (!daemonInitialized) {
- daemonInitialized = true;
- log_execution({
- startMessage: "\ninitializing persistence daemon",
- endMessage: ({ result, error }) => {
- const success = error === null && result !== undefined;
- if (!success) {
- console.log(red("failed to initialize the persistance daemon"));
- process.exit(0);
- }
- return "persistence daemon process closed";
- },
- action: async () => command_line("npx ts-node ./persistence_daemon.ts", "./src/server"),
- color: yellow
- });
- }
- res.redirect("/home");
- }
- });
-
logRegistrationOutcome();
// initialize the web socket (bidirectional communication: if a user changes
diff --git a/src/server/persistence_daemon.ts b/src/server/persistence_daemon.ts
deleted file mode 100644
index 388440b49..000000000
--- a/src/server/persistence_daemon.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as request from "request-promise";
-import { command_line, log_execution } from "./ActionUtilities";
-import { red, yellow, cyan, green } from "colors";
-import * as nodemailer from "nodemailer";
-import { MailOptions } from "nodemailer/lib/json-transport";
-
-const { LOCATION } = process.env;
-const recipient = "samuel_wilkins@brown.edu";
-let restarting = false;
-
-async function listen() {
- if (!LOCATION) {
- console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file."));
- process.exit(0);
- }
- const heartbeat = `${LOCATION}:1050/serverHeartbeat`;
- // if this is on our remote server, the server must be run in release mode
- const suffix = LOCATION.includes("localhost") ? "" : "-release";
- setInterval(async () => {
- let response: any;
- let error: any;
- try {
- response = await request.get(heartbeat);
- } catch (e) {
- error = e;
- } finally {
- if (!response && !restarting) {
- restarting = true;
- console.log(yellow("Detected a server crash!"));
- await log_execution({
- startMessage: "Sending crash notification email",
- endMessage: ({ error, result }) => {
- const success = error === null && result === true;
- return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient;
- },
- action: async () => notify(error || "Hmm, no error to report..."),
- color: cyan
- });
- console.log(await log_execution({
- startMessage: "Initiating server restart",
- endMessage: "Server successfully restarted",
- action: async () => command_line(`npm run start${suffix}`, "../../"),
- color: green
- }));
- restarting = false;
- }
- }
- }, 1000 * 90);
-}
-
-function emailText(error: any) {
- return [
- `Hey ${recipient.split("@")[0]},`,
- "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:",
- `Location: ${LOCATION}\nError: ${error}`,
- "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress."
- ].join("\n\n");
-}
-
-async function notify(error: any) {
- const smtpTransport = nodemailer.createTransport({
- service: 'Gmail',
- auth: {
- user: 'brownptcdash@gmail.com',
- pass: 'browngfx1'
- }
- });
- const mailOptions = {
- to: recipient,
- from: 'brownptcdash@gmail.com',
- subject: 'Dash Server Crash',
- text: emailText(error)
- } as MailOptions;
- return new Promise<boolean>(resolve => {
- smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null));
- });
-}
-
-listen(); \ No newline at end of file
diff --git a/src/server/updateSearch.ts b/src/server/updateSearch.ts
index 5ae6885c5..83094d36a 100644
--- a/src/server/updateSearch.ts
+++ b/src/server/updateSearch.ts
@@ -59,7 +59,14 @@ async function update() {
});
const cursor = await log_execution({
startMessage: "Connecting to and querying for all documents from database...",
- endMessage: "Connection successful and query complete",
+ endMessage: ({ result, error }) => {
+ const success = error === null && result !== undefined;
+ if (!success) {
+ console.log(red("Unable to connect to the database."));
+ process.exit(0);
+ }
+ return "Connection successful and query complete";
+ },
action: () => Database.Instance.query({}),
color: yellow
});
@@ -92,7 +99,7 @@ async function update() {
updates.push(update);
}
}
- await cursor.forEach(updateDoc);
+ await cursor?.forEach(updateDoc);
const result = await log_execution({
startMessage: `Dispatching updates for ${updates.length} documents`,
endMessage: "Dispatched updates complete",
@@ -107,7 +114,7 @@ async function update() {
console.log(result);
console.log("\n");
}
- await cursor.close();
+ await cursor?.close();
process.exit(0);
}
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 9e8ace962..674278126 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -2,6 +2,7 @@
declare module 'googlephotos';
declare module 'react-image-lightbox-with-rotate';
+declare module 'kill-port';
declare module '@react-pdf/renderer' {
import * as React from 'react';