@eifi1/ui-kit 0.45.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 +23 -12
- package/dist/admin/admin-area-labels.d.ts +9 -0
- package/dist/admin/admin-area-labels.js +5 -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.d.ts +3 -2
- package/dist/admin/mail-test-card.js +3 -1
- package/dist/admin/mail-test-card.js.map +1 -1
- 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-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.js +9 -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 +9 -4
- 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/german.js +41 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +12 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +40 -6
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +41 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +41 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +41 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +41 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +41 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +35 -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 +3 -0
- package/dist/lib/format.js +5 -4
- 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/query-persistence.d.ts +26 -1
- package/dist/offline/query-persistence.js +27 -23
- package/dist/offline/query-persistence.js.map +1 -1
- 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.d.ts +5 -2
- package/dist/offline.js +11 -2
- package/dist/offline.js.map +1 -1
- 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/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 +2 -0
- package/dist/shell.js +2 -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-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 +50 -6
- package/dist/tour/tour.js.map +1 -1
- package/package.json +19 -1
- package/src/admin/admin-area-labels.ts +13 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card.tsx +7 -3
- 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-row-actions.tsx +118 -2
- package/src/components/data-table-shared.tsx +21 -2
- package/src/components/data-table.tsx +51 -6
- 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/german.ts +44 -4
- package/src/i18n/kit-labels.tsx +18 -1
- package/src/i18n/locales/en.ts +1 -0
- package/src/i18n/locales/es.ts +44 -4
- package/src/i18n/locales/fr.ts +44 -4
- package/src/i18n/locales/hu.ts +44 -4
- package/src/i18n/locales/it.ts +44 -4
- package/src/i18n/locales/zh.ts +44 -2
- package/src/i18n/review.ts +3 -1
- package/src/index.ts +42 -0
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +10 -4
- 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/query-persistence.ts +57 -24
- package/src/offline/service-worker-updates.ts +81 -0
- package/src/offline.ts +7 -1
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +3 -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-labels.ts +1 -0
- package/src/tour/tour.tsx +100 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/translation-review-labels.ts"],"sourcesContent":["import type { ReviewStatus } from \"../lib/translation-review\";\n\n/**\n * The `translationReview` namespace: every word of the translation-review parts\n * (`ReviewStatusChip`, `TranslationProgress`, `TranslationLocaleTabs`,\n * `TranslationExportButton`, `TranslationReviewEditor`, `TranslationReviewPanel`).\n *\n * Its own module, like `data-table-labels.ts`: `kit-labels.tsx` and `defaults.ts` import\n * it, and neither has to load the components to get the words. The English is keksdose's\n * and kastlan's own (`translation_review.*`, `translations:*`), British.\n */\nexport interface TranslationReviewLabels {\n /** The five statuses — the chip, the filter, the progress legend. */\n statusMissing: string;\n statusUnreviewed: string;\n statusChanged: string;\n statusNeedsChange: string;\n statusApproved: string;\n /** Accessible name of the status filter. */\n statusFilter: string;\n /** The status filter's first option. */\n all: string;\n /** A status filter option with its count: \"Unreviewed · 12\". */\n filterCount: (label: string, count: number) => string;\n /** The source filter's label — shown only when the rows come from several sources\n * (kastlan's screen texts and documents). */\n source: string;\n allSources: string;\n /** The namespace filter's label and its \"every one\" option. */\n namespace: string;\n allNamespaces: string;\n search: string;\n /** The placeholder filter, with how many rows it would show. */\n placeholdersOnly: (count: number) => string;\n /** The chip on a row whose placeholders differ from the reference's. */\n placeholderChip: string;\n /** The key column, and the key's caption in the editor. */\n key: string;\n statusColumn: string;\n /** No row matches the filters. */\n empty: string;\n /** In place of the text of a string the locale lacks. */\n missingText: string;\n /** The progress bar's label. */\n progress: (approved: number, total: number) => string;\n /** A locale tab's badge. */\n localeProgress: (approved: number, total: number) => string;\n /** Accessible name of the locale tabs. */\n locales: string;\n approve: string;\n /** Send a string back: \"Needs a change\". */\n flag: string;\n /** \"Needs a change\" for a MISSING string: the suggestion is the translation to add. */\n suggest: string;\n /** Clear a verdict: back to unreviewed. */\n reset: string;\n /** The editor's way out without saving. */\n cancel: string;\n approveSelected: string;\n resetSelected: string;\n /** Selects every row the filters show — the phone's way into the bulk actions, where\n * the table has no checkboxes. */\n selectShown: string;\n /** Over the old wording of a row that changed since its verdict. */\n changedSince: string;\n reviewedWording: string;\n reviewedReference: string;\n /** The placeholders differ: the reference's and the text's, each a list. */\n placeholderMismatch: (reference: string, text: string) => string;\n lastApproved: (name: string, date: string) => string;\n lastFlagged: (name: string, date: string) => string;\n /** The reviewer's name once the account is gone — what `name` above becomes. */\n erasedReviewer: string;\n /** The editor's wording field, opened on the current text. */\n suggestion: string;\n /** The same field for a missing string. */\n translation: string;\n note: string;\n notePlaceholder: string;\n /** The viewer may read this locale but not review it. */\n readOnly: string;\n /** The reviewer is limited to some areas — `areas` is their names, joined. */\n scope: (areas: string) => string;\n exportCorrections: (count: number) => string;\n /** A callback rejected and no `formatError` says better. */\n failed: string;\n /** 0.25: the Undo toast after an approval (`undo`) — one string, or a batch of them. */\n approvedToast: (count: number) => string;\n /** 0.25: a group's header under `groupBy` — how many of its rows nobody has read yet,\n * of how many the filters show in it. */\n groupCount: (unreviewed: number, total: number) => string;\n /** 0.25: a group's bulk action — approve its unreviewed rows. */\n approveGroup: (count: number) => string;\n /** 0.25: asked first when a group's bulk approve is larger than a page — more than the\n * reviewer can have had on screen. `group` is the group's name. */\n confirmGroup: (count: number, group: string) => string;\n /** The Undo toast after a swipe bound to `clear` took a verdict back (keksdose, live\n * #377 rework: the swipes bound in its settings) — one string, or a batch of them.\n * The swipe itself is named by {@link reset}. */\n clearedToast: (count: number) => string;\n}\n\nexport const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {\n statusMissing: \"Missing\",\n statusUnreviewed: \"Unreviewed\",\n statusChanged: \"Changed since review\",\n statusNeedsChange: \"Needs a change\",\n statusApproved: \"Approved\",\n statusFilter: \"Status\",\n all: \"All\",\n filterCount: (label, count) => `${label} · ${count}`,\n source: \"Texts\",\n allSources: \"All texts\",\n namespace: \"Area\",\n allNamespaces: \"All areas\",\n search: \"Search keys and text\",\n placeholdersOnly: (count) => `Only placeholder problems (${count})`,\n placeholderChip: \"Placeholders\",\n key: \"Key\",\n statusColumn: \"Status\",\n empty: \"No strings match these filters.\",\n missingText: \"Missing in this language\",\n progress: (approved, total) => `${approved} of ${total} approved`,\n localeProgress: (approved, total) => `${approved}/${total}`,\n locales: \"Languages\",\n approve: \"Approve\",\n flag: \"Needs a change\",\n suggest: \"Suggest translation\",\n reset: \"Mark unreviewed\",\n cancel: \"Cancel\",\n approveSelected: \"Approve selected\",\n resetSelected: \"Mark selected unreviewed\",\n selectShown: \"Select all shown\",\n changedSince: \"This string changed after it was reviewed. Please read it again.\",\n reviewedWording: \"Wording when reviewed\",\n reviewedReference: \"Reference when reviewed\",\n placeholderMismatch: (reference, text) =>\n `The placeholders differ from the reference — reference: ${reference}; this text: ${text}. The app fills these in, so they must stay exactly as they are.`,\n lastApproved: (name, date) => `Approved by ${name} on ${date}`,\n lastFlagged: (name, date) => `Marked as needing a change by ${name} on ${date}`,\n erasedReviewer: \"a deleted account\",\n suggestion: \"Better wording\",\n translation: \"Translation\",\n note: \"Note (optional)\",\n notePlaceholder: \"What is wrong, or what to keep in mind\",\n readOnly: \"You can read this language but not review it.\",\n scope: (areas) => `Your review is limited to: ${areas}.`,\n exportCorrections: (count) => `Export corrections (${count})`,\n failed: \"That did not work. Please try again.\",\n approvedToast: (count) => (count === 1 ? \"String approved\" : `${count} strings approved`),\n groupCount: (unreviewed, total) => `${unreviewed} unreviewed / ${total}`,\n approveGroup: (count) => `Approve unreviewed (${count})`,\n confirmGroup: (count, group) =>\n count === 1\n ? `Approve the unreviewed string in ${group}?`\n : `Approve all ${count} unreviewed strings in ${group}, including those not on screen?`,\n clearedToast: (count) => (count === 1 ? \"String marked unreviewed\" : `${count} strings marked unreviewed`),\n};\n\n/** A status's word, from the namespace's five. */\nexport function reviewStatusLabel(labels: TranslationReviewLabels, status: ReviewStatus): string {\n switch (status) {\n case \"missing\":\n return labels.statusMissing;\n case \"unreviewed\":\n return labels.statusUnreviewed;\n case \"changed\":\n return labels.statusChanged;\n case \"needs_change\":\n return labels.statusNeedsChange;\n case \"approved\":\n return labels.statusApproved;\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/components/translation-review-labels.ts"],"sourcesContent":["import type { ReviewStatus } from \"../lib/translation-review\";\n\n/**\n * The `translationReview` namespace: every word of the translation-review parts\n * (`ReviewStatusChip`, `TranslationProgress`, `TranslationLocaleTabs`,\n * `TranslationExportButton`, `TranslationReviewEditor`, `TranslationReviewPanel`).\n *\n * Its own module, like `data-table-labels.ts`: `kit-labels.tsx` and `defaults.ts` import\n * it, and neither has to load the components to get the words. The English is keksdose's\n * and kastlan's own (`translation_review.*`, `translations:*`), British.\n */\nexport interface TranslationReviewLabels {\n /** The five statuses — the chip, the filter, the progress legend. */\n statusMissing: string;\n statusUnreviewed: string;\n statusChanged: string;\n statusNeedsChange: string;\n statusApproved: string;\n /** Accessible name of the status filter. */\n statusFilter: string;\n /** The status filter's first option. */\n all: string;\n /** A status filter option with its count: \"Unreviewed · 12\". */\n filterCount: (label: string, count: number) => string;\n /** The source filter's label — shown only when the rows come from several sources\n * (kastlan's screen texts and documents). */\n source: string;\n allSources: string;\n /** The namespace filter's label and its \"every one\" option. */\n namespace: string;\n allNamespaces: string;\n search: string;\n /** The placeholder filter, with how many rows it would show. */\n placeholdersOnly: (count: number) => string;\n /** The chip on a row whose placeholders differ from the reference's. */\n placeholderChip: string;\n /** The key column, and the key's caption in the editor. */\n key: string;\n statusColumn: string;\n /** No row matches the filters. */\n empty: string;\n /** In place of the text of a string the locale lacks. */\n missingText: string;\n /** The progress bar's label. */\n progress: (approved: number, total: number) => string;\n /** A locale tab's badge. */\n localeProgress: (approved: number, total: number) => string;\n /** Accessible name of the locale tabs. */\n locales: string;\n approve: string;\n /** Send a string back: \"Needs a change\". */\n flag: string;\n /** \"Needs a change\" for a MISSING string: the suggestion is the translation to add. */\n suggest: string;\n /** 0.46 (keksdose B2/S3): the desktop row's button that OPENS the editor to send a\n * string back — its own name, apart from the editor's `flag`, which saves. */\n flagRow: string;\n /** The same for a MISSING string: opens the editor to add the translation. */\n suggestRow: string;\n /** Clear a verdict: back to unreviewed. */\n reset: string;\n /** The editor's way out without saving. */\n cancel: string;\n approveSelected: string;\n resetSelected: string;\n /** Selects every row the filters show — the phone's way into the bulk actions, where\n * the table has no checkboxes. */\n selectShown: string;\n /** Over the old wording of a row that changed since its verdict. */\n changedSince: string;\n reviewedWording: string;\n reviewedReference: string;\n /** The placeholders differ: the reference's and the text's, each a list. */\n placeholderMismatch: (reference: string, text: string) => string;\n lastApproved: (name: string, date: string) => string;\n lastFlagged: (name: string, date: string) => string;\n /** The reviewer's name once the account is gone — what `name` above becomes. */\n erasedReviewer: string;\n /** The editor's wording field, opened on the current text. */\n suggestion: string;\n /** The same field for a missing string. */\n translation: string;\n note: string;\n notePlaceholder: string;\n /** The viewer may read this locale but not review it. */\n readOnly: string;\n /** The reviewer is limited to some areas — `areas` is their names, joined. */\n scope: (areas: string) => string;\n exportCorrections: (count: number) => string;\n /** A callback rejected and no `formatError` says better. */\n failed: string;\n /** 0.25: the Undo toast after an approval (`undo`) — one string, or a batch of them. */\n approvedToast: (count: number) => string;\n /** 0.25: a group's header under `groupBy` — how many of its rows nobody has read yet,\n * of how many the filters show in it. */\n groupCount: (unreviewed: number, total: number) => string;\n /** 0.25: a group's bulk action — approve its unreviewed rows. */\n approveGroup: (count: number) => string;\n /** 0.25: asked first when a group's bulk approve is larger than a page — more than the\n * reviewer can have had on screen. `group` is the group's name. */\n confirmGroup: (count: number, group: string) => string;\n /** The Undo toast after a swipe bound to `clear` took a verdict back (keksdose, live\n * #377 rework: the swipes bound in its settings) — one string, or a batch of them.\n * The swipe itself is named by {@link reset}. */\n clearedToast: (count: number) => string;\n}\n\nexport const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {\n statusMissing: \"Missing\",\n statusUnreviewed: \"Unreviewed\",\n statusChanged: \"Changed since review\",\n statusNeedsChange: \"Needs a change\",\n statusApproved: \"Approved\",\n statusFilter: \"Status\",\n all: \"All\",\n filterCount: (label, count) => `${label} · ${count}`,\n source: \"Texts\",\n allSources: \"All texts\",\n namespace: \"Area\",\n allNamespaces: \"All areas\",\n search: \"Search keys and text\",\n placeholdersOnly: (count) => `Only placeholder problems (${count})`,\n placeholderChip: \"Placeholders\",\n key: \"Key\",\n statusColumn: \"Status\",\n empty: \"No strings match these filters.\",\n missingText: \"Missing in this language\",\n progress: (approved, total) => `${approved} of ${total} approved`,\n localeProgress: (approved, total) => `${approved}/${total}`,\n locales: \"Languages\",\n approve: \"Approve\",\n flag: \"Needs a change\",\n suggest: \"Suggest translation\",\n flagRow: \"Flag for a change…\",\n suggestRow: \"Add a translation…\",\n reset: \"Mark unreviewed\",\n cancel: \"Cancel\",\n approveSelected: \"Approve selected\",\n resetSelected: \"Mark selected unreviewed\",\n selectShown: \"Select all shown\",\n changedSince: \"This string changed after it was reviewed. Please read it again.\",\n reviewedWording: \"Wording when reviewed\",\n reviewedReference: \"Reference when reviewed\",\n placeholderMismatch: (reference, text) =>\n `The placeholders differ from the reference — reference: ${reference}; this text: ${text}. The app fills these in, so they must stay exactly as they are.`,\n lastApproved: (name, date) => `Approved by ${name} on ${date}`,\n lastFlagged: (name, date) => `Marked as needing a change by ${name} on ${date}`,\n erasedReviewer: \"a deleted account\",\n suggestion: \"Better wording\",\n translation: \"Translation\",\n note: \"Note (optional)\",\n notePlaceholder: \"What is wrong, or what to keep in mind\",\n readOnly: \"You can read this language but not review it.\",\n scope: (areas) => `Your review is limited to: ${areas}.`,\n exportCorrections: (count) => `Export corrections (${count})`,\n failed: \"That did not work. Please try again.\",\n approvedToast: (count) => (count === 1 ? \"String approved\" : `${count} strings approved`),\n groupCount: (unreviewed, total) => `${unreviewed} unreviewed / ${total}`,\n approveGroup: (count) => `Approve unreviewed (${count})`,\n confirmGroup: (count, group) =>\n count === 1\n ? `Approve the unreviewed string in ${group}?`\n : `Approve all ${count} unreviewed strings in ${group}, including those not on screen?`,\n clearedToast: (count) => (count === 1 ? \"String marked unreviewed\" : `${count} strings marked unreviewed`),\n};\n\n/** A status's word, from the namespace's five. */\nexport function reviewStatusLabel(labels: TranslationReviewLabels, status: ReviewStatus): string {\n switch (status) {\n case \"missing\":\n return labels.statusMissing;\n case \"unreviewed\":\n return labels.statusUnreviewed;\n case \"changed\":\n return labels.statusChanged;\n case \"needs_change\":\n return labels.statusNeedsChange;\n case \"approved\":\n return labels.statusApproved;\n }\n}\n"],"mappings":";AA2GO,MAAM,oCAA6D;AAAA,EACxE,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,KAAK;AAAA,EACL,aAAa,CAAC,OAAO,UAAU,GAAG,KAAK,SAAM,KAAK;AAAA,EAClD,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,kBAAkB,CAAC,UAAU,8BAA8B,KAAK;AAAA,EAChE,iBAAiB;AAAA,EACjB,KAAK;AAAA,EACL,cAAc;AAAA,EACd,OAAO;AAAA,EACP,aAAa;AAAA,EACb,UAAU,CAAC,UAAU,UAAU,GAAG,QAAQ,OAAO,KAAK;AAAA,EACtD,gBAAgB,CAAC,UAAU,UAAU,GAAG,QAAQ,IAAI,KAAK;AAAA,EACzD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,qBAAqB,CAAC,WAAW,SAC/B,gEAA2D,SAAS,gBAAgB,IAAI;AAAA,EAC1F,cAAc,CAAC,MAAM,SAAS,eAAe,IAAI,OAAO,IAAI;AAAA,EAC5D,aAAa,CAAC,MAAM,SAAS,iCAAiC,IAAI,OAAO,IAAI;AAAA,EAC7E,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,OAAO,CAAC,UAAU,8BAA8B,KAAK;AAAA,EACrD,mBAAmB,CAAC,UAAU,uBAAuB,KAAK;AAAA,EAC1D,QAAQ;AAAA,EACR,eAAe,CAAC,UAAW,UAAU,IAAI,oBAAoB,GAAG,KAAK;AAAA,EACrE,YAAY,CAAC,YAAY,UAAU,GAAG,UAAU,iBAAiB,KAAK;AAAA,EACtE,cAAc,CAAC,UAAU,uBAAuB,KAAK;AAAA,EACrD,cAAc,CAAC,OAAO,UACpB,UAAU,IACN,oCAAoC,KAAK,MACzC,eAAe,KAAK,0BAA0B,KAAK;AAAA,EACzD,cAAc,CAAC,UAAW,UAAU,IAAI,6BAA6B,GAAG,KAAK;AAC/E;AAGO,SAAS,kBAAkB,QAAiC,QAA8B;AAC/F,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,EAClB;AACF;","names":[]}
|
|
@@ -129,13 +129,23 @@ export interface TranslationReviewPanelProps {
|
|
|
129
129
|
* DataTable's swipe sides (`mobileSwipeActions`) on {@link SwipeableRow}: the same
|
|
130
130
|
* gesture, the same feedback, and the same keyboard path — every swipe is also a real
|
|
131
131
|
* button, read out and reachable by Tab, so the gesture is a shortcut and never the only
|
|
132
|
-
* way. A row has no swipe while
|
|
133
|
-
* under a {@link WriteLockProvider} lock, in a `readOnly` locale, and
|
|
134
|
-
* where there is nothing to approve (a missing string, an approved one).
|
|
135
|
-
* limited to `areas` never sees a row outside them, so never swipes one.
|
|
132
|
+
* way. A row has no swipe while its own write is out (a swipe's, a button's, or the open
|
|
133
|
+
* editor's own), under a {@link WriteLockProvider} lock, in a `readOnly` locale, and
|
|
134
|
+
* toward "Approve" where there is nothing to approve (a missing string, an approved one).
|
|
135
|
+
* A reviewer limited to `areas` never sees a row outside them, so never swipes one.
|
|
136
136
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
137
|
+
* An OPEN row swipes too, on its top line (keksdose live #416, Marcel: *"The swipe shall
|
|
138
|
+
* still be possible since it is faster than scrolling down to the buttons"*), as
|
|
139
|
+
* DataTable's open card does since 0.40: the editor below is outside the gesture, and a
|
|
140
|
+
* swipe neither toggles it shut nor resets it. A swipe acts on the STORED string, as the
|
|
141
|
+
* row's buttons do — Approve approves what the locale says, not the editor's draft — and
|
|
142
|
+
* the editor stays open with whatever was typed in it; "Needs a change" puts the cursor
|
|
143
|
+
* back at the end of that draft.
|
|
144
|
+
*
|
|
145
|
+
* Off by default, so no existing page gains a gesture on the bump. The gesture is the
|
|
146
|
+
* phone's; on a wide screen the binding is the row's actions column instead (0.46,
|
|
147
|
+
* keksdose dev#598): Approve where the row can take it, bound or not, then the bound
|
|
148
|
+
* `edit` and `clear`, each once — under a write lock they stay and say why.
|
|
139
149
|
*
|
|
140
150
|
* A BINDING instead of `true` (keksdose — Marcel's live #377 rework, 2026-10-03: *"Add
|
|
141
151
|
* the swipe options to be shown also to the settings /settings#interaction area where
|
|
@@ -153,8 +163,8 @@ export interface TranslationReviewPanelProps {
|
|
|
153
163
|
* `onClear`). An action a row cannot offer drops out and the ladder closes up behind it
|
|
154
164
|
* ({@link translationReviewSwipePlan}): `approve` on an approved or missing string,
|
|
155
165
|
* `clear` on a row with no verdict. Every rule above still holds for every binding —
|
|
156
|
-
* no side swipes in a `readOnly` locale, under a write lock,
|
|
157
|
-
*
|
|
166
|
+
* no side swipes in a `readOnly` locale, under a write lock, or while the row's write
|
|
167
|
+
* (or its open editor's) is out.
|
|
158
168
|
*
|
|
159
169
|
* `true` is {@link DEFAULT_TRANSLATION_REVIEW_SWIPE} — `{ end: ["approve"], start:
|
|
160
170
|
* ["edit"] }`, 0.25's mapping exactly — and `false` or nothing is no swipe. A binding
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Check, ListChecks, PencilLine, RotateCcw } from "lucide-react";
|
|
5
5
|
import { usePhoneLayout } from "../hooks/use-breakpoint.js";
|
|
@@ -33,6 +33,7 @@ import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translat
|
|
|
33
33
|
import { ReviewStatusChip, TranslationProgress } from "./translation-review-parts.js";
|
|
34
34
|
import { Button } from "./button.js";
|
|
35
35
|
import { IconButton } from "./icon-button.js";
|
|
36
|
+
import { DEFAULT_ROW_ACTIONS_LABELS, rowActionsColumn } from "./row-actions.js";
|
|
36
37
|
import { Select } from "./select.js";
|
|
37
38
|
import { COMMIT_EXCEPT_BILLING, useWriteLock } from "./write-lock.js";
|
|
38
39
|
import {
|
|
@@ -125,6 +126,7 @@ function TranslationReviewPanel({
|
|
|
125
126
|
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
126
127
|
const locale = useKitLocale();
|
|
127
128
|
const lock = useWriteLock(COMMIT_EXCEPT_BILLING);
|
|
129
|
+
const rowActionLabels = useKitLabels("rowActions", DEFAULT_ROW_ACTIONS_LABELS);
|
|
128
130
|
const phone = usePhoneLayout();
|
|
129
131
|
const baseId = useId();
|
|
130
132
|
const rootRef = useRef(null);
|
|
@@ -147,6 +149,19 @@ function TranslationReviewPanel({
|
|
|
147
149
|
const [failure, setFailure] = useState(null);
|
|
148
150
|
const busy = queued.length > 0;
|
|
149
151
|
const isQueued = (key) => queued.includes(key);
|
|
152
|
+
const [editorWrites, setEditorWrites] = useState(() => /* @__PURE__ */ new Set());
|
|
153
|
+
const whileEditorWrites = async (rowId, write) => {
|
|
154
|
+
setEditorWrites((prev) => new Set(prev).add(rowId));
|
|
155
|
+
try {
|
|
156
|
+
await write();
|
|
157
|
+
} finally {
|
|
158
|
+
setEditorWrites((prev) => {
|
|
159
|
+
const next = new Set(prev);
|
|
160
|
+
next.delete(rowId);
|
|
161
|
+
return next;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
150
165
|
const rows = useMemo(
|
|
151
166
|
() => areas ? rowsProp.filter((row) => keyInAreas(row.key, areas, inArea)) : [...rowsProp],
|
|
152
167
|
[rowsProp, areas, inArea]
|
|
@@ -238,16 +253,16 @@ function TranslationReviewPanel({
|
|
|
238
253
|
if (ok && undoOn) sayDone(targets, description);
|
|
239
254
|
return ok;
|
|
240
255
|
};
|
|
241
|
-
const clearRow = async (row) => {
|
|
256
|
+
const clearRow = async (row, origin = "swipe") => {
|
|
242
257
|
if (!onClear || row.review === null) return;
|
|
243
258
|
const clear = onClear;
|
|
244
259
|
const keys = [{ locale: row.locale, key: row.key }];
|
|
245
|
-
const ok = await run(`row:${row.id}`, () => clear(keys, { origin
|
|
260
|
+
const ok = await run(`row:${row.id}`, () => clear(keys, { origin, toasted: undoOn }));
|
|
246
261
|
if (ok && undoOn) sayDone([row], row.key, true);
|
|
247
262
|
};
|
|
248
263
|
const saveFromEditor = async (row, write) => {
|
|
249
264
|
const toasted = undoOn && write.verdict === "APPROVED";
|
|
250
|
-
await onSave?.([write], { origin: "editor", toasted });
|
|
265
|
+
await whileEditorWrites(row.id, () => onSave?.([write], { origin: "editor", toasted }));
|
|
251
266
|
if (toasted) sayDone([row], row.key);
|
|
252
267
|
};
|
|
253
268
|
const keepFocusAt = (groupKey) => {
|
|
@@ -324,7 +339,40 @@ function TranslationReviewPanel({
|
|
|
324
339
|
sortBy: (r) => REVIEW_STATUSES.indexOf(r.status)
|
|
325
340
|
}
|
|
326
341
|
];
|
|
327
|
-
if (editable) {
|
|
342
|
+
if (editable && binding) {
|
|
343
|
+
columns.push(
|
|
344
|
+
rowActionsColumn({
|
|
345
|
+
key: "actions",
|
|
346
|
+
headerText: rowActionLabels.actions,
|
|
347
|
+
name: (r) => r.key,
|
|
348
|
+
tooltipSide: "start",
|
|
349
|
+
actions: (r) => translationReviewSwipePlan(
|
|
350
|
+
{ end: ["approve", ...binding.end ?? [], ...binding.start ?? []] },
|
|
351
|
+
r,
|
|
352
|
+
{ canClear: onClear !== void 0 }
|
|
353
|
+
).end.map((id) => {
|
|
354
|
+
const swipe2 = swipeAction(r, id);
|
|
355
|
+
const writes = id !== "edit";
|
|
356
|
+
return {
|
|
357
|
+
key: id,
|
|
358
|
+
// `edit` opens the editor: its own name, apart from the editor's submit
|
|
359
|
+
// ("Needs a change"), which saves (keksdose B2/S3).
|
|
360
|
+
label: id === "edit" ? r.text === "" ? labels.suggestRow : labels.flagRow : swipe2.label,
|
|
361
|
+
icon: /* @__PURE__ */ jsx(Fragment, { children: swipe2.icon }),
|
|
362
|
+
tone: "muted",
|
|
363
|
+
commit: writes ? COMMIT_EXCEPT_BILLING : void 0,
|
|
364
|
+
pending: writes && isQueued(`row:${r.id}`),
|
|
365
|
+
disabled: busy,
|
|
366
|
+
onSelect: id === "approve" ? () => void approve(`row:${r.id}`, [r], "row", r.key) : id === "clear" ? () => void clearRow(r, "row") : (
|
|
367
|
+
// The row's group stays open while its editor is (`keepGroupOpen`).
|
|
368
|
+
() => swipeAction(r, id, groups?.find((g) => g.rows.includes(r))?.key).onCommit()
|
|
369
|
+
)
|
|
370
|
+
};
|
|
371
|
+
}),
|
|
372
|
+
column: { mobileHidden: true }
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
} else if (editable) {
|
|
328
376
|
columns.push({
|
|
329
377
|
key: "approve",
|
|
330
378
|
header: /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.approve }),
|
|
@@ -353,6 +401,7 @@ function TranslationReviewPanel({
|
|
|
353
401
|
switch (id) {
|
|
354
402
|
case "approve":
|
|
355
403
|
return {
|
|
404
|
+
id,
|
|
356
405
|
label: labels.approve,
|
|
357
406
|
icon: /* @__PURE__ */ jsx(Check, { className: "size-4", "aria-hidden": true }),
|
|
358
407
|
onCommit: () => void approve(`row:${r.id}`, [r], "swipe", r.key),
|
|
@@ -362,6 +411,7 @@ function TranslationReviewPanel({
|
|
|
362
411
|
};
|
|
363
412
|
case "edit":
|
|
364
413
|
return {
|
|
414
|
+
id,
|
|
365
415
|
label: missing ? labels.suggest : labels.flag,
|
|
366
416
|
icon: /* @__PURE__ */ jsx(PencilLine, { className: "size-4", "aria-hidden": true }),
|
|
367
417
|
// Opens the editor rather than sending the string back: "needs a change"
|
|
@@ -370,11 +420,17 @@ function TranslationReviewPanel({
|
|
|
370
420
|
keepGroupOpen(groupKey);
|
|
371
421
|
setOpenId(r.id);
|
|
372
422
|
setFocusId(r.id);
|
|
423
|
+
if (openId === r.id) {
|
|
424
|
+
const field = rootRef.current?.querySelector("[data-review-wording]");
|
|
425
|
+
field?.focus();
|
|
426
|
+
field?.setSelectionRange(field.value.length, field.value.length);
|
|
427
|
+
}
|
|
373
428
|
},
|
|
374
429
|
tone: missing ? "brand" : "danger"
|
|
375
430
|
};
|
|
376
431
|
case "clear":
|
|
377
432
|
return {
|
|
433
|
+
id,
|
|
378
434
|
label: labels.reset,
|
|
379
435
|
icon: /* @__PURE__ */ jsx(RotateCcw, { className: "size-4", "aria-hidden": true }),
|
|
380
436
|
onCommit: () => void clearRow(r),
|
|
@@ -386,7 +442,7 @@ function TranslationReviewPanel({
|
|
|
386
442
|
}
|
|
387
443
|
};
|
|
388
444
|
const swipeActions = (r, groupKey) => {
|
|
389
|
-
if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`)) return null;
|
|
445
|
+
if (!binding || !editable || lock.locked || isQueued(`row:${r.id}`) || editorWrites.has(r.id)) return null;
|
|
390
446
|
const plan = translationReviewSwipePlan(binding, r, { canClear: onClear !== void 0 });
|
|
391
447
|
if (plan.start.length === 0 && plan.end.length === 0) return null;
|
|
392
448
|
return {
|
|
@@ -449,7 +505,7 @@ function TranslationReviewPanel({
|
|
|
449
505
|
readOnly: !editable,
|
|
450
506
|
focusWording: focusId === r.id,
|
|
451
507
|
onSave: onSave ? (write) => saveFromEditor(r, write) : void 0,
|
|
452
|
-
onClear: onClear ? (key) => onClear([key], { origin: "editor", toasted: false }) : void 0,
|
|
508
|
+
onClear: onClear ? (key) => whileEditorWrites(r.id, () => onClear([key], { origin: "editor", toasted: false })) : void 0,
|
|
453
509
|
onClose: () => {
|
|
454
510
|
setOpenId(null);
|
|
455
511
|
setFocusId(null);
|
|
@@ -469,6 +525,7 @@ function TranslationReviewPanel({
|
|
|
469
525
|
] })
|
|
470
526
|
] }),
|
|
471
527
|
mobileSwipeActions: binding ? (r) => swipeActions(r, group?.key) : void 0,
|
|
528
|
+
desktopSwipeActions: false,
|
|
472
529
|
selection: selectionFor(list)
|
|
473
530
|
}
|
|
474
531
|
);
|