Skip to content

Commit 8e8080b

Browse files
authoredJan 12, 2025
feat: enhance post management (doocs#514)
* fix: extension installation check * perf: optimize post dialog * feat: enhance post management * fix: close doocs#513
1 parent 5094d90 commit 8e8080b

File tree

3 files changed

+240
-27
lines changed

3 files changed

+240
-27
lines changed
 

‎src/components/CodemirrorEditor/EditorHeader/PostInfo.vue

+98-27
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,58 @@
11
<script setup lang="ts">
2+
import type { Post, PostAccount } from '@/types'
23
import { useStore } from '@/stores'
3-
import { Info } from 'lucide-vue-next'
4-
import { Primitive } from 'radix-vue'
4+
import { Check, Info } from 'lucide-vue-next'
5+
import { CheckboxIndicator, CheckboxRoot, Primitive } from 'radix-vue'
56
67
const store = useStore()
7-
const { output } = storeToRefs(store)
8+
const { output, editor } = storeToRefs(store)
89
910
const dialogVisible = ref(false)
1011
const extensionInstalled = ref(false)
11-
const form = ref<any>({
12+
const allAccounts = ref<PostAccount[]>([])
13+
const postTaskDialogVisible = ref(false)
14+
15+
const form = ref<Post>({
1216
title: ``,
1317
desc: ``,
1418
thumb: ``,
1519
content: ``,
16-
auto: {},
20+
markdown: ``,
21+
accounts: [] as PostAccount[],
1722
})
1823
19-
function prePost() {
20-
let auto = {}
24+
const allowPost = computed(() => extensionInstalled.value && form.value.accounts.some(a => a.checked))
25+
26+
async function prePost() {
27+
let auto: Post = {
28+
thumb: ``,
29+
title: ``,
30+
desc: ``,
31+
content: ``,
32+
markdown: ``,
33+
accounts: [],
34+
}
2135
try {
2236
auto = {
23-
thumb: document.querySelector<HTMLImageElement>(`#output img`)?.src,
37+
thumb: document.querySelector<HTMLImageElement>(`#output img`)?.src ?? ``,
2438
title: [1, 2, 3, 4, 5, 6]
2539
.map(h => document.querySelector(`#output h${h}`)!)
2640
.filter(h => h)[0]
27-
.textContent,
28-
desc: document.querySelector(`#output p`)!.textContent,
41+
.textContent ?? ``,
42+
desc: document.querySelector(`#output p`)!.textContent ?? ``,
2943
content: output.value,
44+
markdown: editor.value?.getValue() ?? ``,
45+
accounts: allAccounts.value,
3046
}
3147
}
3248
catch (error) {
3349
console.log(`error`, error)
3450
}
35-
form.value = {
36-
...auto,
37-
auto,
51+
finally {
52+
form.value = {
53+
...auto,
54+
}
55+
console.log(form.value, `====`)
3856
}
3957
}
4058
@@ -45,24 +63,49 @@ declare global {
4563
}
4664
}
4765
48-
function post() {
49-
dialogVisible.value = false;
50-
(window.syncPost)({
51-
thumb: form.value.thumb || form.value.auto.thumb,
52-
title: form.value.title || form.value.auto.title,
53-
desc: form.value.desc || form.value.auto.desc,
54-
content: form.value.content || form.value.auto.content,
66+
async function getAccounts() {
67+
await window.$syncer?.getAccounts((resp: PostAccount[]) => {
68+
allAccounts.value = resp.map(a => ({ ...a, checked: true }))
5569
})
5670
}
5771
72+
function post() {
73+
form.value.accounts = form.value.accounts.filter(a => a.checked)
74+
postTaskDialogVisible.value = true
75+
dialogVisible.value = false
76+
}
77+
5878
function onUpdate(val: boolean) {
5979
if (!val) {
6080
dialogVisible.value = false
6181
}
6282
}
6383
64-
onMounted(() => {
65-
extensionInstalled.value = window.$syncer !== undefined
84+
function checkExtension() {
85+
if (window.$syncer !== undefined) {
86+
extensionInstalled.value = true
87+
return
88+
}
89+
90+
// 如果插件还没加载,5秒内每 500ms 检查一次
91+
let count = 0
92+
const timer = setInterval(() => {
93+
if (window.$syncer !== undefined) {
94+
extensionInstalled.value = true
95+
getAccounts()
96+
clearInterval(timer)
97+
return
98+
}
99+
100+
count++
101+
if (count > 10) { // 5秒后还是没有检测到,就停止检查
102+
clearInterval(timer)
103+
}
104+
}, 500)
105+
}
106+
107+
onBeforeMount(() => {
108+
checkExtension()
66109
})
67110
</script>
68111

@@ -81,7 +124,7 @@ onMounted(() => {
81124
<Info class="h-4 w-4" />
82125
<AlertTitle>提示</AlertTitle>
83126
<AlertDescription>
84-
此功能由第三方浏览器插件支持,本平台不保证安全性
127+
此功能由第三方浏览器插件支持,本平台不保证安全性及同步准确度
85128
</AlertDescription>
86129
</Alert>
87130

@@ -91,9 +134,7 @@ onMounted(() => {
91134
<AlertDescription>
92135
请安装
93136
<Primitive
94-
as="a"
95-
class="text-blue-500"
96-
href="https://www.wechatsync.com/?utm_source=syncicon#install"
137+
as="a" class="text-blue-500" href="https://www.wechatsync.com/?utm_source=syncicon#install"
97138
target="_blank"
98139
>
99140
文章同步助手
@@ -121,14 +162,44 @@ onMounted(() => {
121162
<Textarea id="desc" v-model="form.desc" placeholder="自动提取第一个段落" />
122163
</div>
123164

165+
<div class="w-full flex items-start gap-4">
166+
<Label class="w-10 text-end">
167+
账号
168+
</Label>
169+
<div class="flex flex-1 flex-col gap-2">
170+
<div v-for="account in form.accounts" :key="account.uid + account.displayName" class="flex items-center gap-2">
171+
<label class="flex flex-row items-center gap-4">
172+
<CheckboxRoot
173+
v-model:checked="account.checked"
174+
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"
175+
>
176+
<CheckboxIndicator>
177+
<Check v-if="account.checked" class="h-4 w-4" />
178+
</CheckboxIndicator>
179+
</CheckboxRoot>
180+
<span class="flex items-center gap-2 text-sm">
181+
<img
182+
:src="account.icon"
183+
alt=""
184+
class="inline-block h-[20px] w-[20px]"
185+
>
186+
{{ account.title }} - {{ account.displayName ?? account.home }}
187+
</span>
188+
</label>
189+
</div>
190+
</div>
191+
</div>
192+
124193
<DialogFooter>
125194
<Button variant="outline" @click="dialogVisible = false">
126195
取 消
127196
</Button>
128-
<Button :disabled="!extensionInstalled" @click="post">
197+
<Button :disabled="!allowPost" @click="post">
129198
确 定
130199
</Button>
131200
</DialogFooter>
132201
</DialogContent>
133202
</Dialog>
203+
204+
<PostTaskDialog v-model:open="postTaskDialogVisible" :post="form" />
134205
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<script setup lang="ts">
2+
import type { Post } from '@/types'
3+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
4+
5+
const props = defineProps<{
6+
post: Post
7+
open: boolean
8+
}>()
9+
10+
const emit = defineEmits([`update:open`])
11+
12+
const dialogVisible = computed({
13+
get: () => props.open,
14+
set: value => emit(`update:open`, value),
15+
})
16+
17+
const taskStatus = ref<any>(null)
18+
const submitting = ref(false)
19+
20+
async function startPost() {
21+
if (!props.post)
22+
return
23+
24+
try {
25+
window.$syncer?.addTask(
26+
{
27+
post: {
28+
title: props.post.title,
29+
content: props.post.content,
30+
markdown: props.post.markdown,
31+
thumb: props.post.thumb,
32+
desc: props.post.desc,
33+
},
34+
accounts: props.post.accounts.filter(a => a.checked),
35+
},
36+
(newStatus: any) => {
37+
taskStatus.value = newStatus
38+
},
39+
() => {
40+
submitting.value = false
41+
},
42+
)
43+
}
44+
catch (error) {
45+
console.error(`发布失败:`, error)
46+
}
47+
}
48+
49+
watch(() => props.open, (newVal) => {
50+
if (newVal) {
51+
startPost()
52+
}
53+
})
54+
</script>
55+
56+
<template>
57+
<Dialog v-model:open="dialogVisible">
58+
<DialogContent>
59+
<DialogHeader>
60+
<DialogTitle>提交发布任务</DialogTitle>
61+
</DialogHeader>
62+
63+
<div class="mt-4">
64+
<div v-if="!taskStatus" class="py-4 text-center">
65+
等待发布..
66+
</div>
67+
<div v-else class="max-h-[400px] flex flex-col overflow-y-auto">
68+
<div
69+
v-for="account in taskStatus?.accounts"
70+
:key="account.uid + account.displayName"
71+
class="border-b py-4 last:border-b-0"
72+
>
73+
<div class="mb-2 flex items-center gap-2">
74+
<img
75+
v-if="account.icon"
76+
:src="account.icon"
77+
class="object-cover h-5 w-5"
78+
alt=""
79+
>
80+
<span>{{ account.title }} - {{ account.displayName || account.home }}</span>
81+
</div>
82+
<div
83+
class="w-full flex-1 gap-2 overflow-auto pl-7 text-sm" :class="{
84+
'text-yellow-600': account.status === 'uploading',
85+
'text-red-600': account.status === 'failed',
86+
'text-green-600': account.status === 'done',
87+
}"
88+
>
89+
<template v-if="account.status === 'uploading'">
90+
{{ account.msg || '发布中' }}
91+
</template>
92+
93+
<template v-if="account.status === 'failed'">
94+
同步失败, 错误内容:{{ account.error }}
95+
</template>
96+
97+
<template v-if="account.status === 'done' && account.editResp">
98+
同步成功
99+
<a
100+
v-if="account.type !== 'wordpress' && account.editResp"
101+
:href="account.editResp.draftLink"
102+
class="ml-2 text-blue-500 hover:underline"
103+
referrerPolicy="no-referrer"
104+
target="_blank"
105+
>查看草稿</a>
106+
</template>
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
</DialogContent>
112+
</Dialog>
113+
</template>
114+
115+
<style scoped>
116+
.account-item {
117+
margin-bottom: 1rem;
118+
}
119+
</style>

‎src/types/index.ts

+23
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,26 @@ export interface Alert {
7272
text: string
7373
tokens: Token[]
7474
}
75+
76+
export interface PostAccount {
77+
avatar: string
78+
displayName: string
79+
home: string
80+
icon: string
81+
supportTypes: string[]
82+
title: string
83+
type: string
84+
uid: string
85+
checked: boolean
86+
status?: string
87+
error?: string
88+
}
89+
90+
export interface Post {
91+
title: string
92+
desc: string
93+
thumb: string
94+
content: string
95+
markdown: string
96+
accounts: PostAccount[]
97+
}

0 commit comments

Comments
 (0)
Please sign in to comment.