aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/LinkManager.ts2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx39
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx32
-rw-r--r--src/client/views/nodes/formattedText/prosemirrorPatches.js55
4 files changed, 103 insertions, 25 deletions
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 9b4dc2630..749fabfcc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -49,7 +49,7 @@ export class LinkManager {
}
public deleteLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
+ if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
return true;
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 664141607..f32c17563 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { baseKeymap, selectAll } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
+import { Fragment, Mark, Node, Slice, Schema } from "prosemirror-model";
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
@@ -16,6 +16,7 @@ import { DateField } from '../../../../fields/DateField';
import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
+import { removeMarkWithAttrs } from "./prosemirrorPatches";
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -56,7 +57,7 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
import React = require("react");
library.add(faEdit);
@@ -87,6 +88,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
private _searchIndex = 0;
+ private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -141,6 +143,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+ // removes all hyperlink anchors for the removed linkDoc
+ // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
+ // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
+ public RemoveLinkFromDoc(linkDoc?: Doc) {
+ const state = this._editorView?.state;
+ if (state && linkDoc && this._editorView) {
+ var allHrefs: any[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+ const foundMark = findLinkMark(node.marks);
+ const newHrefs = foundMark?.attrs.allHrefs.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
+ allHrefs = newHrefs.length ? newHrefs : allHrefs;
+ return true;
+ });
+ if (allHrefs.length) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.link, { allHrefs }));
+ }
+ }
+ }
+ // removes all the specified link referneces from the selection.
+ // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
+ public RemoveLinkFromSelection(allHrefs: { href: string, title: string, linkId: string, targetId: string }[]) {
+ const state = this._editorView?.state;
+ if (state && this._editorView) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allHrefs }));
+ }
+ }
+
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
@@ -626,6 +655,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
componentDidMount() {
+ this._cachedLinks = DocListCast(this.Document.links);
+ this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ newLinks => {
+ this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
+ this._cachedLinks = newLinks;
+ });
this._disposers.buttonBar = reaction(
() => DocumentButtonBar.Instance,
instance => {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 0fcddfc43..6366f299d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -772,28 +772,16 @@ export default class RichTextMenu extends AntimodeMenu {
}
deleteLink = () => {
- if (!this.view) return;
-
- const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
- const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined;
- if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (linkclicked) {
- DocServer.GetRefField(linkclicked).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- LinkManager.Instance.deleteLink(linkDoc);
- this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
- }
- });
- }
- } else {
- if (node) {
- const { tr, schema, selection } = this.view.state;
- const extension = this.linkExtend(selection.$anchor, href);
- this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
- }
+ if (this.view) {
+ const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.link);
+ if (link) {
+ const allHrefs = link.attrs.allHrefs.slice();
+ this.TextView.RemoveLinkFromSelection(link.attrs.allHrefs);
+ // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
+ allHrefs.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+ const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc))
+ });
}
}
}
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 763961958..0969ea4ef 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -9,6 +9,7 @@ var prosemirrorModel = require('prosemirror-model');
exports.liftListItem = liftListItem;
exports.sinkListItem = sinkListItem;
exports.wrappingInputRule = wrappingInputRule;
+exports.removeMarkWithAttrs = removeMarkWithAttrs;
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to lift the list item around the selection up into
// a wrapping list.
@@ -139,3 +140,57 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
}
+// :: ([Mark]) → ?Mark
+// Tests whether there is a mark of this type in the given set.
+function isInSetWithAttrs(mark, set, attrs) {
+ for (var i = 0; i < set.length; i++) {
+ if (set[i].type == mark) {
+ if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+ return p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey]);
+ }, true)) {
+ return set[i];
+ }
+ }
+ }
+};
+
+// :: (number, number, ?union<Mark, MarkType>) → this
+// Remove marks from inline nodes between `from` and `to`. When `mark`
+// is a single mark, remove precisely that mark. When it is a mark type,
+// remove all marks of that type. When it is null, remove all marks of
+// any type.
+function removeMarkWithAttrs(tr, from, to, mark, attrs) {
+ if (mark === void 0) mark = null;
+
+ var matched = [], step = 0;
+ tr.doc.nodesBetween(from, to, function (node, pos) {
+ if (!node.isInline) { return }
+ step++;
+ var toRemove = null;
+ if (mark) {
+ if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+ } else {
+ toRemove = node.marks;
+ }
+ if (toRemove && toRemove.length) {
+ var end = Math.min(pos + node.nodeSize, to);
+ for (var i = 0; i < toRemove.length; i++) {
+ var style = toRemove[i], found$1 = (void 0);
+ for (var j = 0; j < matched.length; j++) {
+ var m = matched[j];
+ if (m.step == step - 1 && style.eq(matched[j].style)) { found$1 = m; }
+ }
+ if (found$1) {
+ found$1.to = end;
+ found$1.step = step;
+ } else {
+ matched.push({ style: style, from: Math.max(pos, from), to: end, step: step });
+ }
+ }
+ }
+ });
+ matched.forEach(function (m) { return tr.step(new prosemirrorTransform.RemoveMarkStep(m.from, m.to, m.style)); });
+ return tr
+};
+
+