@eifi1/ui-kit 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -14
- package/dist/admin/admin-action-log-labels.js +9 -0
- package/dist/admin/admin-action-log-labels.js.map +1 -1
- package/dist/admin/admin-action-log.d.ts +18 -1
- package/dist/admin/admin-action-log.js +25 -16
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/admin-area-labels.d.ts +15 -0
- package/dist/admin/admin-area-labels.js +6 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +16 -1
- package/dist/admin/admin-overview.js +12 -1
- package/dist/admin/admin-overview.js.map +1 -1
- package/dist/admin/mail-test-card-labels.d.ts +3 -0
- package/dist/admin/mail-test-card-labels.js +17 -0
- package/dist/admin/mail-test-card-labels.js.map +1 -0
- package/dist/admin/mail-test-card.d.ts +75 -0
- package/dist/admin/mail-test-card.js +82 -0
- package/dist/admin/mail-test-card.js.map +1 -0
- package/dist/components/appearance-labels.d.ts +10 -0
- package/dist/components/appearance-labels.js +6 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-head.js +2 -2
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.js +17 -2
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +18 -2
- package/dist/components/data-table-row-actions.js +86 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +2 -0
- package/dist/components/data-table-shared.js +17 -2
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +43 -2
- package/dist/components/data-table.js +10 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-format-setting.d.ts +29 -0
- package/dist/components/date-format-setting.js +36 -0
- package/dist/components/date-format-setting.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +8 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tabs.d.ts +3 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -0
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-labels.d.ts +5 -0
- package/dist/components/translation-review-labels.js +2 -0
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +18 -8
- package/dist/components/translation-review.js +64 -7
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/write-lock-labels.js +2 -1
- package/dist/components/write-lock-labels.js.map +1 -1
- package/dist/components/write-lock.d.ts +33 -3
- package/dist/components/write-lock.js +9 -0
- package/dist/components/write-lock.js.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +4 -2
- package/dist/feedback/feedback-row-detail.js +17 -26
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +18 -0
- package/dist/feedback/feedback-swipe.js +10 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +81 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +25 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +97 -7
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +81 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +81 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +81 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +81 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +81 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +16 -2
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/date-format.d.ts +81 -0
- package/dist/lib/date-format.js +56 -0
- package/dist/lib/date-format.js.map +1 -0
- package/dist/lib/format.d.ts +5 -1
- package/dist/lib/format.js +11 -5
- package/dist/lib/format.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +27 -0
- package/dist/notifications/device-zone-report.js +24 -0
- package/dist/notifications/device-zone-report.js.map +1 -0
- package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
- package/dist/notifications/feedback-notification-types-labels.js +4 -1
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +11 -5
- package/dist/notifications/feedback-notification-types.js +2 -1
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +33 -0
- package/dist/notifications/notification-bell.js +74 -0
- package/dist/notifications/notification-bell.js.map +1 -0
- package/dist/notifications/notification-inbox.d.ts +77 -0
- package/dist/notifications/notification-inbox.js +206 -0
- package/dist/notifications/notification-inbox.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +6 -4
- package/dist/notifications/notification-types-setting.js +103 -45
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +19 -7
- package/dist/notifications/notifications-labels.d.ts +41 -7
- package/dist/notifications/notifications-labels.js +18 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +20 -3
- package/dist/notifications/push-devices-setting.js +93 -33
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.d.ts +108 -0
- package/dist/notifications/push-worker.js +62 -0
- package/dist/notifications/push-worker.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +29 -17
- package/dist/notifications/quiet-hours-setting.js +9 -41
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/time-zone-select.d.ts +5 -2
- package/dist/notifications/time-zone-select.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +4 -17
- package/dist/notifications/use-push-device.js +7 -15
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/local-first.d.ts +33 -0
- package/dist/offline/local-first.js +39 -0
- package/dist/offline/local-first.js.map +1 -0
- package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
- package/dist/offline/pwa-update-prompt-labels.js +9 -0
- package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
- package/dist/offline/pwa-update-prompt.d.ts +32 -0
- package/dist/offline/pwa-update-prompt.js +36 -0
- package/dist/offline/pwa-update-prompt.js.map +1 -0
- package/dist/offline/query-persistence.d.ts +125 -0
- package/dist/offline/query-persistence.js +183 -0
- package/dist/offline/query-persistence.js.map +1 -0
- package/dist/offline/service-worker-updates.d.ts +46 -0
- package/dist/offline/service-worker-updates.js +39 -0
- package/dist/offline/service-worker-updates.js.map +1 -0
- package/dist/offline/shared-computer-labels.d.ts +2 -0
- package/dist/offline/shared-computer-labels.js +9 -0
- package/dist/offline/shared-computer-labels.js.map +1 -0
- package/dist/offline/shared-computer.d.ts +44 -0
- package/dist/offline/shared-computer.js +51 -0
- package/dist/offline/shared-computer.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
- package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
- package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
- package/dist/offline/sign-out-pending-dialog.js +66 -0
- package/dist/offline/sign-out-pending-dialog.js.map +1 -0
- package/dist/offline.d.ts +11 -0
- package/dist/offline.js +33 -0
- package/dist/offline.js.map +1 -0
- package/dist/push-worker.d.ts +2 -0
- package/dist/push-worker.js +14 -0
- package/dist/push-worker.js.map +1 -0
- package/dist/shell/account-menu-text-size.d.ts +33 -0
- package/dist/shell/account-menu-text-size.js +51 -0
- package/dist/shell/account-menu-text-size.js.map +1 -0
- package/dist/shell/admin-menu.d.ts +24 -0
- package/dist/shell/admin-menu.js +12 -3
- package/dist/shell/admin-menu.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +6 -2
- package/dist/shell/top-bar-actions.js +1 -0
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell.d.ts +4 -0
- package/dist/shell.js +4 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/db.d.ts +76 -0
- package/dist/sync/db.js +24 -0
- package/dist/sync/db.js.map +1 -0
- package/dist/sync/engine.d.ts +124 -0
- package/dist/sync/engine.js +474 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hooks.d.ts +30 -0
- package/dist/sync/hooks.js +67 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/testing.d.ts +28 -0
- package/dist/sync/testing.js +41 -0
- package/dist/sync/testing.js.map +1 -0
- package/dist/sync/wire.d.ts +50 -0
- package/dist/sync/wire.js +2 -0
- package/dist/sync/wire.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +22 -0
- package/dist/sync.js.map +1 -0
- package/dist/tour/tour-catalogue.d.ts +7 -1
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-labels.js +1 -0
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +30 -0
- package/dist/tour/tour.js +54 -8
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.d.ts +99 -1
- package/dist/tour/use-tour-progress.js +228 -12
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +2 -0
- package/dist/tour.js.map +1 -1
- package/package.json +43 -1
- package/src/admin/admin-action-log-labels.ts +9 -0
- package/src/admin/admin-action-log.tsx +41 -5
- package/src/admin/admin-area-labels.ts +20 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card-labels.ts +19 -0
- package/src/admin/mail-test-card.tsx +190 -0
- package/src/components/appearance-labels.ts +16 -0
- package/src/components/data-table-column-settings.tsx +2 -2
- package/src/components/data-table-head.tsx +2 -2
- package/src/components/data-table-mobile.tsx +26 -3
- package/src/components/data-table-row-actions.tsx +118 -2
- package/src/components/data-table-shared.tsx +39 -3
- package/src/components/data-table.tsx +52 -21
- package/src/components/date-format-setting.tsx +64 -0
- package/src/components/swipeable-row.tsx +8 -0
- package/src/components/tabs.tsx +4 -3
- package/src/components/translation-review-editor.tsx +2 -0
- package/src/components/translation-review-labels.ts +7 -0
- package/src/components/translation-review.tsx +102 -14
- package/src/components/write-lock-labels.ts +1 -0
- package/src/components/write-lock.tsx +36 -3
- package/src/data-table.ts +1 -0
- package/src/feedback/feedback-row-detail.tsx +18 -23
- package/src/feedback/feedback-swipe.tsx +28 -1
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/german.ts +90 -4
- package/src/i18n/kit-labels.tsx +31 -1
- package/src/i18n/locales/en.ts +13 -0
- package/src/i18n/locales/es.ts +90 -4
- package/src/i18n/locales/fr.ts +90 -4
- package/src/i18n/locales/hu.ts +86 -4
- package/src/i18n/locales/it.ts +90 -4
- package/src/i18n/locales/zh.ts +84 -2
- package/src/i18n/review.ts +15 -1
- package/src/index.ts +45 -0
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +23 -6
- package/src/notifications/device-zone-report.ts +54 -0
- package/src/notifications/feedback-notification-types-labels.ts +10 -1
- package/src/notifications/feedback-notification-types.ts +12 -5
- package/src/notifications/notification-bell.tsx +116 -0
- package/src/notifications/notification-inbox.tsx +304 -0
- package/src/notifications/notification-types-setting.tsx +151 -55
- package/src/notifications/notification-types.ts +20 -8
- package/src/notifications/notifications-labels.ts +69 -11
- package/src/notifications/push-devices-setting.tsx +123 -34
- package/src/notifications/push-worker.ts +160 -0
- package/src/notifications/quiet-hours-setting.tsx +41 -58
- package/src/notifications/time-zone-select.tsx +5 -2
- package/src/notifications/use-push-device.ts +20 -33
- package/src/offline/local-first.ts +76 -0
- package/src/offline/pwa-update-prompt-labels.ts +9 -0
- package/src/offline/pwa-update-prompt.tsx +72 -0
- package/src/offline/query-persistence.ts +356 -0
- package/src/offline/service-worker-updates.ts +81 -0
- package/src/offline/shared-computer-labels.ts +8 -0
- package/src/offline/shared-computer.tsx +87 -0
- package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
- package/src/offline/sign-out-pending-dialog.tsx +135 -0
- package/src/offline.ts +32 -0
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/admin-menu.tsx +40 -3
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +7 -0
- package/src/sync/db.ts +103 -0
- package/src/sync/engine.ts +699 -0
- package/src/sync/hooks.ts +93 -0
- package/src/sync/testing.ts +73 -0
- package/src/sync/wire.ts +61 -0
- package/src/sync.ts +29 -0
- package/src/tour/tour-catalogue.tsx +7 -1
- package/src/tour/tour-labels.ts +1 -0
- package/src/tour/tour.tsx +105 -9
- package/src/tour/use-tour-progress.ts +378 -12
- package/src/tour.ts +3 -0
|
@@ -46,6 +46,7 @@ import type { TranslationReviewLabels } from "./translation-review-labels";
|
|
|
46
46
|
import { ReviewStatusChip, TranslationProgress } from "./translation-review-parts";
|
|
47
47
|
import { Button } from "./button";
|
|
48
48
|
import { IconButton } from "./icon-button";
|
|
49
|
+
import { DEFAULT_ROW_ACTIONS_LABELS, rowActionsColumn, type RowAction } from "./row-actions";
|
|
49
50
|
import { Select } from "./select";
|
|
50
51
|
import { COMMIT_EXCEPT_BILLING, useWriteLock } from "./write-lock";
|
|
51
52
|
|
|
@@ -194,13 +195,23 @@ export interface TranslationReviewPanelProps {
|
|
|
194
195
|
* DataTable's swipe sides (`mobileSwipeActions`) on {@link SwipeableRow}: the same
|
|
195
196
|
* gesture, the same feedback, and the same keyboard path — every swipe is also a real
|
|
196
197
|
* button, read out and reachable by Tab, so the gesture is a shortcut and never the only
|
|
197
|
-
* way. A row has no swipe while
|
|
198
|
-
* under a {@link WriteLockProvider} lock, in a `readOnly` locale, and
|
|
199
|
-
* where there is nothing to approve (a missing string, an approved one).
|
|
200
|
-
* limited to `areas` never sees a row outside them, so never swipes one.
|
|
198
|
+
* way. A row has no swipe while its own write is out (a swipe's, a button's, or the open
|
|
199
|
+
* editor's own), under a {@link WriteLockProvider} lock, in a `readOnly` locale, and
|
|
200
|
+
* toward "Approve" where there is nothing to approve (a missing string, an approved one).
|
|
201
|
+
* A reviewer limited to `areas` never sees a row outside them, so never swipes one.
|
|
201
202
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
203
|
+
* An OPEN row swipes too, on its top line (keksdose live #416, Marcel: *"The swipe shall
|
|
204
|
+
* still be possible since it is faster than scrolling down to the buttons"*), as
|
|
205
|
+
* DataTable's open card does since 0.40: the editor below is outside the gesture, and a
|
|
206
|
+
* swipe neither toggles it shut nor resets it. A swipe acts on the STORED string, as the
|
|
207
|
+
* row's buttons do — Approve approves what the locale says, not the editor's draft — and
|
|
208
|
+
* the editor stays open with whatever was typed in it; "Needs a change" puts the cursor
|
|
209
|
+
* back at the end of that draft.
|
|
210
|
+
*
|
|
211
|
+
* Off by default, so no existing page gains a gesture on the bump. The gesture is the
|
|
212
|
+
* phone's; on a wide screen the binding is the row's actions column instead (0.46,
|
|
213
|
+
* keksdose dev#598): Approve where the row can take it, bound or not, then the bound
|
|
214
|
+
* `edit` and `clear`, each once — under a write lock they stay and say why.
|
|
204
215
|
*
|
|
205
216
|
* A BINDING instead of `true` (keksdose — Marcel's live #377 rework, 2026-10-03: *"Add
|
|
206
217
|
* the swipe options to be shown also to the settings /settings#interaction area where
|
|
@@ -218,8 +229,8 @@ export interface TranslationReviewPanelProps {
|
|
|
218
229
|
* `onClear`). An action a row cannot offer drops out and the ladder closes up behind it
|
|
219
230
|
* ({@link translationReviewSwipePlan}): `approve` on an approved or missing string,
|
|
220
231
|
* `clear` on a row with no verdict. Every rule above still holds for every binding —
|
|
221
|
-
* no side swipes in a `readOnly` locale, under a write lock,
|
|
222
|
-
*
|
|
232
|
+
* no side swipes in a `readOnly` locale, under a write lock, or while the row's write
|
|
233
|
+
* (or its open editor's) is out.
|
|
223
234
|
*
|
|
224
235
|
* `true` is {@link DEFAULT_TRANSLATION_REVIEW_SWIPE} — `{ end: ["approve"], start:
|
|
225
236
|
* ["edit"] }`, 0.25's mapping exactly — and `false` or nothing is no swipe. A binding
|
|
@@ -482,6 +493,8 @@ export function TranslationReviewPanel({
|
|
|
482
493
|
// (docs/billing-harmonization.md §12.13, §12.36): the buttons take
|
|
483
494
|
// COMMIT_EXCEPT_BILLING, and the swipes read the lock as they see it.
|
|
484
495
|
const lock = useWriteLock(COMMIT_EXCEPT_BILLING);
|
|
496
|
+
// The desktop actions column's plain-text name (dev#598), the provider's "Actions".
|
|
497
|
+
const rowActionLabels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS);
|
|
485
498
|
// The phone layout DataTable switches its layout on, so "a phone" means the same here
|
|
486
499
|
// as in the table, at every text size: `usePhoneLayout()` itself (§10.4; 0.32.1,
|
|
487
500
|
// keksdose's 0.32 report — `!useBreakpoint("md")` could disagree with it at 767.5 px).
|
|
@@ -514,6 +527,21 @@ export function TranslationReviewPanel({
|
|
|
514
527
|
const [failure, setFailure] = useState<ReactNode>(null);
|
|
515
528
|
const busy = queued.length > 0;
|
|
516
529
|
const isQueued = (key: string) => queued.includes(key);
|
|
530
|
+
// The rows whose open editor has a write of its own out: their swipes stand down until
|
|
531
|
+
// it is answered, as for a write in the queue (0.46, keksdose live #416).
|
|
532
|
+
const [editorWrites, setEditorWrites] = useState<ReadonlySet<string>>(() => new Set());
|
|
533
|
+
const whileEditorWrites = async (rowId: string, write: () => MaybePromise) => {
|
|
534
|
+
setEditorWrites((prev) => new Set(prev).add(rowId));
|
|
535
|
+
try {
|
|
536
|
+
await write();
|
|
537
|
+
} finally {
|
|
538
|
+
setEditorWrites((prev) => {
|
|
539
|
+
const next = new Set(prev);
|
|
540
|
+
next.delete(rowId);
|
|
541
|
+
return next;
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
517
545
|
|
|
518
546
|
const rows = useMemo(
|
|
519
547
|
() => (areas ? rowsProp.filter((row) => keyInAreas(row.key, areas, inArea)) : [...rowsProp]),
|
|
@@ -650,11 +678,11 @@ export function TranslationReviewPanel({
|
|
|
650
678
|
/** A swipe bound to `clear`: the row's verdict taken back, answered like an approval —
|
|
651
679
|
* the Undo toast stores the verdict again. Under the row's own key, so it queues behind
|
|
652
680
|
* (and never overlaps) an approval of the same row. */
|
|
653
|
-
const clearRow = async (row: TranslationRow) => {
|
|
681
|
+
const clearRow = async (row: TranslationRow, origin: TranslationReviewOrigin = "swipe") => {
|
|
654
682
|
if (!onClear || row.review === null) return;
|
|
655
683
|
const clear = onClear;
|
|
656
684
|
const keys = [{ locale: row.locale, key: row.key }];
|
|
657
|
-
const ok = await run(`row:${row.id}`, () => clear(keys, { origin
|
|
685
|
+
const ok = await run(`row:${row.id}`, () => clear(keys, { origin, toasted: undoOn }));
|
|
658
686
|
if (ok && undoOn) sayDone([row], row.key, true);
|
|
659
687
|
};
|
|
660
688
|
|
|
@@ -662,7 +690,7 @@ export function TranslationReviewPanel({
|
|
|
662
690
|
* not go through the queue — but an approval there is answered like any other. */
|
|
663
691
|
const saveFromEditor = async (row: TranslationRow, write: TranslationReviewWrite) => {
|
|
664
692
|
const toasted = undoOn && write.verdict === "APPROVED";
|
|
665
|
-
await onSave?.([write], { origin: "editor", toasted });
|
|
693
|
+
await whileEditorWrites(row.id, () => onSave?.([write], { origin: "editor", toasted }));
|
|
666
694
|
if (toasted) sayDone([row], row.key);
|
|
667
695
|
};
|
|
668
696
|
|
|
@@ -763,7 +791,51 @@ export function TranslationReviewPanel({
|
|
|
763
791
|
sortBy: (r) => REVIEW_STATUSES.indexOf(r.status),
|
|
764
792
|
},
|
|
765
793
|
];
|
|
766
|
-
if (editable) {
|
|
794
|
+
if (editable && binding) {
|
|
795
|
+
// keksdose dev#598 item 2: with a binding, the desktop row offers what the card's
|
|
796
|
+
// swipes offer — `clear` was reachable only by a swipe. Approve first on every row
|
|
797
|
+
// that can take it, bound or not (the desktop always had it), then the binding's
|
|
798
|
+
// actions, end side first, each once. The swipe's words, glyphs and handlers; unlike
|
|
799
|
+
// the swipe, a locked row keeps its buttons and says why (`commit`), and a row whose
|
|
800
|
+
// write is out shows it in flight. Hidden on the phone, where the swipes are.
|
|
801
|
+
columns.push(
|
|
802
|
+
rowActionsColumn<TranslationRow>({
|
|
803
|
+
key: "actions",
|
|
804
|
+
headerText: rowActionLabels.actions,
|
|
805
|
+
name: (r) => r.key,
|
|
806
|
+
tooltipSide: "start",
|
|
807
|
+
actions: (r) =>
|
|
808
|
+
translationReviewSwipePlan(
|
|
809
|
+
{ end: ["approve", ...(binding.end ?? []), ...(binding.start ?? [])] },
|
|
810
|
+
r,
|
|
811
|
+
{ canClear: onClear !== undefined },
|
|
812
|
+
).end.map((id): RowAction => {
|
|
813
|
+
const swipe = swipeAction(r, id);
|
|
814
|
+
// `edit` only opens the editor, whose own Save is the commit.
|
|
815
|
+
const writes = id !== "edit";
|
|
816
|
+
return {
|
|
817
|
+
key: id,
|
|
818
|
+
// `edit` opens the editor: its own name, apart from the editor's submit
|
|
819
|
+
// ("Needs a change"), which saves (keksdose B2/S3).
|
|
820
|
+
label: id === "edit" ? (r.text === "" ? labels.suggestRow : labels.flagRow) : swipe.label,
|
|
821
|
+
icon: <>{swipe.icon}</>,
|
|
822
|
+
tone: "muted",
|
|
823
|
+
commit: writes ? COMMIT_EXCEPT_BILLING : undefined,
|
|
824
|
+
pending: writes && isQueued(`row:${r.id}`),
|
|
825
|
+
disabled: busy,
|
|
826
|
+
onSelect:
|
|
827
|
+
id === "approve"
|
|
828
|
+
? () => void approve(`row:${r.id}`, [r], "row", r.key)
|
|
829
|
+
: id === "clear"
|
|
830
|
+
? () => void clearRow(r, "row")
|
|
831
|
+
: // The row's group stays open while its editor is (`keepGroupOpen`).
|
|
832
|
+
() => swipeAction(r, id, groups?.find((g) => g.rows.includes(r))?.key).onCommit(),
|
|
833
|
+
};
|
|
834
|
+
}),
|
|
835
|
+
column: { mobileHidden: true },
|
|
836
|
+
}),
|
|
837
|
+
);
|
|
838
|
+
} else if (editable) {
|
|
767
839
|
columns.push({
|
|
768
840
|
key: "approve",
|
|
769
841
|
header: <span className="sr-only">{labels.approve}</span>,
|
|
@@ -801,6 +873,7 @@ export function TranslationReviewPanel({
|
|
|
801
873
|
switch (id) {
|
|
802
874
|
case "approve":
|
|
803
875
|
return {
|
|
876
|
+
id,
|
|
804
877
|
label: labels.approve,
|
|
805
878
|
icon: <Check className="size-4" aria-hidden />,
|
|
806
879
|
onCommit: () => void approve(`row:${r.id}`, [r], "swipe", r.key),
|
|
@@ -810,6 +883,7 @@ export function TranslationReviewPanel({
|
|
|
810
883
|
};
|
|
811
884
|
case "edit":
|
|
812
885
|
return {
|
|
886
|
+
id,
|
|
813
887
|
label: missing ? labels.suggest : labels.flag,
|
|
814
888
|
icon: <PencilLine className="size-4" aria-hidden />,
|
|
815
889
|
// Opens the editor rather than sending the string back: "needs a change"
|
|
@@ -818,11 +892,19 @@ export function TranslationReviewPanel({
|
|
|
818
892
|
keepGroupOpen(groupKey);
|
|
819
893
|
setOpenId(r.id);
|
|
820
894
|
setFocusId(r.id);
|
|
895
|
+
// On a row already open (#416) the editor is mounted and keeps its draft:
|
|
896
|
+
// the cursor goes to the end of its wording now.
|
|
897
|
+
if (openId === r.id) {
|
|
898
|
+
const field = rootRef.current?.querySelector<HTMLTextAreaElement>("[data-review-wording]");
|
|
899
|
+
field?.focus();
|
|
900
|
+
field?.setSelectionRange(field.value.length, field.value.length);
|
|
901
|
+
}
|
|
821
902
|
},
|
|
822
903
|
tone: missing ? "brand" : "danger",
|
|
823
904
|
};
|
|
824
905
|
case "clear":
|
|
825
906
|
return {
|
|
907
|
+
id,
|
|
826
908
|
label: labels.reset,
|
|
827
909
|
icon: <RotateCcw className="size-4" aria-hidden />,
|
|
828
910
|
onCommit: () => void clearRow(r),
|
|
@@ -841,7 +923,7 @@ export function TranslationReviewPanel({
|
|
|
841
923
|
* locked or absent.
|
|
842
924
|
*/
|
|
843
925
|
const swipeActions = (r: TranslationRow, groupKey?: string): MobileSwipeActions | null => {
|
|
844
|
-
if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`)) return null;
|
|
926
|
+
if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`) || editorWrites.has(r.id)) return null;
|
|
845
927
|
const plan = translationReviewSwipePlan(binding, r, { canClear: onClear !== undefined });
|
|
846
928
|
if (plan.start.length === 0 && plan.end.length === 0) return null;
|
|
847
929
|
return {
|
|
@@ -908,7 +990,11 @@ export function TranslationReviewPanel({
|
|
|
908
990
|
readOnly={!editable}
|
|
909
991
|
focusWording={focusId === r.id}
|
|
910
992
|
onSave={onSave ? (write) => saveFromEditor(r, write) : undefined}
|
|
911
|
-
onClear={
|
|
993
|
+
onClear={
|
|
994
|
+
onClear
|
|
995
|
+
? (key) => whileEditorWrites(r.id, () => onClear([key], { origin: "editor", toasted: false }))
|
|
996
|
+
: undefined
|
|
997
|
+
}
|
|
912
998
|
onClose={() => {
|
|
913
999
|
setOpenId(null);
|
|
914
1000
|
setFocusId(null);
|
|
@@ -935,6 +1021,8 @@ export function TranslationReviewPanel({
|
|
|
935
1021
|
</div>
|
|
936
1022
|
)}
|
|
937
1023
|
mobileSwipeActions={binding ? (r) => swipeActions(r, group?.key) : undefined}
|
|
1024
|
+
// The desktop's buttons for the binding are the panel's own "actions" column.
|
|
1025
|
+
desktopSwipeActions={false}
|
|
938
1026
|
selection={selectionFor(list)}
|
|
939
1027
|
/>
|
|
940
1028
|
);
|
|
@@ -8,6 +8,8 @@ import { DEFAULT_WRITE_LOCK_LABELS } from "./write-lock-labels";
|
|
|
8
8
|
export interface WriteLockLabels {
|
|
9
9
|
/** The tooltip of a locked commit when the provider gives no `reason`. */
|
|
10
10
|
reason: string;
|
|
11
|
+
/** 0.46 (kastlan K4): the reason of {@link useOfflineWriteLock} — "Needs a connection." */
|
|
12
|
+
offline: string;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
// Defined in ./write-lock-labels, which holds no component code (0.34, KS U8).
|
|
@@ -15,13 +17,13 @@ export { DEFAULT_WRITE_LOCK_LABELS };
|
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* What kind of lock a source is (docs/billing-harmonization.md §12.36, ui-kit 0.33).
|
|
18
|
-
* `"demo"` (the read-only demo, `demo.writeLocked`)
|
|
19
|
-
* `billing.lockReason`) are the kit's; an app names its own — Kurvenschmiede's
|
|
20
|
+
* `"demo"` (the read-only demo, `demo.writeLocked`), `"billing"` (a lapsed plan,
|
|
21
|
+
* `billing.lockReason`) and, since 0.46, `"offline"` ({@link OFFLINE_LOCK}) are the kit's; an app names its own — Kurvenschmiede's
|
|
20
22
|
* `"access"` (a curve shared to view) and `"customer"` (an account that owns nothing).
|
|
21
23
|
* A control exempts itself from a kind with {@link CommitScope}; a source without a
|
|
22
24
|
* kind is never exempted.
|
|
23
25
|
*/
|
|
24
|
-
export type WriteLockKind = "demo" | "billing" | (string & {});
|
|
26
|
+
export type WriteLockKind = "demo" | "billing" | "offline" | (string & {});
|
|
25
27
|
|
|
26
28
|
/** One locked source, as the provider holds it: its kind and its one sentence. */
|
|
27
29
|
export interface WriteLockHold {
|
|
@@ -50,6 +52,19 @@ export type CommitScope = boolean | { readonly except: readonly WriteLockKind[]
|
|
|
50
52
|
* so the call sites read alike. */
|
|
51
53
|
export const COMMIT_EXCEPT_BILLING = { except: ["billing"] } as const;
|
|
52
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 0.46 (kastlan K4, docs/local-first-harmonization.md D4): the kind of the lock "this
|
|
57
|
+
* device has no connection". An action that only the server can do (numbers, money,
|
|
58
|
+
* mail, people, settings) is locked with "Needs a connection." while offline; a control
|
|
59
|
+
* that writes to the device's copy and syncs later takes {@link COMMIT_EXCEPT_OFFLINE}.
|
|
60
|
+
* Feed it with {@link useOfflineWriteLock}.
|
|
61
|
+
*/
|
|
62
|
+
export const OFFLINE_LOCK = "offline" as const;
|
|
63
|
+
|
|
64
|
+
/** Every lock but the offline one: the scope of a control that works on the device's copy
|
|
65
|
+
* (it is still locked for a demo or a lapsed plan). */
|
|
66
|
+
export const COMMIT_EXCEPT_OFFLINE = { except: [OFFLINE_LOCK] } as const;
|
|
67
|
+
|
|
53
68
|
/**
|
|
54
69
|
* What {@link useWriteLock} returns: whether commits are locked here as a control with
|
|
55
70
|
* the scope asked for sees it, and why.
|
|
@@ -218,3 +233,21 @@ export function useCommitReason(commit: CommitScope | undefined, own: ReactNode)
|
|
|
218
233
|
const lock = writeLockFor(useContext(WriteLockContext), commit);
|
|
219
234
|
return lock.locked ? lock.reason : own;
|
|
220
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 0.46 (kastlan K4): the offline lock as one source of `combineWriteLocks` —
|
|
239
|
+
* `{ locked: offline, kind: "offline", reason: writeLock.offline }` ("Needs a
|
|
240
|
+
* connection.", seven languages). `offline` is the app's own reading of the connection
|
|
241
|
+
* (`!navigator.onLine`, or its better guess); the kit does not watch the network here.
|
|
242
|
+
* List it after the demo's and billing's sources: the server refuses those first.
|
|
243
|
+
*
|
|
244
|
+
* <WriteLockProvider {...combineWriteLocks([demo, billing, useOfflineWriteLock(!online)])}>
|
|
245
|
+
* <Button commit={COMMIT_EXCEPT_OFFLINE}>Save handover</Button>
|
|
246
|
+
*/
|
|
247
|
+
export function useOfflineWriteLock(
|
|
248
|
+
offline: boolean,
|
|
249
|
+
labels?: LabelOverride<WriteLockLabels>,
|
|
250
|
+
): { locked: boolean; kind: typeof OFFLINE_LOCK; reason: string } {
|
|
251
|
+
const words = useKitLabels("writeLock", DEFAULT_WRITE_LOCK_LABELS, labels);
|
|
252
|
+
return { locked: offline, kind: OFFLINE_LOCK, reason: words.offline };
|
|
253
|
+
}
|
package/src/data-table.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type {
|
|
|
25
25
|
DataTableDensity,
|
|
26
26
|
DataTableChrome,
|
|
27
27
|
DataTableRowAction,
|
|
28
|
+
DataTableDesktopSwipeActions,
|
|
28
29
|
} from "./components/data-table";
|
|
29
30
|
export * from "./components/data-table-cells";
|
|
30
31
|
export { useTableUrlState, readTableUrlState } from "./components/use-table-state";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import type { MouseEvent, ReactNode } from "react";
|
|
3
|
-
import { Download,
|
|
3
|
+
import { Download, FileText, RotateCcw } from "lucide-react";
|
|
4
4
|
import { AuthedImage } from "../components/authed-image";
|
|
5
5
|
import { CopyButton } from "../components/copy-button";
|
|
6
6
|
import { TextLink } from "../components/text-link";
|
|
@@ -75,7 +75,7 @@ import { DEFAULT_FEEDBACK_COMMENTS_LABELS } from "./feedback-thread-parts-labels
|
|
|
75
75
|
* `showSubject={false}` leaves it out), **Description** (the body; the author's
|
|
76
76
|
* Edit while OPEN / READY / IN_PROGRESS), **URL**, **Attachment**, **Replies** (0.38,
|
|
77
77
|
* §9.7: the timeline, the admin's reply composer with its status picker and the nudge,
|
|
78
|
-
* Rework
|
|
78
|
+
* Rework — on a row without `replies`, a server before 0.38, the frozen
|
|
79
79
|
* **Outcome** read-only in its place), **Comments** (0.38: the app's
|
|
80
80
|
* `FeedbackCommentThread`, when passed), **Send for rework** (only while reworking) and
|
|
81
81
|
* **Status** (with the **Visible to others** switch when `onPublicChange` is passed).
|
|
@@ -143,7 +143,9 @@ export interface FeedbackDetailLabels {
|
|
|
143
143
|
outcomePlaceholder: string;
|
|
144
144
|
/** The button that opens section 5 on an answered row. de-CH: "Nacharbeit" */
|
|
145
145
|
rework: string;
|
|
146
|
-
/** The link to the page the report was filed on. de-CH: "Seite öffnen"
|
|
146
|
+
/** The link to the page the report was filed on. de-CH: "Seite öffnen"
|
|
147
|
+
* @deprecated 0.46 — the Replies section's "Open page" button is gone (keksdose live #449:
|
|
148
|
+
* it repeated the URL section's link); removed in 0.47. Unused, kept for one release. */
|
|
147
149
|
openPage: string;
|
|
148
150
|
/** Section 5's heading. de-CH: "Zur Nacharbeit senden" */
|
|
149
151
|
reworkTitle: string;
|
|
@@ -853,27 +855,20 @@ export function FeedbackRowDetail({
|
|
|
853
855
|
commit={commit}
|
|
854
856
|
/>
|
|
855
857
|
)}
|
|
856
|
-
{(
|
|
858
|
+
{/* No "Open page" button here since 0.46 (keksdose live #449): the URL section
|
|
859
|
+
above already links the page, with the path on screen. */}
|
|
860
|
+
{canRework && !reworking && (
|
|
857
861
|
<div className="flex flex-wrap gap-2">
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
</Button>
|
|
869
|
-
)}
|
|
870
|
-
{pageHref && (
|
|
871
|
-
// A real href (keksdose dev #451): middle-click and ⌘/Ctrl-click are
|
|
872
|
-
// the browser's, and only a link has them.
|
|
873
|
-
<Button href={pageHref} variant="secondary" onClick={(event) => event.stopPropagation()}>
|
|
874
|
-
<ExternalLink className="size-3.5" aria-hidden /> {labels.openPage}
|
|
875
|
-
</Button>
|
|
876
|
-
)}
|
|
862
|
+
<Button
|
|
863
|
+
type="button"
|
|
864
|
+
variant="secondary"
|
|
865
|
+
onClick={(event) => {
|
|
866
|
+
event.stopPropagation();
|
|
867
|
+
setReworking(true);
|
|
868
|
+
}}
|
|
869
|
+
>
|
|
870
|
+
<RotateCcw className="size-3.5" aria-hidden /> {labels.rework}
|
|
871
|
+
</Button>
|
|
877
872
|
</div>
|
|
878
873
|
)}
|
|
879
874
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Ban, Check, ChevronsRight } from "lucide-react";
|
|
2
2
|
import type { SwipeAction } from "../components/swipeable-row";
|
|
3
3
|
import type { MobileSwipeActions } from "../components/data-table";
|
|
4
|
-
import { nextFeedbackStatus, type FeedbackStatus } from "./feedback-vocabulary";
|
|
4
|
+
import { nextFeedbackStatus, visibleFeedbackStatuses, type FeedbackStatus } from "./feedback-vocabulary";
|
|
5
5
|
import type { FeedbackStatusLabels } from "./feedback-labels";
|
|
6
6
|
import type { FeedbackStatusChange, FeedbackStatusRow } from "./feedback-status-undo";
|
|
7
7
|
|
|
@@ -89,9 +89,12 @@ function panel(
|
|
|
89
89
|
{ change, labels }: FeedbackSwipeOptions,
|
|
90
90
|
): SwipeAction {
|
|
91
91
|
const onCommit = () => change(row, target);
|
|
92
|
+
// `id`: the binding's own (0.46, dev#598) — DataTable's desktop column tells the
|
|
93
|
+
// actions apart by it, and `feedbackSwipeOmitShown` reads the target from it.
|
|
92
94
|
switch (id) {
|
|
93
95
|
case "advance":
|
|
94
96
|
return {
|
|
97
|
+
id,
|
|
95
98
|
onCommit,
|
|
96
99
|
label: labels[target],
|
|
97
100
|
icon: <ChevronsRight className="size-4" aria-hidden />,
|
|
@@ -99,6 +102,7 @@ function panel(
|
|
|
99
102
|
};
|
|
100
103
|
case "done":
|
|
101
104
|
return {
|
|
105
|
+
id,
|
|
102
106
|
onCommit,
|
|
103
107
|
label: labels.DONE,
|
|
104
108
|
icon: <Check className="size-4" aria-hidden />,
|
|
@@ -106,6 +110,7 @@ function panel(
|
|
|
106
110
|
};
|
|
107
111
|
case "wont_do":
|
|
108
112
|
return {
|
|
113
|
+
id,
|
|
109
114
|
onCommit,
|
|
110
115
|
label: labels.WONT_DO,
|
|
111
116
|
icon: <Ban className="size-4" aria-hidden />,
|
|
@@ -163,3 +168,25 @@ export function feedbackSwipePlan(
|
|
|
163
168
|
if (start.length === 0 && end.length === 0) return null;
|
|
164
169
|
return { start, end };
|
|
165
170
|
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* For DataTable's `desktopSwipeActions.omit` beside the kit's status cell (0.46, keksdose
|
|
174
|
+
* dev#598 item 3): `true` for a planned action whose target status the cell already
|
|
175
|
+
* offers as its own icon — {@link visibleFeedbackStatuses}, the next and the previous step
|
|
176
|
+
* and the off-chain verdicts — so the desktop column adds only what the cell lacks ("Done"
|
|
177
|
+
* where DONE is more than one step away). Read from the action's `id` and the row, never
|
|
178
|
+
* from the translated label; an action without one of {@link FEEDBACK_SWIPE_ACTIONS} as its
|
|
179
|
+
* id (an app's own) is kept.
|
|
180
|
+
*
|
|
181
|
+
* ```tsx
|
|
182
|
+
* <DataTable
|
|
183
|
+
* mobileSwipeActions={(row) => feedbackSwipePlan(binding, row, options)}
|
|
184
|
+
* desktopSwipeActions={{ omit: feedbackSwipeOmitShown, name: (row) => row.title }}
|
|
185
|
+
* />
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export function feedbackSwipeOmitShown(action: SwipeAction, row: FeedbackStatusRow): boolean {
|
|
189
|
+
if (action.id === undefined) return false;
|
|
190
|
+
const target = targetOf(action.id, row.status);
|
|
191
|
+
return target !== null && visibleFeedbackStatuses(row.status).includes(target);
|
|
192
|
+
}
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -100,6 +100,10 @@ import { DEFAULT_ROW_ACTIONS_LABELS } from "../components/row-actions-labels";
|
|
|
100
100
|
import { DEFAULT_APP_SHELL_MORE_LABELS } from "../shell/app-shell-labels";
|
|
101
101
|
import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips-labels";
|
|
102
102
|
import { DEFAULT_ADMIN_AREA_LABELS } from "../admin/admin-area-labels";
|
|
103
|
+
import { DEFAULT_MAIL_TEST_CARD_LABELS } from "../admin/mail-test-card-labels";
|
|
104
|
+
import { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "../offline/sign-out-pending-dialog-labels";
|
|
105
|
+
import { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "../offline/pwa-update-prompt-labels";
|
|
106
|
+
import { DEFAULT_SHARED_COMPUTER_LABELS } from "../offline/shared-computer-labels";
|
|
103
107
|
import { DEFAULT_TOP_BAR_PARTS_LABELS } from "../shell/top-bar-parts-labels";
|
|
104
108
|
import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card-labels";
|
|
105
109
|
import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog-labels";
|
|
@@ -262,4 +266,8 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
262
266
|
notifications: DEFAULT_NOTIFICATIONS_LABELS,
|
|
263
267
|
adminArea: DEFAULT_ADMIN_AREA_LABELS,
|
|
264
268
|
topBarParts: DEFAULT_TOP_BAR_PARTS_LABELS,
|
|
269
|
+
mailTestCard: DEFAULT_MAIL_TEST_CARD_LABELS,
|
|
270
|
+
signOutPending: DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,
|
|
271
|
+
pwaUpdate: DEFAULT_PWA_UPDATE_PROMPT_LABELS,
|
|
272
|
+
sharedComputer: DEFAULT_SHARED_COMPUTER_LABELS,
|
|
265
273
|
};
|