Skip to content

Commit 0959abf

Browse files
committed
feat(frontend): add TinyMCE editor
1 parent c7b62d6 commit 0959abf

File tree

17 files changed

+1000
-13
lines changed

17 files changed

+1000
-13
lines changed

frontend/package-lock.json

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"core-js": "^3.45.1",
2121
"pinia": "^3.0.3",
2222
"pinia-plugin-persistedstate": "^4.5.0",
23+
"tinymce": "^8.2.1",
2324
"uuid": "^13.0.0",
2425
"vue": "^3.5.22",
2526
"vue-router": "^4.5.1"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!--
2+
This file is part of the QuestionPy SDK. (https://questionpy.org)
3+
The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
4+
(c) Technische Universität Berlin, innoCampus <[email protected]>
5+
-->
6+
7+
<template>
8+
<div :id="id" ref="targetElement"></div>
9+
</template>
10+
11+
<script lang="ts" setup>
12+
// Loosely based on
13+
// - https://www.tiny.cloud/docs/tinymce/latest/vite-es6-npm/
14+
// - https://github.com/tinymce/tinymce-vue
15+
16+
import { ref, watch } from 'vue'
17+
import type { RawEditorOptions } from 'tinymce/tinymce'
18+
19+
import type { RichTextEditor } from '@/types'
20+
21+
import useTinyMCEInstance from './useTinyMCEInstance'
22+
23+
const {
24+
disabled = false,
25+
id,
26+
initOptions: tineMCEOptions = {},
27+
modelValue,
28+
} = defineProps<{
29+
disabled?: boolean
30+
id: string
31+
initOptions?: RawEditorOptions
32+
modelValue?: RichTextEditor
33+
}>()
34+
35+
const emit = defineEmits<{
36+
(e: 'update:modelValue', value: RichTextEditor): void
37+
}>()
38+
39+
const targetElement = ref<HTMLDivElement | undefined>()
40+
41+
// Pass editor updates to model
42+
function onUpdate(state: RichTextEditor) {
43+
emit('update:modelValue', state)
44+
}
45+
46+
const editorInstance = useTinyMCEInstance({
47+
targetElement,
48+
onUpdate,
49+
initialState: modelValue ?? { text: '', files: [] },
50+
tinyMCEOptions: { ...tineMCEOptions, disabled },
51+
})
52+
53+
// Pass model value updates to editor
54+
watch(
55+
() => modelValue,
56+
(newValue) => {
57+
if (editorInstance && newValue?.text !== editorInstance.getContent()) {
58+
editorInstance.setContent(newValue?.text ?? '')
59+
}
60+
},
61+
)
62+
63+
watch(
64+
() => disabled,
65+
(newValue) => {
66+
if (editorInstance) {
67+
editorInstance.options.set('disabled', newValue)
68+
}
69+
},
70+
)
71+
</script>
72+
73+
<style lang="scss" src="./TinyMCEUI.scss" />
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* This file is part of the QuestionPy SDK. (https://questionpy.org)
3+
* The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
4+
* (c) Technische Universität Berlin, innoCampus <[email protected]>
5+
*/
6+
7+
/* TinyMCE content styles, based on `tinymce/skins/content/default/content.css` */
8+
9+
body {
10+
background-color: var(--bs-body-bg);
11+
color: var(--bs-body-color);
12+
font-family: var(--bs-body-font-family);
13+
line-height: var(--bs-body-line-height);
14+
margin: 1rem;
15+
}
16+
17+
table {
18+
border-collapse: collapse;
19+
}
20+
21+
table:not([cellpadding]) th,
22+
table:not([cellpadding]) td {
23+
padding: 0.4rem;
24+
}
25+
26+
table[border]:not([border='0']):not([style*='border-width']) th,
27+
table[border]:not([border='0']):not([style*='border-width']) td {
28+
border-width: 1px;
29+
}
30+
31+
table[border]:not([border='0']):not([style*='border-style']) th,
32+
table[border]:not([border='0']):not([style*='border-style']) td {
33+
border-style: solid;
34+
}
35+
36+
table[border]:not([border='0']):not([style*='border-color']) th,
37+
table[border]:not([border='0']):not([style*='border-color']) td {
38+
border-color: var(--bs-border-color);
39+
}
40+
41+
figure {
42+
display: table;
43+
margin: 1rem auto;
44+
}
45+
46+
figure figcaption {
47+
color: var(--bs-secondary-color);
48+
display: block;
49+
margin-top: 0.25rem;
50+
text-align: center;
51+
}
52+
53+
hr {
54+
border-color: var(--bs-border-color);
55+
border-style: solid;
56+
border-width: 1px 0 0 0;
57+
}
58+
59+
code {
60+
color: var(--bs-code-color);
61+
font-family: var(--bs-font-monospace);
62+
font-size: 0.875em;
63+
}
64+
65+
pre {
66+
font-family: var(--bs-font-monospace);
67+
font-size: 0.875em;
68+
}
69+
70+
a {
71+
color: var(--bs-link-color);
72+
}
73+
74+
a:hover {
75+
color: var(--bs-link-hover-color);
76+
}
77+
78+
.mce-content-body:not([dir='rtl']) blockquote {
79+
border-left: 2px solid var(--bs-border-color);
80+
margin-left: 1.5rem;
81+
padding-left: 1rem;
82+
}
83+
84+
.mce-content-body[dir='rtl'] blockquote {
85+
border-right: 2px solid var(--bs-border-color);
86+
margin-right: 1.5rem;
87+
padding-right: 1rem;
88+
}

0 commit comments

Comments
 (0)