diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/LinkDocPreview.tsx | 22 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 20 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 19 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/marks_rts.ts | 13 |
4 files changed, 59 insertions, 15 deletions
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index f5f773595..d5f3f3b4e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -84,9 +84,16 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { if (anchor instanceof Doc && DocListCast(anchor.links).length) { this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; - this._linkSrc = anchor; - const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; + const automaticLink = this._linkDoc.linkRelationship === "automatic"; + if (automaticLink) { // automatic links specify the target in the link info, not the source + const linkTarget = anchor; + this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); + this._targetDoc = linkTarget; + } else { + this._linkSrc = anchor; + const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; + } this._toolTipText = ""; } })); @@ -99,7 +106,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))); } nextHref = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => { + this._linkDoc = undefined; + this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); + })); } followLink = (e: React.PointerEvent) => { @@ -152,10 +162,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) : <div className="linkDocPreview-inner"> {!this.props.showHeader ? (null) : this.previewHeader} - <div className="linkDocPreview-preview-wrapper"> + <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? "100%" : undefined, overflow: "auto" }}> {this._toolTipText ? this._toolTipText : <DocumentView ref={(r) => { - const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!); + const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); }} Document={this._targetDoc!} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 18be30a88..ec88b8d1d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -381,8 +381,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // creates links between terms in a document and documents which have a matching Id hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => { - if (this._editorView && (this._editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, "")); + const editorView = this._editorView; + if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { + const flattened1 = this.findInNode(editorView, editorView.state.doc, StrCast(target.title).replace(/^@/, "")); var alink: Doc | undefined; flattened1.forEach((flat, i) => { const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, "")); @@ -391,9 +392,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!); newAutoLinks.add(alink); - const allAnchors = [{ href: Doc.localServerPath(this.props.Document), title: "a link", anchorId: this.props.Document[Id] }]; - const link = this._editorView!.state.schema.marks.autoLinkAnchor.create({ allAnchors, linkDoc: alink[Id], title: "auto link", location }); - tr = tr.addMark(flattened[i].from, flattened[i].to, link); + const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); + const sel = flattened[i]; + tr = tr.addMark(sel.from, sel.to, splitter); + tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { + if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { + const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }]; + allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); + const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" }); + tr = tr.addMark(pos, pos + node.nodeSize, link); + } + }); + tr = tr.removeMark(sel.from, sel.to, splitter); }); } return tr; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index bafae84dc..00c03875b 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -294,6 +294,25 @@ export class RichTextRules { return state.tr.deleteRange(start, end).insert(start, fieldView); }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // wiki:title + new InputRule( + new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), + (state, match, start, end) => { + const title = match[1]; + this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); + + this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference"); + + const fstate = this.TextBox.EditorView?.state; + if (fstate) { + const tr = fstate?.tr.deleteRange(start, start + 5); + return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" "); + } + return state.tr; + }), + // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document // {{<layout>}} => show layout for this doc diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 52ef06b7f..1f6ce014f 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -19,13 +19,17 @@ export const marks: { [index: string]: MarkSpec } = { }, - // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title` - // defaults to the empty string. Rendered and parsed as an `<a>` + // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the + // published document's title. + // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because + // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since + // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to + // disambiguate links from one another. + // Rendered and parsed as an `<a>` // element. autoLinkAnchor: { attrs: { allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, - linkDoc: { default: "" }, location: { default: null }, title: { default: null }, }, @@ -44,7 +48,8 @@ export const marks: { [index: string]: MarkSpec } = { return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; } }, - // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title` + // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text, + // and a title for use in menus and hover. `title` // defaults to the empty string. Rendered and parsed as an `<a>` // element. linkAnchor: { |