@antelopejs/dms 0.7.18 → 0.7.20

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.20
4
+
5
+ [compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.19...v0.7.20)
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **table-view:** Reopen a record link to a closed row and keep chips from restoring it ([#249](https://github.com/AntelopeJS/dms/pull/249))
10
+
11
+ ### ❤️ Contributors
12
+
13
+ - Antony Rizzitelli <rizzitelli.antony@pm.me>
14
+
15
+ ## v0.7.19
16
+
17
+ [compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.18...v0.7.19)
18
+
3
19
  ## v0.7.18
4
20
 
5
21
  [compare changes](https://github.com/AntelopeJS/dms/compare/v0.7.17...v0.7.18)
@@ -110,6 +110,11 @@ export interface ActionTargetsConfig {
110
110
  rowNavigation?: RowNavigationSource;
111
111
  /** Where a `deepLink` action writes the row it has open in the URL. */
112
112
  recordScope?: TableUrlScope;
113
+ /**
114
+ * Told the row a `deepLink` action shows each time it writes it in the URL,
115
+ * and `undefined` once its container closed and removed it.
116
+ */
117
+ onRecordShown?: (id?: string) => void;
113
118
  /**
114
119
  * The field a row's id is read from (`_id` by default): it fills `{id}` in
115
120
  * the `from` URL of a row action's confirmation, as on a built-in action.
@@ -326,11 +331,12 @@ export function useActionTargets(config: ActionTargetsConfig) {
326
331
  return (id?: string): void => {
327
332
  if (!deepLink || !config.recordScope || !config.rowNavigation) return;
328
333
  if (!id) {
329
- clearWrittenRecord(written);
334
+ if (clearWrittenRecord(written)) config.onRecordShown?.(undefined);
330
335
  written = undefined;
331
336
  return;
332
337
  }
333
338
  written = writeRecordId(config.recordScope, id);
339
+ config.onRecordShown?.(id);
334
340
  };
335
341
  };
336
342
  const rowRecorder = (deepLink?: boolean) => {
@@ -0,0 +1,93 @@
1
+ import { router as inertiaRouter } from "@inertiajs/vue3";
2
+ import { onBeforeUnmount, onMounted, watch } from "vue";
3
+ import type { CustomRowAction } from "../../../types/row-action";
4
+ import {
5
+ deepLinkOutcome,
6
+ type DeepLinkedRow,
7
+ readRecordId,
8
+ writeRecordId,
9
+ } from "./utils/recordLink";
10
+ import { queryOfUrl } from "./utils/urlFilters";
11
+ import type { TableUrlScope } from "./utils/views";
12
+
13
+ /** What `useTableRecordLink` follows the URL's `?record=` with. */
14
+ export interface TableRecordLinkOptions<T> {
15
+ route: { query: Record<string, unknown> };
16
+ urlScope: TableUrlScope;
17
+ /** The table's `deepLink` action: the container a link opens. */
18
+ action: CustomRowAction;
19
+ /** Finds the row a link names, once the list is loaded. */
20
+ resolve: (id: string) => Promise<DeepLinkedRow<T>>;
21
+ /** Opens the row found in the action's container. */
22
+ open: (row: T) => void;
23
+ /** Opens a side panel's own "no longer available" state on the id. */
24
+ showMissing: (id: string) => void;
25
+ /** Tells the user the link led nowhere, or could not be followed. */
26
+ notify: (outcome: "notify" | "notifyFailed") => void;
27
+ }
28
+
29
+ /** What the table tells the link about the row its container shows. */
30
+ export interface TableRecordLink {
31
+ /**
32
+ * The row the action's container shows, as written in the URL, or
33
+ * `undefined` once it closed: a link to the row shown opens nothing again,
34
+ * a link to a row closed reopens it.
35
+ */
36
+ shown: (id?: string) => void;
37
+ }
38
+
39
+ /**
40
+ * Opens the row the URL's `?record=` names: on arrival, and on each visit
41
+ * that keeps the page mounted (a link to the same page naming a row, back and
42
+ * forward), as a fresh load would. The visit is followed rather than the
43
+ * route's value: the table writes and removes the key in place, so a link to
44
+ * a row closed meanwhile leaves the route's value unchanged. A link naming no
45
+ * row removes the key and tells the user.
46
+ */
47
+ export function useTableRecordLink<T>(
48
+ options: TableRecordLinkOptions<T>,
49
+ ): TableRecordLink {
50
+ const { action, urlScope } = options;
51
+ // The row opened or being opened: arrival and the visit that mounted the
52
+ // table name it twice, a visit while it is open names it again.
53
+ let linkedId: string | undefined;
54
+
55
+ const settle = async (id: string) => {
56
+ const found = await options.resolve(id);
57
+ const outcome = deepLinkOutcome(found, action);
58
+ if (outcome === "open") return options.open(found.row as T);
59
+ if (outcome === "missingPanel") return options.showMissing(id);
60
+ writeRecordId(urlScope);
61
+ if (linkedId === id) linkedId = undefined;
62
+ options.notify(outcome);
63
+ };
64
+
65
+ const follow = (query: Record<string, unknown>) => {
66
+ const id = readRecordId(query, urlScope);
67
+ if (!id || id === linkedId) return;
68
+ linkedId = id;
69
+ void settle(id);
70
+ };
71
+
72
+ let stopFollowingVisits: (() => void) | undefined;
73
+ onMounted(() => {
74
+ follow(options.route.query);
75
+ stopFollowingVisits = inertiaRouter.on("navigate", (event) =>
76
+ follow(queryOfUrl(event.detail.page.url)),
77
+ );
78
+ });
79
+ onBeforeUnmount(() => stopFollowingVisits?.());
80
+
81
+ // A visit Inertia does not announce (a `router.replace`) still moves the
82
+ // route: a new value there is followed as well.
83
+ watch(
84
+ () => readRecordId(options.route.query, urlScope),
85
+ () => follow(options.route.query),
86
+ );
87
+
88
+ return {
89
+ shown: (id) => {
90
+ linkedId = id;
91
+ },
92
+ };
93
+ }
@@ -76,6 +76,8 @@ interface TableRowActionsConfig {
76
76
  rowNavigation?: RowNavigationSource;
77
77
  /** Where a `deepLink` action writes its open row in the URL. */
78
78
  recordScope?: TableUrlScope;
79
+ /** Told the row a `deepLink` action shows, `undefined` once closed. */
80
+ onRecordShown?: (id?: string) => void;
79
81
  }
80
82
 
81
83
  interface BulkActionConfig {
@@ -814,6 +816,7 @@ export const useTableRowActions = <T extends Data>(
814
816
  handleApiError,
815
817
  rowNavigation: config.rowNavigation,
816
818
  recordScope: config.recordScope,
819
+ onRecordShown: config.onRecordShown,
817
820
  rowIdKey: config.rowIdKey,
818
821
  });
819
822
 
@@ -58,15 +58,18 @@ export interface WrittenRecord {
58
58
  /**
59
59
  * Removes the row a table wrote in the URL, only while the URL still holds
60
60
  * it: a container closing as the user leaves the page must not erase the
61
- * `?record=` of the page reached.
61
+ * `?record=` of the page reached. Returns whether it removed it.
62
62
  */
63
- export function clearWrittenRecord(written: WrittenRecord | undefined): void {
64
- if (typeof window === "undefined" || !written) return;
63
+ export function clearWrittenRecord(
64
+ written: WrittenRecord | undefined,
65
+ ): boolean {
66
+ if (typeof window === "undefined" || !written) return false;
65
67
  const url = new URL(window.location.href);
66
- if (url.pathname !== written.pathname) return;
67
- if (url.searchParams.get(written.key) !== written.id) return;
68
+ if (url.pathname !== written.pathname) return false;
69
+ if (url.searchParams.get(written.key) !== written.id) return false;
68
70
  url.searchParams.delete(written.key);
69
71
  window.history.replaceState(window.history.state, "", url);
72
+ return true;
70
73
  }
71
74
 
72
75
  /** Where a deep link finds its row: among the rows listed, or by id. */
@@ -27,3 +27,21 @@ export function withoutQueryParam<Query extends Record<string, unknown>>(
27
27
  const { [param]: _removed, ...rest } = query;
28
28
  return rest as Query;
29
29
  }
30
+
31
+ /** The query of a URL, absolute or relative to the page, one value per key. */
32
+ export function queryOfUrl(url: string): Record<string, string> {
33
+ return Object.fromEntries(new URL(url, "http://dms.invalid").searchParams);
34
+ }
35
+
36
+ /**
37
+ * The query the browser shows, else the route's on a server render. A table
38
+ * writes some keys in place (the open `?record=`, removed on close), so the
39
+ * route, which follows the last visit, can still hold a key the browser no
40
+ * longer shows: a visit rebuilt from it would bring the key back.
41
+ */
42
+ export function currentQuery(
43
+ routeQuery: Record<string, unknown>,
44
+ ): Record<string, unknown> {
45
+ if (typeof window === "undefined") return routeQuery;
46
+ return queryOfUrl(window.location.href);
47
+ }
@@ -78,12 +78,8 @@ import { selectedRowIds } from "../../build/composables/table-view/utils/bulkAct
78
78
  import { isBulkAction } from "../../build/composables/actions/bulkSelection";
79
79
  import { selectionScopeKey } from "../../build/utils/selectionScope";
80
80
  import {
81
- type DeepLinkOutcome,
82
- deepLinkOutcome,
83
81
  isDeepLinkAction,
84
- readRecordId,
85
82
  resolveDeepLinkedRow,
86
- writeRecordId,
87
83
  } from "../../build/composables/table-view/utils/recordLink";
88
84
  import {
89
85
  rowSidePanelOwnerKey,
@@ -119,6 +115,7 @@ import type {
119
115
  TableViewSwitcherItem,
120
116
  } from "../../build/components/table/Table.vue";
121
117
  import {
118
+ currentQuery,
122
119
  visibleUrlFilters,
123
120
  withoutQueryParam,
124
121
  } from "../../build/composables/table-view/utils/urlFilters";
@@ -155,6 +152,7 @@ import {
155
152
  readTableUrlTab,
156
153
  useTableUrlTab,
157
154
  } from "../../build/composables/table-view/useTableUrlTab";
155
+ import { useTableRecordLink } from "../../build/composables/table-view/useTableRecordLink";
158
156
  import TableViews, {
159
157
  type TableViewItem,
160
158
  } from "../../build/components/table/Views.vue";
@@ -411,9 +409,11 @@ const shownUrlFilters = computed<TableUrlFilter[]>(() =>
411
409
  visibleUrlFilters(queryParamFilters, route.query as Record<string, unknown>),
412
410
  );
413
411
 
412
+ // Rebuilt from the URL the browser shows: a `?record=` a closed drawer
413
+ // removed in place must not come back with the visit.
414
414
  const removeUrlFilter = (param: string) =>
415
415
  router.replace({
416
- query: withoutQueryParam(route.query, param),
416
+ query: withoutQueryParam(currentQuery(route.query), param),
417
417
  });
418
418
 
419
419
  const routeParamHiddenFilters = computed<TableFilter[]>(() => {
@@ -1298,6 +1298,7 @@ const {
1298
1298
  rowIdKey: props.rowIdKey ?? ROW_ID_DEFAULT_KEY,
1299
1299
  },
1300
1300
  recordScope: urlScope,
1301
+ onRecordShown: (id) => recordLink?.shown(id),
1301
1302
  });
1302
1303
 
1303
1304
  // Custom actions offered on the selection bar. "Select all N matching"
@@ -1704,59 +1705,40 @@ watch(rowSelect, (selection) => {
1704
1705
  const deepLinkAction = (tableProps.value.rowActions?.custom ?? []).find(
1705
1706
  isDeepLinkAction,
1706
1707
  );
1707
- const openRecordFromUrl = async () => {
1708
- const id = deepLinkAction ? readRecordId(route.query, urlScope) : undefined;
1709
- if (!deepLinkAction || !id) return;
1710
- await until(isListLoading).toBe(false);
1711
- const { row, isMissing } = await resolveDeepLinkedRow(id, {
1712
- listed: shownResults.value ?? undefined,
1713
- idOf: getRowId,
1714
- getRow: tableRows.getRow,
1715
- });
1716
- const outcome = deepLinkOutcome({ row, isMissing }, deepLinkAction);
1717
- deepLinkOutcomes[outcome](deepLinkAction, id, row as Data);
1718
- };
1708
+ const recordLink = deepLinkAction
1709
+ ? useTableRecordLink<T>({
1710
+ route,
1711
+ urlScope,
1712
+ action: deepLinkAction,
1713
+ resolve: async (id) => {
1714
+ await until(isListLoading).toBe(false);
1715
+ return resolveDeepLinkedRow(id, {
1716
+ listed: shownResults.value ?? undefined,
1717
+ idOf: getRowId,
1718
+ getRow: tableRows.getRow,
1719
+ });
1720
+ },
1721
+ open: (row) => handleCustomRowAction(deepLinkAction, row as Data),
1722
+ showMissing: (id) => handleMissingRow(deepLinkAction, id),
1723
+ notify: (outcome) => toast.add(recordLinkToasts[outcome]()),
1724
+ })
1725
+ : undefined;
1719
1726
 
1720
- const notifyUnresolvedRecordLink = () => {
1721
- writeRecordId(urlScope);
1722
- toast.add({
1727
+ const recordLinkToasts = {
1728
+ notify: () => ({
1723
1729
  title: t("dms.record_link.not_found_title"),
1724
1730
  description: t("dms.record_link.not_found_description"),
1725
1731
  color: Color.warning,
1726
1732
  icon: "i-ph-link-break",
1727
- });
1728
- };
1729
-
1730
- const notifyFailedRecordLink = () => {
1731
- writeRecordId(urlScope);
1732
- toast.add({
1733
+ }),
1734
+ notifyFailed: () => ({
1733
1735
  title: t("dms.record_link.failed_title"),
1734
1736
  description: t("dms.record_link.failed_description"),
1735
1737
  color: Color.error,
1736
1738
  icon: "i-ph-warning-circle",
1737
- });
1738
- };
1739
-
1740
- const deepLinkOutcomes: Record<
1741
- DeepLinkOutcome,
1742
- (action: CustomRowAction, id: string, row: Data) => void
1743
- > = {
1744
- open: (action, _id, row) => handleCustomRowAction(action, row),
1745
- missingPanel: (action, id) => handleMissingRow(action, id),
1746
- notify: notifyUnresolvedRecordLink,
1747
- notifyFailed: notifyFailedRecordLink,
1739
+ }),
1748
1740
  };
1749
1741
 
1750
- // A visit that keeps the page mounted (a link to the same page with another
1751
- // `?record=`) opens the row it names, as a fresh load would. The table's own
1752
- // writes replace the URL in place and never reach the route.
1753
- watch(
1754
- () => readRecordId(route.query, urlScope),
1755
- (id, previous) => {
1756
- if (id && id !== previous) void openRecordFromUrl();
1757
- },
1758
- );
1759
-
1760
1742
  const selectedIds = computed(() => selectedRowIds(rowSelect.value));
1761
1743
 
1762
1744
  const isRowActionEnabled = (
@@ -2086,7 +2068,6 @@ const checkUrlParameter = () => {
2086
2068
 
2087
2069
  onMounted(() => {
2088
2070
  checkUrlParameter();
2089
- void openRecordFromUrl();
2090
2071
  void loadRelationQuickFilters();
2091
2072
  void refreshLinkTabCounts();
2092
2073
  // The registry is client-only; once hydrated, degrade to the table display if
@@ -271,9 +271,11 @@ describe("action targets on a selection and on a listed row", () => {
271
271
  it("steps a row's drawer through the rows shown and keeps it in the URL", async () => {
272
272
  window.history.replaceState(null, "", "/runs");
273
273
  const rows = [{ _id: "r1" }, { _id: "r2" }];
274
+ const onRecordShown = vi.fn();
274
275
  const targets = mountTargets({
275
276
  rowNavigation: { rows: () => rows, rowIdKey: "_id" },
276
277
  recordScope: { tableId: "runs", isSoleTableView: true },
278
+ onRecordShown,
277
279
  });
278
280
  targets.handleCustomRowAction(
279
281
  {
@@ -306,6 +308,7 @@ describe("action targets on a selection and on a listed row", () => {
306
308
  await nextTick();
307
309
  await Promise.resolve();
308
310
  expect(window.location.search).toBe("");
311
+ expect(onRecordShown.mock.calls).toEqual([["r1"], ["r2"], [undefined]]);
309
312
  window.dispatchEvent(new KeyboardEvent("keydown", { key: "k" }));
310
313
  expect(drawer!.patches).toHaveLength(1);
311
314
  });
@@ -0,0 +1,207 @@
1
+ // @vitest-environment jsdom
2
+ /**
3
+ * A table with a `deepLink` action opens the row `?record=` names on arrival
4
+ * and on each visit that keeps the page mounted, including a link to a row
5
+ * closed meanwhile: the table removes the key in place, so the route still
6
+ * holds the same value and only the visit tells the link was followed again.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { createApp, defineComponent, h, nextTick, reactive } from "vue";
11
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
12
+ import type { CustomRowAction } from "../layers/dms-ui/app/types/row-action";
13
+ import type { DeepLinkedRow } from "../layers/dms-ui/app/build/composables/table-view/utils/recordLink";
14
+ import {
15
+ clearWrittenRecord,
16
+ writeRecordId,
17
+ } from "../layers/dms-ui/app/build/composables/table-view/utils/recordLink";
18
+ import {
19
+ currentQuery,
20
+ withoutQueryParam,
21
+ } from "../layers/dms-ui/app/build/composables/table-view/utils/urlFilters";
22
+
23
+ type NavigateListener = (event: { detail: { page: { url: string } } }) => void;
24
+ const navigateListeners = new Set<NavigateListener>();
25
+
26
+ vi.mock("@inertiajs/vue3", () => ({
27
+ router: {
28
+ on: (type: string, listener: NavigateListener) => {
29
+ if (type !== "navigate") return () => {};
30
+ navigateListeners.add(listener);
31
+ return () => navigateListeners.delete(listener);
32
+ },
33
+ },
34
+ }));
35
+
36
+ const { useTableRecordLink } = await import(
37
+ "../layers/dms-ui/app/build/composables/table-view/useTableRecordLink"
38
+ );
39
+
40
+ interface Row {
41
+ id: string;
42
+ }
43
+
44
+ const ROWS: Row[] = [{ id: "r1" }, { id: "r2" }];
45
+ const SCOPE = { tableId: "sends", isSoleTableView: true };
46
+ const DRAWER = {
47
+ id: "details",
48
+ deepLink: true,
49
+ target: { type: "drawer" },
50
+ } as unknown as CustomRowAction;
51
+
52
+ const mounted: Array<{ unmount: () => void }> = [];
53
+
54
+ const flush = () => new Promise<void>((done) => setTimeout(done));
55
+
56
+ /** A visit of the page: Inertia pushes its URL, then announces it. */
57
+ async function visit(route: { query: Record<string, unknown> }, url: string) {
58
+ window.history.pushState({}, "", url);
59
+ route.query = Object.fromEntries(new URL(url, "http://x").searchParams);
60
+ await nextTick();
61
+ navigateListeners.forEach((listener) =>
62
+ listener({ detail: { page: { url } } }),
63
+ );
64
+ await flush();
65
+ }
66
+
67
+ function mountTable(url: string) {
68
+ window.history.replaceState({}, "", url);
69
+ const route = reactive({
70
+ query: Object.fromEntries(new URL(url, "http://x").searchParams) as Record<
71
+ string,
72
+ unknown
73
+ >,
74
+ });
75
+ const opened: string[] = [];
76
+ const notify = vi.fn();
77
+ let link: { shown: (id?: string) => void } | undefined;
78
+ const app = createApp(
79
+ defineComponent({
80
+ setup() {
81
+ link = useTableRecordLink<Row>({
82
+ route,
83
+ urlScope: SCOPE,
84
+ action: DRAWER,
85
+ resolve: async (id): Promise<DeepLinkedRow<Row>> => {
86
+ const row = ROWS.find((candidate) => candidate.id === id);
87
+ return row ? { row, isMissing: false } : { isMissing: true };
88
+ },
89
+ // The drawer writes the row it opens, as the action's targets do.
90
+ open: (row) => {
91
+ opened.push(row.id);
92
+ writeRecordId(SCOPE, row.id);
93
+ link?.shown(row.id);
94
+ },
95
+ showMissing: () => {},
96
+ notify,
97
+ });
98
+ return () => h("div");
99
+ },
100
+ }),
101
+ );
102
+ app.mount(document.createElement("div"));
103
+ mounted.push(app);
104
+ // Closing the drawer removes the row from the URL in place.
105
+ const close = () => {
106
+ writeRecordId(SCOPE);
107
+ link?.shown(undefined);
108
+ };
109
+ return { route, opened, notify, close };
110
+ }
111
+
112
+ beforeEach(() => navigateListeners.clear());
113
+
114
+ afterEach(() => {
115
+ mounted.splice(0).forEach((app) => app.unmount());
116
+ window.history.replaceState({}, "", "/");
117
+ });
118
+
119
+ describe("a record link on a mounted table", () => {
120
+ it("opens the row a link names on arrival, once", async () => {
121
+ const { route, opened } = mountTable("/sends?record=r1");
122
+ await flush();
123
+ // The visit that mounted the table is announced after it mounted.
124
+ navigateListeners.forEach((listener) =>
125
+ listener({ detail: { page: { url: "/sends?record=r1" } } }),
126
+ );
127
+ await flush();
128
+ expect(opened).toEqual(["r1"]);
129
+ expect(route.query.record).toBe("r1");
130
+ });
131
+
132
+ it("opens the row a visit to the same page names", async () => {
133
+ const { route, opened } = mountTable("/sends?record=r1");
134
+ await flush();
135
+ await visit(route, "/sends?record=r2");
136
+ expect(opened).toEqual(["r1", "r2"]);
137
+ expect(window.location.search).toBe("?record=r2");
138
+ });
139
+
140
+ it("opens the same row again after its drawer closed", async () => {
141
+ const { route, opened, close } = mountTable("/sends?record=r1");
142
+ await flush();
143
+ // A link to the row shown opens nothing more.
144
+ await visit(route, "/sends?record=r1");
145
+ expect(opened).toEqual(["r1"]);
146
+
147
+ close();
148
+ expect(window.location.search).toBe("");
149
+ await visit(route, "/sends?record=r1");
150
+ expect(opened).toEqual(["r1", "r1"]);
151
+ });
152
+
153
+ it("tells the user a link names no row and removes the key", async () => {
154
+ const { route, opened, notify } = mountTable("/sends?tab=all&record=gone");
155
+ await flush();
156
+ expect(opened).toEqual([]);
157
+ expect(notify).toHaveBeenCalledWith("notify");
158
+ expect(window.location.search).toBe("?tab=all");
159
+
160
+ // Following the same link again tells it again.
161
+ await visit(route, "/sends?tab=all&record=gone");
162
+ expect(notify).toHaveBeenCalledTimes(2);
163
+ });
164
+
165
+ it("stops following visits once unmounted", async () => {
166
+ const { route, opened } = mountTable("/sends");
167
+ mounted.splice(0).forEach((app) => app.unmount());
168
+ await visit(route, "/sends?record=r2");
169
+ expect(opened).toEqual([]);
170
+ });
171
+ });
172
+
173
+ describe("the URL a closed drawer leaves", () => {
174
+ it("removes only the row this drawer wrote, and says so", () => {
175
+ window.history.replaceState({}, "", "/sends");
176
+ const written = writeRecordId(SCOPE, "r1");
177
+ writeRecordId(SCOPE, "r2");
178
+ expect(clearWrittenRecord(written)).toBe(false);
179
+ expect(window.location.search).toBe("?record=r2");
180
+ expect(clearWrittenRecord(writeRecordId(SCOPE, "r2"))).toBe(true);
181
+ expect(window.location.search).toBe("");
182
+ });
183
+
184
+ it("rebuilds a removed filter's visit from the URL the browser shows", () => {
185
+ window.history.replaceState({}, "", "/sends?email=a%40b.test&tab=all");
186
+ // The route still holds the row of the drawer closed in place.
187
+ const routeQuery = { email: "a@b.test", tab: "all", record: "r1" };
188
+ expect(withoutQueryParam(currentQuery(routeQuery), "email")).toEqual({
189
+ tab: "all",
190
+ });
191
+ });
192
+ });
193
+
194
+ it("wires the record link and the filter chips into the table view", () => {
195
+ const source = readFileSync(
196
+ resolve(
197
+ __dirname,
198
+ "../layers/dms-ui/app/components/table-view/TableView.vue",
199
+ ),
200
+ "utf8",
201
+ );
202
+ expect(source).toContain("useTableRecordLink<T>({");
203
+ expect(source).toContain("onRecordShown: (id) => recordLink?.shown(id),");
204
+ expect(source).toContain(
205
+ "withoutQueryParam(currentQuery(route.query), param)",
206
+ );
207
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.7.18",
3
+ "version": "0.7.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/AntelopeJS/dms.git"
@@ -37,7 +37,7 @@
37
37
  "@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
38
38
  "@antelopejs/interface-database": ">=0.1.8 <1.0.0",
39
39
  "@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
40
- "@antelopejs/interface-dms": ">=0.5.11 <0.6.0",
40
+ "@antelopejs/interface-dms": ">=0.5.12 <0.6.0",
41
41
  "@antelopejs/interface-email": ">=0.0.5 <1.0.0",
42
42
  "@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
43
43
  "archiver": "^7.0.1",