mirror of
https://github.com/doocs/md.git
synced 2025-01-22 20:04:39 +08:00
feat: enhance post management (#514)
* fix: extension installation check * perf: optimize post dialog * feat: enhance post management * fix: close #513
This commit is contained in:
parent
5094d90b43
commit
8e8080b228
@ -1,40 +1,58 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Post, PostAccount } from '@/types'
|
||||||
import { useStore } from '@/stores'
|
import { useStore } from '@/stores'
|
||||||
import { Info } from 'lucide-vue-next'
|
import { Check, Info } from 'lucide-vue-next'
|
||||||
import { Primitive } from 'radix-vue'
|
import { CheckboxIndicator, CheckboxRoot, Primitive } from 'radix-vue'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const { output } = storeToRefs(store)
|
const { output, editor } = storeToRefs(store)
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const extensionInstalled = ref(false)
|
const extensionInstalled = ref(false)
|
||||||
const form = ref<any>({
|
const allAccounts = ref<PostAccount[]>([])
|
||||||
|
const postTaskDialogVisible = ref(false)
|
||||||
|
|
||||||
|
const form = ref<Post>({
|
||||||
title: ``,
|
title: ``,
|
||||||
desc: ``,
|
desc: ``,
|
||||||
thumb: ``,
|
thumb: ``,
|
||||||
content: ``,
|
content: ``,
|
||||||
auto: {},
|
markdown: ``,
|
||||||
|
accounts: [] as PostAccount[],
|
||||||
})
|
})
|
||||||
|
|
||||||
function prePost() {
|
const allowPost = computed(() => extensionInstalled.value && form.value.accounts.some(a => a.checked))
|
||||||
let auto = {}
|
|
||||||
|
async function prePost() {
|
||||||
|
let auto: Post = {
|
||||||
|
thumb: ``,
|
||||||
|
title: ``,
|
||||||
|
desc: ``,
|
||||||
|
content: ``,
|
||||||
|
markdown: ``,
|
||||||
|
accounts: [],
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
auto = {
|
auto = {
|
||||||
thumb: document.querySelector<HTMLImageElement>(`#output img`)?.src,
|
thumb: document.querySelector<HTMLImageElement>(`#output img`)?.src ?? ``,
|
||||||
title: [1, 2, 3, 4, 5, 6]
|
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]
|
.filter(h => h)[0]
|
||||||
.textContent,
|
.textContent ?? ``,
|
||||||
desc: document.querySelector(`#output p`)!.textContent,
|
desc: document.querySelector(`#output p`)!.textContent ?? ``,
|
||||||
content: output.value,
|
content: output.value,
|
||||||
|
markdown: editor.value?.getValue() ?? ``,
|
||||||
|
accounts: allAccounts.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.log(`error`, error)
|
console.log(`error`, error)
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
form.value = {
|
form.value = {
|
||||||
...auto,
|
...auto,
|
||||||
auto,
|
}
|
||||||
|
console.log(form.value, `====`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,24 +63,49 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function post() {
|
async function getAccounts() {
|
||||||
dialogVisible.value = false;
|
await window.$syncer?.getAccounts((resp: PostAccount[]) => {
|
||||||
(window.syncPost)({
|
allAccounts.value = resp.map(a => ({ ...a, checked: true }))
|
||||||
thumb: form.value.thumb || form.value.auto.thumb,
|
|
||||||
title: form.value.title || form.value.auto.title,
|
|
||||||
desc: form.value.desc || form.value.auto.desc,
|
|
||||||
content: form.value.content || form.value.auto.content,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function post() {
|
||||||
|
form.value.accounts = form.value.accounts.filter(a => a.checked)
|
||||||
|
postTaskDialogVisible.value = true
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
function onUpdate(val: boolean) {
|
function onUpdate(val: boolean) {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
function checkExtension() {
|
||||||
extensionInstalled.value = window.$syncer !== undefined
|
if (window.$syncer !== undefined) {
|
||||||
|
extensionInstalled.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果插件还没加载,5秒内每 500ms 检查一次
|
||||||
|
let count = 0
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (window.$syncer !== undefined) {
|
||||||
|
extensionInstalled.value = true
|
||||||
|
getAccounts()
|
||||||
|
clearInterval(timer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if (count > 10) { // 5秒后还是没有检测到,就停止检查
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
checkExtension()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -81,7 +124,7 @@ onMounted(() => {
|
|||||||
<Info class="h-4 w-4" />
|
<Info class="h-4 w-4" />
|
||||||
<AlertTitle>提示</AlertTitle>
|
<AlertTitle>提示</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
此功能由第三方浏览器插件支持,本平台不保证安全性。
|
此功能由第三方浏览器插件支持,本平台不保证安全性及同步准确度。
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
@ -91,9 +134,7 @@ onMounted(() => {
|
|||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
请安装
|
请安装
|
||||||
<Primitive
|
<Primitive
|
||||||
as="a"
|
as="a" class="text-blue-500" href="https://www.wechatsync.com/?utm_source=syncicon#install"
|
||||||
class="text-blue-500"
|
|
||||||
href="https://www.wechatsync.com/?utm_source=syncicon#install"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
文章同步助手
|
文章同步助手
|
||||||
@ -121,14 +162,44 @@ onMounted(() => {
|
|||||||
<Textarea id="desc" v-model="form.desc" placeholder="自动提取第一个段落" />
|
<Textarea id="desc" v-model="form.desc" placeholder="自动提取第一个段落" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex items-start gap-4">
|
||||||
|
<Label class="w-10 text-end">
|
||||||
|
账号
|
||||||
|
</Label>
|
||||||
|
<div class="flex flex-1 flex-col gap-2">
|
||||||
|
<div v-for="account in form.accounts" :key="account.uid + account.displayName" class="flex items-center gap-2">
|
||||||
|
<label class="flex flex-row items-center gap-4">
|
||||||
|
<CheckboxRoot
|
||||||
|
v-model:checked="account.checked"
|
||||||
|
class="bg-background hover:bg-muted h-[25px] w-[25px] flex appearance-none items-center justify-center border border-gray-200 rounded-[4px] outline-none"
|
||||||
|
>
|
||||||
|
<CheckboxIndicator>
|
||||||
|
<Check v-if="account.checked" class="h-4 w-4" />
|
||||||
|
</CheckboxIndicator>
|
||||||
|
</CheckboxRoot>
|
||||||
|
<span class="flex items-center gap-2 text-sm">
|
||||||
|
<img
|
||||||
|
:src="account.icon"
|
||||||
|
alt=""
|
||||||
|
class="inline-block h-[20px] w-[20px]"
|
||||||
|
>
|
||||||
|
{{ account.title }} - {{ account.displayName ?? account.home }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" @click="dialogVisible = false">
|
<Button variant="outline" @click="dialogVisible = false">
|
||||||
取 消
|
取 消
|
||||||
</Button>
|
</Button>
|
||||||
<Button :disabled="!extensionInstalled" @click="post">
|
<Button :disabled="!allowPost" @click="post">
|
||||||
确 定
|
确 定
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<PostTaskDialog v-model:open="postTaskDialogVisible" :post="form" />
|
||||||
</template>
|
</template>
|
||||||
|
119
src/components/CodemirrorEditor/EditorHeader/PostTaskDialog.vue
Normal file
119
src/components/CodemirrorEditor/EditorHeader/PostTaskDialog.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Post } from '@/types'
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
post: Post
|
||||||
|
open: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits([`update:open`])
|
||||||
|
|
||||||
|
const dialogVisible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: value => emit(`update:open`, value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const taskStatus = ref<any>(null)
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
async function startPost() {
|
||||||
|
if (!props.post)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.$syncer?.addTask(
|
||||||
|
{
|
||||||
|
post: {
|
||||||
|
title: props.post.title,
|
||||||
|
content: props.post.content,
|
||||||
|
markdown: props.post.markdown,
|
||||||
|
thumb: props.post.thumb,
|
||||||
|
desc: props.post.desc,
|
||||||
|
},
|
||||||
|
accounts: props.post.accounts.filter(a => a.checked),
|
||||||
|
},
|
||||||
|
(newStatus: any) => {
|
||||||
|
taskStatus.value = newStatus
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
submitting.value = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`发布失败:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.open, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
startPost()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog v-model:open="dialogVisible">
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>提交发布任务</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<div v-if="!taskStatus" class="py-4 text-center">
|
||||||
|
等待发布..
|
||||||
|
</div>
|
||||||
|
<div v-else class="max-h-[400px] flex flex-col overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-for="account in taskStatus?.accounts"
|
||||||
|
:key="account.uid + account.displayName"
|
||||||
|
class="border-b py-4 last:border-b-0"
|
||||||
|
>
|
||||||
|
<div class="mb-2 flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
v-if="account.icon"
|
||||||
|
:src="account.icon"
|
||||||
|
class="object-cover h-5 w-5"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<span>{{ account.title }} - {{ account.displayName || account.home }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-full flex-1 gap-2 overflow-auto pl-7 text-sm" :class="{
|
||||||
|
'text-yellow-600': account.status === 'uploading',
|
||||||
|
'text-red-600': account.status === 'failed',
|
||||||
|
'text-green-600': account.status === 'done',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template v-if="account.status === 'uploading'">
|
||||||
|
{{ account.msg || '发布中' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="account.status === 'failed'">
|
||||||
|
同步失败, 错误内容:{{ account.error }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="account.status === 'done' && account.editResp">
|
||||||
|
同步成功
|
||||||
|
<a
|
||||||
|
v-if="account.type !== 'wordpress' && account.editResp"
|
||||||
|
:href="account.editResp.draftLink"
|
||||||
|
class="ml-2 text-blue-500 hover:underline"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
target="_blank"
|
||||||
|
>查看草稿</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.account-item {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -72,3 +72,26 @@ export interface Alert {
|
|||||||
text: string
|
text: string
|
||||||
tokens: Token[]
|
tokens: Token[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PostAccount {
|
||||||
|
avatar: string
|
||||||
|
displayName: string
|
||||||
|
home: string
|
||||||
|
icon: string
|
||||||
|
supportTypes: string[]
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
uid: string
|
||||||
|
checked: boolean
|
||||||
|
status?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Post {
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
thumb: string
|
||||||
|
content: string
|
||||||
|
markdown: string
|
||||||
|
accounts: PostAccount[]
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user