Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ updates:
ignore:
- dependency-name: vue
- dependency-name: vite
- dependency-name: monaco-editor

open-pull-requests-limit: 100
groups:
Expand Down
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
registry=https://registry.npmmirror.com
registry=https://registry.npmmirror.com
ignore-workspace-root-check=true
5 changes: 2 additions & 3 deletions apps/web/src/components/editor/CssEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,9 @@ function tabChanged(tabName: string | number) {
</TabsTrigger>
</TabsList>
</Tabs>
<textarea
<div
id="cssEditor"
type="textarea"
placeholder="Your custom css here."
class="flex-1 min-h-0"
/>

<!-- 新增弹窗 -->
Expand Down
71 changes: 43 additions & 28 deletions apps/web/src/components/editor/CustomUploadForm.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script setup lang='ts'>
import CodeMirror from 'codemirror'
import type { MonacoEditor } from '@md/shared'
import { monaco } from '@md/shared'
import { useStore } from '@/stores'
import { removeLeft } from '@/utils'
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'

const store = useStore()

Expand All @@ -18,38 +20,62 @@ const code = useLocalStorage(`formCustomConfig`, removeLeft(`
})
`).trim())

const formCustomTextarea = useTemplateRef<HTMLTextAreaElement>(`formCustomTextarea`)
const editorRef = useTemplateRef<HTMLDivElement>(`editorRef`)

const editor = ref<CodeMirror.EditorFromTextArea | null>(null)
const editor = ref<MonacoEditor.IStandaloneCodeEditor | null>(null)

onMounted(() => {
editor.value = markRaw(CodeMirror.fromTextArea(formCustomTextarea.value!, {
mode: `javascript`,
theme: store.isDark ? `darcula` : `xq-light`,
lineNumbers: true,
}))
editor.value = monaco.editor.create(editorRef.value!, {
value: code.value,
language: `javascript`,
theme: store.isDark ? `vs-dark` : `vs`,
lineNumbers: `on`,
automaticLayout: true,
fontSize: 12,
lineHeight: 18,
})
})

// 嵌套使用 nextTick 才能确保生效,具体原因未知
nextTick(() => {
nextTick(() => {
editor.value?.setValue(code.value)
function updateEditorStyle(fontSize: number, lineHeight: number) {
if (editor.value) {
editor.value.updateOptions({
fontSize,
lineHeight,
})
}
}

onMounted(() => {
const updateStyle = () => {
if (window.innerWidth >= 768) {
updateEditorStyle(14, 24)
}
else {
updateEditorStyle(12, 20)
}
}

window.addEventListener(`resize`, updateStyle)
updateStyle()

onUnmounted(() => {
window.removeEventListener(`resize`, updateStyle)
})
})

function formCustomSave() {
const str = editor.value!.getValue()
const str = toRaw(editor.value!).getValue()
localStorage.setItem(`formCustomConfig`, str)
toast.success(`保存成功`)
}
</script>

<template>
<div class="space-y-4 min-w-0">
<div class="h-60 border flex flex-col">
<textarea
ref="formCustomTextarea"
placeholder="Your custom code here."
<div class="h-60 border flex flex-col rounded-md">
<div
ref="editorRef"
class="flex-1"
/>
</div>
<Button
Expand All @@ -68,15 +94,4 @@ function formCustomSave() {
</template>

<style scoped>
/* 覆盖全局的 overflow-x: hidden 设置 */
:deep(.CodeMirror-scroll) {
overflow-x: auto !important;
overflow-y: auto !important;
}

@media (max-width: 768px) {
:deep(.CodeMirror) {
font-size: 12px;
}
}
</style>
3 changes: 0 additions & 3 deletions apps/web/src/components/editor/RightSlider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ const { isDark, primaryColor } = storeToRefs(store)

function customStyle() {
displayStore.toggleShowCssEditor()
setTimeout(() => {
store.cssEditor!.refresh()
}, 50)
}

const isOpen = ref(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const {
codeBlockTheme,
legend,
isMacCodeBlock,
cssEditor,
} = storeToRefs(store)

const {
Expand All @@ -53,9 +52,6 @@ function showPicker() {
// 自定义CSS样式
function customStyle() {
toggleShowCssEditor()
setTimeout(() => {
cssEditor.value!.refresh()
}, 50)
}

const pickColorsContainer = useTemplateRef(`pickColorsContainer`)
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createApp } from 'vue'
import App from './App.vue'
import { setupComponents } from './utils/setup-components'

import './userWorker'

import 'vue-sonner/style.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/xq-light.css'
Expand Down
84 changes: 41 additions & 43 deletions apps/web/src/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { MonacoEditor } from '@md/shared'
import type CodeMirror from 'codemirror'
import { initRenderer } from '@md/core'
import { monaco } from '@md/shared'
import {
defaultStyleConfig,
themeMap,
widthOptions,
} from '@md/shared/configs'
import CodeMirror from 'codemirror'
import { toPng } from 'html-to-image'
import { v4 as uuid } from 'uuid'
import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw'

import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw'
import { altKey, shiftKey } from '@/configs/shortcut-key'
import {
addPrefix,
css2json,
Expand All @@ -28,6 +28,7 @@ import {
sanitizeTitle,
} from '@/utils'
import { copyPlain } from '@/utils/clipboard'
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'

/**********************************
* Post 结构接口
Expand Down Expand Up @@ -280,10 +281,12 @@ export const useStore = defineStore(`store`, () => {
}
}

// 自义定 CSS 编辑器
const cssEditor = ref<CodeMirror.EditorFromTextArea | null>(null)
// 自义定 CSS 编辑器 (Monaco Editor)
const cssEditor = ref<MonacoEditor.IStandaloneCodeEditor | null>(null)
const setCssEditorValue = (content: string) => {
cssEditor.value!.setValue(content)
if (cssEditor.value) {
toRaw(cssEditor.value).setValue(content)
}
}
/**
* 自定义 CSS 内容
Expand Down Expand Up @@ -381,7 +384,7 @@ export const useStore = defineStore(`store`, () => {
isShowLineNumber: isShowLineNumber.value,
})

const raw = editor.value!.getValue()
const raw = toRaw(editor.value!).getValue()
const { html: baseHtml, readingTime: readingTimeResult } = renderMarkdown(raw, renderer)
readingTime.chars = raw.length
readingTime.words = readingTimeResult.words
Expand Down Expand Up @@ -409,7 +412,7 @@ export const useStore = defineStore(`store`, () => {

// 更新 CSS
const updateCss = () => {
const json = css2json(cssEditor.value!.getValue())
const json = css2json(toRaw(cssEditor.value!).getValue())
const newTheme = customCssWithTemplate(
json,
primaryColor.value,
Expand All @@ -429,47 +432,40 @@ export const useStore = defineStore(`store`, () => {
const cssEditorDom = document.querySelector<HTMLTextAreaElement>(
`#cssEditor`,
)!
cssEditorDom.value = getCurrentTab().content
const theme = isDark.value ? `darcula` : `xq-light`
cssEditor.value = markRaw(
CodeMirror.fromTextArea(cssEditorDom, {
mode: `css`,
theme,
lineNumbers: false,
lineWrapping: true,
styleActiveLine: true,
matchBrackets: true,
autofocus: true,
extraKeys: {
[`${shiftKey}-${altKey}-F`]: function autoFormat(
editor: CodeMirror.Editor,
) {
formatDoc(editor.getValue(), `css`).then((doc) => {
getCurrentTab().content = doc
editor.setValue(doc)
})
},
},
} as never),
)

// 自动提示
cssEditor.value.on(`keyup`, (cm, e) => {
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
(cm as any).showHint(e)
}
cssEditor.value = monaco.editor.create(cssEditorDom, {
value: getCurrentTab().content,
language: `css`,
theme: isDark.value ? `vs-dark` : `vs`,
lineNumbers: `off`,
automaticLayout: true,
fontSize: 14,
lineHeight: 22,
tabSize: 2,
minimap: { enabled: false },
})

// 实时保存
cssEditor.value.on(`update`, () => {
updateCss()
getCurrentTab().content = cssEditor.value!.getValue()
// 格式化
cssEditor.value.addCommand(monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => {
formatDoc(toRaw(cssEditor.value!).getValue(), `css`).then((doc) => {
toRaw(cssEditor.value!).setValue(doc)
})
})

// 监听内容变化
cssEditor.value.onDidChangeModelContent(() => {
if (cssEditor.value) {
const content = toRaw(cssEditor.value).getValue()
getCurrentTab().content = content
updateCss()
}
})
})

watch(isDark, () => {
const theme = isDark.value ? `darcula` : `xq-light`
toRaw(cssEditor.value)?.setOption?.(`theme`, theme)
if (cssEditor.value) {
cssEditor.value.updateOptions({ theme: isDark.value ? `vs-dark` : `vs` })
}
})

// 重置样式
Expand Down Expand Up @@ -498,7 +494,9 @@ export const useStore = defineStore(`store`, () => {
],
}

cssEditor.value!.setValue(DEFAULT_CSS_CONTENT)
if (cssEditor.value) {
toRaw(cssEditor.value).setValue(DEFAULT_CSS_CONTENT)
}

updateCss()
editorRefresh()
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/userWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'

// @ts-expect-error
globalThis.MonacoEnvironment = {
getWorker(_: any, label: string) {
if (label === `css` || label === `scss` || label === `less`) {
return new CssWorker()
}
if (label === `typescript` || label === `javascript`) {
return new TsWorker()
}
return new EditorWorker()
},
}

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
2 changes: 2 additions & 0 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ export default defineConfig(({ mode }) => {
return `mermaid`
if (id.includes(`highlight.js`))
return `hljs`
if (id.includes(`monaco-editor`))
return `monaco-editor`
const pkg = id
.split(`node_modules/`)[1]
.split(`/`)[0]
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export default antfu({
'no-console': `off`,
'no-debugger': `off`,
'ts/no-namespace': `off`,
'ts/ban-ts-comment': `off`,
},
})
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"postinstall": "simple-git-hooks",
"inspector": "pnpx node-modules-inspector"
},
"dependencies": {
"monaco-editor": "0.44.0"
},
"devDependencies": {
"@antfu/eslint-config": "5.4.1",
"@types/node": "^24.5.2",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './monaco'
5 changes: 5 additions & 0 deletions packages/shared/src/editor/monaco.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
import 'monaco-editor/esm/vs/editor/editor.all.js'

export type { editor as MonacoEditor } from 'monaco-editor/esm/vs/editor/editor.api'
export { monaco }
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './configs'
export * from './constants'
export * from './editor'
export * from './types'
export * from './utils'
Loading