diff --git a/package-lock.json b/package-lock.json index b1c3e6a..8f0fb4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,9 +41,11 @@ }, "devDependencies": { "@antfu/eslint-config": "2.26.0", + "@types/buffer-from": "^1.1.3", "@types/codemirror": "^5.60.15", - "@types/marked": "^4.0.0", + "@types/crypto-js": "^4.2.2", "@types/node": "^22.4.1", + "@types/uuid": "^10.0.0", "@unocss/eslint-plugin": "^0.62.2", "@vitejs/plugin-vue": "^5.1.2", "autoprefixer": "^10.4.20", @@ -913,7 +915,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2229,6 +2230,15 @@ "vue": "^2.7.0 || ^3.0.0" } }, + "node_modules/@types/buffer-from": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@types/buffer-from/-/buffer-from-1.1.3.tgz", + "integrity": "sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/codemirror": { "version": "5.60.15", "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-5.60.15.tgz", @@ -2238,6 +2248,12 @@ "@types/tern": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz", @@ -2273,12 +2289,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/marked": { - "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/@types/marked/-/marked-4.3.2.tgz", - "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", - "dev": true - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.15.tgz", @@ -2318,6 +2328,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", diff --git a/package.json b/package.json index 026e3c6..091ba3e 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,11 @@ }, "devDependencies": { "@antfu/eslint-config": "2.26.0", + "@types/buffer-from": "^1.1.3", "@types/codemirror": "^5.60.15", - "@types/marked": "^4.0.0", + "@types/crypto-js": "^4.2.2", "@types/node": "^22.4.1", + "@types/uuid": "^10.0.0", "@unocss/eslint-plugin": "^0.62.2", "@vitejs/plugin-vue": "^5.1.2", "autoprefixer": "^10.4.20", diff --git a/src/config/theme.ts b/src/config/theme.ts index e9af131..c1a6326 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -1,6 +1,6 @@ import { toMerged } from 'es-toolkit' -import type { Theme } from '@/types' +import type { IConfigOption, Theme } from '@/types' const defaultTheme: Theme = { base: { @@ -342,7 +342,12 @@ const graceTheme = toMerged(defaultTheme, { }, }) -export const themeOptions = [ +export const themeMap = { + default: defaultTheme, + grace: graceTheme, +} + +export const themeOptions: IConfigOption[] = [ { label: `经典`, value: `default`, @@ -354,8 +359,3 @@ export const themeOptions = [ desc: ``, }, ] - -export const themeMap = { - default: defaultTheme, - grace: graceTheme, -} diff --git a/src/stores/index.js b/src/stores/index.ts similarity index 87% rename from src/stores/index.js rename to src/stores/index.ts index 7e9c306..1980e57 100644 --- a/src/stores/index.js +++ b/src/stores/index.ts @@ -31,7 +31,7 @@ export const useStore = defineStore(`store`, () => { const output = ref(``) // 文本字体 - const theme = useStorage(addPrefix(`theme`), themeOptions[0].value) + const theme = useStorage(addPrefix(`theme`), themeOptions[0].value) // 文本字体 const fontFamily = useStorage(`fonts`, fontFamilyOptions[0].value) // 文本大小 @@ -46,15 +46,15 @@ export const useStore = defineStore(`store`, () => { const fontSizeNumber = computed(() => fontSize.value.replace(`px`, ``)) // 内容编辑器编辑器 - const editor = ref(null) + 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) + formatDoc((editor.value!).getValue()).then((doc) => { + editorContent.value = doc; + (editor.value!).setValue(doc) }) } @@ -76,9 +76,9 @@ export const useStore = defineStore(`store`, () => { } // 自义定 CSS 编辑器 - const cssEditor = ref(null) - const setCssEditorValue = (content) => { - cssEditor.value.setValue(content) + const cssEditor = ref(null) + const setCssEditorValue = (content: string) => { + (cssEditor.value!).setValue(content) } // 自定义 CSS 内容 const cssContent = useStorage(`__css_content`, DEFAULT_CSS_CONTENT) @@ -99,24 +99,24 @@ export const useStore = defineStore(`store`, () => { }) const getCurrentTab = () => cssContentConfig.value.tabs.find((tab) => { return tab.name === cssContentConfig.value.active - }) - const tabChanged = (name) => { + })! + const tabChanged = (name: string) => { cssContentConfig.value.active = name const content = cssContentConfig.value.tabs.find((tab) => { return tab.name === name - }).content + })!.content setCssEditorValue(content) } // 重命名 css 方案 - const renameTab = (name) => { - const tab = getCurrentTab() + const renameTab = (name: string) => { + const tab = getCurrentTab()! tab.title = name tab.name = name cssContentConfig.value.active = name } - const addCssContentTab = (name) => { + const addCssContentTab = (name: string) => { cssContentConfig.value.tabs.push({ name, title: name, @@ -125,20 +125,21 @@ export const useStore = defineStore(`store`, () => { cssContentConfig.value.active = name setCssEditorValue(DEFAULT_CSS_CONTENT) } - const validatorTabName = (val) => { + const validatorTabName = (val: string) => { 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, + size: fontSizeNumber.value, }) // 更新编辑器 const editorRefresh = () => { codeThemeChange() renderer.reset({ status: isCiteStatus.value, legend: legend.value }) - let outputTemp = marked.parse(editor.value.getValue(0)) + let outputTemp = marked.parse(editor.value!.getValue()) as string // 去除第一行的 margin-top outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`) @@ -173,7 +174,7 @@ export const useStore = defineStore(`store`, () => { // 更新 CSS const updateCss = () => { - const json = css2json(cssEditor.value.getValue()) + 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, @@ -182,7 +183,7 @@ export const useStore = defineStore(`store`, () => { } // 初始化 CSS 编辑器 onMounted(() => { - const cssEditorDom = document.querySelector(`#cssEditor`) + const cssEditorDom = document.querySelector(`#cssEditor`)! cssEditorDom.value = getCurrentTab().content const theme = isDark.value ? `darcula` : `xq-light` cssEditor.value = markRaw( @@ -190,32 +191,32 @@ export const useStore = defineStore(`store`, () => { mode: `css`, theme, lineNumbers: false, - styleActiveLine: true, lineWrapping: true, + styleActiveLine: true, matchBrackets: true, autofocus: true, extraKeys: { - [`${shiftKey}-${altKey}-F`]: function autoFormat(editor) { + [`${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.showHint(e) + (cm as any).showHint(e) } }) // 实时保存 cssEditor.value.on(`update`, () => { updateCss() - getCurrentTab().content = cssEditor.value.getValue() + getCurrentTab().content = cssEditor.value!.getValue() }) }) @@ -249,25 +250,25 @@ export const useStore = defineStore(`store`, () => { ], } - cssEditor.value.setValue(DEFAULT_CSS_CONTENT) + cssEditor.value!.setValue(DEFAULT_CSS_CONTENT) updateCss() editorRefresh() } // 为函数添加刷新编辑器的功能 - const withAfterRefresh = fn => (...rest) => { + const withAfterRefresh = (fn: (...rest: any[]) => void) => (...rest: any[]) => { fn(...rest) editorRefresh() } - const getTheme = (size, color) => { + const getTheme = (size: string, color: string) => { const newTheme = themeMap[theme.value] const fontSize = size.replace(`px`, ``) return customCssWithTemplate(css2json(getCurrentTab().content), color, customizeTheme(newTheme, { fontSize, color })) } - const themeChanged = withAfterRefresh((newTheme) => { + const themeChanged = withAfterRefresh((newTheme: keyof typeof themeMap) => { renderer.setOptions({ theme: customCssWithTemplate(css2json(getCurrentTab().content), primaryColor.value, customizeTheme(themeMap[newTheme], { fontSize: fontSizeNumber.value })), }) @@ -320,12 +321,12 @@ export const useStore = defineStore(`store`, () => { // 导出编辑器内容为 HTML,并且下载到本地 const exportEditorContent2HTML = () => { exportHTML() - document.querySelector(`#output`).innerHTML = output.value + document.querySelector(`#output`)!.innerHTML = output.value } // 导出编辑器内容到本地 const exportEditorContent2MD = () => { - downloadMD(editor.value.getValue()) + downloadMD(editor.value!.getValue()) } // 导入 Markdown 文档 @@ -336,7 +337,7 @@ export const useStore = defineStore(`store`, () => { input.name = `filename` input.accept = `.md` input.onchange = () => { - const file = input.files[0] + const file = input.files![0] if (!file) { return } @@ -344,7 +345,7 @@ export const useStore = defineStore(`store`, () => { const reader = new FileReader() reader.readAsText(file) reader.onload = (event) => { - editor.value.setValue(event.target.result) + (editor.value!).setValue((event.target !).result as string) ElMessage.success(`文档导入成功`) } } @@ -374,7 +375,7 @@ export const useStore = defineStore(`store`, () => { }) }) .catch(() => { - editor.value.focus() + (editor.value!).focus() }) } diff --git a/src/types/index.ts b/src/types/index.ts index d96c8a7..fe9372b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,7 +1,7 @@ import type { PropertiesHyphen } from 'csstype' -type Block = `h1` | `h2` | `h3` | `h4` | `p` | `blockquote` | `blockquote_p` | `code_pre` | `code` | `image` | `ol` | `ul` | `footnotes` | `figure` | `hr` -type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em` +export type Block = `h1` | `h2` | `h3` | `h4` | `p` | `blockquote` | `blockquote_p` | `code_pre` | `code` | `image` | `ol` | `ul` | `footnotes` | `figure` | `hr` +export type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em` interface CustomCSSProperties { [`--md-primary-color`]?: string @@ -20,14 +20,14 @@ export interface IOpts { theme: Theme fonts: string size: string - legend: string - status: boolean + legend?: string + status?: boolean } export type ThemeStyles = Record -export interface IConfigOption { +export interface IConfigOption { label: string - value: string + value: VT desc: string } diff --git a/src/utils/fetch.js b/src/utils/fetch.ts similarity index 90% rename from src/utils/fetch.js rename to src/utils/fetch.ts index 4ea31a6..476eefd 100644 --- a/src/utils/fetch.js +++ b/src/utils/fetch.ts @@ -8,7 +8,7 @@ const service = axios.create({ service.interceptors.request.use( (config) => { - if (/^(?:post|put|delete)$/i.test(config.method)) { + if (/^(?:post|put|delete)$/i.test(`${config.method}`)) { if (config.data && config.data.upload) { config.headers[`Content-Type`] = `multipart/form-data` } diff --git a/src/utils/file.js b/src/utils/file.ts similarity index 86% rename from src/utils/file.js rename to src/utils/file.ts index 96c5993..8e00877 100644 --- a/src/utils/file.js +++ b/src/utils/file.ts @@ -11,7 +11,7 @@ import { base64encode, safe64, utf16to8 } from '@/utils/tokenTools' import * as tokenTools from '@/utils/tokenTools' import { giteeConfig, githubConfig } from '@/config' -function getConfig(useDefault, platform) { +function getConfig(useDefault: boolean, platform: string) { if (useDefault) { // load default config file const config = platform === `github` ? githubConfig : giteeConfig @@ -29,7 +29,7 @@ function getConfig(useDefault, platform) { } // load configuration from localStorage - const customConfig = JSON.parse(localStorage.getItem(`${platform}Config`)) + const customConfig = JSON.parse(localStorage.getItem(`${platform}Config`)!) // split username/repo const repoUrl = customConfig.repo @@ -62,7 +62,7 @@ function getDir() { * @param {string} filename 文件名 * @returns {string} `时间戳+uuid` */ -function getDateFilename(filename) { +function getDateFilename(filename: string) { const currentTimestamp = new Date().getTime() const fileSuffix = filename.split(`.`)[1] return `${currentTimestamp}-${uuidv4()}.${fileSuffix}` @@ -72,7 +72,7 @@ function getDateFilename(filename) { // GitHub File Upload // ----------------------------------------------------------------------- -async function ghFileUpload(content, filename) { +async function ghFileUpload(content: string, filename: string) { const useDefault = localStorage.getItem(`imgHost`) === `default` const { username, repo, branch, accessToken } = getConfig( useDefault, @@ -81,7 +81,18 @@ async function ghFileUpload(content, filename) { const dir = getDir() const url = `https://api.github.com/repos/${username}/${repo}/contents/${dir}/` const dateFilename = getDateFilename(filename) - const res = await fetch({ + const res = await fetch<{ content: { + download_url: string + } }, { + content: { + download_url: string + } + data: { + content: { + download_url: string + } + } + }>({ url: url + dateFilename, method: `put`, headers: { @@ -95,7 +106,7 @@ async function ghFileUpload(content, filename) { }) const githubResourceUrl = `raw.githubusercontent.com/${username}/${repo}/${branch}/` const cdnResourceUrl = `fastly.jsdelivr.net/gh/${username}/${repo}@${branch}/` - res.content = res.data?.content || res.content + res.content = res.data.content || res.content return useDefault ? res.content.download_url.replace(githubResourceUrl, cdnResourceUrl) : res.content.download_url @@ -105,13 +116,24 @@ async function ghFileUpload(content, filename) { // Gitee File Upload // ----------------------------------------------------------------------- -async function giteeUpload(content, filename) { +async function giteeUpload(content: any, filename: string) { const useDefault = localStorage.getItem(`imgHost`) === `default` const { username, repo, branch, accessToken } = getConfig(useDefault, `gitee`) const dir = getDir() const dateFilename = getDateFilename(filename) const url = `https://gitee.com/api/v5/repos/${username}/${repo}/contents/${dir}/${dateFilename}` - const res = await fetch({ + const res = await fetch<{ content: { + download_url: string + } }, { + content: { + download_url: string + } + data: { + content: { + download_url: string + } + } + }>({ url, method: `POST`, data: { @@ -129,7 +151,10 @@ async function giteeUpload(content, filename) { // Qiniu File Upload // ----------------------------------------------------------------------- -function getQiniuToken(accessKey, secretKey, putPolicy) { +function getQiniuToken(accessKey: string, secretKey: string, putPolicy: { + scope: string + deadline: number +}) { const policy = JSON.stringify(putPolicy) const encoded = base64encode(utf16to8(policy)) const hash = CryptoJS.HmacSHA1(encoded, secretKey) @@ -137,9 +162,9 @@ function getQiniuToken(accessKey, secretKey, putPolicy) { return `${accessKey}:${safe64(encodedSigned)}:${encoded}` } -async function qiniuUpload(file) { +async function qiniuUpload(file: File) { const { accessKey, secretKey, bucket, region, path, domain } = JSON.parse( - localStorage.getItem(`qiniuConfig`), + localStorage.getItem(`qiniuConfig`)!, ) const token = getQiniuToken(accessKey, secretKey, { scope: bucket, @@ -167,10 +192,10 @@ async function qiniuUpload(file) { // AliOSS File Upload // ----------------------------------------------------------------------- -async function aliOSSFileUpload(file) { +async function aliOSSFileUpload(file: File) { const dateFilename = getDateFilename(file.name) const { region, bucket, accessKeyId, accessKeySecret, useSSL, cdnHost, path } - = JSON.parse(localStorage.getItem(`aliOSSConfig`)) + = JSON.parse(localStorage.getItem(`aliOSSConfig`)!) const dir = path ? `${path}/${dateFilename}` : dateFilename const secure = useSSL === undefined || useSSL const protocol = secure ? `https` : `http` @@ -195,10 +220,10 @@ async function aliOSSFileUpload(file) { // TxCOS File Upload // ----------------------------------------------------------------------- -async function txCOSFileUpload(file) { +async function txCOSFileUpload(file: File) { const dateFilename = getDateFilename(file.name) const { secretId, secretKey, bucket, region, path, cdnHost } = JSON.parse( - localStorage.getItem(`txCOSConfig`), + localStorage.getItem(`txCOSConfig`)!, ) const cos = new COS({ SecretId: secretId, @@ -235,13 +260,13 @@ async function txCOSFileUpload(file) { // Minio File Upload // ----------------------------------------------------------------------- -async function minioFileUpload(content, filename) { +async function minioFileUpload(content: string, filename: string) { const dateFilename = getDateFilename(filename) const { endpoint, port, useSSL, bucket, accessKey, secretKey } = JSON.parse( - localStorage.getItem(`minioConfig`), + localStorage.getItem(`minioConfig`)!, ) const buffer = Buffer(content, `base64`) - const conf = { + const conf: Minio.ClientOptions = { endPoint: endpoint, useSSL, accessKey, @@ -278,7 +303,7 @@ async function minioFileUpload(content, filename) { // formCustom File Upload // ----------------------------------------------------------------------- -async function formCustomUpload(content, file) { +async function formCustomUpload(content: string, file: File) { const str = ` async (CUSTOM_ARG) => { ${localStorage.getItem(`formCustomConfig`)} @@ -304,16 +329,18 @@ async function formCustomUpload(content, file) { errCb: reject, // 上传失败调用的函数 } // eslint-disable-next-line no-eval - eval(str)(exportObj).catch((err) => { + eval(str)(exportObj).catch((err: any) => { console.error(err) reject(err) }) }) } -function fileUpload(content, file) { +function fileUpload(content: string, file: File) { const imgHost = localStorage.getItem(`imgHost`) - !imgHost && localStorage.setItem(`imgHost`, `default`) + if (!imgHost) { + localStorage.setItem(`imgHost`, `default`) + } switch (imgHost) { case `aliOSS`: return aliOSSFileUpload(file) diff --git a/src/utils/index.js b/src/utils/index.ts similarity index 74% rename from src/utils/index.js rename to src/utils/index.ts index 97547e0..06d993a 100644 --- a/src/utils/index.js +++ b/src/utils/index.ts @@ -5,31 +5,36 @@ import * as prettierPluginMarkdown from 'prettier/plugins/markdown' import * as prettierPluginBabel from 'prettier/plugins/babel' import * as prettierPluginEstree from 'prettier/plugins/estree' import * as prettierPluginCss from 'prettier/plugins/postcss' +import type { PropertiesHyphen } from 'csstype' import { prefix } from '@/config' +import type { Block, Inline, Theme } from '@/types' -export function addPrefix(str) { +export function addPrefix(str: string) { return `${prefix}__${str}` } -export function customizeTheme(theme, options) { +export function customizeTheme(theme: Theme, options: { + fontSize?: string + color?: string +}) { const newTheme = JSON.parse(JSON.stringify(theme)) const { fontSize, color } = options if (fontSize) { for (let i = 1; i <= 4; i++) { const v = newTheme.block[`h${i}`][`font-size`] - newTheme.block[`h${i}`][`font-size`] = `${fontSize * Number.parseFloat(v)}px` + newTheme.block[`h${i}`][`font-size`] = `${Number(fontSize) * Number.parseFloat(v)}px` } } if (color) { newTheme.base[`--md-primary-color`] = color } - return newTheme + return newTheme as Theme } -export function customCssWithTemplate(jsonString, color, theme) { +export function customCssWithTemplate(jsonString: Partial>, color: string, theme: Theme) { const newTheme = customizeTheme(theme, { color }) - const mergeProperties = (target, source, keys) => { + const mergeProperties = (target: Record, source: Partial>, keys: T[]) => { keys.forEach((key) => { if (source[key]) { target[key] = Object.assign(target[key] || {}, source[key]) @@ -37,7 +42,7 @@ export function customCssWithTemplate(jsonString, color, theme) { }) } - const blockKeys = [ + const blockKeys: Block[] = [ `h1`, `h2`, `h3`, @@ -52,7 +57,7 @@ export function customCssWithTemplate(jsonString, color, theme) { `ul`, `ol`, ] - const inlineKeys = [`strong`, `codespan`, `link`, `wx_link`, `listitem`] + const inlineKeys: Inline[] = [`strong`, `codespan`, `link`, `wx_link`, `listitem`] mergeProperties(newTheme.block, jsonString, blockKeys) mergeProperties(newTheme.inline, jsonString, inlineKeys) @@ -65,16 +70,16 @@ export function customCssWithTemplate(jsonString, color, theme) { * @param {string} css - CSS 字符串 * @returns {object} - JSON 格式的 CSS */ -export function css2json(css) { +export function css2json(css: string): Partial> { // 去除所有 CSS 注释 css = css.replace(/\/\*[\s\S]*?\*\//g, ``) - const json = {} + const json: Partial> = {} // 辅助函数:将声明数组转换为对象 - const toObject = array => - array.reduce((obj, item) => { - const [property, value] = item.split(`:`).map(part => part.trim()) + const toObject = (array: any[]) => + array.reduce<{ [k: string]: string }>((obj, item) => { + const [property, value] = item.split(`:`).map((part: string) => part.trim()) if (property) obj[property] = value return obj @@ -93,7 +98,7 @@ export function css2json(css) { // 获取选择器并去除空格 const selectors = css.substring(0, lbracket) .split(`,`) - .map(selector => selector.trim()) + .map(selector => selector.trim()) as (Block | Inline)[] const declarationObj = toObject(declarations) @@ -106,6 +111,8 @@ export function css2json(css) { css = css.slice(rbracket + 1).trim() } + console.log(`json`, json) + return json } @@ -115,7 +122,7 @@ export function css2json(css) { * @param {'markdown' | 'css'} [type] - 内容类型,决定使用的解析器,默认为'markdown' * @returns {Promise} - 格式化后的内容 */ -export async function formatDoc(content, type = `markdown`) { +export async function formatDoc(content: string, type: `markdown` | `css` = `markdown`) { const plugins = { markdown: [prettierPluginMarkdown, prettierPluginBabel, prettierPluginEstree], css: [prettierPluginCss], @@ -132,7 +139,7 @@ export async function formatDoc(content, type = `markdown`) { * 导出原始 Markdown 文档 * @param {string} doc - 文档内容 */ -export function downloadMD(doc) { +export function downloadMD(doc: string) { const downLink = document.createElement(`a`) downLink.download = `content.md` @@ -149,7 +156,7 @@ export function downloadMD(doc) { * 导出 HTML 生成内容 */ export function exportHTML() { - const element = document.querySelector(`#output`) + const element = document.querySelector(`#output`)! setStyles(element) const htmlStr = element.innerHTML @@ -166,14 +173,14 @@ export function exportHTML() { downLink.click() document.body.removeChild(downLink) - function setStyles(element) { + function setStyles(element: Element) { /** * 获取一个 DOM 元素的所有样式, * @param {DOM 元素} element DOM 元素 * @param {排除的属性} excludes 如果某些属性对结果有不良影响,可以使用这个参数来排除 * @returns 行内样式拼接结果 */ - function getElementStyles(element, excludes = [`width`, `height`]) { + function getElementStyles(element: Element, excludes = [`width`, `height`]) { const styles = getComputedStyle(element, null) return Object.entries(styles) .filter( @@ -188,15 +195,13 @@ export function exportHTML() { 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) { + function isPre(element: Element) { return ( element.tagName === `PRE` && Array.from(element.classList).includes(`code__pre`) @@ -204,16 +209,19 @@ export function exportHTML() { } // 判断是否是包裹代码块的 code 元素 - function isCode(element) { + function isCode(element: Element | null) { + if (element == null) { + return false + } return element.tagName === `CODE` } // 判断是否是包裹代码字符的 span 元素 - function isSpan(element) { + function isSpan(element: Element) { return ( element.tagName === `SPAN` && (isCode(element.parentElement) - || isCode(element.parentElement.parentElement)) + || isCode((element.parentElement!).parentElement)) ) } } @@ -228,7 +236,7 @@ export function exportHTML() { * @param {number} options.cols - 列数 * @returns {string} 生成的 Markdown 表格 */ -export function createTable({ data, rows, cols }) { +export function createTable({ data, rows, cols }: { data: { [k: string]: string }, rows: number, cols: number }) { let table = `` for (let i = 0; i < rows + 2; ++i) { table += `| ` @@ -244,16 +252,16 @@ export function createTable({ data, rows, cols }) { return table } -export function toBase64(file) { +export function toBase64(file: Blob) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.readAsDataURL(file) - reader.onload = () => resolve(reader.result.split(`,`).pop()) + reader.onload = () => resolve((reader.result as string).split(`,`).pop()) reader.onerror = error => reject(error) }) } -export function checkImage(file) { +export function checkImage(file: File) { // 检查文件名后缀 const isValidSuffix = /\.(?:gif|jpe?g|png)$/i.test(file.name) if (!isValidSuffix) { @@ -277,27 +285,27 @@ export function checkImage(file) { /** * 移除左边多余空格 - * @param {*} str + * @param {string} str * @returns string */ -export function removeLeft(str) { +export function removeLeft(str: string) { const lines = str.split(`\n`) // 获取应该删除的空白符数量 const minSpaceNum = lines .filter(item => item.trim()) - .map(item => item.match(/(^\s+)?/)[0].length) + .map(item => (item.match(/(^\s+)?/)!)[0].length) .sort((a, b) => a - b)[0] // 删除空白符 return lines.map(item => item.slice(minSpaceNum)).join(`\n`) } export function solveWeChatImage() { - const clipboardDiv = document.getElementById(`output`) + 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`) + const width = image.getAttribute(`width`)! + const height = image.getAttribute(`height`)! image.removeAttribute(`width`) image.removeAttribute(`height`) image.style.width = width @@ -305,7 +313,7 @@ export function solveWeChatImage() { } } -export function mergeCss(html) { +export function mergeCss(html: string) { return juice(html, { inlinePseudoElements: true, preserveImportant: true, diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 6d44d92..0ea7ae3 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -109,13 +109,13 @@ export function initRenderer(opts: IOpts) { return footnoteIndex } - function reset(newOpts: IOpts): void { + function reset(newOpts: Partial): void { footnotes.length = 0 footnoteIndex = 0 setOptions(newOpts) } - function setOptions(newOpts: IOpts): void { + function setOptions(newOpts: Partial): void { opts = { ...opts, ...newOpts } styleMapping = buildTheme(opts) } @@ -197,7 +197,7 @@ export function initRenderer(opts: IOpts) { }, image({ href, title, text }: Tokens.Image): string { - const subText = styledContent(`figcaption`, transform(opts.legend, text, title)) + const subText = styledContent(`figcaption`, transform(opts.legend!, text, title)) const figureStyles = styles(`figure`) const imgStyles = styles(`image`) return `
${text}${subText}
` diff --git a/src/utils/tokenTools.js b/src/utils/tokenTools.ts similarity index 95% rename from src/utils/tokenTools.js rename to src/utils/tokenTools.ts index a1fd291..73a78fb 100644 --- a/src/utils/tokenTools.js +++ b/src/utils/tokenTools.ts @@ -1,4 +1,4 @@ -export function utf16to8(str) { +export function utf16to8(str: string) { let out = `` const len = str.length @@ -22,7 +22,7 @@ export function utf16to8(str) { return out } -export function utf8to16(str) { +export function utf8to16(str: string) { let out = `` let i = 0 const len = str.length @@ -196,7 +196,7 @@ const base64DecodeChars = [ -1, ] -export function base64encode(str) { +export function base64encode(str: string) { let out = `` let i = 0 const len = str.length @@ -232,7 +232,7 @@ export function base64encode(str) { return out } -export function base64decode(str) { +export function base64decode(str: string) { let c1, c2, c3, c4 let i = 0 const len = str.length @@ -283,7 +283,7 @@ export function base64decode(str) { return out } -export function safe64(base64) { +export function safe64(base64: string) { base64 = base64.replace(/\+/g, `-`) base64 = base64.replace(/\//g, `_`) return base64