mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
chore: convert js to ts (#410)
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
This commit is contained in:
parent
b99ddef0ca
commit
21d5259f35
@ -93,7 +93,7 @@
|
||||
}
|
||||
</script>
|
||||
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
<script src="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/wechatsync/article-syncjs@latest/dist/main.js"></script>
|
||||
</html>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import CodemirrorEditor from '@/views/CodemirrorEditor.vue'
|
||||
</script>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { useStore } from '@/stores'
|
||||
@ -23,7 +23,7 @@ function editTabName() {
|
||||
})
|
||||
}
|
||||
|
||||
function handleTabsEdit(targetName, action) {
|
||||
function handleTabsEdit(targetName: string, action: string) {
|
||||
if (action === `add`) {
|
||||
ElMessageBox.prompt(`请输入方案名称`, `新建自定义 CSS`, {
|
||||
confirmButtonText: `确认`,
|
||||
@ -86,7 +86,7 @@ function handleTabsEdit(targetName, action) {
|
||||
<el-icon
|
||||
v-if="store.cssContentConfig.active === item.name"
|
||||
class="ml-1"
|
||||
@click="editTabName(item.name)"
|
||||
@click="editTabName()"
|
||||
>
|
||||
<ElIconEditPen />
|
||||
</el-icon>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -16,7 +16,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits([`close`])
|
||||
|
||||
function onUpdate(val) {
|
||||
function onUpdate(val: boolean) {
|
||||
if (!val) {
|
||||
emit(`close`)
|
||||
}
|
||||
@ -28,7 +28,7 @@ const links = [
|
||||
{ label: `GitCode 仓库`, url: `https://gitcode.com/doocs/md` },
|
||||
]
|
||||
|
||||
function onRedirect(url) {
|
||||
function onRedirect(url: string) {
|
||||
window.open(url, `_blank`)
|
||||
}
|
||||
</script>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { useStore } from '@/stores'
|
||||
|
||||
const store = useStore()
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { useStore } from '@/stores'
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import AboutDialog from './AboutDialog.vue'
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import {
|
||||
@ -16,22 +16,23 @@ const { output } = storeToRefs(store)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const form = ref({
|
||||
const form = ref<any>({
|
||||
title: ``,
|
||||
desc: ``,
|
||||
thumb: ``,
|
||||
content: ``,
|
||||
auto: {},
|
||||
})
|
||||
|
||||
function prePost() {
|
||||
let auto = {}
|
||||
try {
|
||||
auto = {
|
||||
thumb: document.querySelector(`#output img`)?.src,
|
||||
thumb: document.querySelector<HTMLImageElement>(`#output img`)?.src,
|
||||
title: [1, 2, 3, 4, 5, 6]
|
||||
.map(h => document.querySelector(`#output h${h}`))
|
||||
.map(h => document.querySelector(`#output h${h}`)!)
|
||||
.filter(h => h)[0].textContent,
|
||||
desc: document.querySelector(`#output p`).textContent,
|
||||
desc: document.querySelector(`#output p`)!.textContent,
|
||||
content: output.value,
|
||||
}
|
||||
}
|
||||
@ -45,10 +46,16 @@ function prePost() {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
syncPost: (data: { thumb: string, title: string, desc: string, content: string }) => void
|
||||
}
|
||||
}
|
||||
|
||||
function post() {
|
||||
dialogVisible.value = false
|
||||
dialogVisible.value = false;
|
||||
// 使用 window.$syncer 可以检测是否安装插件
|
||||
window.syncPost({
|
||||
(window.syncPost)({
|
||||
thumb: form.value.thumb || form.value.auto.thumb,
|
||||
title: form.value.title || form.value.auto.title,
|
||||
desc: form.value.desc || form.value.auto.desc,
|
||||
@ -56,7 +63,7 @@ function post() {
|
||||
})
|
||||
}
|
||||
|
||||
function onUpdate(val) {
|
||||
function onUpdate(val: boolean) {
|
||||
if (!val) {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import StyleOptionMenu from './StyleOptionMenu.vue'
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card'
|
||||
|
||||
import {
|
||||
codeBlockThemeOptions,
|
||||
colorOptions,
|
||||
@ -45,10 +43,10 @@ const {
|
||||
toggleShowCssEditor,
|
||||
} = store
|
||||
|
||||
const colorPicker = ref(null)
|
||||
const colorPicker = ref<HTMLElement & { show: () => void } | null>(null)
|
||||
|
||||
function showPicker() {
|
||||
colorPicker.value.show()
|
||||
colorPicker.value?.show()
|
||||
}
|
||||
|
||||
// 自定义CSS样式
|
||||
@ -56,11 +54,11 @@ function customStyle() {
|
||||
toggleShowCssEditor()
|
||||
nextTick(() => {
|
||||
if (!cssEditor.value) {
|
||||
cssEditor.value.refresh()
|
||||
cssEditor.value!.refresh()
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
cssEditor.value.refresh()
|
||||
cssEditor.value!.refresh()
|
||||
}, 50)
|
||||
}
|
||||
</script>
|
||||
@ -114,7 +112,7 @@ function customStyle() {
|
||||
自定义主题色
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent side="right" class="w-min">
|
||||
<el-color-picker
|
||||
<ElColorPicker
|
||||
ref="colorPicker"
|
||||
v-model="primaryColor"
|
||||
:teleported="false"
|
||||
|
@ -1,31 +1,20 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MenubarItem,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
} from '@/components/ui/menubar'
|
||||
import type { IConfigOption } from '@/types'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
current: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
change: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
options: IConfigOption[]
|
||||
current: string
|
||||
change: <T>(val: T) => void
|
||||
}>()
|
||||
|
||||
function setStyle(title, value) {
|
||||
function setStyle(title: string, value: string) {
|
||||
switch (title) {
|
||||
case `字体`:
|
||||
return { fontFamily: value }
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { nextTick } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElNotification } from 'element-plus'
|
||||
@ -78,7 +78,7 @@ const formatItems = [
|
||||
kbd: [altSign, shiftSign, `F`],
|
||||
emitArgs: [`formatContent`],
|
||||
},
|
||||
]
|
||||
] as const
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -90,7 +90,7 @@ const { toggleDark, editorRefresh, citeStatusChanged } = store
|
||||
function copy() {
|
||||
emit(`startCopy`)
|
||||
setTimeout(() => {
|
||||
function modifyHtmlStructure(htmlString) {
|
||||
function modifyHtmlStructure(htmlString: string) {
|
||||
// 创建一个 div 元素来暂存原始 HTML 字符串
|
||||
const tempDiv = document.createElement(`div`)
|
||||
tempDiv.innerHTML = htmlString
|
||||
@ -98,7 +98,7 @@ function copy() {
|
||||
const originalItems = tempDiv.querySelectorAll(`li > ul, li > ol`)
|
||||
|
||||
originalItems.forEach((originalItem) => {
|
||||
originalItem.parentElement.insertAdjacentElement(`afterend`, originalItem)
|
||||
originalItem.parentElement!.insertAdjacentElement(`afterend`, originalItem)
|
||||
})
|
||||
|
||||
// 返回修改后的 HTML 字符串
|
||||
@ -114,7 +114,7 @@ function copy() {
|
||||
nextTick(() => {
|
||||
solveWeChatImage()
|
||||
|
||||
const clipboardDiv = document.getElementById(`output`)
|
||||
const clipboardDiv = document.getElementById(`output`)!
|
||||
clipboardDiv.innerHTML = mergeCss(clipboardDiv.innerHTML)
|
||||
clipboardDiv.innerHTML = modifyHtmlStructure(clipboardDiv.innerHTML)
|
||||
clipboardDiv.innerHTML = clipboardDiv.innerHTML
|
||||
@ -125,14 +125,14 @@ function copy() {
|
||||
.replaceAll(`var(--md-primary-color)`, primaryColor.value)
|
||||
.replaceAll(/--md-primary-color:.+?;/g, ``)
|
||||
clipboardDiv.focus()
|
||||
window.getSelection().removeAllRanges()
|
||||
window.getSelection()!.removeAllRanges()
|
||||
const range = document.createRange()
|
||||
|
||||
range.setStartBefore(clipboardDiv.firstChild)
|
||||
range.setEndAfter(clipboardDiv.lastChild)
|
||||
window.getSelection().addRange(range)
|
||||
range.setStartBefore(clipboardDiv.firstChild!)
|
||||
range.setEndAfter(clipboardDiv.lastChild!)
|
||||
window.getSelection()!.addRange(range)
|
||||
document.execCommand(`copy`)
|
||||
window.getSelection().removeAllRanges()
|
||||
window.getSelection()!.removeAllRanges()
|
||||
clipboardDiv.innerHTML = output.value
|
||||
|
||||
if (isBeforeDark) {
|
||||
@ -165,8 +165,8 @@ function copy() {
|
||||
<MenubarContent class="w-60" align="start">
|
||||
<MenubarItem
|
||||
v-for="{ label, kbd, emitArgs } in formatItems"
|
||||
:key="kbd"
|
||||
@click="$emit(...emitArgs)"
|
||||
:key="label"
|
||||
@click="emitArgs[0] === 'addFormat' ? $emit(emitArgs[0], emitArgs[1]) : $emit(emitArgs[0])"
|
||||
>
|
||||
<el-icon class="mr-2 h-4 w-4" />
|
||||
{{ label }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref, toRaw } from 'vue'
|
||||
import {
|
||||
Dialog,
|
||||
@ -17,7 +17,7 @@ const { toggleShowInsertFormDialog } = store
|
||||
|
||||
const rowNum = ref(3)
|
||||
const colNum = ref(3)
|
||||
const tableData = ref({})
|
||||
const tableData = ref<Record<string, string>>({})
|
||||
|
||||
function resetVal() {
|
||||
rowNum.value = 3
|
||||
@ -32,12 +32,12 @@ function insertTable() {
|
||||
cols: colNum.value,
|
||||
data: tableData.value,
|
||||
})
|
||||
toRaw(store.editor).replaceSelection(`\n${table}\n`, `end`)
|
||||
toRaw(store.editor!).replaceSelection(`\n${table}\n`, `end`)
|
||||
resetVal()
|
||||
toggleShowInsertFormDialog()
|
||||
}
|
||||
|
||||
function onUpdate(val) {
|
||||
function onUpdate(val: boolean) {
|
||||
if (!val) {
|
||||
toggleShowInsertFormDialog(false)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeMount, ref, watch } from 'vue'
|
||||
import CodeMirror from 'codemirror/lib/codemirror'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
|
||||
import CodeMirror from 'codemirror'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
|
||||
import { checkImage, removeLeft } from '@/utils'
|
||||
@ -50,6 +50,7 @@ const formQiniu = ref({
|
||||
bucket: ``,
|
||||
domain: ``,
|
||||
region: ``,
|
||||
path: ``,
|
||||
})
|
||||
|
||||
const minioOSS = ref({
|
||||
@ -61,7 +62,7 @@ const minioOSS = ref({
|
||||
secretKey: ``,
|
||||
})
|
||||
|
||||
const formCustom = ref({
|
||||
const formCustom = ref<{ code: string, editor: CodeMirror.EditorFromTextArea | null }>({
|
||||
code:
|
||||
localStorage.getItem(`formCustomConfig`)
|
||||
|| removeLeft(`
|
||||
@ -76,7 +77,7 @@ const formCustom = ref({
|
||||
errCb(err)
|
||||
})
|
||||
`).trim(),
|
||||
editor: undefined,
|
||||
editor: null,
|
||||
})
|
||||
|
||||
const options = [
|
||||
@ -116,7 +117,7 @@ const options = [
|
||||
|
||||
const imgHost = ref(`default`)
|
||||
|
||||
const formCustomElInput = ref(null)
|
||||
const formCustomElInput = ref<(HTMLInputElement & { $el: HTMLElement }) | null>(null)
|
||||
const activeName = ref(`upload`)
|
||||
|
||||
watch(
|
||||
@ -124,12 +125,10 @@ watch(
|
||||
async (val) => {
|
||||
if (val === `formCustom`) {
|
||||
nextTick(() => {
|
||||
const textarea = formCustomElInput.value.$el.querySelector(`textarea`)
|
||||
formCustom.value.editor
|
||||
= formCustom.value.editor
|
||||
|| CodeMirror.fromTextArea(textarea, {
|
||||
mode: `javascript`,
|
||||
})
|
||||
const textarea = formCustomElInput.value!.$el.querySelector(`textarea`)!
|
||||
formCustom.value.editor ||= CodeMirror.fromTextArea(textarea, {
|
||||
mode: `javascript`,
|
||||
})
|
||||
// formCustom.value.editor.setValue(formCustom.value.code)
|
||||
})
|
||||
}
|
||||
@ -141,25 +140,25 @@ watch(
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (localStorage.getItem(`githubConfig`)) {
|
||||
formGitHub.value = JSON.parse(localStorage.getItem(`githubConfig`))
|
||||
formGitHub.value = JSON.parse(localStorage.getItem(`githubConfig`)!)
|
||||
}
|
||||
// if (localStorage.getItem(`giteeConfig`)) {
|
||||
// formGitee.value = JSON.parse(localStorage.getItem(`giteeConfig`))
|
||||
// formGitee.value = JSON.parse(localStorage.getItem(`giteeConfig`)!)
|
||||
// }
|
||||
if (localStorage.getItem(`aliOSSConfig`)) {
|
||||
formAliOSS.value = JSON.parse(localStorage.getItem(`aliOSSConfig`))
|
||||
formAliOSS.value = JSON.parse(localStorage.getItem(`aliOSSConfig`)!)
|
||||
}
|
||||
if (localStorage.getItem(`txCOSConfig`)) {
|
||||
formTxCOS.value = JSON.parse(localStorage.getItem(`txCOSConfig`))
|
||||
formTxCOS.value = JSON.parse(localStorage.getItem(`txCOSConfig`)!)
|
||||
}
|
||||
if (localStorage.getItem(`qiniuConfig`)) {
|
||||
formQiniu.value = JSON.parse(localStorage.getItem(`qiniuConfig`))
|
||||
formQiniu.value = JSON.parse(localStorage.getItem(`qiniuConfig`)!)
|
||||
}
|
||||
if (localStorage.getItem(`minioConfig`)) {
|
||||
minioOSS.value = JSON.parse(localStorage.getItem(`minioConfig`))
|
||||
minioOSS.value = JSON.parse(localStorage.getItem(`minioConfig`)!)
|
||||
}
|
||||
if (localStorage.getItem(`imgHost`)) {
|
||||
imgHost.value = localStorage.getItem(`imgHost`)
|
||||
imgHost.value = localStorage.getItem(`imgHost`)!
|
||||
}
|
||||
})
|
||||
|
||||
@ -251,12 +250,12 @@ function saveQiniuConfiguration() {
|
||||
}
|
||||
|
||||
function formCustomSave() {
|
||||
const str = formCustom.value.editor.getValue()
|
||||
const str = formCustom.value.editor!.getValue()
|
||||
localStorage.setItem(`formCustomConfig`, str)
|
||||
ElMessage.success(`保存成功`)
|
||||
}
|
||||
|
||||
function beforeImageUpload(file) {
|
||||
function beforeImageUpload(file: File) {
|
||||
// check image
|
||||
const checkResult = checkImage(file)
|
||||
if (!checkResult.ok) {
|
||||
@ -277,7 +276,7 @@ function beforeImageUpload(file) {
|
||||
return true
|
||||
}
|
||||
|
||||
function uploadImage(params) {
|
||||
function uploadImage(params: { file: any }) {
|
||||
emit(`uploadImage`, params.file)
|
||||
}
|
||||
</script>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const loading = ref(true)
|
||||
|
@ -2,9 +2,10 @@ import { ElLoading, ElMessage } from 'element-plus'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import type { App } from 'vue'
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
install(app: App<Element>) {
|
||||
// app.use(ElementPlus, { size: `default` })
|
||||
|
||||
app.config.globalProperties.$loading = ElLoading.service
|
@ -173,7 +173,7 @@ async function qiniuUpload(file: File) {
|
||||
const dir = path ? `${path}/` : ``
|
||||
const dateFilename = dir + getDateFilename(file.name)
|
||||
const observable = qiniu.upload(file, dateFilename, token, {}, { region })
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next: (result) => {
|
||||
console.log(result)
|
||||
@ -229,7 +229,7 @@ async function txCOSFileUpload(file: File) {
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey,
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
cos.putObject(
|
||||
{
|
||||
Bucket: bucket,
|
||||
@ -277,7 +277,7 @@ async function minioFileUpload(content: string, filename: string) {
|
||||
if (isCustomPort) {
|
||||
conf.port = p
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const minioClient = new Minio.Client(conf)
|
||||
try {
|
||||
minioClient.putObject(bucket, dateFilename, buffer, (e) => {
|
||||
@ -309,7 +309,7 @@ async function formCustomUpload(content: string, file: File) {
|
||||
${localStorage.getItem(`formCustomConfig`)}
|
||||
}
|
||||
`
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const exportObj = {
|
||||
content, // 待上传图片的 base64
|
||||
file, // 待上传图片的 file 对象
|
||||
|
@ -253,10 +253,10 @@ export function createTable({ data, rows, cols }: { data: { [k: string]: string
|
||||
}
|
||||
|
||||
export function toBase64(file: Blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => resolve((reader.result as string).split(`,`).pop())
|
||||
reader.onload = () => resolve((reader.result as string).split(`,`).pop()!)
|
||||
reader.onerror = error => reject(error)
|
||||
})
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { onMounted, ref, toRaw, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElCol, ElMessage } from 'element-plus'
|
||||
import CodeMirror from 'codemirror'
|
||||
|
||||
import fileApi from '@/utils/file'
|
||||
@ -45,28 +46,29 @@ const {
|
||||
} = store
|
||||
|
||||
const isImgLoading = ref(false)
|
||||
const timeout = ref(0)
|
||||
const timeout = ref<NodeJS.Timeout>()
|
||||
|
||||
const preview = ref(null)
|
||||
const preview = ref<typeof ElCol | null>(null)
|
||||
|
||||
// 使浏览区与编辑区滚动条建立同步联系
|
||||
function leftAndRightScroll() {
|
||||
const scrollCB = (text) => {
|
||||
let source, target
|
||||
const scrollCB = (text: string) => {
|
||||
let source: HTMLElement
|
||||
let target: HTMLElement
|
||||
|
||||
clearTimeout(timeout.value)
|
||||
if (text === `preview`) {
|
||||
source = preview.value.$el
|
||||
target = document.querySelector(`.CodeMirror-scroll`)
|
||||
source = preview.value!.$el
|
||||
target = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
||||
|
||||
editor.value.off(`scroll`, editorScrollCB)
|
||||
editor.value!.off(`scroll`, editorScrollCB)
|
||||
timeout.value = setTimeout(() => {
|
||||
editor.value.on(`scroll`, editorScrollCB)
|
||||
editor.value!.on(`scroll`, editorScrollCB)
|
||||
}, 300)
|
||||
}
|
||||
else if (text === `editor`) {
|
||||
source = document.querySelector(`.CodeMirror-scroll`)
|
||||
target = preview.value.$el
|
||||
else {
|
||||
source = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
||||
target = preview.value!.$el
|
||||
|
||||
target.removeEventListener(`scroll`, previewScrollCB, false)
|
||||
timeout.value = setTimeout(() => {
|
||||
@ -89,8 +91,8 @@ function leftAndRightScroll() {
|
||||
scrollCB(`preview`)
|
||||
}
|
||||
|
||||
preview.value.$el.addEventListener(`scroll`, previewScrollCB, false)
|
||||
editor.value.on(`scroll`, editorScrollCB)
|
||||
(preview.value!.$el).addEventListener(`scroll`, previewScrollCB, false)
|
||||
editor.value!.on(`scroll`, editorScrollCB)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -120,7 +122,7 @@ function endCopy() {
|
||||
}, 800)
|
||||
}
|
||||
|
||||
function beforeUpload(file) {
|
||||
function beforeUpload(file: File) {
|
||||
// validate image
|
||||
const checkResult = checkImage(file)
|
||||
if (!checkResult.ok) {
|
||||
@ -142,20 +144,20 @@ function beforeUpload(file) {
|
||||
}
|
||||
|
||||
// 图片上传结束
|
||||
function uploaded(imageUrl) {
|
||||
function uploaded(imageUrl: string) {
|
||||
if (!imageUrl) {
|
||||
ElMessage.error(`上传图片未知异常`)
|
||||
return
|
||||
}
|
||||
toggleShowUploadImgDialog(false)
|
||||
// 上传成功,获取光标
|
||||
const cursor = editor.value.getCursor()
|
||||
const cursor = editor.value!.getCursor()
|
||||
const markdownImage = `![](${imageUrl})`
|
||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||
toRaw(store.editor).replaceSelection(`\n${markdownImage}\n`, cursor)
|
||||
toRaw(store.editor!).replaceSelection(`\n${markdownImage}\n`, cursor as any)
|
||||
ElMessage.success(`图片上传成功`)
|
||||
}
|
||||
function uploadImage(file, cb) {
|
||||
function uploadImage(file: File, cb?: { (url: any): void, (arg0: unknown): void } | undefined) {
|
||||
isImgLoading.value = true
|
||||
|
||||
toBase64(file)
|
||||
@ -176,7 +178,7 @@ function uploadImage(file, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
const changeTimer = ref(0)
|
||||
const changeTimer = ref<NodeJS.Timeout>()
|
||||
|
||||
// 监听暗色模式并更新编辑器
|
||||
watch(isDark, () => {
|
||||
@ -186,7 +188,7 @@ watch(isDark, () => {
|
||||
|
||||
// 初始化编辑器
|
||||
function initEditor() {
|
||||
const editorDom = document.querySelector(`#editor`)
|
||||
const editorDom = document.querySelector<HTMLTextAreaElement>(`#editor`)!
|
||||
|
||||
if (!editorDom.value) {
|
||||
editorDom.value = editorContent.value
|
||||
@ -200,7 +202,7 @@ function initEditor() {
|
||||
autoCloseBrackets: true,
|
||||
extraKeys: {
|
||||
[`${shiftKey}-${altKey}-F`]: function autoFormat(editor) {
|
||||
formatDoc(editor.getValue(0)).then((doc) => {
|
||||
formatDoc(editor.getValue()).then((doc) => {
|
||||
editor.setValue(doc)
|
||||
})
|
||||
},
|
||||
@ -241,7 +243,7 @@ function initEditor() {
|
||||
})
|
||||
|
||||
// 粘贴上传图片并插入
|
||||
editor.value.on(`paste`, (cm, e) => {
|
||||
editor.value.on(`paste`, (_cm, e) => {
|
||||
if (!(e.clipboardData && e.clipboardData.items) || isImgLoading.value) {
|
||||
return
|
||||
}
|
||||
@ -249,7 +251,7 @@ function initEditor() {
|
||||
const item = e.clipboardData.items[i]
|
||||
if (item.kind === `file`) {
|
||||
// 校验图床参数
|
||||
const pasteFile = item.getAsFile()
|
||||
const pasteFile = item.getAsFile()!
|
||||
const isValid = beforeUpload(pasteFile)
|
||||
if (!isValid) {
|
||||
continue
|
||||
@ -263,33 +265,34 @@ function initEditor() {
|
||||
const container = ref(null)
|
||||
|
||||
// 工具函数,添加格式
|
||||
function addFormat(cmd) {
|
||||
editor.value.options.extraKeys[cmd](editor.value)
|
||||
function addFormat(cmd: string | number) {
|
||||
(editor.value as any).options.extraKeys[cmd](editor.value)
|
||||
}
|
||||
|
||||
const codeMirrorWrapper = ref(null)
|
||||
const codeMirrorWrapper = ref<ComponentPublicInstance<typeof ElCol> | null>(null)
|
||||
|
||||
// 转换 markdown 中的本地图片为线上图片
|
||||
// todo 处理事件覆盖
|
||||
function mdLocalToRemote() {
|
||||
const dom = codeMirrorWrapper.value.$el
|
||||
const dom = codeMirrorWrapper.value!.$el as HTMLElement
|
||||
console.log(`dom`, dom)
|
||||
|
||||
// 上传 md 中的图片
|
||||
const uploadMdImg = async ({ md, list }) => {
|
||||
const uploadMdImg = async ({ md, list }: { md: { str: string, path: string, file: File }, list: { path: string, file: File }[] }) => {
|
||||
const mdImgList = [
|
||||
...(md.str.matchAll(/!\[(.*?)\]\((.*?)\)/g) || []),
|
||||
].filter((item) => {
|
||||
return item // 获取所有相对地址的图片
|
||||
})
|
||||
const root = md.path.match(/.+?\//)[0]
|
||||
const resList = await Promise.all(
|
||||
const root = md.path.match(/.+?\//)![0]
|
||||
const resList = await Promise.all<{ matchStr: string, url: string }>(
|
||||
mdImgList.map((item) => {
|
||||
return new Promise((resolve) => {
|
||||
let [, , matchStr] = item
|
||||
matchStr = matchStr.replace(/^.\//, ``) // 处理 ./img/ 为 img/ 统一相对路径风格
|
||||
const { file }
|
||||
= list.find(f => f.path === `${root}${matchStr}`) || {}
|
||||
uploadImage(file, (url) => {
|
||||
uploadImage(file!, (url) => {
|
||||
resolve({ matchStr, url })
|
||||
})
|
||||
})
|
||||
@ -300,16 +303,16 @@ function mdLocalToRemote() {
|
||||
.replace(`](./${item.matchStr})`, `](${item.url})`)
|
||||
.replace(`](${item.matchStr})`, `](${item.url})`)
|
||||
})
|
||||
editor.value.setValue(md.str)
|
||||
editor.value!.setValue(md.str)
|
||||
}
|
||||
|
||||
dom.ondragover = evt => evt.preventDefault()
|
||||
dom.ondrop = async (evt) => {
|
||||
dom.ondrop = async (evt: any) => {
|
||||
evt.preventDefault()
|
||||
for (const item of evt.dataTransfer.items) {
|
||||
item.getAsFileSystemHandle().then(async (handle) => {
|
||||
item.getAsFileSystemHandle().then(async (handle: { kind: string, getFile: () => any }) => {
|
||||
if (handle.kind === `directory`) {
|
||||
const list = await showFileStructure(handle)
|
||||
const list = await showFileStructure(handle) as { path: string, file: File }[]
|
||||
const md = await getMd({ list })
|
||||
uploadMdImg({ md, list })
|
||||
}
|
||||
@ -322,14 +325,14 @@ function mdLocalToRemote() {
|
||||
}
|
||||
|
||||
// 从文件列表中查找一个 md 文件并解析
|
||||
async function getMd({ list }) {
|
||||
return new Promise((resolve) => {
|
||||
const { path, file } = list.find(item => item.path.match(/\.md$/))
|
||||
async function getMd({ list }: { list: { path: string, file: File }[] }) {
|
||||
return new Promise<{ str: string, file: File, path: string }>((resolve) => {
|
||||
const { path, file } = list.find(item => item.path.match(/\.md$/))!
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file, `UTF-8`)
|
||||
reader.readAsText(file!, `UTF-8`)
|
||||
reader.onload = (evt) => {
|
||||
resolve({
|
||||
str: evt.target.result,
|
||||
str: evt.target!.result as string,
|
||||
file,
|
||||
path,
|
||||
})
|
||||
@ -338,7 +341,7 @@ function mdLocalToRemote() {
|
||||
}
|
||||
|
||||
// 转换文件系统句柄中的文件为文件列表
|
||||
async function showFileStructure(root) {
|
||||
async function showFileStructure(root: any) {
|
||||
const result = []
|
||||
let cwd = ``
|
||||
try {
|
||||
@ -385,7 +388,7 @@ onMounted(() => {
|
||||
/>
|
||||
<main class="container-main flex-1">
|
||||
<el-row class="container-main-section h-full border-1">
|
||||
<el-col
|
||||
<ElCol
|
||||
ref="codeMirrorWrapper"
|
||||
:span="isShowCssEditor ? 8 : 12"
|
||||
class="codeMirror-wrapper border-r-1"
|
||||
@ -427,8 +430,8 @@ onMounted(() => {
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</el-col>
|
||||
<el-col
|
||||
</ElCol>
|
||||
<ElCol
|
||||
id="preview"
|
||||
ref="preview"
|
||||
:span="isShowCssEditor ? 8 : 12"
|
||||
@ -445,7 +448,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</ElCol>
|
||||
<CssEditor />
|
||||
</el-row>
|
||||
</main>
|
||||
|
Loading…
Reference in New Issue
Block a user