diff --git a/packages/super-editor/src/components/toolbar/super-toolbar.js b/packages/super-editor/src/components/toolbar/super-toolbar.js index 369e1ab272..7547559754 100644 --- a/packages/super-editor/src/components/toolbar/super-toolbar.js +++ b/packages/super-editor/src/components/toolbar/super-toolbar.js @@ -124,11 +124,17 @@ export class SuperToolbar extends EventEmitter { }, setColor: ({ item, argument }) => { - this.#runCommandWithArgumentOnly({ item, argument }); + this.#runCommandWithArgumentOnly({ item, argument }, () => { + this.activeEditor?.commands.setFieldAnnotationsTextColor(argument, true); + }); }, setHighlight: ({ item, argument }) => { - this.#runCommandWithArgumentOnly({ item, argument }); + this.#runCommandWithArgumentOnly({ item, argument, noArgumentCallback: true }, () => { + let arg = argument !== 'none' ? argument : null; + this.activeEditor?.commands.setFieldAnnotationsTextHighlight(arg, true); + this.activeEditor?.commands.setCellBackground(arg); + }); }, toggleRuler: ({ item, argument }) => { @@ -461,7 +467,7 @@ export class SuperToolbar extends EventEmitter { } } - #runCommandWithArgumentOnly({ item, argument }, callback) { + #runCommandWithArgumentOnly({ item, argument, noArgumentCallback = false }, callback) { if (!argument || !this.activeEditor) return; let command = item.command; @@ -469,6 +475,7 @@ export class SuperToolbar extends EventEmitter { if (argument === 'none' && noArgumentCommand in this.activeEditor?.commands) { this.activeEditor.commands[noArgumentCommand](); + if (typeof callback === 'function' && noArgumentCallback) callback(argument); this.updateToolbarState(); return; } diff --git a/packages/super-editor/src/core/helpers/getActiveFormatting.js b/packages/super-editor/src/core/helpers/getActiveFormatting.js index 01339f25e7..1c1700a2eb 100644 --- a/packages/super-editor/src/core/helpers/getActiveFormatting.js +++ b/packages/super-editor/src/core/helpers/getActiveFormatting.js @@ -47,8 +47,26 @@ export function getActiveFormatting(editor) { marksToProcess.push({ name: key, attrs }); }); + // For fieldAnnotation. + const textColor = marksToProcess.find((i) => i.name === 'textColor'); + const textHightlight = marksToProcess.find((i) => i.name === 'textHighlight'); + + if (textColor) { + marksToProcess.push({ + name: 'color', + attrs: { color: textColor.attrs?.textColor }, + }); + } + if (textHightlight) { + marksToProcess.push({ + name: 'highlight', + attrs: { color: textHightlight.attrs?.textHighlight }, + }); + } + const hasPendingFormatting = !!editor.storage.formatCommands?.storedStyle; if (hasPendingFormatting) marksToProcess.push({ name: 'copyFormat', attrs: true }); + return marksToProcess; } diff --git a/packages/super-editor/src/core/super-converter/exporter.js b/packages/super-editor/src/core/super-converter/exporter.js index 9c8468c329..4b29bc01b5 100644 --- a/packages/super-editor/src/core/super-converter/exporter.js +++ b/packages/super-editor/src/core/super-converter/exporter.js @@ -1830,6 +1830,8 @@ const translateFieldAttrsToMarks = (attrs = {}) => { bold, underline, italic, + textColor, + textHighlight, } = attrs; const marks = []; @@ -1838,6 +1840,8 @@ const translateFieldAttrsToMarks = (attrs = {}) => { if (bold) marks.push({ type: 'bold', attrs: {} }); if (underline) marks.push({ type: 'underline', attrs: {} }); if (italic) marks.push({ type: 'italic', attrs: {} }); + if (textColor) marks.push({ type: 'color', attrs: { color: textColor } }); + if (textHighlight) marks.push({ type: 'highlight', attrs: { color: textHighlight } }); return marks; }; @@ -1918,6 +1922,20 @@ function translateFieldAnnotation(params) { 'w:val': attrs.fontSize, }, }, + { + name: 'w:fieldTextColor', + attributes: { + 'xmlns:w': customXmlns, + 'w:val': attrs.textColor, + }, + }, + { + name: 'w:fieldTextHighlight', + attributes: { + 'xmlns:w': customXmlns, + 'w:val': attrs.textHighlight, + }, + }, ], }, { @@ -2128,7 +2146,7 @@ function resizeKeepAspectRatio(width, height, maxWidth) { function applyMarksToHtmlAnnotation(state, marks) { const { tr, doc, schema } = state; - const allowedMarks = ['fontFamily', 'fontSize']; + const allowedMarks = ['fontFamily', 'fontSize', 'highlight']; if ( !marks.some((m) => allowedMarks.includes(m.type)) @@ -2138,32 +2156,41 @@ function applyMarksToHtmlAnnotation(state, marks) { const fontFamily = marks.find((m) => m.type === 'fontFamily'); const fontSize = marks.find((m) => m.type === 'fontSize'); + const highlight = marks.find((m) => m.type === 'highlight'); + + const textStyleType = schema.marks.textStyle; + const highlightType = schema.marks.highlight; doc.descendants((node, pos) => { if (!node.isText) return; - const found = node.marks.find((m) => m.type.name === 'textStyle'); - const textStyleType = schema.marks.textStyle; - - if (!found) { + const foundTextStyle = node.marks.find((m) => m.type.name === 'textStyle'); + const foundHighlight = node.marks.find((m) => m.type.name === 'highlight'); + + // text style (fontFamily, fontSize) + if (!foundTextStyle) { tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ ...fontFamily?.attrs, ...fontSize?.attrs, })); - return; - } - - if (!found?.attrs.fontFamily && fontFamily) { + } else if (!foundTextStyle?.attrs.fontFamily && fontFamily) { tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ - ...found?.attrs, + ...foundTextStyle?.attrs, ...fontFamily.attrs, })); - } else if (!found?.attrs.fontSize && fontSize) { + } else if (!foundTextStyle?.attrs.fontSize && fontSize) { tr.addMark(pos, pos + node.nodeSize, textStyleType.create({ - ...found?.attrs, + ...foundTextStyle?.attrs, ...fontSize.attrs, })); } + + // highlight + if (!foundHighlight) { + tr.addMark(pos, pos + node.nodeSize, highlightType.create({ + ...highlight?.attrs, + })); + } }); return state.apply(tr); diff --git a/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js b/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js index 1c88fdee2b..45c78dfe65 100644 --- a/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js +++ b/packages/super-editor/src/core/super-converter/v2/importer/annotationImporter.js @@ -28,6 +28,8 @@ export const handleAnnotationNode = (params) => { const isMultipleImage = sdtPr?.elements.find((el) => el.name === 'w:fieldMultipleImage')?.attributes['w:val']; const fontFamily = sdtPr?.elements.find((el) => el.name === 'w:fieldFontFamily')?.attributes['w:val']; const fontSize = sdtPr?.elements.find((el) => el.name === 'w:fieldFontSize')?.attributes['w:val']; + const textColor = sdtPr?.elements.find((el) => el.name === 'w:fieldTextColor')?.attributes['w:val']; + const textHighlight = sdtPr?.elements.find((el) => el.name === 'w:fieldTextHighlight')?.attributes['w:val']; const attrs = { type, @@ -38,6 +40,8 @@ export const handleAnnotationNode = (params) => { multipleImage: isMultipleImage === 'true', fontFamily: fontFamily !== 'null' ? fontFamily : null, fontSize: fontSize !== 'null' ? fontSize : null, + textColor: textColor !== 'null' ? textColor : null, + textHighlight: textHighlight !== 'null' ? textHighlight : null, }; const allAttrs = { ...attrs, ...marksAsAttrs }; diff --git a/packages/super-editor/src/dev/components/DeveloperPlayground.vue b/packages/super-editor/src/dev/components/DeveloperPlayground.vue index c04057ffc1..6891e254df 100644 --- a/packages/super-editor/src/dev/components/DeveloperPlayground.vue +++ b/packages/super-editor/src/dev/components/DeveloperPlayground.vue @@ -47,12 +47,11 @@ const onCreate = ({ editor }) => { isDebuggingPagination.value = PaginationPluginKey.getState(editor.state)?.isDebugging; // editor.commands.addFieldAnnotation(0, { - // type: 'html', - // displayLabel: 'Paragraph', - // fieldId: `123`, + // type: 'text', + // displayLabel: 'Some text', + // fieldId: '123', // fieldType: 'TEXTINPUT', // fieldColor: '#980043', - // rawHtml: '
Par 1
Par 2
', // }); }; diff --git a/packages/super-editor/src/extensions/field-annotation/field-annotation.js b/packages/super-editor/src/extensions/field-annotation/field-annotation.js index 775e71f987..25cdb020fe 100644 --- a/packages/super-editor/src/extensions/field-annotation/field-annotation.js +++ b/packages/super-editor/src/extensions/field-annotation/field-annotation.js @@ -272,7 +272,31 @@ export const FieldAnnotation = Node.create({ style: `font-size: ${fontSize}`, }; }, + }, + + textHighlight: { + default: null, + parseDOM: (element) => element.getAttribute('data-text-highlight'), + renderDOM: (attrs) => { + if (!attrs.textHighlight) return {}; + return { + 'data-text-highlight': attrs.textHighlight, + // takes precedence over the fieldColor. + style: `background-color: ${attrs.textHighlight} !important`, + }; + }, + }, + textColor: { + default: null, + parseDOM: (element) => element.getAttribute('data-text-color'), + renderDOM: (attrs) => { + if (!attrs.textColor) return {}; + return { + 'data-text-color': attrs.textColor, + style: `color: ${attrs.textColor}`, + }; + }, }, /// Formatting attrs - end. @@ -843,6 +867,56 @@ export const FieldAnnotation = Node.create({ } } + return true; + }, + + setFieldAnnotationsTextHighlight: + (color, setSelection = false) => + ({ dispatch, tr, state, commands }) => { + let { from, to, node } = state.selection; + let annotations = findFieldAnnotationsBetween(from, to, state.doc); + + if (!annotations.length) { + return true; + } + + if (dispatch) { + annotations.forEach((annotation) => { + commands.updateFieldAnnotationsAttributes([annotation], { + textHighlight: color, + }); + }); + + if (setSelection && node?.type.name === this.name) { + tr.setSelection(NodeSelection.create(tr.doc, from)); + } + } + + return true; + }, + + setFieldAnnotationsTextColor: + (color, setSelection = false) => + ({ dispatch, tr, state, commands }) => { + let { from, to, node } = state.selection; + let annotations = findFieldAnnotationsBetween(from, to, state.doc); + + if (!annotations.length) { + return true; + } + + if (dispatch) { + annotations.forEach((annotation) => { + commands.updateFieldAnnotationsAttributes([annotation], { + textColor: color, + }); + }); + + if (setSelection && node?.type.name === this.name) { + tr.setSelection(NodeSelection.create(tr.doc, from)); + } + } + return true; }, /// Formatting commands - end. diff --git a/packages/super-editor/src/extensions/table/table.js b/packages/super-editor/src/extensions/table/table.js index 1b5d057ef2..96439cac7e 100644 --- a/packages/super-editor/src/extensions/table/table.js +++ b/packages/super-editor/src/extensions/table/table.js @@ -2,8 +2,6 @@ import { Node, Attribute } from '@core/index.js'; import { callOrGet } from '@core/utilities/callOrGet.js'; import { getExtensionConfigField } from '@core/helpers/getExtensionConfigField.js'; import { /* TableView */ createTableView } from './TableView.js'; -import { findParentNodeClosestToPos } from '@helpers/index.js'; -import { Fragment } from "prosemirror-model"; import { createTable } from './tableHelpers/createTable.js'; import { createColGroup } from './tableHelpers/createColGroup.js'; import { deleteTableWhenSelected } from './tableHelpers/deleteTableWhenSelected.js'; @@ -12,8 +10,8 @@ import { createTableBorders } from './tableHelpers/createTableBorders.js'; import { createCellBorders } from '../table-cell/helpers/createCellBorders.js'; import { findParentNode } from '@helpers/findParentNode.js'; import { TextSelection } from 'prosemirror-state'; -import { getFieldAttrs } from '@helpers/annotator.js'; import { getNodeType } from '@core/helpers/getNodeType.js'; +import { isCellSelection } from './tableHelpers/isCellSelection.js'; import { addColumnBefore, addColumnAfter, @@ -268,6 +266,23 @@ export const Table = Node.create({ return true; }, + setCellBackground: + (value) => ({ editor, commands, dispatch }) => { + const { selection } = editor.state; + + if (!isCellSelection(selection)) { + return false; + } + + const color = value?.startsWith('#') ? value.slice(1) : value; + + if (dispatch) { + return commands.setCellAttr('background', { color }); + } + + return true; + }, + deleteCellAndTableBorders: () => ({ chain, state, tr }) => { if (!isInTable(state)) {