Skip to content

Commit 26e222a

Browse files
authored
Merge pull request #454 from Harbour-Enterprises/nick/document-custom-node
Create basic custom node example, update example versions
2 parents ccee25e + 28688bb commit 26e222a

13 files changed

Lines changed: 418 additions & 6 deletions

File tree

examples/docxtemplater-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@fortawesome/free-regular-svg-icons": "^6.7.2",
1515
"@fortawesome/free-solid-svg-icons": "^6.7.2",
1616
"@fortawesome/vue-fontawesome": "^3.0.8",
17-
"@harbour-enterprises/superdoc": "^0.6.35",
17+
"@harbour-enterprises/superdoc": "^0.9.7",
1818
"docxtemplater": "^3.59.0",
1919
"pizzip": "^3.1.8",
2020
"prismjs": "^1.29.0",

examples/nodejs-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"description": "",
1111
"dependencies": {
12-
"@harbour-enterprises/superdoc": "^0.6.35",
12+
"@harbour-enterprises/superdoc": "^0.9.7",
1313
"express": "^4.21.2"
1414
},
1515
"devDependencies": {

examples/react-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"dev": "vite"
88
},
99
"dependencies": {
10-
"@harbour-enterprises/superdoc": "^0.6.35",
10+
"@harbour-enterprises/superdoc": "^0.9.7",
1111
"react": "^19.0.0",
1212
"react-dom": "^19.0.0"
1313
},

examples/typescript-example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13-
"@harbour-enterprises/superdoc": "^0.6.35",
13+
"@harbour-enterprises/superdoc": "^0.9.7",
1414
"react": "^19.0.0",
1515
"react-dom": "^19.0.0"
1616
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>SuperDoc Vue Example</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.js"></script>
11+
</body>
12+
</html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "vue-superdoc-example",
3+
"private": true,
4+
"version": "0.0.1",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite"
8+
},
9+
"dependencies": {
10+
"@harbour-enterprises/superdoc": "^0.9.7",
11+
"vue": "^3.5.13"
12+
},
13+
"devDependencies": {
14+
"@vitejs/plugin-vue": "^4.2.3",
15+
"vite": "^4.4.6"
16+
}
17+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<template>
2+
<div class="app">
3+
<header>
4+
<h1>SuperDoc Example</h1>
5+
<button @click="fileInput?.click()">Load Document</button>
6+
<input
7+
type="file"
8+
ref="fileInput"
9+
accept=".docx,.pdf"
10+
class="hidden"
11+
@change="handleFileChange"
12+
>
13+
</header>
14+
15+
<main>
16+
<DocumentEditor
17+
:document-id="documentId"
18+
:initial-data="documentFile"
19+
@editor-ready="handleEditorReady"
20+
/>
21+
</main>
22+
</div>
23+
</template>
24+
25+
<script setup>
26+
import { ref } from 'vue';
27+
import DocumentEditor from './components/DocumentEditor.vue';
28+
29+
const documentId = ref('example-doc');
30+
const documentFile = ref(null);
31+
const fileInput = ref(null);
32+
33+
const handleFileChange = (event) => {
34+
const file = event.target.files?.[0];
35+
if (file) {
36+
documentFile.value = file;
37+
documentId.value = `doc-${Date.now()}`;
38+
}
39+
};
40+
41+
const handleEditorReady = (editor) => {
42+
console.log('SuperDoc editor is ready', editor);
43+
};
44+
</script>
45+
46+
<style>
47+
.app {
48+
height: 100vh;
49+
display: flex;
50+
flex-direction: column;
51+
}
52+
53+
header {
54+
padding: 1rem;
55+
background: #f5f5f5;
56+
display: flex;
57+
align-items: center;
58+
gap: 1rem;
59+
}
60+
61+
header button {
62+
padding: 0.5rem 1rem;
63+
background: #1355ff;
64+
color: white;
65+
border: none;
66+
border-radius: 4px;
67+
cursor: pointer;
68+
}
69+
70+
header button:hover {
71+
background: #0044ff;
72+
}
73+
74+
.hidden {
75+
display: none;
76+
}
77+
78+
main {
79+
flex: 1;
80+
padding: 1rem;
81+
}
82+
</style>
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<template>
2+
<div class="document-editor">
3+
<div :key="documentKey" class="editor-container">
4+
<div id="superdoc-toolbar" class="toolbar"></div>
5+
<div id="superdoc" class="editor"></div>
6+
</div>
7+
</div>
8+
</template>
9+
10+
<script setup>
11+
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
12+
import { SuperDoc } from '@harbour-enterprises/superdoc';
13+
import '@harbour-enterprises/superdoc/style.css';
14+
15+
// Import our custom node plugin
16+
import { myCustomNode } from '../plugins/MyCustomNodePlugin';
17+
18+
const props = defineProps({
19+
documentId: {
20+
type: String,
21+
required: true
22+
},
23+
initialData: {
24+
type: File,
25+
default: null
26+
},
27+
readOnly: {
28+
type: Boolean,
29+
default: false
30+
}
31+
});
32+
33+
const emit = defineEmits(['editor-ready', 'editor-error']);
34+
35+
// Use ref to track the editor instance
36+
const editor = shallowRef(null);
37+
const documentKey = ref(0);
38+
39+
// Function to safely destroy editor
40+
const destroyEditor = () => {
41+
if (editor.value) {
42+
editor.value = null;
43+
}
44+
};
45+
46+
// Function to initialize editor
47+
const initializeEditor = async () => {
48+
if (props.initialData) {
49+
// If initialData is provided, load the document
50+
await initializeLoadedFile();
51+
} else {
52+
// Otherwise, create a blank document
53+
await initializeBlankDocument();
54+
}
55+
};
56+
57+
const hooks = {
58+
onFocus: () => {
59+
console.log('Editor focused');
60+
},
61+
onBlur: () => {
62+
console.log('Editor lost focus');
63+
},
64+
onReady: () => {
65+
emit('editor-ready', editor.value);
66+
67+
// Let's insert our custom node into the document after the editor is ready
68+
editor.value?.activeEditor.commands.insertContent(`
69+
<div data-node-type='customNode' id='some-id-123'>Custom Node Content</div>
70+
`);
71+
72+
// Now instead of using the generic insertContent, we can use our custom node command
73+
editor.value.activeEditor.commands.insertCustomNode({
74+
id: 'second-node-id',
75+
content: 'Display this text!',
76+
})
77+
78+
// Let's check our current HTML
79+
const html = editor.value?.activeEditor.getHTML();
80+
console.debug('Editor HTML:', html);
81+
},
82+
onError: (error) => {
83+
emit('editor-error', error);
84+
},
85+
};
86+
87+
// Configuration modules: We can customize the toolbar here
88+
const modules = {
89+
toolbar: {
90+
selector: 'superdoc-toolbar',
91+
toolbarGroups: ['center'],
92+
excludeItems: ['underline'],
93+
}
94+
};
95+
96+
const initializeBlankDocument = async () => {
97+
editor.value = new SuperDoc({
98+
selector: '#superdoc',
99+
toolbar: 'superdoc-toolbar',
100+
format: 'docx',
101+
documentMode: props.readOnly ? 'viewing' : 'editing',
102+
103+
// Listen for various editor events
104+
...hooks,
105+
106+
// Include customized modules
107+
modules: {
108+
...modules,
109+
},
110+
111+
// Register our custom node here
112+
editorExtensions: [myCustomNode],
113+
});
114+
};
115+
116+
const initializeLoadedFile = async () => {
117+
try {
118+
// Ensure cleanup of previous instance
119+
destroyEditor();
120+
121+
// Create new editor instance
122+
editor.value = new SuperDoc({
123+
selector: '#superdoc',
124+
toolbar: 'superdoc-toolbar',
125+
documentMode: props.readOnly ? 'viewing' : 'editing',
126+
documents: [{
127+
id: props.documentId,
128+
type: 'docx',
129+
data: props.initialData
130+
}],
131+
132+
// Listen for various editor events
133+
...hooks,
134+
135+
// Include customized modules
136+
...modules,
137+
138+
// Register our custom node here
139+
editorExtensions: [myCustomNode],
140+
});
141+
} catch (error) {
142+
console.error('Failed to initialize editor:', error);
143+
emit('editor-error', error);
144+
}
145+
};
146+
147+
// Watch for changes in props that should trigger re-initialization
148+
watch(
149+
() => [props.documentId, props.initialData, props.readOnly],
150+
() => {
151+
initializeEditor();
152+
}
153+
);
154+
155+
onMounted(() => {
156+
initializeEditor();
157+
});
158+
159+
onUnmounted(() => {
160+
destroyEditor();
161+
});
162+
</script>
163+
164+
<style>
165+
/** Adding global style for our custom node class here, but more commonly you'd place this in your main style.css */
166+
.my-custom-node-default-class {
167+
background-color: #1355FF;
168+
border-radius: 8px;
169+
cursor: pointer;
170+
color: white;
171+
display: inline-block;
172+
padding: 2px 8px;
173+
font-size: 12px;
174+
}
175+
.my-custom-node-default-class:hover {
176+
background-color: #0a3dff;
177+
}
178+
</style>
179+
180+
<style scoped>
181+
.editor-container {
182+
display: flex;
183+
flex-direction: column;
184+
align-items: center;
185+
}
186+
.document-editor {
187+
display: flex;
188+
flex-direction: column;
189+
height: 100%;
190+
width: 100%;
191+
}
192+
193+
.toolbar {
194+
flex: 0 0 auto;
195+
border-bottom: 1px solid #eee;
196+
min-height: 40px; /* Ensure toolbar has minimum height */
197+
}
198+
199+
.editor {
200+
flex: 1 1 auto;
201+
overflow: auto;
202+
margin-top: 10px;
203+
min-height: 400px; /* Ensure editor has minimum height */
204+
}
205+
</style>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createApp } from 'vue';
2+
import App from './App.vue';
3+
4+
createApp(App).mount('#app');

0 commit comments

Comments
 (0)