@webx-ui/module-blog 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,463 @@
1
+ import { defineComponent as H, useId as de, ref as S, useTemplateRef as ge, onMounted as me, nextTick as ve, openBlock as B, createBlock as $, unref as a, isRef as fe, withCtx as i, createElementVNode as pe, withModifiers as O, createVNode as s, withKeys as ye, createTextVNode as m, toDisplayString as v, computed as d, watch as xe, createElementBlock as be, createCommentVNode as we } from "vue";
2
+ import { useRoute as he, useRouter as _e } from "vue-router";
3
+ import { useTranslate as I, useAdmin as ke, useErrorText as Te, rowMenuWidth as qe, WxListScreen as We, WxRowMenu as j } from "@webx-ui/module-admin";
4
+ import { useModal as Se, WxDialog as Be, WxFormItem as Me, WxInput as Re, WxSpace as $e, WxButton as D, createModal as A, confirm as G, toast as x, WxTable as Ie, WxTooltip as Ve, WxText as T, WxActionBar as Ce, WxIcon as ze } from "@webx-ui/core";
5
+ import { u as J, c as Ne, b as Ae, T as De, a as Pe } from "./index-DqN5h0Ej.js";
6
+ const Ue = ["id"], Ee = /* @__PURE__ */ H({
7
+ __name: "TagRenameDialog",
8
+ props: {
9
+ name: {}
10
+ },
11
+ setup(V) {
12
+ const M = V, { open: b, resolve: _, dismiss: u } = Se();
13
+ J();
14
+ const f = I("webx-blog"), t = I("webx-admin"), q = de(), g = S(M.name), c = ge("field");
15
+ me(async () => {
16
+ await ve();
17
+ const o = c.value?.querySelector("input");
18
+ o?.focus(), o?.select();
19
+ });
20
+ function w() {
21
+ const o = g.value.trim();
22
+ if (o === "" || o === M.name) {
23
+ u();
24
+ return;
25
+ }
26
+ _(o);
27
+ }
28
+ return (o, r) => (B(), $(a(Be), {
29
+ open: a(b),
30
+ "onUpdate:open": r[2] || (r[2] = (k) => fe(b) ? b.value = k : null),
31
+ title: a(f)("tag.rename"),
32
+ width: 420
33
+ }, {
34
+ footer: i(() => [
35
+ s(a($e), { size: "sm" }, {
36
+ default: i(() => [
37
+ s(a(D), {
38
+ variant: "outline",
39
+ onClick: r[1] || (r[1] = (k) => a(u)())
40
+ }, {
41
+ default: i(() => [
42
+ m(v(a(f)("tag.cancel")), 1)
43
+ ]),
44
+ _: 1
45
+ }),
46
+ s(a(D), {
47
+ type: "primary",
48
+ "native-type": "submit",
49
+ form: a(q),
50
+ disabled: g.value.trim() === ""
51
+ }, {
52
+ default: i(() => [
53
+ m(v(a(t)("editor.save")), 1)
54
+ ]),
55
+ _: 1
56
+ }, 8, ["form", "disabled"])
57
+ ]),
58
+ _: 1
59
+ })
60
+ ]),
61
+ default: i(() => [
62
+ pe("form", {
63
+ id: a(q),
64
+ ref_key: "field",
65
+ ref: c,
66
+ onSubmit: O(w, ["prevent"])
67
+ }, [
68
+ s(a(Me), {
69
+ label: a(f)("tag.field-title"),
70
+ required: ""
71
+ }, {
72
+ default: i(() => [
73
+ s(a(Re), {
74
+ modelValue: g.value,
75
+ "onUpdate:modelValue": r[0] || (r[0] = (k) => g.value = k),
76
+ "aria-label": a(f)("tag.field-title"),
77
+ onKeydown: ye(O(w, ["prevent"]), ["enter"])
78
+ }, null, 8, ["modelValue", "aria-label", "onKeydown"])
79
+ ]),
80
+ _: 1
81
+ }, 8, ["label"])
82
+ ], 40, Ue)
83
+ ]),
84
+ _: 1
85
+ }, 8, ["open", "title"]));
86
+ }
87
+ }), Fe = { class: "wx-tags" }, Ke = /* @__PURE__ */ H({
88
+ __name: "TagsPage",
89
+ props: {
90
+ base: { default: "/blog" }
91
+ },
92
+ setup(V) {
93
+ const M = V, b = ke(), _ = Ne(b), u = he(), f = _e();
94
+ J();
95
+ const t = I("webx-blog"), q = I("webx-admin"), g = Te(), c = S(null), w = S(!0), o = S([]), r = S(!1), k = A(Ee), Q = A(Ae), X = A(De), C = d(() => b.can("blog.taxonomy.manage")), P = d(
96
+ () => b.state.manifest?.modules.find((e) => e.id === "tags")?.title ?? t("module.tags")
97
+ ), W = d({
98
+ get: () => typeof u.query.view == "string" ? u.query.view : "",
99
+ set: (e) => {
100
+ f.replace({
101
+ query: { ...u.query, view: e === "" ? void 0 : String(e), page: void 0 }
102
+ });
103
+ }
104
+ }), z = d(() => c.value?.filters ?? { total: 0, empty: 0, noindex: 0 }), Y = d(() => [
105
+ { value: "", label: t("tag.view-all"), badge: z.value.total },
106
+ { value: "empty", label: t("tag.view-empty"), badge: z.value.empty },
107
+ { value: "noindex", label: t("tag.view-noindex"), badge: z.value.noindex }
108
+ ]), U = d(() => c.value?.data ?? []), p = d(() => U.value.filter((e) => o.value.includes(e.id))), Z = d(() => [
109
+ { key: "title", label: t("tag.column-title"), minWidth: 150, sortable: !0 },
110
+ // The number is what the other columns and the narrowest the title may be add up to without
111
+ // this one: below it the address is what does not fit, and a fixed table does not shrink —
112
+ // it scrolls sideways and takes the ··· with it (CLAUDE.md §4). Measured, not guessed: at
113
+ // 375px the row is the word, the number and the menu, and nothing hangs off the edge.
114
+ { key: "path", label: t("tag.column-address"), width: 180, hideBelow: 760 },
115
+ {
116
+ key: "articles_count",
117
+ label: t("tag.column-articles"),
118
+ width: 80,
119
+ align: "right",
120
+ sortable: !0
121
+ },
122
+ // Wide enough for the longest of the three states on one line, in every language: measured
123
+ // at 180px of text, and a cell keeps 32 of its own. Narrower, the ruled row wrapped and
124
+ // stood eighteen pixels taller than the ones around it.
125
+ { key: "indexing", label: t("tag.column-indexing"), width: 230, hideBelow: 560 },
126
+ { key: "actions", label: "", width: qe, align: "right" }
127
+ ]);
128
+ function ee(e) {
129
+ return {
130
+ q: e?.search ?? (typeof u.query.q == "string" ? u.query.q : ""),
131
+ empty: W.value === "empty",
132
+ noindex: W.value === "noindex",
133
+ // Only when it is not the default: most used first is what the list is, and a query
134
+ // string that repeats the default is a link that outlives a change of mind about it.
135
+ sort: E(e),
136
+ page: e?.page ?? (Number(u.query.page ?? 1) || 1),
137
+ per_page: e?.perPage
138
+ };
139
+ }
140
+ async function h(e) {
141
+ w.value = !0;
142
+ try {
143
+ c.value = await _.tagsPage(ee(e));
144
+ } catch (l) {
145
+ x.danger(g(l));
146
+ } finally {
147
+ w.value = !1;
148
+ }
149
+ }
150
+ function E(e) {
151
+ const l = e?.sort ?? te();
152
+ if (!l) return;
153
+ const n = l.key === "title" ? "name" : "articles", y = `${l.order === "desc" ? "-" : ""}${n}`;
154
+ return y === "articles" ? void 0 : y;
155
+ }
156
+ function te() {
157
+ const e = typeof u.query.sort == "string" ? u.query.sort : "";
158
+ if (e === "") return null;
159
+ const l = e.startsWith("-");
160
+ return { key: (l ? e.slice(1) : e) === "name" ? "title" : "articles_count", order: l ? "desc" : "asc" };
161
+ }
162
+ function ae(e) {
163
+ const l = {
164
+ ...u.query,
165
+ q: e.search === "" ? void 0 : e.search,
166
+ sort: E(e),
167
+ page: e.page === 1 ? void 0 : String(e.page)
168
+ };
169
+ f.replace({ query: l }), h(e);
170
+ }
171
+ xe(W, () => {
172
+ o.value = [], h();
173
+ }), h();
174
+ async function ne() {
175
+ await Q({}) && await h();
176
+ }
177
+ async function le(e) {
178
+ const l = await k({ name: e.title });
179
+ l && await K(e, { title: l }, t("tag.renamed"));
180
+ }
181
+ async function F(e, l) {
182
+ await K(e, { noindex: l }, t(l ? "tag.kept-out" : "tag.indexed"));
183
+ }
184
+ async function K(e, l, n) {
185
+ try {
186
+ const y = await _.saveTag(e.id, l);
187
+ c.value && (c.value = {
188
+ ...c.value,
189
+ data: c.value.data.map((R) => R.id === y.id ? y : R)
190
+ }), x.success(n);
191
+ } catch (y) {
192
+ x.danger(g(y));
193
+ }
194
+ }
195
+ async function ie(e) {
196
+ if (await G({
197
+ title: t("tag.delete-title", { title: e.title }),
198
+ message: e.articles_count > 0 ? t("tag.delete-text-used", { count: e.articles_count }) : t("tag.delete-text"),
199
+ confirmText: t("tag.delete"),
200
+ cancelText: t("tag.cancel"),
201
+ tone: "danger"
202
+ }))
203
+ try {
204
+ await _.removeTag(e.id), x.success(t("tag.deleted")), o.value = o.value.filter((n) => n !== e.id), await h();
205
+ } catch (n) {
206
+ x.danger(g(n));
207
+ }
208
+ }
209
+ const oe = d(() => [
210
+ {
211
+ key: "index",
212
+ icon: "eye",
213
+ label: t("tag.index"),
214
+ disabled: r.value,
215
+ run: () => {
216
+ N("index");
217
+ }
218
+ },
219
+ {
220
+ key: "noindex",
221
+ icon: "eye-off",
222
+ label: t("tag.noindex"),
223
+ disabled: r.value,
224
+ run: () => {
225
+ N("noindex");
226
+ }
227
+ },
228
+ {
229
+ key: "delete",
230
+ icon: "trash",
231
+ label: t("tag.delete"),
232
+ danger: !0,
233
+ disabled: r.value,
234
+ run: () => {
235
+ N("delete");
236
+ }
237
+ }
238
+ ]);
239
+ function se(e) {
240
+ if (!C.value) return [];
241
+ const l = [
242
+ { key: "rename", icon: "edit", label: t("tag.rename"), run: () => {
243
+ le(e);
244
+ } }
245
+ ];
246
+ return e.url && l.push({
247
+ key: "site",
248
+ icon: "external-link",
249
+ label: t("tag.open-on-site"),
250
+ href: e.url
251
+ }), l.push({
252
+ key: "articles",
253
+ icon: "list",
254
+ label: t("tag.show-articles"),
255
+ run: () => {
256
+ f.push({ path: `${M.base}/articles`, query: { tag: String(e.id) } });
257
+ }
258
+ }), l.push(
259
+ e.noindex ? {
260
+ key: "index",
261
+ icon: "eye",
262
+ label: t("tag.index"),
263
+ run: () => {
264
+ F(e, !1);
265
+ }
266
+ } : {
267
+ key: "noindex",
268
+ icon: "eye-off",
269
+ label: t("tag.noindex"),
270
+ run: () => {
271
+ F(e, !0);
272
+ }
273
+ }
274
+ ), l.push({
275
+ key: "delete",
276
+ icon: "trash",
277
+ label: t("tag.delete"),
278
+ danger: !0,
279
+ run: () => {
280
+ ie(e);
281
+ }
282
+ }), l;
283
+ }
284
+ function L(e) {
285
+ return e === "noindex" ? "muted" : "success";
286
+ }
287
+ async function re() {
288
+ if (p.value.length < 2) return;
289
+ const e = await X({ tags: p.value });
290
+ e && (x.success(t("tag.merged", { tag: e.tag.title, count: e.articles_count })), o.value = [], await h());
291
+ }
292
+ async function N(e) {
293
+ const l = p.value.map((n) => n.id);
294
+ if (!(l.length === 0 || r.value)) {
295
+ if (e === "delete") {
296
+ const n = p.value.reduce((R, ce) => R + ce.articles_count, 0);
297
+ if (!await G({
298
+ title: t("tag.delete-many-title", { count: l.length }),
299
+ message: n > 0 ? t("tag.delete-many-text-used", { count: n }) : t("tag.delete-many-text"),
300
+ confirmText: t("tag.delete"),
301
+ cancelText: t("tag.cancel"),
302
+ tone: "danger"
303
+ })) return;
304
+ }
305
+ r.value = !0;
306
+ try {
307
+ const n = await _.massTags(l, e);
308
+ x.success(
309
+ e === "delete" ? t("tag.deleted-many", { count: n }) : e === "index" ? t("tag.indexed-many", { count: n }) : t("tag.kept-out-many", { count: n })
310
+ ), o.value = [], await h();
311
+ } catch (n) {
312
+ x.danger(g(n));
313
+ } finally {
314
+ r.value = !1;
315
+ }
316
+ }
317
+ }
318
+ const ue = d(
319
+ () => C.value ? [{ key: "new", label: t("tag.new"), icon: "plus", primary: !0, run: () => {
320
+ ne();
321
+ } }] : []
322
+ );
323
+ return (e, l) => (B(), be("div", Fe, [
324
+ s(a(We), {
325
+ view: W.value,
326
+ "onUpdate:view": l[1] || (l[1] = (n) => W.value = n),
327
+ title: P.value,
328
+ views: Y.value,
329
+ actions: ue.value
330
+ }, {
331
+ default: i(() => [
332
+ s(a(Ie), {
333
+ selected: o.value,
334
+ "onUpdate:selected": l[0] || (l[0] = (n) => o.value = n),
335
+ data: c.value,
336
+ columns: Z.value,
337
+ "row-key": "id",
338
+ searchable: "",
339
+ hover: "",
340
+ flush: "",
341
+ layout: "fixed",
342
+ loading: w.value,
343
+ selectable: C.value,
344
+ "select-row-label": a(q)("filters.select-row"),
345
+ "select-all-label": a(q)("filters.select-all"),
346
+ "cards-below": 0,
347
+ "search-placeholder": a(t)("tag.search"),
348
+ "empty-text": U.value.length === 0 && !w.value ? a(t)("tag.empty") : void 0,
349
+ "aria-label": P.value,
350
+ onStateChange: ae
351
+ }, {
352
+ "cell-title": i(({ row: n }) => [
353
+ s(a(T), { truncate: "" }, {
354
+ default: i(() => [
355
+ m(v(n.title), 1)
356
+ ]),
357
+ _: 2
358
+ }, 1024)
359
+ ]),
360
+ "cell-path": i(({ row: n }) => [
361
+ s(a(T), {
362
+ size: "sm",
363
+ tone: "muted",
364
+ mono: "",
365
+ truncate: ""
366
+ }, {
367
+ default: i(() => [
368
+ m(v(n.path ?? a(t)("tag.no-address")), 1)
369
+ ]),
370
+ _: 2
371
+ }, 1024)
372
+ ]),
373
+ "cell-articles_count": i(({ row: n }) => [
374
+ s(a(T), {
375
+ tone: n.articles_count === 0 ? "muted" : "default"
376
+ }, {
377
+ default: i(() => [
378
+ m(v(n.articles_count), 1)
379
+ ]),
380
+ _: 2
381
+ }, 1032, ["tone"])
382
+ ]),
383
+ "cell-indexing": i(({ row: n }) => [
384
+ n.indexing === "rule" ? (B(), $(a(Ve), {
385
+ key: 0,
386
+ content: a(t)("tag.indexing-rule-help")
387
+ }, {
388
+ default: i(() => [
389
+ s(a(T), {
390
+ size: "sm",
391
+ tone: L(n.indexing)
392
+ }, {
393
+ default: i(() => [
394
+ m(v(a(t)("tag.indexing-rule")), 1)
395
+ ]),
396
+ _: 1
397
+ }, 8, ["tone"])
398
+ ]),
399
+ _: 2
400
+ }, 1032, ["content"])) : (B(), $(a(T), {
401
+ key: 1,
402
+ size: "sm",
403
+ tone: L(n.indexing)
404
+ }, {
405
+ default: i(() => [
406
+ m(v(a(t)(`tag.indexing-${n.indexing}`)), 1)
407
+ ]),
408
+ _: 2
409
+ }, 1032, ["tone"]))
410
+ ]),
411
+ "cell-actions": i(({ row: n }) => [
412
+ s(a(j), {
413
+ actions: se(n),
414
+ label: n.title
415
+ }, null, 8, ["actions", "label"])
416
+ ]),
417
+ _: 1
418
+ }, 8, ["selected", "data", "columns", "loading", "selectable", "select-row-label", "select-all-label", "search-placeholder", "empty-text", "aria-label"])
419
+ ]),
420
+ _: 1
421
+ }, 8, ["view", "title", "views", "actions"]),
422
+ p.value.length > 0 ? (B(), $(a(Ce), {
423
+ key: 0,
424
+ class: "wx-tags__bar",
425
+ sticky: ""
426
+ }, {
427
+ state: i(() => [
428
+ s(a(T), { weight: "medium" }, {
429
+ default: i(() => [
430
+ m(v(a(t)("tag.selected", { count: p.value.length })), 1)
431
+ ]),
432
+ _: 1
433
+ })
434
+ ]),
435
+ default: i(() => [
436
+ s(a(D), {
437
+ type: "primary",
438
+ disabled: p.value.length < 2 || r.value,
439
+ onClick: re
440
+ }, {
441
+ icon: i(() => [
442
+ s(a(ze), { name: "link" })
443
+ ]),
444
+ default: i(() => [
445
+ m(" " + v(a(t)("tag.merge")), 1)
446
+ ]),
447
+ _: 1
448
+ }, 8, ["disabled"]),
449
+ s(a(j), {
450
+ size: "lg",
451
+ actions: oe.value,
452
+ label: a(t)("tag.selected", { count: p.value.length })
453
+ }, null, 8, ["actions", "label"])
454
+ ]),
455
+ _: 1
456
+ })) : we("", !0)
457
+ ]));
458
+ }
459
+ }), Je = /* @__PURE__ */ Pe(Ke, [["__scopeId", "data-v-d9b8d73d"]]);
460
+ export {
461
+ Je as default
462
+ };
463
+ //# sourceMappingURL=TagsPage-rFfpCqPs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TagsPage-rFfpCqPs.js","sources":["../src/TagRenameDialog.vue","../src/TagsPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useId, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { useBlogMessages } from './i18n'\n\n/**\n * Renaming a tag: one field, one button, and nothing changes until it is pressed.\n *\n * It used to be done in the cell — the name turned into a box when it was clicked. Two things\n * were wrong with that. A name that is quietly an `<input>` reads as a name: nothing says it can\n * be typed in, and a stray click on a row is a rename nobody asked for. And the box saved itself\n * on `blur`, which never fired: `blur` does not bubble, so the listener on the field's wrapper\n * heard nothing and clicking away simply lost what had been typed.\n *\n * The address deliberately does not follow the word. A tag respelled three times before lunch\n * would otherwise leave three aliases behind a decision nobody made; the address has a menu item\n * of its own.\n */\nconst props = defineProps<{ name: string }>()\n\nconst { open, resolve, dismiss } = useModal<string>()\n\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\n/* 'Save' is the panel's word, translated once for every module that has a form. */\nconst panel = useTranslate('webx-admin')\n\n/* The footer button lives outside the form, so it says which form it submits. */\nconst formId = useId()\n\nconst draft = ref(props.name)\nconst field = useTemplateRef<HTMLFormElement>('field')\n\nonMounted(async () => {\n await nextTick()\n\n const input = field.value?.querySelector('input')\n\n // Selected rather than merely focused: renaming is usually replacing, not appending.\n input?.focus()\n input?.select()\n})\n\nfunction submit(): void {\n const next = draft.value.trim()\n\n // An empty name is not a rename, it is a tag with nothing to call it by.\n if (next === '' || next === props.name) {\n dismiss()\n\n return\n }\n\n resolve(next)\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('tag.rename')\" :width=\"420\">\n <!-- A real form, so that Enter does what the button does. A listener on the field is not\n the same thing: a form with one text field submits on Enter by itself, which is what a\n one-field dialog is for. -->\n <form :id=\"formId\" ref=\"field\" @submit.prevent=\"submit\">\n <wx-form-item :label=\"t('tag.field-title')\" required>\n <!-- The key as well as the form. Implicit submission is a browser behaviour and this\n field lives inside a dialog that takes the keyboard over; measured here, a real\n Enter reached the input, was not prevented by anybody, and submitted nothing. -->\n <wx-input\n v-model=\"draft\"\n :aria-label=\"t('tag.field-title')\"\n @keydown.enter.prevent=\"submit\"\n />\n </wx-form-item>\n </form>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('tag.cancel') }}</wx-button>\n <wx-button\n type=\"primary\"\n native-type=\"submit\"\n :form=\"formId\"\n :disabled=\"draft.trim() === ''\"\n >\n {{ panel('editor.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxListScreen,\n rowMenuWidth,\n WxRowMenu,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxActionBar,\n WxButton,\n WxIcon,\n WxTable,\n WxText,\n WxTooltip,\n type RowKey,\n type TabItem,\n type TableColumn,\n type TableSort,\n type TableState,\n type TabValue,\n} from '@webx-ui/core'\nimport TagCreateDialog from './TagCreateDialog.vue'\nimport TagRenameDialog from './TagRenameDialog.vue'\nimport TagMergeDialog from './TagMergeDialog.vue'\nimport { createBlogApi } from './api'\nimport { useBlogMessages } from './i18n'\nimport type { TagIndexing, TagMerged, TagQuery, TagRow, TagsPage } from './types'\n\n/**\n * Tags, and the raking over of them (§10).\n *\n * Entered from the article form by the hundred, which is what the whole screen is shaped by:\n * within six months the table holds \"belts\", \"belt\" and \"drive belts\", and all three are right.\n * So renaming is done in place — a tag is one word, and a form to change one word is a form\n * nobody opens — and everything else is done to a pile of rows at once.\n *\n * It stays a table at every width, with columns dropping out by `hideBelow` until the word and\n * the `···` are left. Cards would be 250px each for a word and a number (CLAUDE.md §4), and\n * this is a screen somebody scrolls through sixty rows of.\n *\n * \"Not indexed\" is the one filter that is not a `where`: it is the answer of the same rule the\n * rendered page follows, rules in the SEO section included, so the column, the tab's count and\n * the filter are one answer worked out on the server (§12).\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/blog' })\n\nconst context = useAdmin()\nconst api = createBlogApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseBlogMessages()\n\nconst t = useTranslate('webx-blog')\nconst panel = useTranslate('webx-admin')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst page = ref<TagsPage | null>(null)\nconst loading = ref(true)\nconst selected = ref<RowKey[]>([])\nconst working = ref(false)\n\nconst rename = createModal<string, { name: string }>(TagRenameDialog)\n\nconst create = createModal<TagRow, Record<string, never>>(TagCreateDialog)\nconst merge = createModal<TagMerged, { tags: TagRow[] }>(TagMergeDialog)\n\nconst canManage = computed(() => context.can('blog.taxonomy.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'tags')?.title ??\n t('module.tags'),\n)\n\n/** Everything the list is looking at lives in the address, so a link to it is a link. */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value), page: undefined },\n })\n },\n})\n\nconst counts = computed(() => page.value?.filters ?? { total: 0, empty: 0, noindex: 0 })\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('tag.view-all'), badge: counts.value.total },\n { value: 'empty', label: t('tag.view-empty'), badge: counts.value.empty },\n { value: 'noindex', label: t('tag.view-noindex'), badge: counts.value.noindex },\n])\n\nconst rows = computed(() => page.value?.data ?? [])\n\nconst chosen = computed(() => rows.value.filter((tag) => selected.value.includes(tag.id as RowKey)))\n\n/**\n * Five columns, and never cards.\n *\n * The word and the menu stay at every width; the address goes first, because it is the one\n * thing a tag's name already tells you, then the state of the index. What is left on a phone is\n * a word, a number and a `···`, which is the screen this is (CLAUDE.md §4).\n */\nconst columns = computed<TableColumn<TagRow>[]>(() => [\n { key: 'title', label: t('tag.column-title'), minWidth: 150, sortable: true },\n // The number is what the other columns and the narrowest the title may be add up to without\n // this one: below it the address is what does not fit, and a fixed table does not shrink —\n // it scrolls sideways and takes the ··· with it (CLAUDE.md §4). Measured, not guessed: at\n // 375px the row is the word, the number and the menu, and nothing hangs off the edge.\n { key: 'path', label: t('tag.column-address'), width: 180, hideBelow: 760 },\n {\n key: 'articles_count',\n label: t('tag.column-articles'),\n width: 80,\n align: 'right',\n sortable: true,\n },\n // Wide enough for the longest of the three states on one line, in every language: measured\n // at 180px of text, and a cell keeps 32 of its own. Narrower, the ruled row wrapped and\n // stood eighteen pixels taller than the ones around it.\n { key: 'indexing', label: t('tag.column-indexing'), width: 230, hideBelow: 560 },\n { key: 'actions', label: '', width: rowMenuWidth, align: 'right' },\n])\n\nfunction query(state?: TableState): TagQuery {\n return {\n q: state?.search ?? (typeof route.query.q === 'string' ? route.query.q : ''),\n empty: view.value === 'empty',\n noindex: view.value === 'noindex',\n // Only when it is not the default: most used first is what the list is, and a query\n // string that repeats the default is a link that outlives a change of mind about it.\n sort: sortOf(state),\n page: state?.page ?? (Number(route.query.page ?? 1) || 1),\n per_page: state?.perPage,\n }\n}\n\nasync function load(state?: TableState): Promise<void> {\n loading.value = true\n\n try {\n page.value = await api.tagsPage(query(state))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * The order the table asks for, in the words the server knows.\n *\n * Two keys and a direction: the word from A, or the used ones first — and a minus turns either\n * of them around. The default is most used first, so the query string leaves it out: a link that\n * repeats the default outlives a change of mind about it.\n */\nfunction sortOf(state?: TableState): TagQuery['sort'] {\n const from = state?.sort ?? sortFromRoute()\n\n if (!from) return undefined\n\n const key = from.key === 'title' ? 'name' : 'articles'\n const sort = `${from.order === 'desc' ? '-' : ''}${key}` as NonNullable<TagQuery['sort']>\n\n return sort === 'articles' ? undefined : sort\n}\n\n/** What the address says the order is, for a list arrived at by a link. */\nfunction sortFromRoute(): TableSort | null {\n const value = typeof route.query.sort === 'string' ? route.query.sort : ''\n\n if (value === '') return null\n\n const desc = value.startsWith('-')\n const key = (desc ? value.slice(1) : value) === 'name' ? 'title' : 'articles_count'\n\n return { key, order: desc ? 'desc' : 'asc' }\n}\n\n/** The table reports everything it knows in one event, and the address is written from it. */\nfunction onState(state: TableState): void {\n const wanted: LocationQueryRaw = {\n ...route.query,\n q: state.search === '' ? undefined : state.search,\n sort: sortOf(state),\n page: state.page === 1 ? undefined : String(state.page),\n }\n\n void router.replace({ query: wanted })\n void load(state)\n}\n\nwatch(view, () => {\n // A new view is a new list, and a selection made in the old one is a selection of rows\n // nobody can see any more.\n selected.value = []\n void load()\n})\n\nvoid load()\n\nasync function add(): Promise<void> {\n if (await create({})) await load()\n}\n\n/**\n * Renaming, as an act rather than a side effect.\n *\n * A dialog with one field, because a name that is quietly an `<input>` reads as a name: nothing\n * says it can be typed in, and a stray click on a row is a rename nobody asked for. The address\n * does not move with the word — a tag respelled three times before lunch would leave three\n * aliases behind a decision nobody made — so it has a menu item of its own.\n */\nasync function startRename(tag: TagRow): Promise<void> {\n const title = await rename({ name: tag.title })\n\n if (title) await patch(tag, { title }, t('tag.renamed'))\n}\n\nasync function setIndexing(tag: TagRow, noindex: boolean): Promise<void> {\n await patch(tag, { noindex }, noindex ? t('tag.kept-out') : t('tag.indexed'))\n}\n\n/**\n * One tag changed, and the row it changed in.\n *\n * The answer replaces the row rather than reloading the page: the list is sixty words long and\n * somebody is halfway down it, and a reload after every rename would take their place away.\n */\nasync function patch(\n tag: TagRow,\n input: { title?: string; noindex?: boolean },\n said: string,\n): Promise<void> {\n try {\n const saved = await api.saveTag(tag.id, input)\n\n if (page.value) {\n page.value = {\n ...page.value,\n data: page.value.data.map((row) => (row.id === saved.id ? saved : row)),\n }\n }\n\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\nasync function remove(tag: TagRow): Promise<void> {\n const agreed = await confirm({\n title: t('tag.delete-title', { title: tag.title }),\n message:\n tag.articles_count > 0\n ? t('tag.delete-text-used', { count: tag.articles_count })\n : t('tag.delete-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.removeTag(tag.id)\n toast.success(t('tag.deleted'))\n selected.value = selected.value.filter((key) => key !== tag.id)\n await load()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * What can be done to the pile, behind the same `···` a single row has.\n *\n * Three buttons in the bar were three lines of chrome on a phone and the same three words every\n * row already offers; merging stays a button because it is the one thing here that cannot be\n * done to a row on its own.\n */\nconst massActions = computed<RowAction[]>(() => [\n {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n disabled: working.value,\n run: () => void massSelected('index'),\n },\n {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n disabled: working.value,\n run: () => void massSelected('noindex'),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n disabled: working.value,\n run: () => void massSelected('delete'),\n },\n])\n\nfunction actionsFor(tag: TagRow): RowAction[] {\n if (!canManage.value) return []\n\n const actions: RowAction[] = [\n { key: 'rename', icon: 'edit', label: t('tag.rename'), run: () => void startRename(tag) },\n ]\n\n if (tag.url) {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('tag.open-on-site'),\n href: tag.url,\n })\n }\n\n actions.push({\n key: 'articles',\n icon: 'list',\n label: t('tag.show-articles'),\n run: () => void router.push({ path: `${props.base}/articles`, query: { tag: String(tag.id) } }),\n })\n\n actions.push(\n tag.noindex\n ? {\n key: 'index',\n icon: 'eye',\n label: t('tag.index'),\n run: () => void setIndexing(tag, false),\n }\n : {\n key: 'noindex',\n icon: 'eye-off',\n label: t('tag.noindex'),\n run: () => void setIndexing(tag, true),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('tag.delete'),\n danger: true,\n run: () => void remove(tag),\n })\n\n return actions\n}\n\n/** The colour the state of the index is said in: green for in, quiet for out (§12). */\nfunction tone(indexing: TagIndexing): 'success' | 'muted' {\n return indexing === 'noindex' ? 'muted' : 'success'\n}\n\nasync function mergeSelected(): Promise<void> {\n if (chosen.value.length < 2) return\n\n const merged = await merge({ tags: chosen.value })\n\n if (!merged) return\n\n toast.success(t('tag.merged', { tag: merged.tag.title, count: merged.articles_count }))\n selected.value = []\n await load()\n}\n\nasync function massSelected(action: 'index' | 'noindex' | 'delete'): Promise<void> {\n const ids = chosen.value.map((tag) => tag.id)\n\n if (ids.length === 0 || working.value) return\n\n if (action === 'delete') {\n const held = chosen.value.reduce((sum, tag) => sum + tag.articles_count, 0)\n\n const agreed = await confirm({\n title: t('tag.delete-many-title', { count: ids.length }),\n message:\n held > 0 ? t('tag.delete-many-text-used', { count: held }) : t('tag.delete-many-text'),\n confirmText: t('tag.delete'),\n cancelText: t('tag.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n }\n\n working.value = true\n\n try {\n const affected = await api.massTags(ids, action)\n\n toast.success(\n action === 'delete'\n ? t('tag.deleted-many', { count: affected })\n : action === 'index'\n ? t('tag.indexed-many', { count: affected })\n : t('tag.kept-out-many', { count: affected }),\n )\n selected.value = []\n await load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('tag.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-tags\">\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :actions=\"actions\">\n <wx-table\n v-model:selected=\"selected\"\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n hover\n flush\n layout=\"fixed\"\n :loading=\"loading\"\n :selectable=\"canManage\"\n :select-row-label=\"panel('filters.select-row')\"\n :select-all-label=\"panel('filters.select-all')\"\n :cards-below=\"0\"\n :search-placeholder=\"t('tag.search')\"\n :empty-text=\"rows.length === 0 && !loading ? t('tag.empty') : undefined\"\n :aria-label=\"title\"\n @state-change=\"onState\"\n >\n <template #cell-title=\"{ row }\">\n <wx-text truncate>{{ row.title }}</wx-text>\n </template>\n\n <template #cell-path=\"{ row }\">\n <wx-text size=\"sm\" tone=\"muted\" mono truncate>\n {{ row.path ?? t('tag.no-address') }}\n </wx-text>\n </template>\n\n <template #cell-articles_count=\"{ row }\">\n <wx-text :tone=\"row.articles_count === 0 ? 'muted' : 'default'\">\n {{ row.articles_count }}\n </wx-text>\n </template>\n\n <!--\n Three states and not two. A rule in the SEO section opens the page whatever the flag\n says, and an editor who wrote that rule must not be left looking at a row that says\n `noindex` and disagreeing with it (§12).\n -->\n <template #cell-indexing=\"{ row }\">\n <wx-tooltip v-if=\"row.indexing === 'rule'\" :content=\"t('tag.indexing-rule-help')\">\n <wx-text size=\"sm\" :tone=\"tone(row.indexing)\">{{ t('tag.indexing-rule') }}</wx-text>\n </wx-tooltip>\n <wx-text v-else size=\"sm\" :tone=\"tone(row.indexing)\">\n {{ t(`tag.indexing-${row.indexing}`) }}\n </wx-text>\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.title\" />\n </template>\n </wx-table>\n </wx-list-screen>\n\n <!--\n The selection bar, at the bottom of the screen and only while something is selected.\n `WxActionBar` rather than a floating pill of its own: the panel already has one shape for\n \"what can be done from here\", and a second one would be a second one to keep in step.\n -->\n <wx-action-bar v-if=\"chosen.length > 0\" class=\"wx-tags__bar\" sticky>\n <template #state>\n <wx-text weight=\"medium\">{{ t('tag.selected', { count: chosen.length }) }}</wx-text>\n </template>\n\n <!--\n One button and a menu. Merging is what this bar exists for — it is the only thing here\n that cannot be done to a row on its own — and the other three are the row's own menu\n applied to several rows at once, which is where a reader already looks for them.\n\n The icon is a slot and not a prop: `WxButton` takes `#icon`, and `icon=\"…\"` lands on the\n element as an attribute and draws nothing at all (CLAUDE.md §4).\n -->\n <wx-button type=\"primary\" :disabled=\"chosen.length < 2 || working\" @click=\"mergeSelected\">\n <template #icon><wx-icon name=\"link\" /></template>\n {{ t('tag.merge') }}\n </wx-button>\n\n <!--\n No way out of the selection here. Untick the rows, or untick them all from the box in\n the heading — a × beside a red \"Delete\" is a button whose whole job is to undo something\n harmless, standing where the dangerous one is, and it read as a way to close the bar.\n\n `lg` because the menu stands beside a button rather than at the end of a row: a row menu\n is 30px, a button is 42, and the pair read as a control and a leftover. In a bar the two\n are the same two things and have to be the same size.\n -->\n <wx-row-menu\n size=\"lg\"\n :actions=\"massActions\"\n :label=\"t('tag.selected', { count: chosen.length })\"\n />\n </wx-action-bar>\n </div>\n</template>\n\n<style scoped>\n.wx-tags {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-tags__rename {\n display: block;\n min-width: 0;\n}\n\n.wx-tags__sort {\n display: flex;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * The name is the rename: a tag is one word, and a form to change one word is a form nobody\n * opens. It reads as text until it is hovered, because a table of sixty underlined words is a\n * table of sixty links to nowhere.\n */\n.wx-tags__name {\n max-width: 100%;\n overflow: hidden;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: text;\n}\n\n.wx-tags__name:hover {\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n\n/*\n * One line on a phone, not two.\n *\n * The bar keeps its state box at 220px so that a sentence about a draft — \"Saved · goes out on\n * 25 September\" — has room to be read before the buttons take a line of their own. What stands\n * here is two words and a number, and asking for 220 of them pushed \"Merge\" and the ··· onto a\n * second line at every phone width. `:deep()` because the box is the bar's element and the rule\n * is ours. Measured at 375: the words 89px, the button and the ··· 203, and 41px of air left\n * between them.\n */\n.wx-tags__bar :deep(.wx-action-bar__state) {\n flex: 0 1 auto;\n}\n</style>\n"],"names":["props","__props","open","resolve","dismiss","useModal","useBlogMessages","t","useTranslate","panel","formId","useId","draft","ref","field","useTemplateRef","onMounted","nextTick","input","submit","next","_createBlock","_unref","WxDialog","$event","_createVNode","WxSpace","WxButton","_createElementVNode","WxFormItem","WxInput","context","useAdmin","api","createBlogApi","route","useRoute","router","useRouter","message","useErrorText","page","loading","selected","working","rename","createModal","TagRenameDialog","create","TagCreateDialog","merge","TagMergeDialog","canManage","computed","title","module","view","value","counts","views","rows","chosen","tag","columns","rowMenuWidth","query","state","sortOf","load","error","toast","from","sortFromRoute","key","sort","desc","onState","wanted","watch","add","startRename","patch","setIndexing","noindex","said","saved","row","remove","confirm","massActions","massSelected","actionsFor","actions","tone","indexing","mergeSelected","merged","action","ids","held","sum","affected","_openBlock","_createElementBlock","_hoisted_1","WxListScreen","WxTable","_withCtx","WxText","_createTextVNode","_toDisplayString","WxTooltip","WxRowMenu","WxActionBar","WxIcon"],"mappings":"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"}