diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
11 files changed, 263 insertions, 516 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 1fbd3af5c..91d123efe 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -236,7 +236,9 @@ export class DashDocView extends React.Component<IDashDocView> { PanelWidth={finalLayout[WidthSym]} PanelHeight={finalLayout[HeightSym]} focus={this.outerFocus} - styleProvider={returnEmptyString} + styleProvider={self._textBox.props.styleProvider} + layerProvider={self._textBox.props.layerProvider} + docViewPath={self._textBox.props.docViewPath} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 81bca4c00..0f2f9cdb7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -41,6 +41,7 @@ audiotag:hover { display: flex; flex-direction: row; transition: opacity 1s; + width: 100%; .formattedTextBox-dictation { height: 12px; @@ -389,6 +390,7 @@ footnote::after { overflow-x: hidden; color: initial; max-height: 100%; + width: 100%; display: flex; flex-direction: row; transition: opacity 1s; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d24ccd9ad..183719e31 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -55,9 +55,8 @@ import { DocumentButtonBar } from '../../DocumentButtonBar'; import { AudioBox } from '../AudioBox'; import { FieldView, FieldViewProps } from "../FieldView"; import "./FormattedTextBox.scss"; -import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment'; +import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment'; import React = require("react"); -import { LinkManager } from '../../../util/LinkManager'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { CollectionViewType } from '../../collections/CollectionView'; import { SnappingManager } from '../../../util/SnappingManager'; @@ -67,12 +66,16 @@ import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; +import { LightboxView } from '../../LightboxView'; +import { DocAfterFocusFunc } from '../DocumentView'; +const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar) xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yMargin?: number; + noSidebar?: boolean; dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded } export const GoogleRef = "googleDocId"; @@ -90,11 +93,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static Instance: FormattedTextBox; public ProseRef?: HTMLDivElement; public get EditorView() { return this._editorView; } + public get SidebarKey() { return this.fieldKey + "-sidebar"; } private _boxRef: React.RefObject<HTMLDivElement> = React.createRef(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; - private _applyingChange: boolean = false; + private _applyingChange: string = ""; private _searchIndex = 0; private _cachedLinks: Doc[] = []; private _undoTyping?: UndoManager.Batch; @@ -102,15 +106,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _pause: boolean = false; - private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating + private _ignoreScroll = false; @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } - @observable private _entered = false; - public static FocusedBox: FormattedTextBox | undefined; public static SelectOnLoad = ""; public static PasteOnLoad: ClipboardEvent | undefined; @@ -162,29 +164,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // 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) { + this.unhighlightSearchTerms(); const state = this._editorView?.state; - if (state && linkDoc && this._editorView) { - var allLinks: any[] = []; + const a1 = linkDoc?.anchor1 as Doc; + const a2 = linkDoc?.anchor2 as Doc; + if (state && a1 && a2 && this._editorView) { + this.removeDocument(a1); + this.removeDocument(a2); + var allFoundLinkAnchors: any[] = []; state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => { - const foundMark = findLinkMark(node.marks); - const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || []; - allLinks = newHrefs.length ? newHrefs : allLinks; + const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || []; + allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors; return true; }); - if (allLinks.length) { - this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks })); + if (allFoundLinkAnchors.length) { + this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors })); + + this.setupEditor(this.config, this.fieldKey); } } } - // removes all the specified link referneces from the selection. + // removes all the specified link references 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(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) { + public RemoveAnchorFromSelection(allAnchors: { 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, { allLinks })); + this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors })); + this.setupEditor(this.config, this.fieldKey); } } + getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); + linkOnDeselect: Map<string, string> = new Map(); doLinkOnDeselect() { @@ -232,11 +243,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, { dragComplete: e => { + const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link"); if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note"); + e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note"); e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc; } - e.linkDocument && e.annoDragData?.dropDocument && this.makeLinkToSelection(e.linkDocument[Id], "a link", "add:right", e.annoDragData.dropDocument[Id]); e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument } }); @@ -246,6 +257,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); } + _lastText = ""; dispatchTransaction = (tx: Transaction) => { let timeStamp; clearTimeout(timeStamp); @@ -267,8 +279,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.linkOnDeselect.set(key, value); const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value }); + const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }]; + const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, location: "add:right", 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); @@ -296,8 +308,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { - if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) { - this._applyingChange = true; + if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { + this._applyingChange = this.fieldKey; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { @@ -325,7 +337,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have unchanged = false; } - this._applyingChange = false; + this._applyingChange = ""; if (!unchanged) { this.updateTitle(); this.tryUpdateHeight(); @@ -404,9 +416,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp res.map(r => r.map(h => flattened.push(h))); const lastSel = Math.min(flattened.length - 1, this._searchIndex); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!; - const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location }); + const anchor = new Doc(); + const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; + const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; + const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location }); this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } @@ -504,8 +517,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } linkDrop = (data: { linkDocument: Doc }) => { const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-"; - const anchor1Id = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1[Id] : ""; - this.makeLinkToSelection(data.linkDocument[Id], anchor1Title, "add:right", anchor1Id); + const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined; + this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title); } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { @@ -602,6 +615,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const bounds = this.CurrentDiv.getBoundingClientRect(); this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; + e.preventDefault(); return false; } @undoBatch @@ -816,43 +830,51 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp props: { attributes: { class: "ProseMirror-example-setup-style" } } - }), - formattedTextBoxCommentPlugin + }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) ] }; } - makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) { + makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) { const state = this._editorView?.state; if (state) { - const href = targetHref ?? Utils.prepend("/doc/" + linkId); const sel = state.selection; const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); - sel.from !== sel.to && 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 allLinks = [{ href, title, targetId, linkId }]; - allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? [])); - const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId }); - tr = tr.addMark(pos, pos + node.nodeSize, link); - } - }); - this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents - this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); - this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; + if (sel.from !== sel.to) { + const anchor = anchorDoc ?? Docs.Create.TextanchorDocument(); + const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]); + if (anchor !== anchorDoc) this.addDocument(anchor); + 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, title, anchorId: anchor[Id] }]; + allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? [])); + const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location }); + tr = tr.addMark(pos, pos + node.nodeSize, link); + } + }); + this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents + this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); + this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; + Doc.GetProto(anchor).title = this._editorView?.state.doc.textBetween(sel.from, sel.to); + return anchor; + } + return anchorDoc ?? this.rootDoc; } + return anchorDoc ?? this.rootDoc; } IsActive = () => { return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - scrollToLinkId = (linkId: string) => { - const findLinkFrag = (frag: Fragment, editor: EditorView) => { + scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { + const anchorId = doc[Id]; + const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; let hadStart = start !== 0; frag.forEach((node, index) => { - const examinedNode = findLinkNode(node, editor); + const examinedNode = findAnchorNode(node, editor); if (examinedNode?.node.textContent) { nodes.push(examinedNode.node); !hadStart && (start = index + examinedNode.start); @@ -861,20 +883,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); return { frag: Fragment.fromArray(nodes), start }; }; - const findLinkNode = (node: Node, editor: EditorView) => { + const findAnchorNode = (node: Node, editor: EditorView) => { if (!node.isText) { - const content = findLinkFrag(node.content, editor); + const content = findAnchorFrag(node.content, editor); return { node: node.copy(content.frag), start: content.start }; } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); - return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => linkId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; + return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => anchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; }; let start = 0; - if (this._editorView && linkId) { + if (this._editorView && anchorId) { const editor = this._editorView; - const ret = findLinkFrag(editor.state.doc.content, editor); + const ret = findAnchorFrag(editor.state.doc.content, editor); if (ret.frag.size > 2 && ret.start >= 0) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start @@ -882,16 +904,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); + const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; + addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); + setTimeout(() => { + clearStyleSheetRules(FormattedTextBox._highlightStyleSheet); + afterFocus?.(true); + }, 1500); } - Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); - Doc.SetInPlace(this.layoutDoc, "_scrollToPreviewLinkID", undefined, false); + } else { + afterFocus?.(false); } } componentDidMount() { + this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this.props.contentsActive?.(this.IsActive); this._cachedLinks = DocListCast(this.Document.links); this._disposers.sidebarheight = reaction( @@ -913,34 +939,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - this._disposers.linkMaker = reaction( - () => this.props.makeLink?.(), - (linkDoc: Opt<Doc>) => { - if (linkDoc) { - const a1 = Cast(linkDoc.anchor1, Doc, null); - const a2 = Cast(linkDoc.anchor2, Doc, null); - const otherAnchor = Doc.AreProtosEqual(a1, this.rootDoc) ? a2 : a1; - const anchor2Title = StrCast(otherAnchor.title, "-untitled-"); - const anchor2Id = otherAnchor?.[Id] || ""; - this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id); - } - }, - { fireImmediately: true } - ); this._disposers.editorState = reaction( () => { - if (!this.dataDoc || !this.layoutDoc) return undefined; - if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) { - return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; - } - return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data; + const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : + this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ? + this.dataDoc : this.layoutDoc; + return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) }; }, incomingValue => { - if (incomingValue !== undefined && this._editorView && !this._applyingChange) { - const updatedState = JSON.parse(incomingValue); - if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + if (this._editorView && this._applyingChange !== this.fieldKey) { + if (incomingValue?.data) { + const updatedState = JSON.parse(incomingValue.data.Data); + if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } + } else if (incomingValue?.str) { + selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); } } }, @@ -1006,38 +1021,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } ); } - this._disposers.previewScrollToRegion = reaction(() => StrCast(this.layoutDoc._scrollToPreviewLinkID), - scrollToLinkID => this.props.renderDepth === -1 && scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true } - ); - this._disposers.scrollToRegion = reaction(() => StrCast(this.layoutDoc.scrollToLinkID), - scrollToLinkID => scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true } - ); + var quickScroll: string | undefined = ""; this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), pos => { - const durationMiliStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); - const durationSecStr = StrCast(this.Document._viewTransition).match(/([0-9.]*)s/); - const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; - if (duration) { - this._animatingScroll++; - this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0)); - setTimeout(() => this._animatingScroll--, duration); - } else { - this._scrollRef.current?.scrollTo({ top: pos }); + if (!this._ignoreScroll && this._scrollRef.current) { + const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); + const durationMiliStr = viewTrans.match(/([0-9]*)ms/); + const durationSecStr = viewTrans.match(/([0-9.]*)s/); + const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; + if (duration) { + smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0)); + } else { + this._scrollRef.current.scrollTo({ top: pos }); + } } }, { fireImmediately: true } ); - this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null), - pos => { - this.Document._scrollY = undefined; - pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250); - }, { fireImmediately: true } - ); - this._disposers.scrollPreviewY = reaction(() => Cast(this.layoutDoc.scrollPreviewY, "number", null), - pos => { - this.Document.scrollPreviewY = undefined; - pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250); - }, { fireImmediately: true } - ); + quickScroll = undefined; setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight))); } @@ -1221,8 +1221,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp handleScrollToSelection: (editorView) => { const docPos = editorView.coordsAtPos(editorView.state.selection.from); const viewRect = self._ref.current!.getBoundingClientRect(); - if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) { - docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale); + const scrollRef = self._scrollRef.current; + if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) { + const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale; + if (!LinkDocPreview.LinkInfo) { + scrollPos && smoothScroll(500, scrollRef, scrollPos); + } else { + scrollRef.scrollTo({ top: scrollPos }); + } } return true; }, @@ -1238,7 +1244,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView); const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { const { state: { tr }, dispatch } = this._editorView; @@ -1247,7 +1252,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp (this._editorView as any).TextView = this; } - const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; + const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); @@ -1341,7 +1346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onPointerUp = (e: React.PointerEvent): void => { if (!this._downEvent) return; this._downEvent = false; - if (!(e.nativeEvent as any).formattedHandled) { + if (!(e.nativeEvent as any).formattedHandled && this.active(true)) { const editor = this._editorView!; FormattedTextBoxComment.textBox = this; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); @@ -1369,13 +1374,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.preventDefault(); } FormattedTextBoxComment.Hide(); - if (FormattedTextBoxComment.linkDoc) { - if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right"); - } else { - LinkManager.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, this.props, false); - } - } (e.nativeEvent as any).formattedHandled = true; @@ -1388,6 +1386,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onFocused = (e: React.FocusEvent): void => { FormattedTextBox.FocusedBox = this; this.tryUpdateHeight(); + //applyDevTools.applyDevTools(this._editorView); // see if we need to preserve the insertion point const prosediv = this.ProseRef?.children?.[0] as any; @@ -1416,6 +1415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } + static _highlightStyleSheet: any = addStyleSheet(); static _bulletStyleSheet: any = addStyleSheet(); static _userStyleSheet: any = addStyleSheet(); _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle @@ -1541,6 +1541,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; + + const state = this._editorView!.state; + const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); + if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { + try { + translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => { + setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => { + this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; + }), 1000); + }); + } catch (e) { console.log(e.message); } + this._lastText = curText; + } } _lastTimedMark: Mark | undefined = undefined; @@ -1589,9 +1602,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. } - onscrolled = (ev: React.UIEvent) => { - if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && !this._animatingScroll) { - this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop; + onScroll = (ev: React.UIEvent) => { + if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { + this._ignoreScroll = true; + this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop; + this._ignoreScroll = false; } } @@ -1607,8 +1622,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.layoutDoc.limitHeight = undefined; this.layoutDoc._autoHeight = false; } - const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0); - const dh = NumCast(this.rootDoc._height, 0); + const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); + const dh = NumCast(this.rootDoc._height); const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight); if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... @@ -1635,8 +1650,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } @computed get sidebarHandle() { - const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length; - return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) : + const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; + return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) : <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }} onPointerDown={this.sidebarDown} @@ -1655,9 +1670,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp xMargin: 0, yMargin: 0, chromeStatus: "enabled", - scaleField: this.annotationKey + "-scale", + scaleField: this.SidebarKey + "-scale", isAnnotationOverlay: true, - fieldKey: this.annotationKey, + fieldKey: this.SidebarKey, fitContentsToDoc: fitToBox, select: emptyFunction, active: this.annotationsActive, @@ -1670,12 +1685,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ScreenToLocalTransform: this.sidebarScreenToLocal, renderDepth: this.props.renderDepth + 1, }; - return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : + return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? - <CollectionFreeFormView {...collectionProps} /> : - <CollectionStackingView {...collectionProps} />} + {this.layoutDoc.sidebarViewType === "translation" ? + <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> : + this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? + <CollectionFreeFormView {...collectionProps} /> : + <CollectionStackingView {...collectionProps} />} </div>; } @@ -1709,7 +1726,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp <div className={`formattedTextBox-cont`} ref={this._ref} style={{ overflow: this.layoutDoc._autoHeight ? "hidden" : undefined, - width: "100%", height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined), background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)), color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)), @@ -1728,14 +1744,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onPointerDown={this.onPointerDown} onMouseUp={this.onMouseUp} onWheel={this.onPointerWheel} - onPointerEnter={action(() => this._entered = true)} - onPointerLeave={action(e => { - this._entered = false; - const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); - for (let child: any = target; child; child = child?.parentElement) { - child === this._ref.current! && (this._entered = true); - } - })} onDoubleClick={this.onDoubleClick} > <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef} @@ -1744,7 +1752,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined, overflow: this.layoutDoc._singleLine ? "hidden" : undefined, }} - onScroll={this.onscrolled} onDrop={this.ondrop} > + onScroll={this.onScroll} onDrop={this.ondrop} > <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget} style={{ padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`, diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss index 81afba4d7..55b8446e9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -1,6 +1,8 @@ -.FormattedTextBox-tooltip { +.formattedTextBox-tooltip { position: absolute; - pointer-events: none; + pointer-events: all; + height: 100%; + overflow: hidden; z-index: 20; background: white; border: 1px solid silver; @@ -9,70 +11,16 @@ -webkit-transform: translateX(-50%); transform: translateX(-50%); box-shadow: 3px 3px 1.5px grey; - - .FormattedTextBoxComment { - background-color: white; - border: 8px solid white; - //width: 200px; - - //display: flex; - .FormattedTextBoxComment-info { - - margin-bottom: 37px; - - .FormattedTextBoxComment-title { - padding-right: 4px; - float: left; - - .FormattedTextBoxComment-description { - text-decoration: none; - font-style: italic; - color: rgb(95, 97, 102); - font-size: 10px; - } - } - - .FormattedTextBoxComment-button { - display: inline; - padding-left: 6px; - padding-right: 6px; - padding-top: 2.5px; - padding-bottom: 2.5px; - width: 17px; - height: 17px; - margin: 0; - margin-right: 3px; - border-radius: 50%; - pointer-events: auto; - background-color: rgb(0, 0, 0); - color: rgb(255, 255, 255); - transition: transform 0.2s; - text-align: center; - position: relative; - font-size: 12px; - - &:hover { - background-color: rgb(77, 77, 77); - cursor: pointer; - } - } - } - - .FormattedTextBoxComment-preview-wrapper { - //width: 170px; - height: 170px; - overflow: hidden; - //padding-top: 5px; - margin-top: 10px; - margin-bottom: 8px; - align-content: center; - justify-content: center; - background-color: rgb(160, 160, 160); - } + max-width: 400; + max-height: 235; + height:max-content; + .formattedTextBox-tooltipText { + height: max-content; + text-overflow: ellipsis; } } -.FormattedTextBox-tooltip:before { +.formattedTextBox-tooltip:before { content: ""; height: 0; width: 0; @@ -85,7 +33,7 @@ border-top-color: silver; } -.FormattedTextBox-tooltip:after { +.formattedTextBox-tooltip:after { content: ""; height: 0; width: 0; @@ -96,12 +44,4 @@ border: 5px solid transparent; border-bottom-width: 0; border-top-color: white; -} - -.FormattedTextBoxComment-buttons { - display: none; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); }
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 5371bd10a..89df5e246 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,43 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, observable } from "mobx"; import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import wiki from "wikijs"; -import { Doc, DocCastAsync, DocListCast, Opt } from "../../../../fields/Doc"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { LinkManager } from "../../../util/LinkManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { DocumentLinksButton } from "../DocumentLinksButton"; -import { DocumentView } from "../DocumentView"; +import { Doc } from "../../../../fields/Doc"; import { LinkDocPreview } from "../LinkDocPreview"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import { schema } from "./schema_rts"; -import React = require("react"); -export let formattedTextBoxCommentPlugin = new Plugin({ - view(editorView) { return new FormattedTextBoxComment(editorView); } -}); -export function findOtherUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); -} -export function findUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid); -} -export function findLinkMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.type === schema.marks.linkAnchor); -} +export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } +export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } +export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); } export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0; - let nbef = rpos.nodeBefore; + let before = 0, nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { before += nbef.nodeSize; rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); @@ -46,8 +20,7 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma return before; } export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0; - let naft = rpos.nodeAfter; + let after = 0, naft = rpos.nodeAfter; while (naft && finder(naft.marks)) { after += naft.nodeSize; rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); @@ -59,283 +32,93 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target. // this will also display metadata information about text when the view is configured to display things like other people who authored text. // - export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; - static tooltipInput: HTMLInputElement; - static start: number; - static end: number; - static mark: Mark; + static startUserMarkRegion: number; + static endUserMarkRegion: number; + static userMark: Mark; static textBox: FormattedTextBox | undefined; - static linkDoc: Doc | undefined; - - static _deleteRef: Opt<HTMLDivElement | null>; - static _followRef: Opt<HTMLDivElement | null>; - static _nextRef: Opt<HTMLDivElement | null>; - - static _lastState?: EditorState; - static _lastView?: EditorView; - - @observable static _hrefInd = 0; - static _hrefs: string[] | undefined = []; constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { - const root = document.getElementById("root"); - FormattedTextBoxComment.tooltipInput = document.createElement("input"); - FormattedTextBoxComment.tooltipInput.type = "checkbox"; - FormattedTextBoxComment.tooltip = document.createElement("div"); - FormattedTextBoxComment.tooltipText = document.createElement("div"); - //FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; - FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; - FormattedTextBoxComment.tooltip.style.maxWidth = "400px"; - FormattedTextBoxComment.tooltip.style.maxHeight = "235px"; - //FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - FormattedTextBoxComment.tooltip.style.overflow = "hidden"; - FormattedTextBoxComment.tooltip.style.display = "none"; - // FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); - FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => { - const keep = e.target && (e.target as any).type === "checkbox" ? true : false; - const textBox = FormattedTextBoxComment.textBox; - const linkDoc = FormattedTextBoxComment.linkDoc; - if (linkDoc && !keep && textBox) { - if (linkDoc.author) { - if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) { - this.deleteLink(); - } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) { - FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length]); - } else { - FormattedTextBoxComment.linkDoc = undefined; - if (linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right"); - } else { - const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc); - target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey); - } - } - } - } 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, useCors: true }), "add:right"); - } - keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( - FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); + const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div"); + const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div"); + tooltip.className = "FormattedTextBox-tooltip"; + tooltipText.className = "FormattedTextBox-tooltipText"; + tooltip.style.display = "none"; + tooltip.appendChild(tooltipText); + tooltip.onpointerdown = (e: PointerEvent) => { + const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment; + false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark); e.stopPropagation(); e.preventDefault(); }; - root?.appendChild(FormattedTextBoxComment.tooltip); + document.getElementById("root")?.appendChild(tooltip); } } - - @undoBatch - deleteLink = action(() => { - FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null; - LinkDocPreview.LinkInfo = undefined; - DocumentLinksButton.EditLink = undefined; - FormattedTextBoxComment.Hide(); - }); - public static Hide() { FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.linkDoc = undefined; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText); - } catch (e) { } + FormattedTextBoxComment.tooltip.style.display = "none"; } - public static SetState(textBox: any, start: number, end: number, mark: Mark) { + public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; - FormattedTextBoxComment.start = start; - FormattedTextBoxComment.end = end; - FormattedTextBoxComment.mark = mark; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); + FormattedTextBoxComment.startUserMarkRegion = start; + FormattedTextBoxComment.endUserMarkRegion = end; + FormattedTextBoxComment.userMark = mark; + FormattedTextBoxComment.tooltip.style.display = ""; } - static showCommentbox(set: string, view: EditorView, nbef: number) { + static showCommentbox(view: EditorView, nbef: number) { const state = view.state; - if (set !== "none") { - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - } - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); + // These are in screen coordinates + const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left) + const left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + FormattedTextBoxComment.tooltip.style.display = ""; } - static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") { - // Don't do anything if the document/selection didn't change - if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) { - return; + static update(view: EditorView, lastState?: EditorState, hrefs: string = "") { + if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { + FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined); } - FormattedTextBoxComment._lastState = lastState; - FormattedTextBoxComment._lastView = view; - FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined; - FormattedTextBoxComment._hrefInd = 0; - FormattedTextBoxComment.linkDoc = undefined; - FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]); } - static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") { + static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) { const state = view.state; - const textBox = FormattedTextBoxComment.textBox; - if (!textBox || !textBox.props) { - return; - } - let set = "none"; - let nbef = 0; - FormattedTextBoxComment.tooltipInput.style.display = "none"; - FormattedTextBoxComment.tooltip.style.width = ""; - FormattedTextBoxComment.tooltip.style.height = ""; - (FormattedTextBoxComment.tooltipText as any).href = ""; - FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; - FormattedTextBoxComment.tooltipText.style.overflow = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const noselection = view.state.selection.$from === view.state.selection.$to; + const noselection = state.selection.$from === state.selection.$to; let child: any = null; 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, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); + FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString(); - set = ""; - FormattedTextBoxComment.tooltipInput.style.display = ""; - } + FormattedTextBoxComment.showCommentbox(view, nbef); + } else FormattedTextBoxComment.Hide(); } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. - if (set === "none" && state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + if (state.selection.$from && hrefs) { + const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - child = child || (nbef && state.selection.$from.nodeBefore); - const mark = child ? findLinkMark(child.marks) : undefined; - const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href; - if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) { - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText); - } catch (e) { } - FormattedTextBoxComment.tooltipText = document.createElement("div"); - FormattedTextBoxComment.tooltipText.className = "FormattedTextBoxComment-toolTipText"; - FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltipText.style.cursor = "pointer"; - FormattedTextBoxComment.tooltipText.textContent = "URL: " + href; - (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - - if (href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(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"; - } - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - FormattedTextBoxComment.tooltipText.textContent = "target not found..."; - (FormattedTextBoxComment.tooltipText as any).href = ""; - docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { - if (linkDoc instanceof Doc) { - (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.linkDoc = linkDoc; - const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; - if (anchor !== target && anchor && target) { - target._scrollPreviewY = NumCast(anchor?.y); - } - if (target?.author) { - FormattedTextBoxComment.showCommentbox("", view, nbef); - - const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title; - - const docPreview = <div className="FormattedTextBoxComment"> - <div className="FormattedTextBoxComment-info"> - <div className="FormattedTextBoxComment-title"> - {title} - {FormattedTextBoxComment.linkDoc.description === "" ? (null) : - <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>} - </div> - <div className="wrapper" style={{ float: "right" }}> - {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" /> - </div> - </Tooltip>} - - <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" /> - </div> - </Tooltip> - - <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" /> - </div> - </Tooltip> - </div> - </div> - <div className="FormattedTextBoxComment-preview-wrapper"> - <DocumentView - Document={target} - moveDocument={returnFalse} - rootSelected={returnFalse} - ScreenToLocalTransform={Transform.Identity} - parentActive={returnFalse} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - dontRegisterView={true} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - renderDepth={-1} - PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))} - focus={emptyFunction} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined} - NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined} - /> - </div> - </div>; - - FormattedTextBoxComment.showCommentbox("", view, nbef); - - ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText); - - //FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - } - } - }); - } - set = ""; - } + nbef && naft && LinkDocPreview.SetLinkInfo({ + docProps: textBox.props, + linkSrc: textBox.rootDoc, + location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)), + hrefs, + showHeader: true + }); } - FormattedTextBoxComment.showCommentbox(set, view, nbef); } destroy() { } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index dc630af74..5da868281 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -806,7 +806,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { <p>Linked to:</p> <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} /> <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button> - <div className="divider"></div> + <div className="divider" /> <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button> </div>; @@ -819,7 +819,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const node = this.view.state.selection.$from.nodeAfter; const link = node && node.marks.find(m => m.type.name === "link"); if (link) { - const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined; + const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined; if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; @@ -851,21 +851,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target); + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); } @undoBatch @action deleteLink = () => { if (this.view) { - const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor); - if (link) { - const allLinks = link.attrs.allLinks.slice(); - this.TextView.RemoveLinkFromSelection(link.attrs.allLinks); + const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor); + if (linkAnchor) { + const allAnchors = linkAnchor.attrs.allAnchors.slice(); + this.TextView.RemoveAnchorFromSelection(allAnchors); // 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. - allLinks.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)); + allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => { + const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); }); } } @@ -877,8 +877,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { let startIndex = $start.index(); let endIndex = $start.indexAfter(); - while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--; - while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++; + while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--; + while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++; let startPos = $start.start(); let endPos = startPos; diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index abbb89780..2252de3d6 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -138,6 +138,7 @@ export class DashDocView { removeDocument={removeDoc} layerProvider={this._textBox.props.layerProvider} styleProvider={this._textBox.props.styleProvider} + docViewPath={this._textBox.props.docViewPath} ScreenToLocalTransform={this.getDocTransform} addDocTab={this._textBox.props.addDocTab} pinToPres={returnFalse} diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss index e2149e9c1..0e4b752ac 100644 --- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss +++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss @@ -103,7 +103,7 @@ } .tooltipMenu, .basic-tools { - z-index: 20000; + z-index: 3000; pointer-events: all; padding: 3px; padding-bottom: 5px; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index ea239e4d3..655ee7e44 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -22,8 +22,7 @@ export const marks: { [index: string]: MarkSpec } = { // element. linkAnchor: { attrs: { - allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] }, - showPreview: { default: true }, + allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, location: { default: null }, title: { default: null }, docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text @@ -31,17 +30,23 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, parseDOM: [{ tag: "a[href]", getAttrs(dom: any) { - return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.dataset.targetids }], location: dom.getAttribute("location"), }; + return { + location: dom.getAttribute("location"), + title: dom.getAttribute("title") + }; } }], toDOM(node: any) { - const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, ""); - const targethrefs = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.href, ""); - const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, ""); + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { + ...node.attrs, + class: "prosemirror-attribution", + href: node.attrs.allAnchors[0].href, + }, node.attrs.title], ["br"]] : //node.attrs.allLinks.length === 1 ? - ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, "data-targethrefs": targethrefs, title: `${node.attrs.title}`, href: node.attrs.allLinks[0]?.href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0]; + ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; // ["div", { class: "prosemirror-anchor" }, // ["span", { class: "prosemirror-linkBtn" }, // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0], diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 722c0a836..5d9c8b56d 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -22,6 +22,8 @@ export const nodes: { [index: string]: NodeSpec } = { content: "block+" }, + paragraph: ParagraphNodeSpec, + audiotag: { group: "block", attrs: { @@ -64,8 +66,6 @@ export const nodes: { [index: string]: NodeSpec } = { parseDOM: [{ tag: "footnote" }] }, - paragraph: ParagraphNodeSpec, - // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. blockquote: { content: "block*", diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js index 746c93868..5bc323a4d 100644 --- a/src/client/views/nodes/formattedText/prosemirrorPatches.js +++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js @@ -143,17 +143,21 @@ 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) { + var markAllAnchors = attrs.allAnchors !== undefined ? Array.from(attrs.allAnchors.map(al => al.anchorId)) : []; for (var i = 0; i < set.length; i++) { if (set[i].type == mark) { - if (Array.from(Object.keys(attrs)).reduce((p, akey) => { + return Array.from(Object.keys(attrs)).reduce((p, akey) => { if (p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey])) return true; - set[i].attrs.allLinks = Array.from(set[i].attrs.allLinks).filter(a => !Array.from(attrs.allLinks.map(al => al.targetId)).includes(a.targetId) || !Array.from(attrs.allLinks.map(al => al.linkId).includes(a.linkId))) + // bcz: hack to allow special case of anchors in Dash to be removed from a mark which has multiple + if (set[i].attrs.allAnchors !== undefined) { + set[i].attrs.allAnchors = set[i].attrs.allAnchors.filter(a => !markAllAnchors.includes(a.anchorId)); + if (set[i].attrs.allAnchors.length) return undefined; + } return false; - }, true)) { - return set[i]; - } + }, true); } } + return undefined; }; // :: (number, number, ?union<Mark, MarkType>) → this @@ -170,7 +174,9 @@ function removeMarkWithAttrs(tr, from, to, mark, attrs) { step++; var toRemove = null; if (mark) { - if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; } + const inset = isInSetWithAttrs(mark, node.marks, attrs); + if (inset === true) { toRemove = [mark]; } + // if (inset === undefined) { console.log("lightened") } // anchorids were removed from the mark, but the mark wasn't removed since there are still anchorsids left } else { toRemove = node.marks; } |
