md-wechat/src/stores/index.js
2024-09-10 08:21:25 +08:00

441 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { computed, markRaw, onMounted, ref } from 'vue'
import { createPinia, defineStore } from 'pinia'
import { marked } from 'marked'
import CodeMirror from 'codemirror'
import { useDark, useStorage, useToggle } from '@vueuse/core'
import { ElMessage, ElMessageBox } from 'element-plus'
import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config'
import { initRenderer } from '@/utils/renderer'
import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw'
import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw'
import { addPrefix, css2json, customCssWithTemplate, customizeTheme, downloadMD, exportHTML, formatDoc } from '@/utils'
export const useStore = defineStore(`store`, () => {
// 是否开启深色模式
const isDark = useDark()
const toggleDark = useToggle(isDark)
// 是否开启 Mac 代码块
const isMacCodeBlock = useStorage(`isMacCodeBlock`, true)
const toggleMacCodeBlock = useToggle(isMacCodeBlock)
// 是否在左侧编辑
const isEditOnLeft = useStorage(`isEditOnLeft`, true)
const toggleEditOnLeft = useToggle(isEditOnLeft)
// 是否开启微信外链接底部引用
const isCiteStatus = useStorage(`isCiteStatus`, false)
const toggleCiteStatus = useToggle(isCiteStatus)
const output = ref(``)
// 文本字体
const theme = useStorage(addPrefix(`theme`), themeOptions[0].value)
// 文本字体
const fontFamily = useStorage(`fonts`, fontFamilyOptions[0].value)
// 文本大小
const fontSize = useStorage(`size`, fontSizeOptions[2].value)
// 主色
const primaryColor = useStorage(`color`, colorOptions[0].value)
// 代码块主题
const codeBlockTheme = useStorage(`codeBlockTheme`, codeBlockThemeOptions[23].value)
// 图注格式
const legend = useStorage(`legend`, legendOptions[3].value)
const fontSizeNumber = computed(() => fontSize.value.replace(`px`, ``))
// 内容编辑器编辑器
const editor = ref(null)
// 编辑区域内容
const editorContent = useStorage(`__editor_content`, DEFAULT_CONTENT)
// 格式化文档
const formatContent = () => {
formatDoc(editor.value.getValue()).then((doc) => {
editorContent.value = doc
editor.value.setValue(doc)
})
}
// 切换 highlight.js 代码主题
const codeThemeChange = () => {
const cssUrl = codeBlockTheme.value
const el = document.querySelector(`#hljs`)
if (el) {
el.setAttribute(`href`, cssUrl)
}
else {
const link = document.createElement(`link`)
link.setAttribute(`type`, `text/css`)
link.setAttribute(`rel`, `stylesheet`)
link.setAttribute(`href`, cssUrl)
link.setAttribute(`id`, `hljs`)
document.head.appendChild(link)
}
}
// 自义定 CSS 编辑器
const cssEditor = ref(null)
const setCssEditorValue = (content) => {
cssEditor.value.setValue(content)
}
// 自定义 CSS 内容
const cssContent = useStorage(`__css_content`, DEFAULT_CSS_CONTENT)
const cssContentConfig = useStorage(addPrefix(`css_content_config`), {
active: `方案1`,
tabs: [
{
title: `方案1`,
name: `方案1`,
// 兼容之前的方案
content: cssContent.value || DEFAULT_CSS_CONTENT,
},
],
})
onMounted(() => {
// 清空过往历史记录
cssContent.value = ``
})
const getCurrentTab = () => cssContentConfig.value.tabs.find((tab) => {
return tab.name === cssContentConfig.value.active
})
const tabChanged = (name) => {
cssContentConfig.value.active = name
const content = cssContentConfig.value.tabs.find((tab) => {
return tab.name === name
}).content
setCssEditorValue(content)
}
// 重命名 css 方案
const renameTab = (name) => {
const tab = getCurrentTab()
tab.title = name
tab.name = name
cssContentConfig.value.active = name
}
const addCssContentTab = (name) => {
cssContentConfig.value.tabs.push({
name,
title: name,
content: DEFAULT_CSS_CONTENT,
})
cssContentConfig.value.active = name
setCssEditorValue(DEFAULT_CSS_CONTENT)
}
const validatorTabName = (val) => {
return cssContentConfig.value.tabs.every(({ name }) => name !== val)
}
const renderer = initRenderer({
theme: customCssWithTemplate(css2json(getCurrentTab().content), primaryColor.value, customizeTheme(themeMap[theme.value], { fontSize: fontSizeNumber.value, color: primaryColor.value })),
fonts: fontFamily.value,
})
// 更新编辑器
const editorRefresh = () => {
codeThemeChange()
renderer.reset({ status: isCiteStatus.value, legend: legend.value })
let outputTemp = marked.parse(editor.value.getValue(0))
// 去除第一行的 margin-top
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
// 引用脚注
outputTemp += renderer.buildFootnotes()
// 附加的一些 style
outputTemp += renderer.buildAddition()
if (isMacCodeBlock.value) {
outputTemp += `
<style>
.hljs.code__pre > .mac-sign {
display: inline-block;
}
.hljs.code__pre {
padding: 0!important;
}
.hljs.code__pre code {
display: -webkit-box;
padding: 0.5em 1em 1em;
overflow-x: auto;
text-indent: 0;
}
</style>
`
}
output.value = outputTemp
}
// 更新 CSS
const updateCss = () => {
const json = css2json(cssEditor.value.getValue())
const newTheme = customCssWithTemplate(json, primaryColor.value, customizeTheme(themeMap[theme.value], { fontSize: fontSizeNumber.value, color: primaryColor.value }))
renderer.setOptions({
theme: newTheme,
})
editorRefresh()
}
// 初始化 CSS 编辑器
onMounted(() => {
const cssEditorDom = document.querySelector(`#cssEditor`)
cssEditorDom.value = getCurrentTab().content
cssEditor.value = markRaw(
CodeMirror.fromTextArea(cssEditorDom, {
mode: `css`,
theme: `style-mirror`,
lineNumbers: false,
lineWrapping: true,
matchBrackets: true,
autofocus: true,
extraKeys: {
[`${shiftKey}-${altKey}-F`]: function autoFormat(editor) {
formatDoc(editor.getValue(), `css`).then((doc) => {
getCurrentTab().content = doc
editor.setValue(doc)
})
},
},
}),
)
// 自动提示
cssEditor.value.on(`keyup`, (cm, e) => {
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
cm.showHint(e)
}
})
// 实时保存
cssEditor.value.on(`update`, () => {
updateCss()
getCurrentTab().content = cssEditor.value.getValue()
})
})
// 重置样式
const resetStyle = () => {
isCiteStatus.value = false
isMacCodeBlock.value = true
theme.value = themeOptions[0].value
fontFamily.value = fontFamilyOptions[0].value
fontFamily.value = fontFamilyOptions[0].value
fontSize.value = fontSizeOptions[2].value
primaryColor.value = colorOptions[0].value
codeBlockTheme.value = codeBlockThemeOptions[2].value
legend.value = legendOptions[3].value
cssContentConfig.value = {
active: `方案 1`,
tabs: [
{
title: `方案 1`,
name: `方案 1`,
// 兼容之前的方案
content: cssContent.value || DEFAULT_CSS_CONTENT,
},
],
}
cssEditor.value.setValue(DEFAULT_CSS_CONTENT)
updateCss()
editorRefresh()
}
// 为函数添加刷新编辑器的功能
const withAfterRefresh = fn => (...rest) => {
fn(...rest)
editorRefresh()
}
const getTheme = (size, color) => {
const newTheme = themeMap[theme.value]
const fontSize = size.replace(`px`, ``)
return customCssWithTemplate(css2json(getCurrentTab().content), color, customizeTheme(newTheme, { fontSize, color }))
}
const themeChanged = withAfterRefresh((newTheme) => {
renderer.setOptions({
theme: customCssWithTemplate(css2json(getCurrentTab().content), primaryColor.value, customizeTheme(themeMap[newTheme], { fontSize: fontSizeNumber.value })),
})
theme.value = newTheme
})
const fontChanged = withAfterRefresh((fonts) => {
renderer.setOptions({
fonts,
})
fontFamily.value = fonts
})
const sizeChanged = withAfterRefresh((size) => {
const theme = getTheme(size, primaryColor.value)
renderer.setOptions({
size,
theme,
})
fontSize.value = size
})
const colorChanged = withAfterRefresh((newColor) => {
const theme = getTheme(fontSize.value, newColor)
renderer.setOptions({
theme,
})
primaryColor.value = newColor
})
const codeBlockThemeChanged = withAfterRefresh((newTheme) => {
codeBlockTheme.value = newTheme
})
const legendChanged = withAfterRefresh((newVal) => {
legend.value = newVal
})
const macCodeBlockChanged = withAfterRefresh(() => {
toggleMacCodeBlock()
})
const citeStatusChanged = withAfterRefresh(() => {
toggleCiteStatus()
})
// 导出编辑器内容为 HTML并且下载到本地
const exportEditorContent2HTML = () => {
exportHTML()
document.querySelector(`#output`).innerHTML = output.value
}
// 导出编辑器内容到本地
const exportEditorContent2MD = () => {
downloadMD(editor.value.getValue())
}
// 导入 Markdown 文档
const importMarkdownContent = () => {
const body = document.body
const input = document.createElement(`input`)
input.type = `file`
input.name = `filename`
input.accept = `.md`
input.onchange = () => {
const file = input.files[0]
if (!file) {
return
}
const reader = new FileReader()
reader.readAsText(file)
reader.onload = (event) => {
editor.value.setValue(event.target.result)
ElMessage.success(`文档导入成功`)
}
}
body.appendChild(input)
input.click()
body.removeChild(input)
}
// 重置样式
const resetStyleConfirm = () => {
ElMessageBox.confirm(
`此操作将丢失本地自定义样式,是否继续?`,
`提示`,
{
confirmButtonText: `确定`,
cancelButtonText: `取消`,
type: `warning`,
center: true,
},
)
.then(() => {
resetStyle()
ElMessage({
type: `success`,
message: `样式重置成功~`,
})
})
.catch(() => {
editor.value.focus()
})
}
const isShowCssEditor = ref(false)
const toggleShowCssEditor = useToggle(isShowCssEditor)
const isShowInsertFormDialog = ref(false)
const toggleShowInsertFormDialog = useToggle(isShowInsertFormDialog)
const isShowUploadImgDialog = ref(false)
const toggleShowUploadImgDialog = useToggle(isShowUploadImgDialog)
return {
isShowCssEditor,
toggleShowCssEditor,
isShowInsertFormDialog,
toggleShowInsertFormDialog,
isShowUploadImgDialog,
toggleShowUploadImgDialog,
isDark,
toggleDark,
isEditOnLeft,
toggleEditOnLeft,
isMacCodeBlock,
isCiteStatus,
citeStatusChanged,
output,
editor,
cssEditor,
theme,
fontFamily,
fontSize,
primaryColor,
codeBlockTheme,
legend,
editorRefresh,
themeChanged,
fontChanged,
sizeChanged,
colorChanged,
codeBlockThemeChanged,
legendChanged,
macCodeBlockChanged,
formatContent,
exportEditorContent2HTML,
exportEditorContent2MD,
importMarkdownContent,
resetStyleConfirm,
editorContent,
cssContentConfig,
addCssContentTab,
validatorTabName,
setCssEditorValue,
tabChanged,
renameTab,
}
})
export default createPinia()