diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
6 files changed, 201 insertions, 73 deletions
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 8718bf329..8ae71c035 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -190,7 +190,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna } list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = this._fieldKey; + alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey; this.props.tbox.props.addDocTab(alias, "onRight"); } } @@ -205,9 +205,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna {this._fieldKey} </span>} - {/* <div className="dashFieldView-fieldSpan"> */} - {this.fieldValueContent} - {/* </div> */} + {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent} {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fabda4d20..4b06f2800 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -235,7 +235,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks); const tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); @@ -251,7 +250,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; - (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); + const lastmodified = "lastmodified"; + (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))) && (this.dataDoc[lastmodified] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || curLayout?.Data.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 (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) { if (!this._pause && !this.layoutDoc._timeStampOnEnter) { @@ -353,7 +353,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { let node = this._editorView.state.doc; - while (node.firstChild) node = node.firstChild; + while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; const titlestr = str.substr(0, Math.min(40, str.length)); this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); @@ -375,18 +375,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } - public highlightSearchTerms = (terms: string[]) => { + public highlightSearchTerms = (terms: string[], alt: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const length = res[0].length; let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); + + const lastSel = Math.min(flattened.length - 1, this._searchIndex); flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + if (alt === true) { + if (this._searchIndex > 1) { + this._searchIndex += -2; + } + else if (this._searchIndex === 1) { + this._searchIndex = length - 1; + } + else if (this._searchIndex === 0 && length !== 1) { + this._searchIndex = length - 2; + } + + } + else { + + } + const index = this._searchIndex; + + Doc.GetProto(this.dataDoc).searchIndex = index; } } @@ -397,6 +419,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + } if (FormattedTextBox.PasteOnLoad) { const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); @@ -620,6 +643,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); + appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" }); cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const options = cm.findByDescription("Options..."); @@ -631,6 +655,67 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._downX = this._downY = Number.NaN; } + progressivizeText = () => { + const list = this.ProseRef?.getElementsByTagName("li"); + const mainBulletText: string[] = []; + const mainBulletList: Doc[] = []; + if (list) { + const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0]; + mainBulletList.push.apply(mainBulletList, newBullets); + } + console.log(mainBulletList.length); + const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 }); + mainBulletList.push(title); + const doc = Docs.Create.FreeformDocument(mainBulletList, { + title: StrCast(this.rootDoc.title), + x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, + _width: 400, _height: 225, _fitToBox: true, + }); + this.props.addDocument?.(doc); + } + + recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): [Doc[], number] => { + const mainBulletList: Doc[] = []; + let b = d ? d : 0; + let yLoc = y ? y : 0; + let nestCount = 0; + let count: string = before ? before : ''; + const fontSize: string = (16 - (nestDepth * 2)) + 'pt'; + const xLoc: number = (nestDepth * 20); + const width: number = 390 - xLoc; + const height: number = 55 - (nestDepth * 5); + Array.from(list).forEach(listItem => { + const mainBullets: number = Number(listItem.getAttribute("data-bulletstyle")); + if (mainBullets === nestDepth) { + if (listItem.childElementCount > 1) { + b++; + nestCount++; + yLoc += height; + count = before ? count + nestCount + "." : nestCount + "."; + const text = listItem.getElementsByTagName("p")[0].innerText; + const length = text.length; + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + // yLoc += NumCast(bullet1._height); + mainBulletList.push(bullet1); + const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count); + mainBulletList.push.apply(mainBulletList, newList[0]); + yLoc += newList.length * (55 - ((nestDepth + 1) * 5)); + } else { + b++; + nestCount++; + yLoc += height; + count = before ? count + nestCount + "." : nestCount + "."; + const text = listItem.innerText; + const length = text.length; + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + // yLoc += NumCast(bullet1._height); + mainBulletList.push(bullet1); + } + } + }); + return [mainBulletList, yLoc]; + } + recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, @@ -822,9 +907,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); - this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(), { fireImmediately: true }); + this._disposers.search = reaction(() => this.rootDoc.searchMatch, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(), + { fireImmediately: this.rootDoc.searchMatch ? true : false }); this._disposers.record = reaction(() => this._recording, () => { @@ -1077,7 +1165,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView); + // !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; @@ -1094,10 +1182,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBox.SelectOnLoadChar = ""; } - (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); + selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - if (!this._editorView!.state.storedMarks || !this._editorView!.state.storedMarks.some(mark => mark.type === schema.marks.user_mark)) { - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; + if (!this._editorView!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; } } getFont(font: string) { @@ -1351,7 +1439,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.doLinkOnDeselect(); // move the richtextmenu offscreen - if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); + //if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); } _lastTimedMark: Mark | undefined = undefined; @@ -1433,7 +1521,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } render() { TraceMobx(); - const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1); + const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground; setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected @@ -1461,6 +1549,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), pointerEvents: interactive ? undefined : "none", fontSize: Cast(this.layoutDoc._fontSize, "string", null), + fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), transition: "opacity 1s" }} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 47a4911b8..459632ec8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -77,7 +77,8 @@ export default class RichTextMenu extends AntimodeMenu { super(props); RichTextMenu.Instance = this; this._canFade = false; - this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); + //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); + this.Pinned = true; this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, @@ -184,11 +185,15 @@ export default class RichTextMenu extends AntimodeMenu { const active = this.getActiveFontStylesOnSelection(); const activeFamilies = active.activeFamilies; const activeSizes = active.activeSizes; + const activeColors = active.activeColors; + const activeHighlights = active.activeHighlights; this.activeListType = this.getActiveListStyle(); this.activeAlignment = this.getActiveAlignment(); this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; this.activeFontSize = !activeSizes.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "..."; + this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "..."; + this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "..."; // update link in current selection const targetTitle = await this.getTextLinkTargetTitle(); @@ -223,7 +228,7 @@ export default class RichTextMenu extends AntimodeMenu { if (this.view && this.TextView.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { - if (path[i]?.type === this.view.state.schema.nodes.paragraph) { + if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { return path[i].attrs.align || "left"; } } @@ -249,10 +254,12 @@ export default class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveFontStylesOnSelection() { - if (!this.view) return { activeFamilies: [], activeSizes: [] }; + if (!this.view) return { activeFamilies: [], activeSizes: [], activeColors: [], activeHighlights: [] }; const activeFamilies: string[] = []; const activeSizes: string[] = []; + const activeColors: string[] = []; + const activeHighlights: string[] = []; if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; @@ -260,15 +267,20 @@ export default class RichTextMenu extends AntimodeMenu { if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { ref_node.marks.forEach(m => { m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); + m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); }); } !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); + !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); } !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); - return { activeFamilies, activeSizes }; + !activeColors.length && (activeColors.push(StrCast(Doc.UserDoc().fontColor, "black"))); + !activeHighlights.length && (activeHighlights.push(StrCast(Doc.UserDoc().fontHighlight, ""))); + return { activeFamilies, activeSizes, activeColors, activeHighlights }; } getMarksInSelection(state: EditorState<any>) { @@ -425,10 +437,16 @@ export default class RichTextMenu extends AntimodeMenu { } changeFontSize = (mark: Mark, view: EditorView) => { + if ((this.view?.state.selection.$from.pos || 0) < 2) { + this.TextView.layoutDoc._fontSize = mark.attrs.fontSize; + } this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch, true); } changeFontFamily = (mark: Mark, view: EditorView) => { + if ((this.view?.state.selection.$from.pos || 0) < 2) { + this.TextView.layoutDoc._fontFamily = mark.attrs.family; + } this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch, true); } @@ -490,7 +508,7 @@ export default class RichTextMenu extends AntimodeMenu { alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph) { + if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks); return false; } @@ -503,7 +521,7 @@ export default class RichTextMenu extends AntimodeMenu { insetParagraph(state: EditorState<any>, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph) { + if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10; tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); return false; @@ -516,7 +534,7 @@ export default class RichTextMenu extends AntimodeMenu { outsetParagraph(state: EditorState<any>, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph) { + if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10); tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); return false; @@ -529,8 +547,9 @@ export default class RichTextMenu extends AntimodeMenu { indentParagraph(state: EditorState<any>, dispatch: any) { var tr = state.tr; + const heading = false; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph) { + if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25; tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); @@ -538,14 +557,14 @@ export default class RichTextMenu extends AntimodeMenu { } return true; }); - dispatch?.(tr); + !heading && dispatch?.(tr); return true; } hangingIndentParagraph(state: EditorState<any>, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph) { + if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10; tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); @@ -604,8 +623,11 @@ export default class RichTextMenu extends AntimodeMenu { label = "No marks are currently stored"; } + //onPointerDown={onBrushClick} + const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom"> - <button className="antimodeMenu-button" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}> + + <button className="antimodeMenu-button" style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}> <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} /> </button> </Tooltip>; @@ -614,11 +636,11 @@ export default class RichTextMenu extends AntimodeMenu { <div className="dropdown"> <p>{label}</p> <button onPointerDown={this.clearBrush}>Clear brush</button> - <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input> + <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress} /> </div>; return ( - <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> ); } @@ -677,8 +699,9 @@ export default class RichTextMenu extends AntimodeMenu { self.TextView.EditorView!.focus(); } + // onPointerDown={onColorClick} const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom"> - <button className="antimodeMenu-button color-preview-button" onPointerDown={onColorClick}> + <button className="antimodeMenu-button color-preview-button"> <FontAwesomeIcon icon="palette" size="lg" /> <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div> </button> @@ -699,7 +722,7 @@ export default class RichTextMenu extends AntimodeMenu { </div>; return ( - <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> ); } @@ -731,8 +754,9 @@ export default class RichTextMenu extends AntimodeMenu { UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter"); } + //onPointerDown={onHighlightClick} const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom"> - <button className="antimodeMenu-button color-preview-button" key="highilghter-button" onPointerDown={onHighlightClick}> + <button className="antimodeMenu-button color-preview-button" key="highilghter-button" > <FontAwesomeIcon icon="highlighter" size="lg" /> <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div> </button> @@ -753,7 +777,7 @@ export default class RichTextMenu extends AntimodeMenu { </div>; return ( - <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} /> + <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} /> ); } @@ -776,7 +800,9 @@ export default class RichTextMenu extends AntimodeMenu { const link = this.currentLink ? this.currentLink : ""; const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom"> - <div><FontAwesomeIcon icon="link" size="lg" /> </div> + <button className="antimodeMenu-button color-preview-button"> + <FontAwesomeIcon icon="link" size="lg" /> + </button> </Tooltip>; const dropdownContent = @@ -788,7 +814,8 @@ export default class RichTextMenu extends AntimodeMenu { <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button> </div>; - return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />; + return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} + openDropdownOnButton={true} link={true} />; } async getTextLinkTargetTitle() { @@ -827,6 +854,7 @@ export default class RichTextMenu extends AntimodeMenu { } // TODO: should check for valid URL + @undoBatch makeLinkToURL = (target: string, lcoation: string) => { ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target); } @@ -874,10 +902,12 @@ export default class RichTextMenu extends AntimodeMenu { if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { ref_node = pos.nodeBefore; } - else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { - ref_node = pos.nodeAfter; + if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { + if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) { + ref_node = pos.nodeAfter; + } } - else if (pos.pos > 0) { + if (!ref_node && pos.pos > 0) { let skip = false; for (let i: number = pos.pos - 1; i > 0; i--) { this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => { @@ -918,16 +948,22 @@ export default class RichTextMenu extends AntimodeMenu { render() { TraceMobx(); const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[ - !this.collapsed ? this.getDragger() : (null), - !this.Pinned ? (null) : <div key="frag1"> {[ - this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), - this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), - this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), - this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), - this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), - this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), - <div className="richTextMenu-divider" key="divider" /> - ]}</div>, + //!this.collapsed ? this.getDragger() : (null), + // !this.Pinned ? (null) : <div key="frag1"> {[ + // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), + // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), + // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), + // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), + // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), + // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), + // <div className="richTextMenu-divider" key="divider" /> + // ]}</div>, + this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), + this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), + this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), + this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), + this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), + this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), this.createColorButton(), this.createHighlighterButton(), this.createLinkButton(), @@ -955,16 +991,16 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule), <div className="richTextMenu-divider" key="divider 5" />,]} </div> - <div key="collapser"> - {/* <div key="collapser"> + {/* <div key="collapser"> + {<div key="collapser"> <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}> <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} /> </button> - </div> */} + </div> } <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}> <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> </button> - </div> + </div> */} </div>; return ( @@ -980,6 +1016,7 @@ interface ButtonDropdownProps { button: JSX.Element; dropdownContent: JSX.Element; openDropdownOnButton?: boolean; + link?: boolean; } @observer @@ -1022,18 +1059,10 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> { render() { return ( <div className="button-dropdown-wrapper" ref={node => this.ref = node}> - {this.props.openDropdownOnButton ? - <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}> - {this.props.button} - <FontAwesomeIcon icon="caret-down" size="sm" /> - </button> : - <> - {this.props.button} - <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> - <FontAwesomeIcon icon="caret-down" size="sm" /> - </button> - </>} - + <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}> + {this.props.button} + <div style={{ marginTop: "-8.5" }}><FontAwesomeIcon icon="caret-down" size="sm" /></div> + </div> {this.showDropdown ? this.props.dropdownContent : (null)} </div> ); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index ef0fead4a..dc1d8a2c8 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -317,13 +317,12 @@ export class RichTextRules { // create an inline view of a tag stored under the '#' field new InputRule( - new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/), + new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - const multiple = tag.split(";"); - this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag; - const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" }); + this.Document[DataSym]["#" + tag] = "."; + const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 2ce61ab58..ce784c3d9 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -40,7 +40,7 @@ export const marks: { [index: string]: MarkSpec } = { 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"]] : node.attrs.allLinks.length === 1 ? - ["a", { ...node.attrs, class: linkids, dataTargetids: targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] : + ["a", { ...node.attrs, class: linkids, dataTargetids: targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0] : ["div", { class: "prosemirror-anchor" }, ["span", { class: "prosemirror-linkBtn" }, ["a", { ...node.attrs, class: linkids, dataTargetids: 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 1af821738..1616500f6 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -66,9 +66,11 @@ export const nodes: { [index: string]: NodeSpec } = { // should hold the number 1 to 6. Parsed and serialized as `<h1>` to // `<h6>` elements. heading: { - attrs: { level: { default: 1 } }, - content: "inline*", - group: "block", + ...ParagraphNodeSpec, + attrs: { + ...ParagraphNodeSpec.attrs, + level: { default: 1 }, + }, defining: true, parseDOM: [{ tag: "h1", attrs: { level: 1 } }, { tag: "h2", attrs: { level: 2 } }, @@ -76,7 +78,18 @@ export const nodes: { [index: string]: NodeSpec } = { { tag: "h4", attrs: { level: 4 } }, { tag: "h5", attrs: { level: 5 } }, { tag: "h6", attrs: { level: 6 } }], - toDOM(node: any) { return ["h" + node.attrs.level, 0]; } + toDOM(node) { + const dom = toParagraphDOM(node) as any; + const level = node.attrs.level || 1; + dom[0] = 'h' + level; + return dom; + }, + getAttrs(dom: any) { + const attrs = getParagraphNodeAttrs(dom) as any; + const level = Number(dom.nodeName.substring(1)) || 1; + attrs.level = level; + return attrs; + } }, // :: NodeSpec A code listing. Disallows marks or non-text inline |
