diff options
Diffstat (limited to 'src')
20 files changed, 386 insertions, 117 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 533bc8485..417ddf989 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -218,7 +218,7 @@ export namespace DragManager { docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); - remProps.map(prop => drop[prop] = undefined) + remProps.map(prop => drop[prop] = undefined); }); batch.end(); } @@ -353,7 +353,7 @@ export namespace DragManager { const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; const rect = ele.getBoundingClientRect(); const scaleX = rect.width / ele.offsetWidth, - scaleY = rect.height / ele.offsetHeight; + scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; elesCont.left = Math.min(rect.left, elesCont.left); elesCont.top = Math.min(rect.top, elesCont.top); elesCont.right = Math.max(rect.right, elesCont.right); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 62a95116f..49c15f764 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,28 +1,28 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowAltCircleDown, faPhotoVideo, faArrowAltCircleUp, faArrowAltCircleRight, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faArrowAltCircleDown, faArrowAltCircleRight, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faPhotoVideo, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../fields/Doc"; import { RichTextField } from '../../fields/RichTextField'; -import { NumCast, StrCast, Cast } from "../../fields/Types"; +import { Cast, NumCast } from "../../fields/Types"; import { emptyFunction, setupMoveUpEvents } from "../../Utils"; +import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; +import { Docs, DocUtils } from '../documents/Documents'; +import { DragManager } from '../util/DragManager'; import { UndoManager } from "../util/UndoManager"; import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView'; import { ParentDocSelector } from './collections/ParentDocumentSelector'; import './collections/ParentDocumentSelector.scss'; import './DocumentButtonBar.scss'; -import { LinkMenu } from "./linking/LinkMenu"; +import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentView } from './nodes/DocumentView'; import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); -import { DragManager } from '../util/DragManager'; -import { MetadataEntryMenu } from './MetadataEntryMenu'; -import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; -import { Docs } from '../documents/Documents'; +import { DocumentLinksButton } from './nodes/DocumentLinksButton'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -113,39 +113,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get view0() { return this.props.views()?.[0]; } - @action - onLinkButtonMoved = (e: PointerEvent) => { - if (this._linkButton.current !== null) { - const linkDrag = UndoManager.StartBatch("Drag Link"); - this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, { - dragComplete: dropEv => { - const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop - if (this.view0 && linkDoc) { - !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink"); - - // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks) - // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures - // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView - // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it. - dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData); - runInAction(() => this.view0!._link = linkDoc); - setTimeout(action(() => this.view0!._link = undefined), 0); - } - linkDrag?.end(); - }, - hideSource: false - }); - return true; - } - return false; - } - - - onLinkButtonDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, emptyFunction); - } - - @computed get considerGoogleDocsPush() { const targetDoc = this.view0?.props.Document; @@ -236,20 +203,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV } @computed - get linkButton() { - const view0 = this.view0; - const linkCount = view0 && DocListCast(view0.props.Document.links).length; - return !view0 ? (null) : <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} - content={<LinkMenu docView={view0} addDocTab={view0.props.addDocTab} changeFlyout={emptyFunction} />}> - <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} > - {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />} - </div> - </Flyout> - </div>; - } - - @computed get metadataButton() { const view0 = this.view0; return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout"> @@ -318,7 +271,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const considerPush = isText && this.considerGoogleDocsPush; return <div className="documentButtonBar"> <div className="documentButtonBar-button"> - {this.linkButton} + <DocumentLinksButton View={this.view0!} /> </div> <div className="documentButtonBar-button"> {this.templateButton} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index beb6155ca..d7b0ab7a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -552,7 +552,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div> </div > - <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2, top: bounds.b + this._resizeBorderWidth / 2 }}> + <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}> <DocumentButtonBar views={SelectionManager.SelectedDocuments} /> </div> </div > diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 27755737e..7bc8cf6a7 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -19,6 +19,7 @@ import { MarqueeView } from "./collections/collectionFreeForm/MarqueeView"; import { DocumentDecorations } from "./DocumentDecorations"; import { MainView } from "./MainView"; import { DocumentView } from "./nodes/DocumentView"; +import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>; @@ -77,6 +78,7 @@ export default class KeyManager { // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; case "escape": + DocumentLinksButton.StartLink = undefined; const main = MainView.Instance; Doc.SetSelectedTool(InkTool.None); if (main.isPointerDown) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 885e50aba..055e39669 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -5,7 +5,7 @@ import { faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, - faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft + faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown } from '@fortawesome/free-solid-svg-icons'; import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -56,6 +56,9 @@ import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { SnappingManager } from '../util/SnappingManager'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { DocumentManager } from '../util/DocumentManager'; +import { DocumentLinksButton } from './nodes/DocumentLinksButton'; +import { LinkMenu } from './linking/LinkMenu'; +import { LinkDocPreview } from './nodes/LinkDocPreview'; @observer export class MainView extends React.Component { @@ -136,7 +139,7 @@ export class MainView extends React.Component { faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell, - faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft); + faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faQuoteLeft, faSortAmountDown); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -585,6 +588,10 @@ export class MainView extends React.Component { {this.mainContent} </GestureOverlay> <PreviewCursor /> + {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)} + {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors} + linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} + addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)} <ContextMenu /> <RadialMenu /> <PDFMenu /> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ee987abdb..79c577b6d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -527,7 +527,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp dragData.dropAction = doc.dropAction as dropActionType; DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY); return true; - } return false + } + return false; }, returnFalse, emptyFunction); }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3a8778c14..936174b52 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -47,6 +47,7 @@ import { Timeline } from "../../animationtimeline/Timeline"; import { SnappingManager } from "../../../util/SnappingManager"; import { ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke"; import { DocumentType } from "../../../documents/DocumentTypes"; +import { DocumentLinksButton } from "../../nodes/DocumentLinksButton"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -245,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P // const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); // this.props.addDocument(source); // linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed - return false; + return false; } else { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); this.props.addDocument(source); @@ -589,6 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onClick = (e: React.MouseEvent) => { if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { if (Date.now() - this._lastTap < 300) { + runInAction(() => DocumentLinksButton.StartLink = undefined); const docpt = this.getTransform().transformPoint(e.clientX, e.clientY); this.scaleAtPt(docpt, 1); e.stopPropagation(); diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss index 4e85fe0ca..3e54d001b 100644 --- a/src/client/views/globalCssVariables.scss +++ b/src/client/views/globalCssVariables.scss @@ -12,6 +12,7 @@ $lighter-alt-accent: rgb(207, 220, 240); $darker-alt-accent: rgb(178, 206, 248); $intermediate-color: #9c9396; $dark-color: #121721; +$link-color: lightBlue; $antimodemenu-height: 35px; // fonts $sans-serif: "Noto Sans", diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss index 7dee22f66..c372e7098 100644 --- a/src/client/views/linking/LinkMenu.scss +++ b/src/client/views/linking/LinkMenu.scss @@ -8,6 +8,10 @@ .linkMenu-list { max-height: 200px; overflow-y: scroll; + position: absolute; + z-index: 10; + background: $link-color; + min-width: 150px } .linkMenu-group { diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 56f40ad69..0fcc0f0b9 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -4,11 +4,12 @@ import { DocumentView } from "../nodes/DocumentView"; import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); -import { Doc } from "../../../fields/Doc"; +import { Doc, Opt } from "../../../fields/Doc"; import { LinkManager } from "../../util/LinkManager"; import { LinkMenuGroup } from "./LinkMenuGroup"; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { library } from "@fortawesome/fontawesome-svg-core"; +import { DocumentLinksButton } from "../nodes/DocumentLinksButton"; library.add(faTrash); @@ -16,16 +17,29 @@ interface Props { docView: DocumentView; changeFlyout: () => void; addDocTab: (document: Doc, where: string) => boolean; + location: number[]; } @observer export class LinkMenu extends React.Component<Props> { @observable private _editingLink?: Doc; + @observable private _linkMenuRef: Opt<HTMLDivElement | null>; @action + onClick = (e: PointerEvent) => { + if (!Array.from(this._linkMenuRef?.getElementsByTagName((e.target as HTMLElement).tagName) || []).includes(e.target as any)) { + DocumentLinksButton.EditLink = undefined; + } + } + @action componentDidMount() { this._editingLink = undefined; + document.addEventListener("pointerdown", this.onClick); + } + + componentWillUnmount() { + document.removeEventListener("pointerdown", this.onClick); } clearAllLinks = () => { @@ -56,20 +70,11 @@ export class LinkMenu extends React.Component<Props> { render() { const sourceDoc = this.props.docView.props.Document; const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); - if (this._editingLink === undefined) { - return ( - <div className="linkMenu"> - {/* <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button> */} - {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */} - <div className="linkMenu-list"> - {this.renderAllGroups(groups)} - </div> - </div> - ); - } else { - return ( - <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor> - ); - } + return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}> + {!this._editingLink ? + this.renderAllGroups(groups) : + <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} /> + } + </div>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss new file mode 100644 index 000000000..484f8c469 --- /dev/null +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -0,0 +1,38 @@ +@import "../globalCssVariables.scss"; + + +.documentLinksButton, +.documentLinksButton-endLink, +.documentLinksButton-startLink { + height: 20px; + width: 20px; + position: absolute; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + color: black; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background: deepskyblue; + transform: scale(1.05); + cursor: default; + } +} +.documentLinksButton { + background-color: $link-color; +} +.documentLinksButton-endLink { + border: red solid 2px; +} +.documentLinksButton-startLink { + border: red solid 2px; + background-color: rgba(255, 192, 203, 0.5); +}
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx new file mode 100644 index 000000000..ef900fff6 --- /dev/null +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -0,0 +1,103 @@ +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast } from "../../../fields/Doc"; +import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils"; +import { DragManager } from "../../util/DragManager"; +import { UndoManager } from "../../util/UndoManager"; +import './DocumentLinksButton.scss'; +import { DocumentView } from "./DocumentView"; +import React = require("react"); +import { DocUtils } from "../../documents/Documents"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { LinkDocPreview } from "./LinkDocPreview"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +interface DocumentLinksButtonProps { + View: DocumentView; + Offset?: number[]; +} +@observer +export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> { + private _linkButton = React.createRef<HTMLDivElement>(); + + @action + onLinkButtonMoved = (e: PointerEvent) => { + if (this._linkButton.current !== null) { + const linkDrag = UndoManager.StartBatch("Drag Link"); + this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, { + dragComplete: dropEv => { + const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop + if (this.props.View && linkDoc) { + !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink"); + + // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks) + // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures + // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView + // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it. + dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData); + runInAction(() => this.props.View._link = linkDoc); + setTimeout(action(() => this.props.View._link = undefined), 0); + } + linkDrag?.end(); + }, + hideSource: false + }); + return true; + } + return false; + } + + @observable static StartLink: DocumentView | undefined; + onLinkButtonDown = (e: React.PointerEvent): void => { + setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { + if (doubleTap) { + DocumentLinksButton.StartLink = this.props.View; + } else { + DocumentLinksButton.EditLink = this.props.View; + DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY]; + } + })); + } + completeLink = (e: React.PointerEvent): void => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => { + if (doubleTap) { + if (DocumentLinksButton.StartLink === this.props.View) { + DocumentLinksButton.StartLink = undefined; + } else { + DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View && + DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"); + } + } + })); + } + + @observable + public static EditLink: DocumentView | undefined; + public static EditLinkLoc: number[] = [0, 0]; + + @computed + get linkButton() { + const links = DocListCast(this.props.View.props.Document.links); + return !links.length || links[0].hidden ? (null) : + <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ position: "absolute", left: this.props.Offset?.[0] }}> + <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }} + onPointerDown={this.onLinkButtonDown} + onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} + onPointerEnter={action(e => LinkDocPreview.LinkInfo = { + addDocTab: this.props.View.props.addDocTab, + linkSrc: this.props.View.props.Document, + linkDoc: links[0], + Location: [e.clientX, e.clientY + 20] + })} > + {links.length ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />} + </div> + {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)} + {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)} + </div>; + } + render() { + return this.linkButton; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4e6038f86..8178c8aa7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import "./DocumentView.scss"; import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +import { DocumentLinksButton } from './DocumentLinksButton'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -583,10 +584,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu toggleLinkButtonBehavior = (): void => { if (this.Document.isLinkButton || this.layoutDoc.onClick || this.Document.ignoreClick) { this.Document.isLinkButton = false; + const first = DocListCast(this.Document.links).find(d => d instanceof Doc); + first && (first.hidden = false); this.Document.ignoreClick = false; this.layoutDoc.onClick = undefined; } else { this.Document.isLinkButton = true; + const first = DocListCast(this.Document.links).find(d => d instanceof Doc); + first && (first.hidden = true); this.Document.followLinkZoom = false; this.Document.followLinkLocation = undefined; } @@ -596,8 +601,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu toggleFollowInPlace = (): void => { if (this.Document.isLinkButton) { this.Document.isLinkButton = false; + const first = DocListCast(this.Document.links).find(d => d instanceof Doc); + first && (first.hidden = false); } else { this.Document.isLinkButton = true; + const first = DocListCast(this.Document.links).find(d => d instanceof Doc); + first && (first.hidden = true); this.Document.followLinkZoom = true; this.Document.followLinkLocation = "inPlace"; } @@ -607,6 +616,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu toggleFollowOnRight = (): void => { if (this.Document.isLinkButton) { this.Document.isLinkButton = false; + const first = DocListCast(this.Document.links).find(d => d instanceof Doc); + first && (first.hidden = false); } else { this.Document.isLinkButton = true; this.Document.followLinkZoom = false; @@ -735,10 +746,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); - if (!options) { - options = { description: "Options...", subitems: optionItems, icon: "compass" }; - cm.addItem(options); - } + optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" }); + !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; @@ -1038,7 +1047,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu select={this.select} onClick={this.onClickHandler} layoutKey={this.finalLayoutKey} /> - {this.anchors} + {this.layoutDoc.showLinks ? this.anchors : (null)} + {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />} </div> ); } @@ -1062,7 +1072,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu anchorPanelHeight = () => this.props.PanelHeight() || 1; @computed get anchors() { TraceMobx(); - return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => + return this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.presBox || this.props.dontRegisterView ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => <DocumentView {...this.props} key={i + 1} Document={d} ContainingCollectionView={this.props.ContainingCollectionView} @@ -1070,6 +1080,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu PanelWidth={this.anchorPanelWidth} PanelHeight={this.anchorPanelHeight} ContentScaling={returnOne} + dontRegisterView={false} forcedBackgroundColor={returnTransparent} removeDocument={this.hideLinkAnchor} pointerEvents={false} @@ -1193,7 +1204,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu color: StrCast(this.layoutDoc.color, "inherit"), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, - boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined, + boxShadow: this.Document.isLinkButton && !this.props.dontRegisterView && this.props.forcedBackgroundColor?.(this.Document) !== "transparent" ? + StrCast(this.props.Document._linkButtonShadow, "lightblue 0em 0em 1em") : + this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : + undefined, background: finalColor, opacity: finalOpacity, fontFamily: StrCast(this.Document._fontFamily, "inherit"), diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 2b64cdab6..2bcc6168b 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -18,6 +18,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SelectionManager } from "../../util/SelectionManager"; import { TraceMobx } from "../../../fields/util"; import { Id } from "../../../fields/FieldSymbols"; +import { LinkDocPreview } from "./LinkDocPreview"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -132,6 +133,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch ); const small = this.props.PanelWidth() <= 1; return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`} + onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} + onPointerEnter={action(e => LinkDocPreview.LinkInfo = { + addDocTab: this.props.addDocTab, + linkSrc: this.props.ContainingCollectionDoc!, + linkDoc: this.rootDoc, + Location: [e.clientX, e.clientY + 20] + })} onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu} ref={this._ref} style={{ background: c, diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx new file mode 100644 index 000000000..126dc240a --- /dev/null +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -0,0 +1,107 @@ +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from "mobx-react"; +import wiki from "wikijs"; +import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { DocumentManager } from "../../util/DocumentManager"; +import { Transform } from "../../util/Transform"; +import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; +import React = require("react"); +import { DocumentView } from './DocumentView'; + +interface Props { + linkDoc?: Doc; + linkSrc?: Doc; + href?: string; + backgroundColor: (doc: Doc) => string; + addDocTab: (document: Doc, where: string) => boolean; + location: number[]; +} +@observer +export class LinkDocPreview extends React.Component<Props> { + @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>; + @observable _targetDoc: Opt<Doc>; + @observable _toolTipText = ""; + + componentDidUpdate() { this.updatePreview() } + componentDidMount() { this.updatePreview() } + async updatePreview() { + const linkDoc = this.props.linkDoc; + const linkSrc = this.props.linkSrc; + if (this.props.href) { + if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) { + wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500)))); + } else { + runInAction(() => this._toolTipText = "external => " + this.props.href); + } + } else if (linkDoc && linkSrc) { + const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); + const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + runInAction(() => { + this._toolTipText = ""; + this._targetDoc = target; + if (anchor !== this._targetDoc && anchor && this._targetDoc) { + this._targetDoc._scrollY = NumCast(anchor?.y); + } + }); + } + } + pointerDown = (e: React.PointerEvent) => { + if (this.props.linkDoc && this.props.linkSrc) { + DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc, + (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } else if (this.props.href) { + this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight"); + } + } + width = () => Math.min(350, NumCast(this._targetDoc?.[WidthSym](), 350)); + height = () => Math.min(350, NumCast(this._targetDoc?.[HeightSym](), 350)); + @computed get targetDocView() { + return !this._targetDoc ? + <div style={{ pointerEvents: "all", maxWidth: 350, maxHeight: 250, width: "100%", height: "100%", overflow: "hidden" }}> + <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}> + {this._toolTipText} + </div> + </div> : + <ContentFittingDocumentView + Document={this._targetDoc} + LibraryPath={emptyPath} + fitToBox={true} + backgroundColor={this.props.backgroundColor} + moveDocument={returnFalse} + rootSelected={returnFalse} + ScreenToLocalTransform={Transform.Identity} + parentActive={returnFalse} + addDocument={returnFalse} + removeDocument={returnFalse} + addDocTab={returnFalse} + pinToPres={returnFalse} + dontRegisterView={true} + docFilters={returnEmptyFilter} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + renderDepth={0} + PanelWidth={this.width} + PanelHeight={this.height} + focus={emptyFunction} + whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} + NativeWidth={returnZero} + NativeHeight={returnZero} + />; + } + + render() { + return <div className="linkDocPreview" + style={{ + position: "absolute", left: this.props.location[0], + top: this.props.location[1], width: this.width(), height: this.height(), + boxShadow: "black 2px 2px 1em" + }}> + {this.targetDocView} + </div>; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e01144f82..cdf231985 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -933,7 +933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - // applyDevTools.applyDevTools(this._editorView); + //applyDevTools.applyDevTools(this._editorView); const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { const { state: { tr }, dispatch } = this._editorView; @@ -1043,11 +1043,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (bounds && this.layoutDoc._chromeStatus !== "disabled") { const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); - console.log("y = " + y + " hgt = " + RichTextMenu.Instance.height + " cords = " + coords.top); if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); } - RichTextMenu.Instance.jumpTo(x, y); + setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250); } } onPointerWheel = (e: React.WheelEvent): void => { diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts index d80e64634..30da91710 100644 --- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts +++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts @@ -28,7 +28,7 @@ const ALIGN_PATTERN = /(left|right|center|justify)/; // https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js // :: NodeSpec A plain paragraph textblock. Represented in the DOM // as a `<p>` element. -const ParagraphNodeSpec: NodeSpec = { +export const ParagraphNodeSpec: NodeSpec = { attrs: { align: { default: null }, color: { default: null }, diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 68101fbde..839943aac 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,30 +1,33 @@ import React = require("react"); -import AntimodeMenu from "../../AntimodeMenu"; -import { observable, action, } from "mobx"; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; -import { schema } from "./schema_rts"; -import { EditorView } from "prosemirror-view"; +import { lift, wrapIn } from "prosemirror-commands"; +import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; +import { wrapInList } from "prosemirror-schema-list"; import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import { FieldViewProps } from "../FieldView"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { FormattedTextBoxProps, FormattedTextBox } from "./FormattedTextBox"; +import { EditorView } from "prosemirror-view"; +import { Doc } from "../../../../fields/Doc"; +import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; +import { Cast, StrCast, BoolCast } from "../../../../fields/Types"; import { unimplementedFunction, Utils } from "../../../../Utils"; -import { wrapInList } from "prosemirror-schema-list"; -import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; -import "./RichTextMenu.scss"; import { DocServer } from "../../../DocServer"; -import { Doc } from "../../../../fields/Doc"; -import { SelectionManager } from "../../../util/SelectionManager"; import { LinkManager } from "../../../util/LinkManager"; -const { toggleMark, setBlockType } = require("prosemirror-commands"); +import { SelectionManager } from "../../../util/SelectionManager"; +import AntimodeMenu from "../../AntimodeMenu"; +import { FieldViewProps } from "../FieldView"; +import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox"; +import { updateBullets } from "./ProsemirrorExampleTransfer"; +import "./RichTextMenu.scss"; +import { schema } from "./schema_rts"; +import { TraceMobx } from "../../../../fields/util"; +const { toggleMark } = require("prosemirror-commands"); library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); + @observer export default class RichTextMenu extends AntimodeMenu { static Instance: RichTextMenu; @@ -69,6 +72,7 @@ export default class RichTextMenu extends AntimodeMenu { super(props); RichTextMenu.Instance = this; this._canFade = false; + this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, @@ -143,7 +147,6 @@ export default class RichTextMenu extends AntimodeMenu { this.updateFromDash(view, lastState, this.editorProps); } - public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { if (this.view) { const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); @@ -201,7 +204,7 @@ export default class RichTextMenu extends AntimodeMenu { } else dispatch(tx); }); } else { - toggleMark(mark.type, mark.attrs)(state, (tx: any) => dispatch(tx)); + toggleMark(mark.type, mark.attrs)(state, dispatch); } } } @@ -411,8 +414,12 @@ export default class RichTextMenu extends AntimodeMenu { } insertBlockquote(state: EditorState<any>, dispatch: any) { - if (state.selection.empty) return false; - setBlockType(state.schema.nodes.blockquote)(state, (tx: any) => dispatch(tx)); + const path = (state.selection.$from as any).path; + if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { + lift(state, dispatch); + } else { + wrapIn(schema.nodes.blockquote)(state, dispatch); + } return true; } @@ -742,7 +749,7 @@ export default class RichTextMenu extends AntimodeMenu { @action toggleMenuPin = (e: React.MouseEvent) => { - this.Pinned = !this.Pinned; + Doc.UserDoc()["menuRichText-pinned"] = this.Pinned = !this.Pinned; if (!this.Pinned) { this.fadeOut(true); } @@ -758,7 +765,7 @@ export default class RichTextMenu extends AntimodeMenu { } render() { - + TraceMobx(); const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[ !this.collapsed ? this.getDragger() : (null), this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), @@ -771,7 +778,7 @@ export default class RichTextMenu extends AntimodeMenu { this.createHighlighterButton(), this.createLinkButton(), this.createBrushButton(), - this.createButton("indent", "Summarize", undefined, this.insertSummarizer), + this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote), ]}</div>; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 1a292d9af..0a867912f 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -1,7 +1,7 @@ import React = require("react"); import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import ParagraphNodeSpec from "./ParagraphNodeSpec"; +import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec"; const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -32,13 +32,29 @@ export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. blockquote: { - content: "block+", + content: "block*", group: "block", defining: true, parseDOM: [{ tag: "blockquote" }], toDOM() { return blockquoteDOM; } }, + + // blockquote: { + // ...ParagraphNodeSpec, + // defining: true, + // parseDOM: [{ + // tag: "blockquote", getAttrs(dom: any) { + // return getParagraphNodeAttrs(dom); + // } + // }], + // toDOM(node: any) { + // const dom = toParagraphDOM(node); + // (dom as any)[0] = 'blockquote'; + // return dom; + // }, + // }, + // :: NodeSpec A horizontal rule (`<hr>`). horizontal_rule: { group: "block", diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js index 269423482..763961958 100644 --- a/src/client/views/nodes/formattedText/prosemirrorPatches.js +++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js @@ -136,4 +136,6 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } return tr }) -}
\ No newline at end of file +} + + |