Skip to content

Commit b0b8b16

Browse files
authored
Merge pull request #171 from opencloud-eu/md-editor
md editor
2 parents e83f13c + 68e41a2 commit b0b8b16

File tree

12 files changed

+1878
-1610
lines changed

12 files changed

+1878
-1610
lines changed

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ shared-workspace-lockfile=true
33

44
# @vue/component-compiler-utils
55
hoist-pattern[]=sass
6+
hoist-pattern[]=*@codemirror*
7+
hoist-pattern[]=*@lezer*
68

79
# eslint (public so they are picked up by IDEs)
810
public-hoist-pattern[]=*eslint*

package.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,6 @@
9595
"node": "22.14.0"
9696
},
9797
"pnpm": {
98-
"packageExtensions": {
99-
"@toast-ui/editor": {
100-
"dependencies": {
101-
"prosemirror-transform": "1.8.0"
102-
}
103-
}
104-
},
10598
"patchedDependencies": {
10699
107100

packages/web-app-files/src/components/Spaces/SpaceHeader.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@
5757
<div
5858
v-if="markdownResource && markdownContent"
5959
ref="markdownContainerRef"
60-
class="markdown-container oc-flex oc-flex-middle"
60+
class="markdown-container oc-flex"
6161
>
6262
<text-editor
63-
:resource="markdownResource"
63+
class="markdown-container-content"
64+
is-read-only
6465
:current-content="markdownContent"
65-
:is-read-only="true"
6666
/>
6767
<div class="markdown-container-edit oc-ml-s">
6868
<router-link
@@ -93,6 +93,7 @@ import {
9393
computed,
9494
defineComponent,
9595
inject,
96+
nextTick,
9697
onBeforeUnmount,
9798
onMounted,
9899
PropType,
@@ -210,6 +211,7 @@ export default defineComponent({
210211
markdownContent.value = fileContentsResponse.body
211212
markdownResource.value = fileInfoResponse
212213
214+
await nextTick()
213215
if (unref(markdownContent)) {
214216
observeMarkdownContainerResize()
215217
}
@@ -344,6 +346,14 @@ export default defineComponent({
344346
white-space: nowrap;
345347
}
346348
349+
.markdown-container {
350+
&-content {
351+
.md-editor-preview-wrapper {
352+
padding: 0;
353+
}
354+
}
355+
}
356+
347357
.markdown-container.collapsed {
348358
max-height: 100px;
349359
overflow: hidden;

packages/web-app-files/tests/unit/components/Spaces/__snapshots__/SpaceHeader.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ exports[`SpaceHeader > space description > should show the description 1`] = `
5252
</button>
5353
</div>
5454
<!--v-if-->
55-
<div class="markdown-container oc-flex oc-flex-middle">
55+
<div class="markdown-container oc-flex">
5656
<!---->
5757
<div class="markdown-container-edit oc-ml-s"><a attrs="[object Object]" size="small" appearance="raw"></a></div>
5858
</div>

packages/web-app-text-editor/src/App.vue

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,8 @@ export default defineComponent({
3636

3737
<style lang="scss">
3838
.oc-text-editor-readonly {
39-
//Toastui Editor doesn't have margins in view mode, adjusted for uniformity
40-
padding: 18px 25px;
41-
4239
//Fixes in readonly mode vertical scrolling is not available
4340
height: calc(100vh - 52px);
4441
overflow: auto;
4542
}
46-
47-
// Make url links limited in height and scrollable since base64 encoded images might be very long
48-
.toastui-editor-md-link-url {
49-
display: block;
50-
max-height: 100px;
51-
overflow: auto;
52-
}
53-
54-
.toastui-editor-defaultUI {
55-
// Adjustments to match our theming
56-
border: none;
57-
// Adjustments to not overlay with the app switcher
58-
z-index: 0;
59-
}
60-
61-
.toastui-editor-defaultUI-toolbar,
62-
.toastui-editor-md-tab-container {
63-
background-color: var(--oc-color-background-default) !important;
64-
}
6543
</style>

packages/web-pkg/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@
4040
"@opencloud-eu/design-system": "workspace:^",
4141
"@opencloud-eu/web-client": "workspace:^",
4242
"@sentry/vue": "^9.0.0",
43-
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
44-
"@toast-ui/editor": "^3.2.2",
4543
"@uppy/core": "^4.3.1",
4644
"@uppy/drop-target": "^3.0.2",
4745
"@uppy/tus": "^4.1.5",
4846
"@uppy/utils": "^6.0.6",
4947
"@uppy/xhr-upload": "^4.2.3",
48+
"@vavt/cm-extension": "^1.6.0",
5049
"@vue/shared": "^3.5.11",
5150
"@vueuse/core": "^12.0.0",
5251
"axios": "^1.7.7",
52+
"cropperjs": "^1.6.2",
5353
"deepmerge": "^4.2.2",
5454
"dompurify": "^3.1.7",
5555
"filesize": "^10.1.0",
@@ -58,13 +58,15 @@
5858
"lodash-es": "^4.17.21",
5959
"luxon": "^3.5.0",
6060
"mark.js": "^8.11.1",
61+
"md-editor-v3": "^5.2.1",
6162
"oidc-client-ts": "^2.4.0 || ^3.0.0",
6263
"p-queue": "^8.0.0",
6364
"password-sheriff": "^1.1.1",
6465
"pinia": "^3.0.0",
6566
"portal-vue": "^3.0.0",
6667
"prismjs": "^1.29.0",
6768
"qs": "^6.13.0",
69+
"screenfull": "^6.0.2",
6870
"semver": "^7.6.3",
6971
"uuid": "^11.0.0",
7072
"vue-concurrency": "^5.0.1",
Lines changed: 80 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,70 @@
11
<template>
2-
<app-loading-spinner v-if="loading" />
3-
<div v-else id="text-editor-container" ref="toastUiEditorRef" :data-markdown-mode="isMarkdown" />
2+
<div id="text-editor-container" class="oc-height-1-1">
3+
<md-preview
4+
v-if="isReadOnly"
5+
id="text-editor-preview-component"
6+
:model-value="currentContent"
7+
no-katex
8+
no-mermaid
9+
no-prettier
10+
no-upload-img
11+
no-highlight
12+
:language="languages[language.current] || 'en-US'"
13+
:theme="theme"
14+
read-only
15+
:toolbars="[]"
16+
/>
17+
<md-editor
18+
v-else
19+
id="text-editor-component"
20+
:model-value="currentContent"
21+
no-katex
22+
no-mermaid
23+
no-prettier
24+
no-upload-img
25+
no-highlight
26+
:language="languages[language.current] || 'en-US'"
27+
:theme="theme"
28+
:preview="isMarkdown"
29+
:toolbars="isMarkdown ? undefined : []"
30+
:toolbars-exclude="[
31+
'save',
32+
'katex',
33+
'github',
34+
'catalog',
35+
'mermaid',
36+
'prettier',
37+
'fullscreen',
38+
'htmlPreview',
39+
'pageFullscreen'
40+
]"
41+
:read-only="isReadOnly"
42+
@on-change="(value) => $emit('update:currentContent', value)"
43+
/>
44+
</div>
445
</template>
546

647
<script lang="ts">
7-
import { computed, defineComponent, unref, PropType, ref, onMounted, nextTick } from 'vue'
48+
import { computed, defineComponent, unref, PropType } from 'vue'
849
import { Resource } from '@opencloud-eu/web-client'
950
10-
import '@toast-ui/editor/dist/toastui-editor.css'
11-
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css'
51+
import { config, MdEditor, MdPreview } from 'md-editor-v3'
52+
import 'md-editor-v3/lib/style.css'
53+
54+
import { languageUserDefined, languages } from './l18n'
1255
13-
// @ts-ignore
14-
import { Editor, EditorCore, EditorOptions } from '@toast-ui/editor'
1556
import { useGettext } from 'vue3-gettext'
1657
import { useThemeStore } from '../../composables'
1758
import { AppConfigObject } from '../../apps'
18-
import AppLoadingSpinner from './../AppLoadingSpinner.vue'
59+
60+
import screenfull from 'screenfull'
61+
62+
import Cropper from 'cropperjs'
63+
import 'cropperjs/dist/cropper.css'
1964
2065
export default defineComponent({
2166
name: 'TextEditor',
22-
components: { AppLoadingSpinner },
67+
components: { MdEditor, MdPreview },
2368
props: {
2469
applicationConfig: { type: Object as PropType<AppConfigObject>, required: false },
2570
currentContent: {
@@ -31,15 +76,11 @@ export default defineComponent({
3176
resource: { type: Object as PropType<Resource>, required: false }
3277
},
3378
emits: ['update:currentContent'],
34-
setup(props, { emit }) {
79+
setup(props) {
3580
const language = useGettext()
36-
const themeStore = useThemeStore()
37-
const toastUiEditorRef = ref<HTMLElement>()
38-
const loading = ref(true)
81+
const { currentTheme } = useThemeStore()
3982
40-
// Should not be a ref, otherwise functions like setMarkdown won't work
41-
let toastUiEditor: EditorCore = null
42-
const config = computed(() => {
83+
const editorConfig = computed(() => {
4384
// TODO: Remove typecasting once vue-tsc has figured it out
4485
const { showPreviewOnlyMd = true } = props.applicationConfig as AppConfigObject
4586
return { showPreviewOnlyMd }
@@ -49,102 +90,45 @@ export default defineComponent({
4990
return (
5091
props.markdownMode ||
5192
['md', 'markdown'].includes(props.resource?.extension) ||
52-
!unref(config).showPreviewOnlyMd
93+
!unref(editorConfig).showPreviewOnlyMd
5394
)
5495
})
5596
56-
const keyDownHandler = (event: KeyboardEvent, editor: EditorCore) => {
57-
const ctrl = event.ctrlKey || event.metaKey
58-
if (!ctrl) {
59-
return
60-
}
61-
62-
switch (event.key) {
63-
case 'y':
64-
editor.exec('redo')
65-
break
66-
case 'z':
67-
editor.exec('undo')
68-
break
69-
}
70-
}
71-
72-
const loadSyntaxHighlighting = async () => {
73-
const [plugin] = await Promise.all([
74-
import(
75-
`@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js`
76-
),
77-
import(
78-
// @ts-ignore
79-
`@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css`
80-
),
81-
// @ts-ignore
82-
import('prismjs/themes/prism.css')
83-
])
84-
85-
return plugin.default
86-
}
87-
88-
onMounted(async () => {
89-
let codeSyntaxHighlight: unknown
90-
if (unref(isMarkdown)) {
91-
codeSyntaxHighlight = await loadSyntaxHighlighting()
97+
const theme = computed(() => (unref(currentTheme).isDark ? 'dark' : 'light'))
9298
93-
if (!props.isReadOnly) {
94-
await import('./l18n')
95-
}
96-
}
97-
98-
loading.value = false
99-
await nextTick()
100-
101-
let config: EditorOptions = {
102-
el: unref(toastUiEditorRef),
103-
usageStatistics: false, // sends hostname to google analytics DISABLE
104-
initialValue: props.currentContent,
105-
useCommandShortcut: false,
106-
hideModeSwitch: true,
107-
language: language.current,
108-
height: '100%',
109-
plugins: [...((codeSyntaxHighlight && [codeSyntaxHighlight]) || [])],
110-
viewer: props.isReadOnly,
111-
events: {
112-
change: () => {
113-
emit('update:currentContent', toastUiEditor.getMarkdown())
114-
},
115-
keydown: (_mode: string, event: KeyboardEvent) => {
116-
keyDownHandler(event, toastUiEditor)
117-
}
99+
config({
100+
editorConfig: {
101+
languageUserDefined
102+
},
103+
editorExtensions: {
104+
screenfull: {
105+
instance: screenfull
118106
},
119-
...(themeStore.currentTheme.isDark && { theme: 'dark' })
120-
}
121-
122-
if (!unref(isMarkdown)) {
123-
config = {
124-
...config,
125-
toolbarItems: [],
126-
initialEditType: 'wysiwyg'
107+
cropper: {
108+
instance: Cropper
127109
}
128110
}
129-
130-
toastUiEditor = Editor.factory(config) as EditorCore
131111
})
132112
133113
return {
134-
toastUiEditorRef,
135114
isMarkdown,
136-
loading
115+
theme,
116+
language,
117+
languages
137118
}
138119
}
139120
})
140121
</script>
141122
<style lang="scss">
142-
.toastui-editor-tabs {
143-
// Fix tab with for long i18n text
144-
.tab-item {
145-
width: auto;
146-
padding-left: var(--oc-space-small);
147-
padding-right: var(--oc-space-small);
123+
.md-editor {
124+
height: 100%;
125+
126+
&-code-head {
127+
justify-content: end !important;
128+
}
129+
130+
&-code-flag {
131+
display: none;
148132
}
149133
}
150134
</style>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defineAsyncComponent } from 'vue'
22

3-
// async component to avoid loading the huge toastjs package on page load
3+
// async component to avoid loading the huge m3-editor package on page load
44
export const TextEditor = defineAsyncComponent(
55
async () => (await import('./TextEditor.vue')).default
66
)

0 commit comments

Comments
 (0)