feat: enhance post management (#514)
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
Build and Push Docker Images / build (push) Has been skipped

* fix: extension installation check

* perf: optimize post dialog

* feat: enhance post management

* fix: close #513
This commit is contained in:
dribble-njr 2025-01-12 09:19:18 +08:00 committed by GitHub
parent 5094d90b43
commit 8e8080b228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 240 additions and 27 deletions

View File

@ -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)
} }
form.value = { finally {
...auto, form.value = {
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>

View 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>

View File

@ -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[]
}