@webx-ui/module-faq 0.1.2 → 0.1.4

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.
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
- import { useAdmin as ee, useTranslate as te, reorderItems as we, useErrorText as ce, WxScreenHead as be, WxScreen as xe, WxSaveState as ke, useItemOrder as _e, WxListScreen as Se, WxRowMenu as Te, categoryRoutes as We } from "@webx-ui/module-admin";
2
- import { provide as $e, inject as Ce, defineComponent as ae, computed as q, openBlock as v, createElementBlock as A, Fragment as K, createVNode as y, unref as t, withCtx as d, createTextVNode as $, toDisplayString as _, createBlock as b, ref as S, onMounted as Ae, onBeforeUnmount as de, createSlots as Be, createCommentVNode as L, watch as ie, createElementVNode as j, normalizeClass as G, resolveDynamicComponent as Ne, renderList as Le } from "vue";
3
- import { WxText as le, WxButton as ve, WxIcon as X, toast as w, useLocales as Qe, localizedValue as Ve, WxSkeleton as fe, WxBadge as O, WxActionBar as Fe, confirm as Y, WxCard as ze, WxListDetail as Ie, WxEmpty as re, WxInput as Pe, WxSelect as Me, WxSortableList as Re, WxTooltip as Ue, WxAlert as Ee } from "@webx-ui/core";
4
- import { onBeforeRouteUpdate as De, onBeforeRouteLeave as Oe, useRoute as Ke, useRouter as He } from "vue-router";
1
+ import { useAdmin as ee, useTranslate as te, reorderItems as we, useErrorText as ce, useBodyKeys as be, WxScreenHead as xe, WxScreen as ke, WxSaveState as _e, useItemOrder as Se, WxListScreen as Te, WxRowMenu as We, categoryRoutes as $e } from "@webx-ui/module-admin";
2
+ import { provide as Ce, inject as Ae, defineComponent as ae, computed as q, openBlock as v, createElementBlock as A, Fragment as H, createVNode as y, unref as t, withCtx as d, createTextVNode as $, toDisplayString as _, createBlock as b, ref as S, onMounted as Be, onBeforeUnmount as de, createSlots as Ne, createCommentVNode as L, watch as ie, createElementVNode as j, normalizeClass as G, resolveDynamicComponent as Le, renderList as Qe } from "vue";
3
+ import { WxText as le, WxButton as ve, WxIcon as X, toast as w, useLocales as Ve, localizedValue as Fe, WxSkeleton as fe, WxBadge as K, WxActionBar as ze, confirm as Y, WxCard as Ie, WxListDetail as Pe, WxEmpty as re, WxInput as Me, WxSelect as Re, WxSortableList as Ue, WxTooltip as Ee, WxAlert as De } from "@webx-ui/core";
4
+ import { onBeforeRouteUpdate as Oe, onBeforeRouteLeave as Ke, useRoute as He, useRouter as Je } from "vue-router";
5
5
  const qe = /* @__PURE__ */ Symbol("wx-faq-question");
6
- function Je(l) {
7
- $e(qe, l);
6
+ function je(l) {
7
+ Ce(qe, l);
8
8
  }
9
- function je() {
10
- return Ce(qe, null);
9
+ function Ge() {
10
+ return Ae(qe, null);
11
11
  }
12
- const Ge = {
12
+ const Xe = {
13
13
  module: {
14
14
  group: "FAQ",
15
15
  questions: "Questions",
@@ -82,15 +82,15 @@ const Ge = {
82
82
  function ne() {
83
83
  try {
84
84
  const { i18n: l } = ee();
85
- ue.has(l) || (l.defaults("webx-faq", Ge), ue.add(l));
85
+ ue.has(l) || (l.defaults("webx-faq", Xe), ue.add(l));
86
86
  } catch {
87
87
  }
88
88
  }
89
- const Xe = { class: "wx-faq-anchor" }, Ye = /* @__PURE__ */ ae({
89
+ const Ye = { class: "wx-faq-anchor" }, Ze = /* @__PURE__ */ ae({
90
90
  name: "WxFaqAnchor",
91
91
  __name: "AnchorField",
92
92
  setup(l) {
93
- const r = je();
93
+ const r = Ge();
94
94
  ne();
95
95
  const s = te("webx-faq"), a = q(() => r?.question.value?.anchor ?? ""), c = q(() => a.value === "" ? "" : `#${a.value}`);
96
96
  async function n() {
@@ -99,8 +99,8 @@ const Xe = { class: "wx-faq-anchor" }, Ye = /* @__PURE__ */ ae({
99
99
  } catch {
100
100
  }
101
101
  }
102
- return (B, u) => (v(), A("div", Xe, [
103
- c.value !== "" ? (v(), A(K, { key: 0 }, [
102
+ return (B, u) => (v(), A("div", Ye, [
103
+ c.value !== "" ? (v(), A(H, { key: 0 }, [
104
104
  y(t(le), {
105
105
  class: "wx-faq-anchor__value",
106
106
  mono: "",
@@ -141,7 +141,7 @@ const Xe = { class: "wx-faq-anchor" }, Ye = /* @__PURE__ */ ae({
141
141
  for (const [a, c] of r)
142
142
  s[a] = c;
143
143
  return s;
144
- }, Ze = /* @__PURE__ */ oe(Ye, [["__scopeId", "data-v-e6091b48"]]), Z = "faq/questions";
144
+ }, et = /* @__PURE__ */ oe(Ze, [["__scopeId", "data-v-e6091b48"]]), Z = "faq/questions";
145
145
  function he(l) {
146
146
  const r = `${l.apiPath}/${Z}`, s = (a) => a.data;
147
147
  return {
@@ -158,7 +158,7 @@ function he(l) {
158
158
  reorder: (a, c = null) => we(l, Z, a, c)
159
159
  };
160
160
  }
161
- const et = /* @__PURE__ */ ae({
161
+ const tt = /* @__PURE__ */ ae({
162
162
  name: "WxFaqQuestionPane",
163
163
  __name: "QuestionPane",
164
164
  props: {
@@ -168,13 +168,13 @@ const et = /* @__PURE__ */ ae({
168
168
  },
169
169
  emits: ["back", "saved", "created", "removed"],
170
170
  setup(l, { emit: r }) {
171
- const s = l, a = r, c = ee(), n = he(c), B = Qe();
171
+ const s = l, a = r, c = ee(), n = he(c), B = Ve();
172
172
  ne();
173
- const u = te("webx-faq"), Q = ce(), x = S(null), g = S({}), N = S(""), C = S(s.id !== null), f = S(!1), T = S({}), k = q(() => c.can("faq.manage")), V = q(() => JSON.stringify(g.value)), m = q(() => N.value !== "" && V.value !== N.value), R = q(() => f.value ? "saving" : m.value || s.id === null ? "unsaved" : "saved"), H = q(() => {
173
+ const u = te("webx-faq"), Q = ce(), x = S(null), g = S({}), N = S(""), C = S(s.id !== null), f = S(!1), T = S({}), k = q(() => c.can("faq.manage")), V = q(() => JSON.stringify(g.value)), m = q(() => N.value !== "" && V.value !== N.value), R = q(() => f.value ? "saving" : m.value || s.id === null ? "unsaved" : "saved"), J = q(() => {
174
174
  const o = g.value.question, h = s.id === null ? u("question.new") : x.value?.question ?? "";
175
- return Ve(o, B.active.value, h) || u("question.untitled");
175
+ return Fe(o, B.active.value, h) || u("question.untitled");
176
176
  });
177
- Je({ question: x });
177
+ je({ question: x });
178
178
  function F(o) {
179
179
  x.value = o.question, g.value = o.values, N.value = JSON.stringify(o.values);
180
180
  }
@@ -231,10 +231,10 @@ const et = /* @__PURE__ */ ae({
231
231
  function z(o) {
232
232
  m.value && o.preventDefault();
233
233
  }
234
- function J(o) {
234
+ function D(o) {
235
235
  (o.ctrlKey || o.metaKey) && o.key.toLowerCase() === "s" && (o.preventDefault(), (m.value || s.id === null) && E());
236
236
  }
237
- Ae(() => {
237
+ be(D), Be(() => {
238
238
  U(), window.addEventListener("beforeunload", z);
239
239
  }), de(() => {
240
240
  window.removeEventListener("beforeunload", z);
@@ -248,10 +248,10 @@ const et = /* @__PURE__ */ ae({
248
248
  tone: "danger"
249
249
  });
250
250
  }
251
- De(
251
+ Oe(
252
252
  async (o, h) => o.query.question === h.query.question ? !0 : await I()
253
- ), Oe(I);
254
- const D = q(
253
+ ), Ke(I);
254
+ const O = q(
255
255
  () => k.value && x.value !== null ? [
256
256
  {
257
257
  key: "delete",
@@ -267,27 +267,27 @@ const et = /* @__PURE__ */ ae({
267
267
  );
268
268
  return (o, h) => (v(), A("div", {
269
269
  class: "wx-faq-question",
270
- onKeydown: J
270
+ onKeydown: D
271
271
  }, [
272
272
  C.value ? (v(), b(t(fe), {
273
273
  key: 0,
274
274
  class: "wx-faq-question__ghost",
275
275
  title: "",
276
276
  rows: 6
277
- })) : (v(), A(K, { key: 1 }, [
278
- y(t(be), {
277
+ })) : (v(), A(H, { key: 1 }, [
278
+ y(t(xe), {
279
279
  level: 3,
280
280
  back: !s.inline,
281
281
  "back-label": t(u)("question.back"),
282
- title: H.value,
283
- actions: D.value,
282
+ title: J.value,
283
+ actions: O.value,
284
284
  "collapse-below": 480,
285
285
  onBack: h[0] || (h[0] = (P) => a("back"))
286
- }, Be({ _: 2 }, [
286
+ }, Ne({ _: 2 }, [
287
287
  x.value && !x.value.published ? {
288
288
  name: "title-after",
289
289
  fn: d(() => [
290
- y(t(O), { dot: "" }, {
290
+ y(t(K), { dot: "" }, {
291
291
  default: d(() => [
292
292
  $(_(t(u)("question.not-published")), 1)
293
293
  ]),
@@ -297,16 +297,16 @@ const et = /* @__PURE__ */ ae({
297
297
  key: "0"
298
298
  } : void 0
299
299
  ]), 1032, ["back", "back-label", "title", "actions"]),
300
- y(t(xe), {
300
+ y(t(ke), {
301
301
  modelValue: g.value,
302
302
  "onUpdate:modelValue": h[1] || (h[1] = (P) => g.value = P),
303
303
  name: "faq.form",
304
304
  errors: T.value,
305
305
  disabled: !k.value || f.value
306
306
  }, null, 8, ["modelValue", "errors", "disabled"]),
307
- k.value ? (v(), b(t(Fe), { key: 0 }, {
307
+ k.value ? (v(), b(t(ze), { key: 0 }, {
308
308
  state: d(() => [
309
- y(t(ke), { state: R.value }, null, 8, ["state"])
309
+ y(t(_e), { state: R.value }, null, 8, ["state"])
310
310
  ]),
311
311
  default: d(() => [
312
312
  y(t(ve), {
@@ -326,16 +326,16 @@ const et = /* @__PURE__ */ ae({
326
326
  ], 64))
327
327
  ], 32));
328
328
  }
329
- }), tt = /* @__PURE__ */ oe(et, [["__scopeId", "data-v-0589ef85"]]), at = { class: "wx-faq__bar" }, nt = { class: "wx-faq-row__question" }, ot = { class: "wx-faq-row__facts" }, st = {
329
+ }), at = /* @__PURE__ */ oe(tt, [["__scopeId", "data-v-fe493d3f"]]), nt = { class: "wx-faq__bar" }, ot = { class: "wx-faq-row__question" }, st = { class: "wx-faq-row__facts" }, it = {
330
330
  key: 2,
331
331
  class: "wx-faq-row__locales"
332
- }, it = /* @__PURE__ */ ae({
332
+ }, lt = /* @__PURE__ */ ae({
333
333
  __name: "QuestionsPage",
334
334
  props: {
335
335
  base: { default: "/faq" }
336
336
  },
337
337
  setup(l) {
338
- const r = ee(), s = he(r), a = Ke(), c = He();
338
+ const r = ee(), s = he(r), a = He(), c = Je();
339
339
  ne();
340
340
  const n = te("webx-faq"), B = ce(), u = S([]), Q = S([]), x = S(!0), g = q(() => r.can("faq.manage")), N = q(
341
341
  () => r.state.manifest?.modules.find((e) => e.id === "faq")?.title ?? n("module.questions")
@@ -357,12 +357,12 @@ const et = /* @__PURE__ */ ae({
357
357
  }), R = q({
358
358
  get: () => m.value !== null,
359
359
  set: (e) => {
360
- e || D();
360
+ e || O();
361
361
  }
362
- }), H = q(() => [
362
+ }), J = q(() => [
363
363
  { value: "", label: n("question.all") },
364
364
  { value: "trashed", label: n("question.bin"), icon: "trash" }
365
- ]), F = _e(Z, () => ({
365
+ ]), F = Se(Z, () => ({
366
366
  q: k.value,
367
367
  category: T.value,
368
368
  filtered: f.value
@@ -394,7 +394,7 @@ const et = /* @__PURE__ */ ae({
394
394
  c.replace({ query: { ...a.query, q: e.trim() === "" ? void 0 : e } });
395
395
  }, 300);
396
396
  }), de(() => clearTimeout(z));
397
- function J(e) {
397
+ function D(e) {
398
398
  c.replace({
399
399
  query: { ...a.query, category: typeof e == "number" ? String(e) : void 0 }
400
400
  });
@@ -402,7 +402,7 @@ const et = /* @__PURE__ */ ae({
402
402
  function I(e) {
403
403
  c.replace({ query: { ...a.query, question: String(e) } });
404
404
  }
405
- function D() {
405
+ function O() {
406
406
  const e = { ...a.query };
407
407
  delete e.question, c.replace({ query: e });
408
408
  }
@@ -461,7 +461,7 @@ const et = /* @__PURE__ */ ae({
461
461
  }
462
462
  }
463
463
  function se(e) {
464
- m.value === e && D(), W();
464
+ m.value === e && O(), W();
465
465
  }
466
466
  async function pe(e) {
467
467
  try {
@@ -481,20 +481,20 @@ const et = /* @__PURE__ */ ae({
481
481
  function me(e) {
482
482
  return e.published && e.locales.length === 0;
483
483
  }
484
- return (e, p) => (v(), b(t(Se), {
484
+ return (e, p) => (v(), b(t(Te), {
485
485
  view: C.value,
486
486
  "onUpdate:view": p[4] || (p[4] = (i) => C.value = i),
487
487
  title: N.value,
488
- views: H.value,
488
+ views: J.value,
489
489
  card: !1
490
490
  }, {
491
491
  default: d(() => [
492
- y(t(ze), {
492
+ y(t(Ie), {
493
493
  class: "wx-faq",
494
494
  padding: "none"
495
495
  }, {
496
496
  default: d(() => [
497
- y(t(Ie), {
497
+ y(t(Pe), {
498
498
  open: R.value,
499
499
  "onUpdate:open": p[3] || (p[3] = (i) => R.value = i),
500
500
  class: "wx-faq__panes",
@@ -503,8 +503,8 @@ const et = /* @__PURE__ */ ae({
503
503
  "detail-label": t(n)("module.questions")
504
504
  }, {
505
505
  list: d(() => [
506
- j("div", at, [
507
- y(t(Pe), {
506
+ j("div", nt, [
507
+ y(t(Me), {
508
508
  modelValue: V.value,
509
509
  "onUpdate:modelValue": p[0] || (p[0] = (i) => V.value = i),
510
510
  class: "wx-faq__search",
@@ -521,7 +521,7 @@ const et = /* @__PURE__ */ ae({
521
521
  ]),
522
522
  _: 1
523
523
  }, 8, ["modelValue", "placeholder", "aria-label"]),
524
- f.value ? L("", !0) : (v(), b(t(Me), {
524
+ f.value ? L("", !0) : (v(), b(t(Re), {
525
525
  key: 0,
526
526
  "model-value": T.value,
527
527
  options: Q.value.map((i) => ({ value: i.id, label: i.title })),
@@ -529,7 +529,7 @@ const et = /* @__PURE__ */ ae({
529
529
  "aria-label": t(n)("question.filter-category"),
530
530
  clearable: "",
531
531
  size: "sm",
532
- "onUpdate:modelValue": J
532
+ "onUpdate:modelValue": D
533
533
  }, null, 8, ["model-value", "options", "placeholder", "aria-label"]))
534
534
  ]),
535
535
  g.value && !f.value ? (v(), A("button", {
@@ -552,8 +552,8 @@ const et = /* @__PURE__ */ ae({
552
552
  key: 2,
553
553
  title: E.value,
554
554
  description: !f.value && k.value === "" ? t(n)("question.empty-help") : void 0
555
- }, null, 8, ["title", "description"])) : (v(), A(K, { key: 3 }, [
556
- y(t(Re), {
555
+ }, null, 8, ["title", "description"])) : (v(), A(H, { key: 3 }, [
556
+ y(t(Ue), {
557
557
  modelValue: u.value,
558
558
  "onUpdate:modelValue": p[2] || (p[2] = (i) => u.value = i),
559
559
  class: G(["wx-faq__list", { "is-loading": x.value }]),
@@ -565,20 +565,20 @@ const et = /* @__PURE__ */ ae({
565
565
  onMove: h
566
566
  }, {
567
567
  default: d(({ item: i }) => [
568
- (v(), b(Ne(f.value ? "div" : "button"), {
568
+ (v(), b(Le(f.value ? "div" : "button"), {
569
569
  type: f.value ? void 0 : "button",
570
570
  class: G(["wx-faq-row", { "is-current": m.value === i.id }]),
571
571
  onClick: (M) => f.value ? void 0 : I(i.id)
572
572
  }, {
573
573
  default: d(() => [
574
- j("span", nt, _(i.question), 1),
575
- j("span", ot, [
576
- i.published ? me(i) ? (v(), b(t(Ue), {
574
+ j("span", ot, _(i.question), 1),
575
+ j("span", st, [
576
+ i.published ? me(i) ? (v(), b(t(Ee), {
577
577
  key: 1,
578
578
  content: t(n)("question.seen-nowhere")
579
579
  }, {
580
580
  default: d(() => [
581
- y(t(O), {
581
+ y(t(K), {
582
582
  type: "warning",
583
583
  dot: "",
584
584
  size: "sm"
@@ -590,7 +590,7 @@ const et = /* @__PURE__ */ ae({
590
590
  })
591
591
  ]),
592
592
  _: 1
593
- }, 8, ["content"])) : L("", !0) : (v(), b(t(O), {
593
+ }, 8, ["content"])) : L("", !0) : (v(), b(t(K), {
594
594
  key: 0,
595
595
  dot: "",
596
596
  size: "sm"
@@ -600,7 +600,7 @@ const et = /* @__PURE__ */ ae({
600
600
  ]),
601
601
  _: 1
602
602
  })),
603
- (v(!0), A(K, null, Le(i.categories, (M) => (v(), b(t(O), {
603
+ (v(!0), A(H, null, Qe(i.categories, (M) => (v(), b(t(K), {
604
604
  key: M.id,
605
605
  size: "sm",
606
606
  round: ""
@@ -610,21 +610,21 @@ const et = /* @__PURE__ */ ae({
610
610
  ]),
611
611
  _: 2
612
612
  }, 1024))), 128)),
613
- i.locales.length > 0 ? (v(), A("span", st, _(i.locales.join(" · ")), 1)) : L("", !0)
613
+ i.locales.length > 0 ? (v(), A("span", it, _(i.locales.join(" · ")), 1)) : L("", !0)
614
614
  ])
615
615
  ]),
616
616
  _: 2
617
617
  }, 1032, ["type", "class", "onClick"]))
618
618
  ]),
619
619
  actions: d(({ item: i }) => [
620
- y(t(Te), {
620
+ y(t(We), {
621
621
  actions: P(i),
622
622
  label: i.question
623
623
  }, null, 8, ["actions", "label"])
624
624
  ]),
625
625
  _: 1
626
626
  }, 8, ["modelValue", "class", "handle", "item-label", "disabled"]),
627
- g.value && !f.value ? (v(), b(t(Ee), {
627
+ g.value && !f.value ? (v(), b(t(De), {
628
628
  key: 0,
629
629
  class: "wx-faq__note",
630
630
  type: "info",
@@ -633,7 +633,7 @@ const et = /* @__PURE__ */ ae({
633
633
  ], 64))
634
634
  ]),
635
635
  detail: d(({ inline: i, back: M }) => [
636
- m.value !== null ? (v(), b(tt, {
636
+ m.value !== null ? (v(), b(at, {
637
637
  id: m.value === "new" ? null : m.value,
638
638
  key: String(m.value),
639
639
  category: T.value,
@@ -659,8 +659,8 @@ const et = /* @__PURE__ */ ae({
659
659
  _: 1
660
660
  }, 8, ["view", "title", "views"]));
661
661
  }
662
- }), lt = /* @__PURE__ */ oe(it, [["__scopeId", "data-v-6b3bda78"]]);
663
- function rt(l = "/faq") {
662
+ }), rt = /* @__PURE__ */ oe(lt, [["__scopeId", "data-v-6b3bda78"]]);
663
+ function ut(l = "/faq") {
664
664
  return {
665
665
  api: "faq/categories",
666
666
  path: `${l}/categories`,
@@ -687,7 +687,7 @@ function rt(l = "/faq") {
687
687
  }
688
688
  };
689
689
  }
690
- function ft(l = {}) {
690
+ function qt(l = {}) {
691
691
  const r = l.path ?? "/faq";
692
692
  return [
693
693
  {
@@ -695,33 +695,33 @@ function ft(l = {}) {
695
695
  path: r,
696
696
  // One route: the open question is in the address (`?question=`), beside the list it was
697
697
  // opened from, so a link to it is a link to both.
698
- routes: [{ path: r, name: "webx.faq", component: lt, props: { base: r } }],
698
+ routes: [{ path: r, name: "webx.faq", component: rt, props: { base: r } }],
699
699
  /*
700
700
  * The one node of `faq.form` only this module can draw. A field in the screen's eyes — it
701
701
  * gets the label and the help of its node — but without a name, so nothing is bound.
702
702
  */
703
703
  types: {
704
- "wx-faq-anchor": { component: Ze, kind: "field" }
704
+ "wx-faq-anchor": { component: et, kind: "field" }
705
705
  }
706
706
  },
707
707
  {
708
708
  id: "faq-categories",
709
709
  path: `${r}/categories`,
710
- routes: We(rt(r))
710
+ routes: $e(ut(r))
711
711
  }
712
712
  ];
713
713
  }
714
714
  export {
715
715
  Z as FAQ_API,
716
- Ze as WxFaqAnchor,
717
- tt as WxFaqQuestionPane,
718
- lt as WxFaqQuestionsPage,
716
+ et as WxFaqAnchor,
717
+ at as WxFaqQuestionPane,
718
+ rt as WxFaqQuestionsPage,
719
719
  he as createFaqApi,
720
- ft as faq,
721
- rt as faqCategoriesOptions,
722
- Ge as faqMessages,
723
- Je as provideQuestionEditor,
720
+ qt as faq,
721
+ ut as faqCategoriesOptions,
722
+ Xe as faqMessages,
723
+ je as provideQuestionEditor,
724
724
  qe as questionEditorKey,
725
- je as useQuestionEditor
725
+ Ge as useQuestionEditor
726
726
  };
727
727
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/AnchorField.vue","../src/api.ts","../src/QuestionPane.vue","../src/QuestionsPage.vue","../src/module.ts"],"sourcesContent":["import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { QuestionRow } from './types'\n\n/**\n * What the form knows and the nodes of `faq.form` do not: the question being edited.\n *\n * The anchor is not a value of the screen — nobody writes it, it is made once on the server and\n * never changes (decision 10) — so the node that shows it (`wx-faq-anchor`) reads the record.\n */\nexport interface QuestionEditorContext {\n /** The question as the server last answered it; `null` while it is new and not yet saved. */\n question: Ref<QuestionRow | null>\n}\n\nexport const questionEditorKey: InjectionKey<QuestionEditorContext> = Symbol('wx-faq-question')\n\nexport function provideQuestionEditor(editor: QuestionEditorContext): void {\n provide(questionEditorKey, editor)\n}\n\n/** The form above this node, or `null` outside one — a demo or a test draws the empty state. */\nexport function useQuestionEditor(): QuestionEditorContext | null {\n return inject(questionEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-faq::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const faqMessages: Record<string, Messages> = {\n module: {\n group: 'FAQ',\n questions: 'Questions',\n categories: 'Categories',\n },\n question: {\n new: 'New question',\n untitled: 'Untitled',\n search: 'Search the questions',\n empty: 'No questions yet.',\n 'empty-help': 'A question reaches the site in a FAQ block, on any page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n bin: 'Bin',\n 'filter-category': 'Category',\n 'any-category': 'Any category',\n 'not-published': 'Not published',\n 'seen-nowhere': 'Not seen in any language: it needs a question and an answer in one.',\n choose: 'Choose a question',\n 'choose-help': 'Or write a new one: the first line of the list.',\n save: 'Save',\n saved: 'The question is saved.',\n 'save-failed': 'The question was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete “:title”?',\n 'delete-text':\n 'It goes to the bin and leaves every block on the site. Restored, it comes back with the same link.',\n deleted: 'The question is in the bin.',\n restore: 'Restore',\n restored: 'The question is back.',\n 'copy-link': 'Copy the link',\n 'link-copied': 'The link is on the clipboard.',\n 'anchor-later': 'Made from the question when it is first saved.',\n 'reorder-failed': 'The new order was not saved.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you wrote in this question is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n },\n screen: {\n question: 'Question',\n answer: 'Answer',\n 'answer-help':\n 'A question is shown in a language only when it has both the question and the answer in it.',\n settings: 'Settings',\n published: 'Published',\n 'published-help':\n 'On the site in every block that shows its categories, from the moment it is saved.',\n categories: 'Categories',\n 'categories-help': 'What a block picks the question by, and the buttons of its filter.',\n anchor: 'Link',\n 'anchor-help': 'Made once from the question and never changed, so links to it keep working.',\n },\n category: {\n 'field-title': 'Title',\n visible: 'Shown on the site',\n 'visible-help':\n 'Hidden, it drops out of every filter; its questions stay in the blocks that show them.',\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help':\n 'A category groups questions: a block shows the ones you pick, and its filter has a button for each.',\n order: 'Drag to change the order of the filter buttons on the site.',\n hidden: 'Hidden from the site',\n questions: 'Questions: :count',\n 'show-questions': 'Show its questions',\n 'delete-blocked': 'This category still holds questions. Move them first.',\n 'delete-text': 'It goes to the bin and its button leaves every filter. Its questions stay.',\n deleted: 'The category is in the bin.',\n saved: 'The category is saved.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { faqMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useFaqMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-faq', faqMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { toast, WxButton, WxIcon, WxText } from '@webx-ui/core'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useQuestionEditor } from './editor'\nimport { useFaqMessages } from './i18n'\n\n/**\n * The anchor of a question: shown, never written (decision 10).\n *\n * Registered as a field only so that the screen draws it the way it draws a field — the label and\n * the help of its node around it. It has no name, so there is no value to bind, and it reads the\n * anchor off the question the form is editing.\n *\n * What is copied is the fragment, `#anchor`, and not an address: a question has no page of its\n * own, and the page it is linked on is whichever page the editor put the block on.\n */\ndefineOptions({ name: 'WxFaqAnchor' })\n\nconst editor = useQuestionEditor()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n\nconst anchor = computed(() => editor?.question.value?.anchor ?? '')\nconst fragment = computed(() => (anchor.value === '' ? '' : `#${anchor.value}`))\n\nasync function copy(): Promise<void> {\n try {\n await navigator.clipboard.writeText(fragment.value)\n toast.success(t('question.link-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the anchor is on screen.\n }\n}\n</script>\n\n<template>\n <div class=\"wx-faq-anchor\">\n <template v-if=\"fragment !== ''\">\n <wx-text class=\"wx-faq-anchor__value\" mono truncate>{{ fragment }}</wx-text>\n <wx-button variant=\"outline\" size=\"sm\" @click=\"copy\">\n <template #icon><wx-icon name=\"copy\" /></template>\n {{ t('question.copy-link') }}\n </wx-button>\n </template>\n\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('question.anchor-later') }}</wx-text>\n </div>\n</template>\n\n<style scoped>\n.wx-faq-anchor {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-faq-anchor__value {\n flex: 1 1 auto;\n min-width: 0;\n}\n</style>\n","import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { QuestionDetail, QuestionQuery, QuestionRow, QuestionsList } from './types'\n\nexport interface FaqApi {\n /** Every question, with the categories the list can be narrowed to. */\n list(query?: QuestionQuery): Promise<QuestionsList>\n get(id: number): Promise<QuestionDetail>\n /**\n * A new question out of the values of its form, in one request: the form is filled in first\n * and the record made on the first save, so nobody leaves an empty question behind.\n */\n create(values: ScreenModel): Promise<QuestionDetail>\n /** Refused with a 422 under the name of the field. */\n save(id: number, values: ScreenModel): Promise<QuestionDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<QuestionRow>\n /** The order on screen: of the whole list, or — with `category` — of that category alone. */\n reorder(ids: number[], category?: number | null): Promise<void>\n}\n\n/** The path of the list under the panel's API, and of its order (`CategoryRoutes::items()`). */\nexport const FAQ_API = 'faq/questions'\n\n/**\n * Everything under `/faq/questions`, below the panel's API path — except the categories, which\n * are the panel's shared ones and are asked for with `createCategoriesApi(admin, 'faq/categories')`.\n */\nexport function createFaqApi(admin: AdminContext): FaqApi {\n const base = `${admin.apiPath}/${FAQ_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `category=` would ask for the questions of no category.\n if (query.search) search.set('search', query.search)\n if (query.category != null) search.set('category', String(query.category))\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<QuestionsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: QuestionDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: QuestionDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: QuestionDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: QuestionRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids, category = null) => reorderItems(admin, FAQ_API, ids, category),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createFaqApi } from './api'\nimport { provideQuestionEditor } from './editor'\nimport { useFaqMessages } from './i18n'\nimport type { QuestionDetail, QuestionRow } from './types'\n\n/**\n * One question: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `faq.form` with a head over it and one button under it. One explicit\n * save, and not the autosave of an article: a question has no draft (decision 12), so what is\n * saved is on the site at once — the button is the moment somebody decides it is ready.\n *\n * A new question is a form first and a record on the first save: an empty row made by pressing\n * \"New question\" and walking away would be a question with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxFaqQuestionPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — a question not written yet. */\n id: number | null\n /** The category the list is narrowed to: a new question starts in it. */\n category?: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { category: null, inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The question was written; the list redraws its row. */\n saved: [row: QuestionRow]\n /** The first save of a new question: it is a record now, with an id to be opened by. */\n created: [row: QuestionRow]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createFaqApi(context)\nconst locales = useLocales()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst question = ref<QuestionRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('faq.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The question follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.question as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('question.new') : (question.value?.question ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('question.untitled')\n})\n\nprovideQuestionEditor({ question })\n\nfunction take(detail: QuestionDetail): void {\n question.value = detail.question\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for a question with nothing in it yet.\n const blank: ScreenModel = {\n question: {},\n answer: {},\n published: false,\n categories: props.category === null ? [] : [props.category],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('question.saved'))\n if (made) emit('created', detail.question)\n else emit('saved', detail.question)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('question.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const row = question.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('question.delete-title', { title: row.question }),\n message: t('question.delete-text'),\n confirmText: t('question.delete'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('question.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('question.leave-title'),\n message: t('question.leave-text'),\n confirmText: t('question.leave'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another question is an update of it rather than a\n * way out — and so is every keystroke in the search above the list, which goes into the address\n * too. Only a change of the question asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.question === from.query.question ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the question. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && question.value !== null\n ? [\n {\n key: 'delete',\n label: t('question.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-faq-question\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-faq-question__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('question.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"question && !question.published\" #title-after>\n <wx-badge dot>{{ t('question.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"faq.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('question.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-faq-question {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-faq-question__ghost {\n max-width: 480px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createFaqApi, FAQ_API } from './api'\nimport { useFaqMessages } from './i18n'\nimport QuestionPane from './QuestionPane.vue'\nimport type { QuestionCategoryRef, QuestionRow } from './types'\n\n/**\n * The questions: the whole FAQ on the left, the one being written on the right (§4.5).\n *\n * The list and the form side by side rather than a page per question, because a question is\n * written in a minute and the next one is usually the one under it: a round trip through a\n * page of its own for every question is most of the time spent. On a phone the form is the\n * whole screen, in the drawer `WxListDetail` raises, and draws its own way back.\n *\n * The order is decision 4, as with services: without a filter a drag moves the common order;\n * narrowed to one category, the category's own; a search or the bin shows a selection, so the\n * grips go away and the line under the list says why.\n *\n * Everything the screen is looking at lives in the address — the view, the category, the\n * search and the open question — so a link to a question is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/faq' })\n\nconst context = useAdmin()\nconst api = createFaqApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<QuestionRow[]>([])\nconst categories = ref<QuestionCategoryRef[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('faq.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'faq')?.title ??\n t('module.questions'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // A question from the list is not a question from the bin: the form closes with the view.\n delete query.question\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst category = computed<number | null>(() => {\n const raw = route.query.category\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open question: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.question\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"a question is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('question.all') },\n { value: 'trashed', label: t('question.bin'), icon: 'trash' },\n])\n\nconst order = useItemOrder(FAQ_API, () => ({\n q: q.value,\n category: category.value,\n filtered: inBin.value,\n}))\n\n/* Dragged only by somebody who may write, and only in a list that is a whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('question.empty-search')\n\n return inBin.value ? t('question.empty-bin') : t('question.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n search: q.value,\n category: inBin.value ? null : category.value,\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n categories.value = answer.filters.categories\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, category.value, q.value] as const,\n () => void load(),\n)\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(value: unknown): void {\n void router.replace({\n query: { ...route.query, category: typeof value === 'number' ? String(value) : undefined },\n })\n}\n\n/* `replace` and not `push`: going through twenty questions is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, question: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.question\n void router.replace({ query })\n}\n\n/** A new question was saved: it is a record now, and the list has it. */\nfunction created(row: QuestionRow): void {\n choose(row.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole — the list or the category's, whichever this is. The\n * list is already in its new order (`WxSortableList` reorders the model before it says so), so a\n * failure has to put it back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('question.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: QuestionRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('question.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'copy',\n icon: 'copy',\n label: t('question.copy-link'),\n run: () => void copyLink(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('question.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: QuestionRow): Promise<void> {\n const agreed = await confirm({\n title: t('question.delete-title', { title: row.question }),\n message: t('question.delete-text'),\n confirmText: t('question.delete'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('question.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: QuestionRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('question.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function copyLink(row: QuestionRow): Promise<void> {\n try {\n await navigator.clipboard.writeText(`#${row.anchor}`)\n toast.success(t('question.link-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts.\n }\n}\n\n/* Published and yet shown in no language: the one state the list has to point out (decision 9). */\nfunction unseen(row: QuestionRow): boolean {\n return row.published && row.locales.length === 0\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-faq\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-faq__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.questions')\"\n >\n <template #list>\n <div class=\"wx-faq__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-faq__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('question.search')\"\n :aria-label=\"t('question.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!-- Always in sight rather than behind a funnel: it is the one filter that decides\n which order a drag writes, and the list says so right under it. -->\n <wx-select\n v-if=\"!inBin\"\n :model-value=\"category\"\n :options=\"categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('question.any-category')\"\n :aria-label=\"t('question.filter-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow\"\n />\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new question is written\n where it will stand, beside the ones it joins — and the form opens the same way any\n other question's does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-faq__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('question.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-faq__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('question.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a question, and a row that both\n opens and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-faq__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: QuestionRow) => item.question\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-faq-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <span class=\"wx-faq-row__question\">{{ item.question }}</span>\n\n <span class=\"wx-faq-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('question.not-published') }}\n </wx-badge>\n <wx-tooltip v-else-if=\"unseen(item)\" :content=\"t('question.seen-nowhere')\">\n <wx-badge type=\"warning\" dot size=\"sm\">\n {{ t('question.not-published') }}\n </wx-badge>\n </wx-tooltip>\n\n <wx-badge v-for=\"chip in item.categories\" :key=\"chip.id\" size=\"sm\" round>\n {{ chip.title }}\n </wx-badge>\n\n <span v-if=\"item.locales.length > 0\" class=\"wx-faq-row__locales\">\n {{ item.locales.join(' · ') }}\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.question\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-faq__note\"\n type=\"info\"\n :description=\"order.hint.value\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <question-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :category=\"category\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('question.choose')\"\n :description=\"canManage ? t('question.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-faq > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-faq__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-faq__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n/* The select is a fragment (it carries its dropdown beside it), so our scope attribute never\n reaches it: styled from our own wrapper (CLAUDE.md §4). */\n.wx-faq__bar > :deep(.wx-select) {\n flex: 1 1 140px;\n min-width: 0;\n}\n\n.wx-faq__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-faq__new:hover,\n.wx-faq__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-faq__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-faq__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-faq__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-faq__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-faq__list :deep(.wx-sortable-list__row:hover),\n.wx-faq__list :deep(.wx-sortable-list__row:has(.is-current)) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-faq__note {\n margin: var(--wx-space-8);\n}\n\n.wx-faq-row {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--wx-space-4);\n /* The row is the target: a short question should not leave half the line dead — and a\n button, unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-faq-row {\n cursor: pointer;\n}\n\n.wx-faq-row.is-current .wx-faq-row__question {\n color: var(--wx-color-primary);\n}\n\n/* Two lines, then an ellipsis: a question is a sentence, and one line of it is rarely enough to\n tell two apart. */\n.wx-faq-row__question {\n display: -webkit-box;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow-wrap: anywhere;\n}\n\n.wx-faq-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-faq-row__facts:empty {\n display: none;\n}\n\n.wx-faq-row__locales {\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n text-transform: uppercase;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport AnchorField from './AnchorField.vue'\nimport QuestionsPage from './QuestionsPage.vue'\n\nexport interface FaqOptions {\n /** Where the questions live inside the panel. The categories sit under it. */\n path?: string\n}\n\n/**\n * The categories of the FAQ as the panel's shared category screens see them. Exported so that a\n * panel mounting the screens somewhere of its own does not have to repeat the words.\n *\n * No words about addresses: a category here has none (decision 2) — it is a button of the filter\n * on the site, and the shared list leaves the address line out when the server says so.\n */\nexport function faqCategoriesOptions(path = '/faq'): CategoriesOptions {\n return {\n api: 'faq/categories',\n path: `${path}/categories`,\n name: 'webx.faq.categories',\n module: 'faq-categories',\n screen: 'faq.category-form',\n manage: 'faq.categories.manage',\n count: 'questions_count',\n // The list narrowed to one category is also where its own order is dragged.\n items: (id) => ({ path, query: { category: String(id) } }),\n words: {\n new: 'webx-faq::category.new',\n empty: 'webx-faq::category.empty',\n 'empty-help': 'webx-faq::category.empty-help',\n order: 'webx-faq::category.order',\n hidden: 'webx-faq::category.hidden',\n count: 'webx-faq::category.questions',\n 'show-items': 'webx-faq::category.show-questions',\n 'delete-blocked': 'webx-faq::category.delete-blocked',\n 'delete-text': 'webx-faq::category.delete-text',\n deleted: 'webx-faq::category.deleted',\n saved: 'webx-faq::category.saved',\n 'field-title': 'webx-faq::category.field-title',\n },\n }\n}\n\n/**\n * The FAQ as sections of the panel: questions, and their categories (§4.5).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * both in the `faq` group, which is what draws them under one heading. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function faq(options: FaqOptions = {}): AdminModule[] {\n const path = options.path ?? '/faq'\n\n return [\n {\n id: 'faq',\n path,\n // One route: the open question is in the address (`?question=`), beside the list it was\n // opened from, so a link to it is a link to both.\n routes: [{ path, name: 'webx.faq', component: QuestionsPage, props: { base: path } }],\n /*\n * The one node of `faq.form` only this module can draw. A field in the screen's eyes — it\n * gets the label and the help of its node — but without a name, so nothing is bound.\n */\n types: {\n 'wx-faq-anchor': { component: AnchorField, kind: 'field' },\n },\n },\n {\n id: 'faq-categories',\n path: `${path}/categories`,\n routes: categoryRoutes(faqCategoriesOptions(path)),\n },\n ]\n}\n"],"names":["questionEditorKey","provideQuestionEditor","editor","provide","useQuestionEditor","inject","faqMessages","seeded","useFaqMessages","i18n","useAdmin","t","useTranslate","anchor","computed","fragment","copy","toast","_openBlock","_createElementBlock","_hoisted_1","_Fragment","_createVNode","_unref","WxText","WxButton","WxIcon","_createTextVNode","_createBlock","FAQ_API","createFaqApi","admin","base","data","body","query","search","id","values","ids","category","reorderItems","props","__props","emit","__emit","context","api","locales","useLocales","message","useErrorText","question","ref","snapshot","loading","saving","errors","canManage","current","dirty","state","title","written","fallback","localizedValue","take","detail","blank","load","error","save","made","remove","row","confirm","leaveGuard","event","onKeydown","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","WxSkeleton","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","route","useRoute","router","useRouter","rows","categories","module","view","value","inBin","raw","q","typed","selected","open","close","views","order","useItemOrder","sortable","emptyText","answer","watch","pause","narrow","choose","created","reorder","actionsFor","restore","copyLink","removed","unseen","WxListScreen","WxCard","WxListDetail","_createElementVNode","WxInput","WxSelect","item","_normalizeClass","WxEmpty","WxSortableList","_withCtx","_resolveDynamicComponent","_hoisted_2","_toDisplayString","_hoisted_3","WxTooltip","_renderList","chip","_hoisted_4","WxRowMenu","WxAlert","inline","back","QuestionPane","faqCategoriesOptions","path","faq","options","QuestionsPage","AnchorField","categoryRoutes"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/editor.ts","../src/messages.ts","../src/i18n.ts","../src/AnchorField.vue","../src/api.ts","../src/QuestionPane.vue","../src/QuestionsPage.vue","../src/module.ts"],"sourcesContent":["import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { QuestionRow } from './types'\n\n/**\n * What the form knows and the nodes of `faq.form` do not: the question being edited.\n *\n * The anchor is not a value of the screen — nobody writes it, it is made once on the server and\n * never changes (decision 10) — so the node that shows it (`wx-faq-anchor`) reads the record.\n */\nexport interface QuestionEditorContext {\n /** The question as the server last answered it; `null` while it is new and not yet saved. */\n question: Ref<QuestionRow | null>\n}\n\nexport const questionEditorKey: InjectionKey<QuestionEditorContext> = Symbol('wx-faq-question')\n\nexport function provideQuestionEditor(editor: QuestionEditorContext): void {\n provide(questionEditorKey, editor)\n}\n\n/** The form above this node, or `null` outside one — a demo or a test draws the empty state. */\nexport function useQuestionEditor(): QuestionEditorContext | null {\n return inject(questionEditorKey, null)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-faq::` and overrides these;\n * `messages.test.ts` keeps the two sets of keys equal.\n */\nexport const faqMessages: Record<string, Messages> = {\n module: {\n group: 'FAQ',\n questions: 'Questions',\n categories: 'Categories',\n },\n question: {\n new: 'New question',\n untitled: 'Untitled',\n search: 'Search the questions',\n empty: 'No questions yet.',\n 'empty-help': 'A question reaches the site in a FAQ block, on any page.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n all: 'All',\n bin: 'Bin',\n 'filter-category': 'Category',\n 'any-category': 'Any category',\n 'not-published': 'Not published',\n 'seen-nowhere': 'Not seen in any language: it needs a question and an answer in one.',\n choose: 'Choose a question',\n 'choose-help': 'Or write a new one: the first line of the list.',\n save: 'Save',\n saved: 'The question is saved.',\n 'save-failed': 'The question was not saved.',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete “:title”?',\n 'delete-text':\n 'It goes to the bin and leaves every block on the site. Restored, it comes back with the same link.',\n deleted: 'The question is in the bin.',\n restore: 'Restore',\n restored: 'The question is back.',\n 'copy-link': 'Copy the link',\n 'link-copied': 'The link is on the clipboard.',\n 'anchor-later': 'Made from the question when it is first saved.',\n 'reorder-failed': 'The new order was not saved.',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'What you wrote in this question is not on the server.',\n leave: 'Leave',\n back: 'Back to the list',\n },\n screen: {\n question: 'Question',\n answer: 'Answer',\n 'answer-help':\n 'A question is shown in a language only when it has both the question and the answer in it.',\n settings: 'Settings',\n published: 'Published',\n 'published-help':\n 'On the site in every block that shows its categories, from the moment it is saved.',\n categories: 'Categories',\n 'categories-help': 'What a block picks the question by, and the buttons of its filter.',\n anchor: 'Link',\n 'anchor-help': 'Made once from the question and never changed, so links to it keep working.',\n },\n category: {\n 'field-title': 'Title',\n visible: 'Shown on the site',\n 'visible-help':\n 'Hidden, it drops out of every filter; its questions stay in the blocks that show them.',\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help':\n 'A category groups questions: a block shows the ones you pick, and its filter has a button for each.',\n order: 'Drag to change the order of the filter buttons on the site.',\n hidden: 'Hidden from the site',\n questions: 'Questions: :count',\n 'show-questions': 'Show its questions',\n 'delete-blocked': 'This category still holds questions. Move them first.',\n 'delete-text': 'It goes to the bin and its button leaves every filter. Its questions stay.',\n deleted: 'The category is in the bin.',\n saved: 'The category is saved.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { faqMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useFaqMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-faq', faqMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { toast, WxButton, WxIcon, WxText } from '@webx-ui/core'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useQuestionEditor } from './editor'\nimport { useFaqMessages } from './i18n'\n\n/**\n * The anchor of a question: shown, never written (decision 10).\n *\n * Registered as a field only so that the screen draws it the way it draws a field — the label and\n * the help of its node around it. It has no name, so there is no value to bind, and it reads the\n * anchor off the question the form is editing.\n *\n * What is copied is the fragment, `#anchor`, and not an address: a question has no page of its\n * own, and the page it is linked on is whichever page the editor put the block on.\n */\ndefineOptions({ name: 'WxFaqAnchor' })\n\nconst editor = useQuestionEditor()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n\nconst anchor = computed(() => editor?.question.value?.anchor ?? '')\nconst fragment = computed(() => (anchor.value === '' ? '' : `#${anchor.value}`))\n\nasync function copy(): Promise<void> {\n try {\n await navigator.clipboard.writeText(fragment.value)\n toast.success(t('question.link-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts; the anchor is on screen.\n }\n}\n</script>\n\n<template>\n <div class=\"wx-faq-anchor\">\n <template v-if=\"fragment !== ''\">\n <wx-text class=\"wx-faq-anchor__value\" mono truncate>{{ fragment }}</wx-text>\n <wx-button variant=\"outline\" size=\"sm\" @click=\"copy\">\n <template #icon><wx-icon name=\"copy\" /></template>\n {{ t('question.copy-link') }}\n </wx-button>\n </template>\n\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('question.anchor-later') }}</wx-text>\n </div>\n</template>\n\n<style scoped>\n.wx-faq-anchor {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-8);\n min-width: 0;\n}\n\n.wx-faq-anchor__value {\n flex: 1 1 auto;\n min-width: 0;\n}\n</style>\n","import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport type { QuestionDetail, QuestionQuery, QuestionRow, QuestionsList } from './types'\n\nexport interface FaqApi {\n /** Every question, with the categories the list can be narrowed to. */\n list(query?: QuestionQuery): Promise<QuestionsList>\n get(id: number): Promise<QuestionDetail>\n /**\n * A new question out of the values of its form, in one request: the form is filled in first\n * and the record made on the first save, so nobody leaves an empty question behind.\n */\n create(values: ScreenModel): Promise<QuestionDetail>\n /** Refused with a 422 under the name of the field. */\n save(id: number, values: ScreenModel): Promise<QuestionDetail>\n remove(id: number): Promise<void>\n restore(id: number): Promise<QuestionRow>\n /** The order on screen: of the whole list, or — with `category` — of that category alone. */\n reorder(ids: number[], category?: number | null): Promise<void>\n}\n\n/** The path of the list under the panel's API, and of its order (`CategoryRoutes::items()`). */\nexport const FAQ_API = 'faq/questions'\n\n/**\n * Everything under `/faq/questions`, below the panel's API path — except the categories, which\n * are the panel's shared ones and are asked for with `createCategoriesApi(admin, 'faq/categories')`.\n */\nexport function createFaqApi(admin: AdminContext): FaqApi {\n const base = `${admin.apiPath}/${FAQ_API}`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // Only what was asked for: `category=` would ask for the questions of no category.\n if (query.search) search.set('search', query.search)\n if (query.category != null) search.set('category', String(query.category))\n if (query.trashed) search.set('trashed', '1')\n\n return admin.http.get<QuestionsList>(search.size > 0 ? `${base}?${search}` : base)\n },\n get: (id) => admin.http.get<{ data: QuestionDetail }>(`${base}/${id}`).then(data),\n create: (values) => admin.http.post<{ data: QuestionDetail }>(base, { values }).then(data),\n save: (id, values) =>\n admin.http.put<{ data: QuestionDetail }>(`${base}/${id}`, { values }).then(data),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => admin.http.post<{ data: QuestionRow }>(`${base}/${id}/restore`, {}).then(data),\n reorder: (ids, category = null) => reorderItems(admin, FAQ_API, ids, category),\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createFaqApi } from './api'\nimport { provideQuestionEditor } from './editor'\nimport { useFaqMessages } from './i18n'\nimport type { QuestionDetail, QuestionRow } from './types'\n\n/**\n * One question: the pane beside the list, and the whole screen on a phone.\n *\n * The described screen `faq.form` with a head over it and one button under it. One explicit\n * save, and not the autosave of an article: a question has no draft (decision 12), so what is\n * saved is on the site at once — the button is the moment somebody decides it is ready.\n *\n * A new question is a form first and a record on the first save: an empty row made by pressing\n * \"New question\" and walking away would be a question with nothing in it on the list for ever.\n *\n * The way back is drawn here and not by the pane around it: the drawer a narrow screen opens\n * this in has no close of its own on purpose (CLAUDE.md §4).\n */\ndefineOptions({ name: 'WxFaqQuestionPane' })\n\nconst props = withDefaults(\n defineProps<{\n /** `null` — a question not written yet. */\n id: number | null\n /** The category the list is narrowed to: a new question starts in it. */\n category?: number | null\n /** False in the drawer a narrow screen opens this in. */\n inline?: boolean\n }>(),\n { category: null, inline: true },\n)\n\nconst emit = defineEmits<{\n back: []\n /** The question was written; the list redraws its row. */\n saved: [row: QuestionRow]\n /** The first save of a new question: it is a record now, with an id to be opened by. */\n created: [row: QuestionRow]\n removed: [id: number]\n}>()\n\nconst context = useAdmin()\nconst api = createFaqApi(context)\nconst locales = useLocales()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst question = ref<QuestionRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\n\nconst loading = ref(props.id !== null)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => context.can('faq.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || props.id === null ? 'unsaved' : 'saved'\n})\n\n/** The question follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.question as LocalizedValue | string | null | undefined\n const fallback = props.id === null ? t('question.new') : (question.value?.question ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('question.untitled')\n})\n\nprovideQuestionEditor({ question })\n\nfunction take(detail: QuestionDetail): void {\n question.value = detail.question\n values.value = detail.values\n snapshot.value = JSON.stringify(detail.values)\n}\n\nif (props.id === null) {\n // What the server would answer for a question with nothing in it yet.\n const blank: ScreenModel = {\n question: {},\n answer: {},\n published: false,\n categories: props.category === null ? [] : [props.category],\n }\n\n values.value = blank\n snapshot.value = JSON.stringify(blank)\n}\n\nasync function load(): Promise<void> {\n if (props.id === null) return\n\n loading.value = true\n\n try {\n take(await api.get(props.id))\n } catch (error) {\n toast.danger(message(error))\n emit('back')\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = props.id === null\n const detail = made\n ? await api.create(values.value)\n : await api.save(props.id as number, values.value)\n\n take(detail)\n toast.success(t('question.saved'))\n if (made) emit('created', detail.question)\n else emit('saved', detail.question)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('question.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const row = question.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('question.delete-title', { title: row.question }),\n message: t('question.delete-text'),\n confirmText: t('question.delete'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('question.deleted'))\n // Nothing left to protect: the record is in the bin.\n snapshot.value = current.value\n emit('removed', row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || props.id === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nasync function mayLeave(): Promise<boolean> {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('question.leave-title'),\n message: t('question.leave-text'),\n confirmText: t('question.leave'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n}\n\n/*\n * The list and this form are one route, so another question is an update of it rather than a\n * way out — and so is every keystroke in the search above the list, which goes into the address\n * too. Only a change of the question asks.\n */\nonBeforeRouteUpdate(async (to: RouteLocationNormalized, from: RouteLocationNormalized) =>\n to.query.question === from.query.question ? true : await mayLeave(),\n)\n\nonBeforeRouteLeave(mayLeave)\n\n/* What leads away from the question. On a narrow pane the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && question.value !== null\n ? [\n {\n key: 'delete',\n label: t('question.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-faq-question\" @keydown=\"onKeydown\">\n <wx-skeleton v-if=\"loading\" class=\"wx-faq-question__ghost\" title :rows=\"6\" />\n\n <template v-else>\n <wx-screen-head\n :level=\"3\"\n :back=\"!props.inline\"\n :back-label=\"t('question.back')\"\n :title=\"title\"\n :actions=\"actions\"\n :collapse-below=\"480\"\n @back=\"emit('back')\"\n >\n <template v-if=\"question && !question.published\" #title-after>\n <wx-badge dot>{{ t('question.not-published') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-screen\n v-model=\"values\"\n name=\"faq.form\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"!dirty && props.id !== null\"\n @click=\"save\"\n >\n {{ t('question.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-faq-question {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-width: 0;\n padding: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-faq-question__ghost {\n max-width: 480px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n confirm,\n toast,\n WxAlert,\n WxBadge,\n WxCard,\n WxEmpty,\n WxIcon,\n WxInput,\n WxListDetail,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useItemOrder,\n useTranslate,\n WxListScreen,\n WxRowMenu,\n type RowAction,\n} from '@webx-ui/module-admin'\nimport { createFaqApi, FAQ_API } from './api'\nimport { useFaqMessages } from './i18n'\nimport QuestionPane from './QuestionPane.vue'\nimport type { QuestionCategoryRef, QuestionRow } from './types'\n\n/**\n * The questions: the whole FAQ on the left, the one being written on the right (§4.5).\n *\n * The list and the form side by side rather than a page per question, because a question is\n * written in a minute and the next one is usually the one under it: a round trip through a\n * page of its own for every question is most of the time spent. On a phone the form is the\n * whole screen, in the drawer `WxListDetail` raises, and draws its own way back.\n *\n * The order is decision 4, as with services: without a filter a drag moves the common order;\n * narrowed to one category, the category's own; a search or the bin shows a selection, so the\n * grips go away and the line under the list says why.\n *\n * Everything the screen is looking at lives in the address — the view, the category, the\n * search and the open question — so a link to a question is a link to it in its list.\n */\nwithDefaults(defineProps<{ base?: string }>(), { base: '/faq' })\n\nconst context = useAdmin()\nconst api = createFaqApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseFaqMessages()\n\nconst t = useTranslate('webx-faq')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<QuestionRow[]>([])\nconst categories = ref<QuestionCategoryRef[]>([])\nconst loading = ref(true)\n\nconst canManage = computed(() => context.can('faq.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'faq')?.title ??\n t('module.questions'),\n)\n\nconst view = computed<TabValue>({\n get: () => (route.query.view === 'trashed' ? 'trashed' : ''),\n set: (value) => {\n const query: LocationQueryRaw = {\n ...route.query,\n view: value === '' ? undefined : String(value),\n }\n\n // A question from the list is not a question from the bin: the form closes with the view.\n delete query.question\n void router.replace({ query })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst category = computed<number | null>(() => {\n const raw = route.query.category\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\n/** The open question: a number, `'new'` for one not yet saved, or nothing. */\nconst selected = computed<number | 'new' | null>(() => {\n const raw = route.query.question\n\n if (raw === 'new') return canManage.value ? 'new' : null\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\n/*\n * Open is \"a question is chosen\", and closing is un-choosing it — so the address, the list's\n * highlight and the drawer on a phone can never disagree about it.\n */\nconst open = computed({\n get: () => selected.value !== null,\n set: (value) => {\n if (!value) close()\n },\n})\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('question.all') },\n { value: 'trashed', label: t('question.bin'), icon: 'trash' },\n])\n\nconst order = useItemOrder(FAQ_API, () => ({\n q: q.value,\n category: category.value,\n filtered: inBin.value,\n}))\n\n/* Dragged only by somebody who may write, and only in a list that is a whole order. */\nconst sortable = computed(() => canManage.value && order.sortable.value)\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('question.empty-search')\n\n return inBin.value ? t('question.empty-bin') : t('question.empty')\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n search: q.value,\n category: inBin.value ? null : category.value,\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n categories.value = answer.filters.categories\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\nwatch(\n () => [view.value, category.value, q.value] as const,\n () => void load(),\n)\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(value: unknown): void {\n void router.replace({\n query: { ...route.query, category: typeof value === 'number' ? String(value) : undefined },\n })\n}\n\n/* `replace` and not `push`: going through twenty questions is not twenty steps of history. */\nfunction choose(id: number | 'new'): void {\n void router.replace({ query: { ...route.query, question: String(id) } })\n}\n\nfunction close(): void {\n const query = { ...route.query }\n\n delete query.question\n void router.replace({ query })\n}\n\n/** A new question was saved: it is a record now, and the list has it. */\nfunction created(row: QuestionRow): void {\n choose(row.id)\n void load()\n}\n\n/**\n * The order on screen, written as a whole — the list or the category's, whichever this is. The\n * list is already in its new order (`WxSortableList` reorders the model before it says so), so a\n * failure has to put it back rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await order.move(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('question.reorder-failed')))\n await load()\n }\n}\n\nfunction actionsFor(row: QuestionRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('question.restore'),\n run: () => void restore(row),\n },\n ]\n }\n\n return [\n {\n key: 'copy',\n icon: 'copy',\n label: t('question.copy-link'),\n run: () => void copyLink(row),\n },\n {\n key: 'delete',\n icon: 'trash',\n label: t('question.delete'),\n danger: true,\n run: () => void remove(row),\n },\n ]\n}\n\nasync function remove(row: QuestionRow): Promise<void> {\n const agreed = await confirm({\n title: t('question.delete-title', { title: row.question }),\n message: t('question.delete-text'),\n confirmText: t('question.delete'),\n cancelText: t('question.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(row.id)\n toast.success(t('question.deleted'))\n removed(row.id)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/** Gone from the list; and if it was the open one, the form goes with it. */\nfunction removed(id: number): void {\n if (selected.value === id) close()\n\n void load()\n}\n\nasync function restore(row: QuestionRow): Promise<void> {\n try {\n await api.restore(row.id)\n toast.success(t('question.restored'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function copyLink(row: QuestionRow): Promise<void> {\n try {\n await navigator.clipboard.writeText(`#${row.anchor}`)\n toast.success(t('question.link-copied'))\n } catch {\n // A browser that refuses the clipboard without a gesture it trusts.\n }\n}\n\n/* Published and yet shown in no language: the one state the list has to point out (decision 9). */\nfunction unseen(row: QuestionRow): boolean {\n return row.published && row.locales.length === 0\n}\n</script>\n\n<template>\n <wx-list-screen v-model:view=\"view\" :title=\"title\" :views=\"views\" :card=\"false\">\n <wx-card class=\"wx-faq\" padding=\"none\">\n <wx-list-detail\n v-model:open=\"open\"\n class=\"wx-faq__panes\"\n :list-width=\"360\"\n :detail-min=\"460\"\n :detail-label=\"t('module.questions')\"\n >\n <template #list>\n <div class=\"wx-faq__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-faq__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('question.search')\"\n :aria-label=\"t('question.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!-- Always in sight rather than behind a funnel: it is the one filter that decides\n which order a drag writes, and the list says so right under it. -->\n <wx-select\n v-if=\"!inBin\"\n :model-value=\"category\"\n :options=\"categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('question.any-category')\"\n :aria-label=\"t('question.filter-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"narrow\"\n />\n </div>\n\n <!--\n A line of the list rather than a button in the head: the new question is written\n where it will stand, beside the ones it joins — and the form opens the same way any\n other question's does.\n -->\n <button\n v-if=\"canManage && !inBin\"\n type=\"button\"\n class=\"wx-faq__new\"\n :class=\"{ 'is-current': selected === 'new' }\"\n @click=\"choose('new')\"\n >\n <wx-icon name=\"plus\" size=\"sm\" />\n {{ t('question.new') }}\n </button>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-faq__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"!inBin && q === '' ? t('question.empty-help') : undefined\"\n />\n\n <template v-else>\n <!-- A grip and not the whole row: the row opens a question, and a row that both\n opens and drags is one where one of the two happens by accident. -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-faq__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: QuestionRow) => item.question\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'button'\"\n :type=\"inBin ? undefined : 'button'\"\n class=\"wx-faq-row\"\n :class=\"{ 'is-current': selected === item.id }\"\n @click=\"inBin ? undefined : choose(item.id)\"\n >\n <span class=\"wx-faq-row__question\">{{ item.question }}</span>\n\n <span class=\"wx-faq-row__facts\">\n <wx-badge v-if=\"!item.published\" dot size=\"sm\">\n {{ t('question.not-published') }}\n </wx-badge>\n <wx-tooltip v-else-if=\"unseen(item)\" :content=\"t('question.seen-nowhere')\">\n <wx-badge type=\"warning\" dot size=\"sm\">\n {{ t('question.not-published') }}\n </wx-badge>\n </wx-tooltip>\n\n <wx-badge v-for=\"chip in item.categories\" :key=\"chip.id\" size=\"sm\" round>\n {{ chip.title }}\n </wx-badge>\n\n <span v-if=\"item.locales.length > 0\" class=\"wx-faq-row__locales\">\n {{ item.locales.join(' · ') }}\n </span>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.question\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-faq__note\"\n type=\"info\"\n :description=\"order.hint.value\"\n />\n </template>\n </template>\n\n <template #detail=\"{ inline, back }\">\n <question-pane\n v-if=\"selected !== null\"\n :id=\"selected === 'new' ? null : selected\"\n :key=\"String(selected)\"\n :category=\"category\"\n :inline=\"inline\"\n @back=\"back\"\n @saved=\"load\"\n @created=\"created\"\n @removed=\"removed\"\n />\n </template>\n\n <template #empty>\n <wx-empty\n :title=\"t('question.choose')\"\n :description=\"canManage ? t('question.choose-help') : undefined\"\n />\n </template>\n </wx-list-detail>\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n/*\n * What scrolls here is the page, the way it does on every other list of the panel. The card's\n * corners are the screen's, and the panes paint their own background to the edge: `clip`, not\n * `hidden`, which would make this a scroll container and pin everything sticky inside to a box\n * that never moves (CLAUDE.md §4).\n */\n.wx-faq > .wx-card__body {\n display: flex;\n flex-direction: column;\n overflow: clip;\n border-radius: inherit;\n}\n</style>\n\n<style scoped>\n.wx-faq__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n padding: var(--wx-space-12) var(--wx-space-12) var(--wx-space-8);\n}\n\n.wx-faq__search {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n/* The select is a fragment (it carries its dropdown beside it), so our scope attribute never\n reaches it: styled from our own wrapper (CLAUDE.md §4). */\n.wx-faq__bar > :deep(.wx-select) {\n flex: 1 1 140px;\n min-width: 0;\n}\n\n.wx-faq__new {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n width: calc(100% - 2 * var(--wx-space-8));\n margin: 0 var(--wx-space-8) var(--wx-space-4);\n padding: var(--wx-space-8);\n border: 1px dashed var(--wx-border-default);\n border-radius: var(--wx-radius-sm);\n background: none;\n font: inherit;\n color: var(--wx-color-primary);\n text-align: start;\n cursor: pointer;\n}\n\n.wx-faq__new:hover,\n.wx-faq__new.is-current {\n background: var(--wx-bg-subtle);\n}\n\n.wx-faq__loading {\n padding: var(--wx-space-12);\n}\n\n.wx-faq__list {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-faq__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-faq__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-4);\n padding-inline: var(--wx-space-4);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-faq__list :deep(.wx-sortable-list__row:hover),\n.wx-faq__list :deep(.wx-sortable-list__row:has(.is-current)) {\n background: var(--wx-bg-subtle);\n}\n\n.wx-faq__note {\n margin: var(--wx-space-8);\n}\n\n.wx-faq-row {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--wx-space-4);\n /* The row is the target: a short question should not leave half the line dead — and a\n button, unlike a link, is only as wide as what is in it until it is told otherwise. */\n flex: 1 1 auto;\n width: 100%;\n min-width: 0;\n padding-block: var(--wx-space-8);\n padding-inline: 0;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: start;\n}\n\nbutton.wx-faq-row {\n cursor: pointer;\n}\n\n.wx-faq-row.is-current .wx-faq-row__question {\n color: var(--wx-color-primary);\n}\n\n/* Two lines, then an ellipsis: a question is a sentence, and one line of it is rarely enough to\n tell two apart. */\n.wx-faq-row__question {\n display: -webkit-box;\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow-wrap: anywhere;\n}\n\n.wx-faq-row__facts {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n.wx-faq-row__facts:empty {\n display: none;\n}\n\n.wx-faq-row__locales {\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n text-transform: uppercase;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport AnchorField from './AnchorField.vue'\nimport QuestionsPage from './QuestionsPage.vue'\n\nexport interface FaqOptions {\n /** Where the questions live inside the panel. The categories sit under it. */\n path?: string\n}\n\n/**\n * The categories of the FAQ as the panel's shared category screens see them. Exported so that a\n * panel mounting the screens somewhere of its own does not have to repeat the words.\n *\n * No words about addresses: a category here has none (decision 2) — it is a button of the filter\n * on the site, and the shared list leaves the address line out when the server says so.\n */\nexport function faqCategoriesOptions(path = '/faq'): CategoriesOptions {\n return {\n api: 'faq/categories',\n path: `${path}/categories`,\n name: 'webx.faq.categories',\n module: 'faq-categories',\n screen: 'faq.category-form',\n manage: 'faq.categories.manage',\n count: 'questions_count',\n // The list narrowed to one category is also where its own order is dragged.\n items: (id) => ({ path, query: { category: String(id) } }),\n words: {\n new: 'webx-faq::category.new',\n empty: 'webx-faq::category.empty',\n 'empty-help': 'webx-faq::category.empty-help',\n order: 'webx-faq::category.order',\n hidden: 'webx-faq::category.hidden',\n count: 'webx-faq::category.questions',\n 'show-items': 'webx-faq::category.show-questions',\n 'delete-blocked': 'webx-faq::category.delete-blocked',\n 'delete-text': 'webx-faq::category.delete-text',\n deleted: 'webx-faq::category.deleted',\n saved: 'webx-faq::category.saved',\n 'field-title': 'webx-faq::category.field-title',\n },\n }\n}\n\n/**\n * The FAQ as sections of the panel: questions, and their categories (§4.5).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * both in the `faq` group, which is what draws them under one heading. A section whose server\n * half is not installed never appears — the entry is built from the manifest.\n */\nexport function faq(options: FaqOptions = {}): AdminModule[] {\n const path = options.path ?? '/faq'\n\n return [\n {\n id: 'faq',\n path,\n // One route: the open question is in the address (`?question=`), beside the list it was\n // opened from, so a link to it is a link to both.\n routes: [{ path, name: 'webx.faq', component: QuestionsPage, props: { base: path } }],\n /*\n * The one node of `faq.form` only this module can draw. A field in the screen's eyes — it\n * gets the label and the help of its node — but without a name, so nothing is bound.\n */\n types: {\n 'wx-faq-anchor': { component: AnchorField, kind: 'field' },\n },\n },\n {\n id: 'faq-categories',\n path: `${path}/categories`,\n routes: categoryRoutes(faqCategoriesOptions(path)),\n },\n ]\n}\n"],"names":["questionEditorKey","provideQuestionEditor","editor","provide","useQuestionEditor","inject","faqMessages","seeded","useFaqMessages","i18n","useAdmin","t","useTranslate","anchor","computed","fragment","copy","toast","_openBlock","_createElementBlock","_hoisted_1","_Fragment","_createVNode","_unref","WxText","WxButton","WxIcon","_createTextVNode","_createBlock","FAQ_API","createFaqApi","admin","base","data","body","query","search","id","values","ids","category","reorderItems","props","__props","emit","__emit","context","api","locales","useLocales","message","useErrorText","question","ref","snapshot","loading","saving","errors","canManage","current","dirty","state","title","written","fallback","localizedValue","take","detail","blank","load","error","save","made","remove","row","confirm","leaveGuard","event","onKeydown","useBodyKeys","onMounted","onBeforeUnmount","mayLeave","onBeforeRouteUpdate","to","from","onBeforeRouteLeave","actions","WxSkeleton","WxScreenHead","WxBadge","WxScreen","$event","WxActionBar","WxSaveState","route","useRoute","router","useRouter","rows","categories","module","view","value","inBin","raw","q","typed","selected","open","close","views","order","useItemOrder","sortable","emptyText","answer","watch","pause","narrow","choose","created","reorder","actionsFor","restore","copyLink","removed","unseen","WxListScreen","WxCard","WxListDetail","_createElementVNode","WxInput","WxSelect","item","_normalizeClass","WxEmpty","WxSortableList","_withCtx","_resolveDynamicComponent","_hoisted_2","_toDisplayString","_hoisted_3","WxTooltip","_renderList","chip","_hoisted_4","WxRowMenu","WxAlert","inline","back","QuestionPane","faqCategoriesOptions","path","faq","options","QuestionsPage","AnchorField","categoryRoutes"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-faq-anchor[data-v-e6091b48]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-8);min-width:0}.wx-faq-anchor__value[data-v-e6091b48]{flex:1 1 auto;min-width:0}.wx-faq-question[data-v-0589ef85]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-width:0;padding:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-faq-question__ghost[data-v-0589ef85]{max-width:480px}.wx-faq>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-faq__bar[data-v-6b3bda78]{display:flex;flex-wrap:wrap;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-12) var(--wx-space-8)}.wx-faq__search[data-v-6b3bda78]{flex:1 1 180px;min-width:0}.wx-faq__bar[data-v-6b3bda78]>.wx-select{flex:1 1 140px;min-width:0}.wx-faq__new[data-v-6b3bda78]{display:flex;align-items:center;gap:var(--wx-space-8);width:calc(100% - 2 * var(--wx-space-8));margin:0 var(--wx-space-8) var(--wx-space-4);padding:var(--wx-space-8);border:1px dashed var(--wx-border-default);border-radius:var(--wx-radius-sm);background:none;font:inherit;color:var(--wx-color-primary);text-align:start;cursor:pointer}.wx-faq__new[data-v-6b3bda78]:hover,.wx-faq__new.is-current[data-v-6b3bda78]{background:var(--wx-bg-subtle)}.wx-faq__loading[data-v-6b3bda78]{padding:var(--wx-space-12)}.wx-faq__list[data-v-6b3bda78]{padding-inline:var(--wx-space-8)}.wx-faq__list.is-loading[data-v-6b3bda78]{opacity:.6}.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4);border-radius:var(--wx-radius-sm)}.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row:hover,.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row:has(.is-current){background:var(--wx-bg-subtle)}.wx-faq__note[data-v-6b3bda78]{margin:var(--wx-space-8)}.wx-faq-row[data-v-6b3bda78]{display:flex;flex-direction:column;align-items:stretch;gap:var(--wx-space-4);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start}button.wx-faq-row[data-v-6b3bda78]{cursor:pointer}.wx-faq-row.is-current .wx-faq-row__question[data-v-6b3bda78]{color:var(--wx-color-primary)}.wx-faq-row__question[data-v-6b3bda78]{display:-webkit-box;overflow:hidden;font-weight:var(--wx-font-weight-medium);-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow-wrap:anywhere}.wx-faq-row__facts[data-v-6b3bda78]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-4);min-width:0}.wx-faq-row__facts[data-v-6b3bda78]:empty{display:none}.wx-faq-row__locales[data-v-6b3bda78]{color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);text-transform:uppercase}
1
+ .wx-faq-anchor[data-v-e6091b48]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-8);min-width:0}.wx-faq-anchor__value[data-v-e6091b48]{flex:1 1 auto;min-width:0}.wx-faq-question[data-v-fe493d3f]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-width:0;padding:var(--wx-gap, var(--wx-space-16));container-type:inline-size}.wx-faq-question__ghost[data-v-fe493d3f]{max-width:480px}.wx-faq>.wx-card__body{display:flex;flex-direction:column;overflow:clip;border-radius:inherit}.wx-faq__bar[data-v-6b3bda78]{display:flex;flex-wrap:wrap;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-12) var(--wx-space-8)}.wx-faq__search[data-v-6b3bda78]{flex:1 1 180px;min-width:0}.wx-faq__bar[data-v-6b3bda78]>.wx-select{flex:1 1 140px;min-width:0}.wx-faq__new[data-v-6b3bda78]{display:flex;align-items:center;gap:var(--wx-space-8);width:calc(100% - 2 * var(--wx-space-8));margin:0 var(--wx-space-8) var(--wx-space-4);padding:var(--wx-space-8);border:1px dashed var(--wx-border-default);border-radius:var(--wx-radius-sm);background:none;font:inherit;color:var(--wx-color-primary);text-align:start;cursor:pointer}.wx-faq__new[data-v-6b3bda78]:hover,.wx-faq__new.is-current[data-v-6b3bda78]{background:var(--wx-bg-subtle)}.wx-faq__loading[data-v-6b3bda78]{padding:var(--wx-space-12)}.wx-faq__list[data-v-6b3bda78]{padding-inline:var(--wx-space-8)}.wx-faq__list.is-loading[data-v-6b3bda78]{opacity:.6}.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row{gap:var(--wx-space-4);padding-inline:var(--wx-space-4);border-radius:var(--wx-radius-sm)}.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row:hover,.wx-faq__list[data-v-6b3bda78] .wx-sortable-list__row:has(.is-current){background:var(--wx-bg-subtle)}.wx-faq__note[data-v-6b3bda78]{margin:var(--wx-space-8)}.wx-faq-row[data-v-6b3bda78]{display:flex;flex-direction:column;align-items:stretch;gap:var(--wx-space-4);flex:1 1 auto;width:100%;min-width:0;padding-block:var(--wx-space-8);padding-inline:0;border:0;background:none;font:inherit;color:inherit;text-align:start}button.wx-faq-row[data-v-6b3bda78]{cursor:pointer}.wx-faq-row.is-current .wx-faq-row__question[data-v-6b3bda78]{color:var(--wx-color-primary)}.wx-faq-row__question[data-v-6b3bda78]{display:-webkit-box;overflow:hidden;font-weight:var(--wx-font-weight-medium);-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow-wrap:anywhere}.wx-faq-row__facts[data-v-6b3bda78]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-4);min-width:0}.wx-faq-row__facts[data-v-6b3bda78]:empty{display:none}.wx-faq-row__locales[data-v-6b3bda78]{color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);text-transform:uppercase}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-faq",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "FAQ for the WebX UI admin panel: questions and answers with flat categories, ordered by hand, shown on the site by a block.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,9 +43,9 @@
43
43
  "vue-router": "^4.5.0"
44
44
  },
45
45
  "dependencies": {
46
- "@webx-ui/core": "^0.34.0",
47
- "@webx-ui/module-admin": "^0.18.1",
48
- "@webx-ui/schema": "^0.6.2"
46
+ "@webx-ui/core": "^0.34.1",
47
+ "@webx-ui/module-admin": "^0.19.0",
48
+ "@webx-ui/schema": "^0.6.4"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^24.10.1",