From b25ceb62040201149f51800f6ee3bc495346fdcd Mon Sep 17 00:00:00 2001 From: YangFong Date: Wed, 14 Aug 2024 21:55:22 +0800 Subject: [PATCH] refactor: migration and recombination --- src/api/config.js | 55 -- src/assets/scripts/converter.js | 22 - src/assets/scripts/themes/default-theme.js | 193 ----- src/assets/scripts/util.js | 372 --------- src/components/CodemirrorEditor/CssEditor.vue | 11 +- .../{ => EditorHeader}/AboutDialog.vue | 0 .../EditorHeader/EditDropdown.vue | 44 + .../EditorHeader/HelpDropdown.vue | 2 +- .../EditorHeader/PostInfo.vue | 2 - .../EditorHeader/StyleDropdown.vue | 137 ++++ .../CodemirrorEditor/EditorHeader/index.vue | 146 +--- .../CodemirrorEditor/InsertFormDialog.vue | 23 +- .../CodemirrorEditor/UploadImgDialog.vue | 755 +++++++----------- src/config/index.js | 479 ++++++++--- src/main.js | 4 +- src/stores/index.js | 58 +- src/{api => utils}/fetch.js | 0 src/{api => utils}/file.js | 9 +- src/utils/index.js | 396 ++++++++- src/{assets/scripts => utils}/tokenTools.js | 6 +- .../renderers => utils}/wx-renderer.js | 1 + src/views/CodemirrorEditor.vue | 36 +- 22 files changed, 1321 insertions(+), 1430 deletions(-) delete mode 100644 src/api/config.js delete mode 100644 src/assets/scripts/converter.js delete mode 100644 src/assets/scripts/themes/default-theme.js delete mode 100644 src/assets/scripts/util.js rename src/components/CodemirrorEditor/{ => EditorHeader}/AboutDialog.vue (100%) create mode 100644 src/components/CodemirrorEditor/EditorHeader/EditDropdown.vue create mode 100644 src/components/CodemirrorEditor/EditorHeader/StyleDropdown.vue rename src/{api => utils}/fetch.js (100%) rename src/{api => utils}/file.js (97%) rename src/{assets/scripts => utils}/tokenTools.js (99%) rename src/{assets/scripts/renderers => utils}/wx-renderer.js (99%) diff --git a/src/api/config.js b/src/api/config.js deleted file mode 100644 index fec7048ef..000000000 --- a/src/api/config.js +++ /dev/null @@ -1,55 +0,0 @@ -const githubConfig = { - username: `filess`, - repoList: Array.from( - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], - e => `img${e}`, - ), - branch: `main`, - accessTokenList: [ - `7715d7ca67b5d3837cfdoocsmde8c38421815aa423510af`, - `c411415bf95dbe39625doocsmd5047ba9b7a2a6c9642abe`, - `2821cd8819fa345c053doocsmdca86ac653f8bc20db1f1b`, - `445f0dae46ef1f2a4d6doocsmdc797301e94797b4750a4c`, - `cc1d0c1426d0fd0902bdoocsmdd2d7184b14da61b86ec46`, - `b67e9d15cb6f910492fdoocsmdac6b44d379c953bb19eff`, - `618c4dc2244ccbbc088doocsmd125d17fd31b7d06a50cf3`, - `a4b581732e1c1507458doocsmdc5b223b27dae5e2e16a55`, - `77904db41aee57ad79bdoocsmd760f848201dac9c96fd5e`, - `02f251cb14ac62ab100doocsmdddbfc8527d773f1f04ce1`, - `eb321079a95ba7028d9doocsmde2e84c502dac70de7cf08`, - `22f74fcfb071a961fa2doocsmde28dabc746f0503a15e5d`, - `85124c2bfe7abba0938doocsmd0af7f67918b99d085a5fd`, - `0a561b4d4bbecb2de7edoocsmdd9ba3833d11dbc5e430f5`, - `e8a01491188d8d5a097doocsmd03ede0aad1fe9e3af24e9`, - `36e1f420d7e5bdebd67doocsmd65463562f5f25b20b8377`, - ], -} - -const giteeConfig = { - username: `filesss`, - repoList: Array.from( - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], - e => `img${e}`, - ), - branch: `main`, - accessTokenList: [ - `ed5fc9866bd6c2fdoocsmddd433f806fd2f399c`, - `5448ffebbbf1151doocsmdc4e337cf814fc8a62`, - `25b05efd2557ca2doocsmd75b5c0835e3395911`, - `11628c7a5aef015doocsmd2eeff9fb9566f0458`, - `cb2f5145ed938dedoocsmdbd063b4ed244eecf8`, - `d8c0b57500672c1doocsmd55f48b866b5ebcd98`, - `78c56eadb88e453doocsmd43ddd95753351771a`, - `03e1a688003948fdoocsmda16fcf41e6f03f1f0`, - `c49121cf4d191fbdoocsmdd6a7877ed537e474a`, - `adfeb2fadcdc4aadoocsmdfe1ee869ac9c968ff`, - `116c94549ca4a0ddoocsmd192653af5c0694616`, - `ecf30ed7f2eb184doocsmd51ea4ec8300371d9e`, - `5837cf2bd5afd93doocsmd73904bed31934949e`, - `b5b7e1c7d57e01fdoocsmd5266f552574297d78`, - `684d55564ffbd0bdoocsmd7d747e5cc23aed6d6`, - `3fc04a9d272ab71doocsmd010c56cb57d88d2ba`, - ], -} - -export { githubConfig, giteeConfig } diff --git a/src/assets/scripts/converter.js b/src/assets/scripts/converter.js deleted file mode 100644 index 1f54f8d64..000000000 --- a/src/assets/scripts/converter.js +++ /dev/null @@ -1,22 +0,0 @@ -import juice from 'juice' - -export function solveWeChatImage() { - const clipboardDiv = document.getElementById(`output`) - const images = clipboardDiv.getElementsByTagName(`img`) - for (let i = 0; i < images.length; i++) { - const image = images[i] - const width = image.getAttribute(`width`) - const height = image.getAttribute(`height`) - image.removeAttribute(`width`) - image.removeAttribute(`height`) - image.style.width = width - image.style.height = height - } -} - -export function mergeCss(html) { - return juice(html, { - inlinePseudoElements: true, - preserveImportant: true, - }) -} diff --git a/src/assets/scripts/themes/default-theme.js b/src/assets/scripts/themes/default-theme.js deleted file mode 100644 index dfb014944..000000000 --- a/src/assets/scripts/themes/default-theme.js +++ /dev/null @@ -1,193 +0,0 @@ -const baseColor = `#3f3f3f` - -export default { - BASE: { - 'text-align': `left`, - 'line-height': `1.75`, - }, - block: { - // 一级标题样式 - h1: { - 'font-size': `1.2em`, - 'text-align': `center`, - 'font-weight': `bold`, - 'display': `table`, - 'margin': `2em auto 1em`, - 'padding': `0 1em`, - 'border-bottom': `2px solid rgba(0, 152, 116, 0.9)`, - 'color': 'var(--el-text-color-regular)', - }, - - // 二级标题样式 - h2: { - 'font-size': `1.2em`, - 'text-align': `center`, - 'font-weight': `bold`, - 'display': `table`, - 'margin': `4em auto 2em`, - 'padding': `0 0.2em`, - 'background': `rgba(0, 152, 116, 0.9)`, - 'color': `#fff`, - }, - - // 三级标题样式 - h3: { - 'font-weight': `bold`, - 'font-size': `1.1em`, - 'margin': `2em 8px 0.75em 0`, - 'line-height': `1.2`, - 'padding-left': `8px`, - 'border-left': `3px solid rgba(0, 152, 116, 0.9)`, - 'color': 'var(--el-text-color-regular)', - }, - - // 四级标题样式 - h4: { - 'font-weight': `bold`, - 'font-size': `1em`, - 'margin': `2em 8px 0.5em`, - 'color': `rgba(66, 185, 131, 0.9)`, - }, - - // 段落样式 - p: { - 'margin': `1.5em 8px`, - 'letter-spacing': `0.1em`, - 'color': 'var(--el-text-color-regular)', - 'text-align': `justify`, - }, - - // 引用样式 - blockquote: { - 'font-style': `normal`, - 'border-left': `none`, - 'padding': `1em`, - 'border-radius': `8px`, - 'color': `rgba(0,0,0,0.5)`, - 'background': `#f7f7f7`, - 'margin': `2em 8px`, - }, - - blockquote_p: { - 'letter-spacing': `0.1em`, - 'color': `rgb(80, 80, 80)`, - 'font-size': `1em`, - 'display': `block`, - }, - code_pre: { - 'font-size': `14px`, - 'overflow-x': `auto`, - 'border-radius': `8px`, - 'padding': `1em`, - 'line-height': `1.5`, - 'margin': `10px 8px`, - }, - code: { - 'margin': 0, - 'white-space': `nowrap`, - 'font-family': `Menlo, Operator Mono, Consolas, Monaco, monospace`, - }, - - image: { - 'border-radius': `4px`, - 'display': `block`, - 'margin': `0.1em auto 0.5em`, - 'width': `100% !important`, - }, - - ol: { - 'margin-left': `0`, - 'padding-left': `1em`, - 'color': 'var(--el-text-color-regular)', - }, - - ul: { - 'margin-left': `0`, - 'padding-left': `1em`, - 'list-style': `circle`, - 'color': 'var(--el-text-color-regular)', - }, - - footnotes: { - 'margin': `0.5em 8px`, - 'font-size': `80%`, - 'color': 'var(--el-text-color-regular)', - }, - - figure: { - margin: `1.5em 8px`, - 'color': 'var(--el-text-color-regular)', - }, - hr: { - 'border-style': `solid`, - 'border-width': `1px 0 0`, - 'border-color': `rgba(0,0,0,0.1)`, - '-webkit-transform-origin': `0 0`, - '-webkit-transform': `scale(1, 0.5)`, - 'transform-origin': `0 0`, - 'transform': `scale(1, 0.5)`, - }, - }, - inline: { - listitem: { - 'text-indent': `-1em`, - 'display': `block`, - 'margin': `0.2em 8px`, - 'color': 'var(--el-text-color-regular)', - }, - - codespan: { - 'font-size': `90%`, - 'color': `#d14`, - 'background': `rgba(27,31,35,.05)`, - 'padding': `3px 5px`, - 'border-radius': `4px`, - // 'word-break': `break-all`, - }, - - link: { - color: `#576b95`, - }, - - wx_link: { - 'color': `#576b95`, - 'text-decoration': `none`, - }, - - // 字体加粗样式 - strong: { - 'color': `rgba(15, 76, 129, 0.9)`, - 'font-weight': `bold`, - }, - - table: { - 'border-collapse': `collapse`, - 'text-align': `center`, - 'margin': `1em 8px`, - 'color': 'var(--el-text-color-regular)', - }, - - thead: { - 'background': `rgba(0, 0, 0, 0.05)`, - 'font-weight': `bold`, - 'color': 'var(--el-text-color-regular)', - }, - - td: { - border: `1px solid #dfdfdf`, - padding: `0.25em 0.5em`, - color: baseColor, - }, - - footnote: { - 'font-size': `12px`, - 'color': 'var(--el-text-color-regular)', - }, - - figcaption: { - 'text-align': `center`, - 'color': `#888`, - 'font-size': `0.8em`, - }, - }, -} diff --git a/src/assets/scripts/util.js b/src/assets/scripts/util.js deleted file mode 100644 index df4dd40b5..000000000 --- a/src/assets/scripts/util.js +++ /dev/null @@ -1,372 +0,0 @@ -import prettier from 'prettier/standalone' -import prettierCss from 'prettier/parser-postcss' -import prettierMarkdown from 'prettier/parser-markdown' -import defaultTheme from './themes/default-theme' - -function createCustomTheme(theme, color) { - const customTheme = JSON.parse(JSON.stringify(theme)) - customTheme.block.h1[`border-bottom`] = `2px solid ${color}` - customTheme.block.h2.background = color - customTheme.block.h3[`border-left`] = `3px solid ${color}` - customTheme.block.h4.color = color - customTheme.inline.strong.color = color - return customTheme -} - -// 设置自定义颜色 -export function setColorWithTemplate(theme) { - return (color) => { - return createCustomTheme(theme, color) - } -} - -export function setColorWithCustomTemplate(theme, color) { - return createCustomTheme(theme, color) -} - -// 设置自定义字体大小 -export function setFontSizeWithTemplate(template) { - return function (fontSize) { - const customTheme = JSON.parse(JSON.stringify(template)) - customTheme.block.h1[`font-size`] = `${fontSize * 1.14}px` - customTheme.block.h2[`font-size`] = `${fontSize * 1.1}px` - customTheme.block.h3[`font-size`] = `${fontSize}px` - customTheme.block.h4[`font-size`] = `${fontSize}px` - return customTheme - } -} - -export const setColor = setColorWithTemplate(defaultTheme) -export const setFontSize = setFontSizeWithTemplate(defaultTheme) - -export function customCssWithTemplate(jsonString, color, theme) { - // block - const customTheme = createCustomTheme(theme, color) - - customTheme.block.h1 = Object.assign(customTheme.block.h1, jsonString.h1) - customTheme.block.h2 = Object.assign(customTheme.block.h2, jsonString.h2) - customTheme.block.h3 = Object.assign(customTheme.block.h3, jsonString.h3) - customTheme.block.h4 = Object.assign(customTheme.block.h4, jsonString.h4) - customTheme.block.code = Object.assign( - customTheme.block.code, - jsonString.code, - ) - customTheme.block.p = Object.assign(customTheme.block.p, jsonString.p) - customTheme.block.hr = Object.assign(customTheme.block.hr, jsonString.hr) - customTheme.block.blockquote = Object.assign( - customTheme.block.blockquote, - jsonString.blockquote, - ) - customTheme.block.blockquote_p = Object.assign( - customTheme.block.blockquote_p, - jsonString.blockquote_p, - ) - customTheme.block.image = Object.assign( - customTheme.block.image, - jsonString.image, - ) - - // inline - customTheme.inline.strong = Object.assign( - customTheme.inline.strong, - jsonString.strong, - ) - customTheme.inline.codespan = Object.assign( - customTheme.inline.codespan, - jsonString.codespan, - ) - customTheme.inline.link = Object.assign( - customTheme.inline.link, - jsonString.link, - ) - customTheme.inline.wx_link = Object.assign( - customTheme.inline.wx_link, - jsonString.wx_link, - ) - customTheme.block.ul = Object.assign(customTheme.block.ul, jsonString.ul) - customTheme.block.ol = Object.assign(customTheme.block.ol, jsonString.ol) - customTheme.inline.listitem = Object.assign( - customTheme.inline.listitem, - jsonString.li, - ) - return customTheme -} - -/** - * 将CSS形式的字符串转换为JSON - * - * @param {string} css - css字符串 - */ -export function css2json(css) { - // 移除CSS所有注释 - let open, close - while ( - (open = css.indexOf(`/*`)) !== -1 - && (close = css.indexOf(`*/`)) !== -1 - ) { - css = css.substring(0, open) + css.substring(close + 2) - } - - // 初始化返回值 - const json = {} - - while (css.length > 0 && css.includes(`{`) && css.includes(`}`)) { - // 存储第一个左/右花括号的下标 - const lbracket = css.indexOf(`{`) - const rbracket = css.indexOf(`}`) - - // 第一步:将声明转换为Object,如: - // `font: 'Times New Roman' 1em; color: #ff0000; margin-top: 1em;` - // ==> - // `{"font": "'Times New Roman' 1em", "color": "#ff0000", "margin-top": "1em"}` - - // 辅助方法:将array转为object - - function toObject(array) { - const ret = {} - array.forEach((e) => { - const index = e.indexOf(`:`) - const property = e.substring(0, index).trim() - ret[property] = e.substring(index + 1).trim() - }) - return ret - } - - // 切割声明块并移除空白符,然后放入数组中 - let declarations = css - .substring(lbracket + 1, rbracket) - .split(`;`) - .map(e => e.trim()) - .filter(e => e.length > 0) // 移除所有""空值 - - // 转为Object对象 - declarations = toObject(declarations) - - // 第二步:选择器处理,每个选择器会与它对应的声明相关联,如: - // `h1, p#bar {color: red}` - // ==> - // {"h1": {color: red}, "p#bar": {color: red}} - - const selectors = css - .substring(0, lbracket) - // 以,切割,并移除空格:`"h1, p#bar, span.foo"` => ["h1", "p#bar", "span.foo"] - .split(`,`) - .map(selector => selector.trim()) - - // 迭代赋值 - selectors.forEach((selector) => { - // 若不存在,则先初始化 - if (!json[selector]) - json[selector] = {} - // 赋值到JSON - Object.keys(declarations).forEach((key) => { - json[selector][key] = declarations[key] - }) - }) - - // 继续下个声明块 - css = css.slice(rbracket + 1).trim() - } - - // 返回JSON形式的结果串 - return json -} - -/** - * 将编辑器内容保存到 LocalStorage - * @param {*} editor - * @param {*} name - */ -export function saveEditorContent(editor, name) { - const content = editor.getValue(0) - if (content) { - localStorage.setItem(name, content) - } - else { - localStorage.removeItem(name) - } -} - -/** - * 格式化文档 - * @param {string} content - 文档内容 - */ -export function formatDoc(content) { - return prettier.format(content, { - parser: `markdown`, - plugins: [prettierMarkdown], - }) -} - -/** - * 格式化css - * @param {string} content - css内容 - */ -export function formatCss(content) { - return prettier.format(content, { - parser: `css`, - plugins: [prettierCss], - }) -} - -/** - * 导出原始 Markdown 文档 - * @param {string} doc - 文档内容 - */ -export function downloadMD(doc) { - const downLink = document.createElement(`a`) - - downLink.download = `content.md` - downLink.style.display = `none` - const blob = new Blob([doc]) - - downLink.href = URL.createObjectURL(blob) - document.body.appendChild(downLink) - downLink.click() - document.body.removeChild(downLink) -} - -/** - * 导出 HTML 生成内容 - */ -export function exportHTML() { - const element = document.querySelector(`#output`) - setStyles(element) - const htmlStr = element.innerHTML - - const downLink = document.createElement(`a`) - - downLink.download = `content.html` - downLink.style.display = `none` - const blob = new Blob([ - `
${htmlStr}
`, - ]) - - downLink.href = URL.createObjectURL(blob) - document.body.appendChild(downLink) - downLink.click() - document.body.removeChild(downLink) - - function setStyles(element) { - /** - * 获取一个 DOM 元素的所有样式, - * @param {DOM 元素} element DOM 元素 - * @param {排除的属性} excludes 如果某些属性对结果有不良影响,可以使用这个参数来排除 - * @returns 行内样式拼接结果 - */ - function getElementStyles(element, excludes = [`width`, `height`]) { - const styles = getComputedStyle(element, null) - return Object.entries(styles) - .filter( - ([key]) => styles.getPropertyValue(key) && !excludes.includes(key), - ) - .map(([key, value]) => `${key}:${value};`) - .join(``) - } - - switch (true) { - case isPre(element): - case isCode(element): - case isSpan(element): - element.setAttribute(`style`, getElementStyles(element)) - // eslint-disable-next-line no-fallthrough - default: - } - if (element.children.length) { - Array.from(element.children).forEach(child => setStyles(child)) - } - - // 判断是否是包裹代码块的 pre 元素 - function isPre(element) { - return ( - element.tagName === `PRE` - && Array.from(element.classList).includes(`code__pre`) - ) - } - - // 判断是否是包裹代码块的 code 元素 - function isCode(element) { - return ( - element.tagName === `CODE` - ) - } - - // 判断是否是包裹代码字符的 span 元素 - function isSpan(element) { - return ( - element.tagName === `SPAN` - && (isCode(element.parentElement) - || isCode(element.parentElement.parentElement)) - ) - } - } -} - -/** - * 生成列表字符串 - * @param {*} data 对应内容集合 - * @param {*} rows 行 - * @param {*} cols 列 - */ -export function createTable({ data, rows, cols }) { - let table = `` - for (let i = 0; i < rows + 2; ++i) { - table += `| ` - const currRow = [] - for (let j = 0; j < cols; ++j) { - const rowIdx = i > 1 ? i - 1 : i - currRow.push(i === 1 ? `---` : data[`k_${rowIdx}_${j}`] || ` `) - } - table += currRow.join(` | `) - table += ` |\n` - } - - return table -} - -export function toBase64(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = () => resolve(reader.result.split(`,`).pop()) - reader.onerror = error => reject(error) - }) -} - -export function checkImage(file) { - // check filename suffix - const isValidSuffix = /\.(gif|jpg|jpeg|png|GIF|JPG|PNG)$/.test(file.name) - if (!isValidSuffix) { - return { - ok: false, - msg: `请上传 JPG/PNG/GIF 格式的图片`, - } - } - - // check file size - const maxSize = 10 - const valid = file.size / 1024 / 1024 <= maxSize - if (!valid) { - return { - ok: false, - msg: `由于公众号限制,图片大小不能超过 ${maxSize}M`, - } - } - return { ok: true } -} - -/** - * 移除左边多余空格 - * @param {*} str - * @returns - */ -export function removeLeft(str) { - const lines = str.split(`\n`) - // 获取应该删除的空白符数量 - const minSpaceNum = lines - .filter(item => item.trim()) - .map(item => item.match(/(^\s+)?/)[0].length) - .sort((a, b) => a - b)[0] - // 删除空白符 - return lines.map(item => item.slice(minSpaceNum)).join(`\n`) -} diff --git a/src/components/CodemirrorEditor/CssEditor.vue b/src/components/CodemirrorEditor/CssEditor.vue index b0779efcc..16cd6aafe 100644 --- a/src/components/CodemirrorEditor/CssEditor.vue +++ b/src/components/CodemirrorEditor/CssEditor.vue @@ -1,15 +1,12 @@