Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions examples/vue-custom-mark/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const init = () => {
editorExtensions: [CustomMark],
onReady: myCustomOnReady,
});

};


Expand Down Expand Up @@ -53,6 +52,12 @@ const insertCustomMark = () => {
superdoc.value?.activeEditor?.commands.setMyCustomMark(randomId);
};

const exportDocx = () => {
superdoc.value?.export({
exportType: ['docx']
});
};

onMounted(() => init());
</script>

Expand All @@ -65,8 +70,9 @@ onMounted(() => init());
<div id="toolbar" class="my-custom-toolbar"></div>
<div class="editor-and-button">
<div id="editor" class="main-editor"></div>
<div>
<div class="editor-buttons">
<button class="insert-mark" @click="insertCustomMark">Insert custom mark</button>
<button class="insert-mark" @click="exportDocx">Export</button>
</div>
</div>
</div>
Expand All @@ -85,6 +91,13 @@ onMounted(() => init());
align-items: flex-start;
justify-content: center;
}
.editor-buttons {
display: flex;
flex-direction: column;
}
.editor-buttons button {
margin-bottom: 10px;
}
.insert-mark {
padding: 8px 12px;
border-radius: 8px;
Expand Down
1 change: 1 addition & 0 deletions packages/super-editor/src/core/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@ export class Editor extends EventEmitter {
isFinalDoc,
commentsType,
comments,
this,
);

const customXml = this.converter.schemaToXml(this.converter.convertedXml['docProps/custom.xml'].elements[0]);
Expand Down
8 changes: 8 additions & 0 deletions packages/super-editor/src/core/ExtensionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export class ExtensionService {
this.editor = editor;

this.externalExtensions = userExtensions || [];

this.externalExtensions = this.externalExtensions.map((extension) => {
return {
...extension,
isExternal: true,
};
});

this.extensions = ExtensionService.getResolvedExtensions([...extensions, ...this.externalExtensions]);
this.schema = Schema.createSchemaByExtensions(this.extensions, editor);
this.#setupExtensions();
Expand Down
4 changes: 4 additions & 0 deletions packages/super-editor/src/core/Mark.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export class Mark {

storage;

isExternal;

config = {
name: this.name,
};
Expand All @@ -25,6 +27,8 @@ export class Mark {

this.name = this.config.name;

this.isExternal = Boolean(this.config.isExternal)

if (this.config.addOptions) {
this.options = callOrGet(
getExtensionConfigField(this, 'addOptions', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ class SuperConverter {
isFinalDoc = false,
commentsExportType,
comments = [],
editor,
) {
const commentsWithParaIds = comments.map((c) => prepareCommentParaIds(c));
const commentDefinitions = commentsWithParaIds
Expand All @@ -332,7 +333,8 @@ class SuperConverter {
comments,
commentDefinitions,
commentsExportType,
isFinalDoc
isFinalDoc,
editor,
});

const exporter = new DocxExporter(this);
Expand Down Expand Up @@ -378,7 +380,8 @@ class SuperConverter {
comments,
commentDefinitions,
commentsExportType = 'clean',
isFinalDoc = false
isFinalDoc = false,
editor,
}) {
const bodyNode = this.savedTagsToRestore.find((el) => el.name === 'w:body');

Expand All @@ -395,6 +398,7 @@ class SuperConverter {
comments,
commentsExportType,
exportedCommentDefs: commentDefinitions,
editor,
});

return { result, params };
Expand Down
67 changes: 56 additions & 11 deletions packages/super-editor/src/core/super-converter/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,19 +344,61 @@ function translateChildNodes(params) {
* @returns {XmlReadyNode} The translated text node
*/

function getTextNodeForExport(text, marks) {
function getTextNodeForExport(text, marks, params) {
const hasLeadingOrTrailingSpace = /^\s|\s$/.test(text);
const space = hasLeadingOrTrailingSpace ? 'preserve' : null;
const nodeAttrs = space ? { 'xml:space': space } : null;

const textNodes = [];

const outputMarks = processOutputMarks(marks);
const textNode = {
textNodes.push({
name: 'w:t',
elements: [{ text, type: 'text' }],
attributes: nodeAttrs,
};
});

// For custom mark export, we need to add a bookmark start and end tag
// And store attributes in the bookmark name
if (params) {
const { editor } = params;
const customMarks = editor.extensionService.extensions.filter((e) => e.isExternal === true);

marks.forEach((mark) => {
const isCustomMark = customMarks.some((customMark) => {
const customMarkName = customMark.name;
return mark.type === customMarkName;
});

if (!isCustomMark) return;

return wrapTextInRun(textNode, outputMarks);
let attrsString = '';
Object.entries(mark.attrs).forEach(([key, value]) => {
if (value) {
attrsString += `${key}=${value};`;
}
});

if (isCustomMark) {
textNodes.unshift({
type: 'element',
name: 'w:bookmarkStart',
attributes: {
'w:id': '5000',
'w:name': mark.type + ';' + attrsString,
}
});
textNodes.push({
type: 'element',
name: 'w:bookmarkEnd',
attributes: {
'w:id': '5000',
}
});
};
});
}

return wrapTextInRun(textNodes, outputMarks);
}

/**
Expand All @@ -383,7 +425,7 @@ function translateTextNode(params) {

const { text, marks = [] } = node;

return getTextNodeForExport(text, marks);
return getTextNodeForExport(text, marks, params);
}

function createTrackStyleMark(marks) {
Expand Down Expand Up @@ -448,8 +490,11 @@ function translateTrackedNode(params) {
* @param {XmlReadyNode} node
* @returns {XmlReadyNode} The wrapped run node
*/
function wrapTextInRun(node, marks) {
const elements = [node];
function wrapTextInRun(nodeOrNodes, marks) {
let elements = [];
if (Array.isArray(nodeOrNodes)) elements = nodeOrNodes;
else elements = [nodeOrNodes];

if (marks && marks.length) elements.unshift(generateRunProps(marks));
return {
name: 'w:r',
Expand Down Expand Up @@ -1546,7 +1591,7 @@ function prepareTextAnnotation(params) {
} = params;

const marksFromAttrs = translateFieldAttrsToMarks(attrs);
return getTextNodeForExport(attrs.displayLabel, [...marks, ...marksFromAttrs]);
return getTextNodeForExport(attrs.displayLabel, [...marks, ...marksFromAttrs], params);
}

/**
Expand All @@ -1560,7 +1605,7 @@ function prepareCheckboxAnnotation(params) {
node: { attrs = {}, marks = [] },
} = params;
const content = he.decode(attrs.displayLabel);
return getTextNodeForExport(content, marks);
return getTextNodeForExport(content, marks, params);
}

/**
Expand Down Expand Up @@ -1613,7 +1658,7 @@ function prepareUrlAnnotation(params) {
} = params;
const newId = addNewLinkRelationship(params, attrs.linkUrl);

const linkTextNode = getTextNodeForExport(attrs.linkUrl, marks);
const linkTextNode = getTextNodeForExport(attrs.linkUrl, marks, params);

return {
name: 'w:hyperlink',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
* @type {import("docxImporter").NodeHandler}
*/
export const handleBookmarkNode = (params) => {
const { nodes, nodeListHandler } = params;
const { nodes, nodeListHandler, editor } = params;
if (nodes.length === 0 || nodes[0].name !== 'w:bookmarkStart') {
return { nodes: [], consumed: 0 };
}
const node = nodes[0];

const handleStandardNode = nodeListHandler.handlerEntities.find(
(e) => e.handlerName === 'standardNodeHandler',
)?.handler;
Expand All @@ -16,6 +15,36 @@ export const handleBookmarkNode = (params) => {
return { nodes: [], consumed: 0 };
};

// Check if this bookmark is a custom mark
const customMarks = editor?.extensionService?.extensions?.filter((e) => e.isExternal === true) || [];
const bookmarkName = node.attributes['w:name']?.split(';')[0];
const customMark = customMarks.find((mark) => mark.name === bookmarkName);
if (customMark) {
const bookmarkEndIndex = nodes.findIndex((n) => n.name === 'w:bookmarkEnd' && n.attributes['w:id'] === node.attributes['w:id']);
const textNodes = nodes.slice(1, bookmarkEndIndex);

const nodeListHandler = params.nodeListHandler;
const attrs = {};
node.attributes['w:name'].split(';').forEach((name) => {
const [key, value] = name.split('=');
if (key && value) {
attrs[key] = value;
}
});

const translatedText = nodeListHandler.handler({ ...params, nodes: textNodes });
translatedText.forEach((n) => {
n.marks.push({
type: customMark.name,
attrs,
})
});
return {
nodes: translatedText,
consumed: translatedText.length + 2,
};
}

const updatedParams = {...params, nodes: [node]};
const result = handleStandardNode(updatedParams);
if (result.nodes.length === 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ const handleRunNode = (params) => {
}

if (node.marks) marks.push(...node.marks);
processedRun = processedRun.map((n) => ({ ...n, marks: createImportMarks(marks), attributes }));
const newMarks = createImportMarks(marks);
processedRun = processedRun.map((n) => {
const existingMarks = n.marks || [];
return { ...n, marks: [...newMarks, ...existingMarks], attributes }
});
} else if (defaultNodeStyles.marks) {
processedRun = processedRun.map((n) => ({ ...n, marks: createImportMarks(defaultNodeStyles.marks) }));
}
Expand Down
Loading