@teipublisher/pb-components 3.1.0 → 3.2.1

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.
@@ -10924,6 +10924,11 @@
10924
10924
  "name": "height",
10925
10925
  "type": "string"
10926
10926
  },
10927
+ {
10928
+ "name": "visible-columns",
10929
+ "description": "Optional list of column ids/properties to show. If empty or undefined, all columns are visible.",
10930
+ "type": "array"
10931
+ },
10927
10932
  {
10928
10933
  "name": "subscribe",
10929
10934
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -11012,6 +11017,12 @@
11012
11017
  "type": "boolean",
11013
11018
  "default": "false"
11014
11019
  },
11020
+ {
11021
+ "name": "visibleColumns",
11022
+ "attribute": "visible-columns",
11023
+ "description": "Optional list of column ids/properties to show. If empty or undefined, all columns are visible.",
11024
+ "type": "array"
11025
+ },
11015
11026
  {
11016
11027
  "name": "subscribe",
11017
11028
  "attribute": "subscribe",
@@ -126,6 +126,23 @@
126
126
  "content": "Inhalte durchsuchen",
127
127
  "scope": "Suchbereich"
128
128
  },
129
+ "tableGrid": {
130
+ "search": "Suche",
131
+ "searchPlaceholder": "Schlüsselwort eingeben...",
132
+ "sortAsc": "Spalte aufsteigend sortieren",
133
+ "sortDesc": "Spalte absteigend sortieren",
134
+ "previous": "Vorherige",
135
+ "next": "Nächste",
136
+ "navigate": "Seite {{page}} von {{pages}}",
137
+ "page": "Seite {{page}}",
138
+ "showing": "Anzeigen",
139
+ "to": "zu",
140
+ "of": "von",
141
+ "results": "Ergebnisse",
142
+ "loading": "Laden...",
143
+ "noRecordsFound": "Keine passenden Einträge gefunden",
144
+ "error": "Ein Fehler ist aufgetreten, während die Daten abgerufen wurden"
145
+ },
129
146
  "dts": {
130
147
  "endpoint": "Server wählen",
131
148
  "note": "Experimentell: Zugriff auf externe Editionen über DTS API",
@@ -126,6 +126,23 @@
126
126
  "content": "Search content",
127
127
  "scope": "Query scope"
128
128
  },
129
+ "tableGrid": {
130
+ "search": "Search",
131
+ "searchPlaceholder": "Type a keyword...",
132
+ "sortAsc": "Sort column ascending",
133
+ "sortDesc": "Sort column descending",
134
+ "previous": "Previous",
135
+ "next": "Next",
136
+ "navigate": "Page {{page}} of {{pages}}",
137
+ "page": "Page {{page}}",
138
+ "showing": "Showing",
139
+ "to": "to",
140
+ "of": "of",
141
+ "results": "results",
142
+ "loading": "Loading...",
143
+ "noRecordsFound": "No matching records found",
144
+ "error": "An error occurred while fetching data"
145
+ },
129
146
  "dts": {
130
147
  "endpoint": "Select Endpoint",
131
148
  "note": "Experimental: access other editions which expose the DTS API",
@@ -126,6 +126,23 @@
126
126
  "content": "Przeszukaj zawartość",
127
127
  "scope": "Obszar kwerendy"
128
128
  },
129
+ "tableGrid": {
130
+ "search": "Szukaj",
131
+ "searchPlaceholder": "Wpisz słowo kluczowe...",
132
+ "sortAsc": "Sortuj kolumnę rosnąco",
133
+ "sortDesc": "Sortuj kolumnę malejąco",
134
+ "previous": "Poprzednia",
135
+ "next": "Następna",
136
+ "navigate": "Strona {{page}} z {{pages}}",
137
+ "page": "Strona {{page}}",
138
+ "showing": "Wyświetlanie",
139
+ "to": "do",
140
+ "of": "z",
141
+ "results": "wyników",
142
+ "loading": "Ładowanie...",
143
+ "noRecordsFound": "Brak pasujących rekordów",
144
+ "error": "Wystąpił błąd podczas pobierania danych"
145
+ },
129
146
  "dts": {
130
147
  "endpoint": "Wybierz Endpoint",
131
148
  "note": "Wersja eksperymentalna: uzyskaj dostęp do edycji, które udostępniają API DTS",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teipublisher/pb-components",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "Collection of webcomponents underlying TEI Publisher",
5
5
  "repository": "https://github.com/eeditiones/tei-publisher-components.git",
6
6
  "main": "index.html",
package/pb-elements.json CHANGED
@@ -10924,6 +10924,11 @@
10924
10924
  "name": "height",
10925
10925
  "type": "string"
10926
10926
  },
10927
+ {
10928
+ "name": "visible-columns",
10929
+ "description": "Optional list of column ids/properties to show. If empty or undefined, all columns are visible.",
10930
+ "type": "array"
10931
+ },
10927
10932
  {
10928
10933
  "name": "subscribe",
10929
10934
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -11012,6 +11017,12 @@
11012
11017
  "type": "boolean",
11013
11018
  "default": "false"
11014
11019
  },
11020
+ {
11021
+ "name": "visibleColumns",
11022
+ "attribute": "visible-columns",
11023
+ "description": "Optional list of column ids/properties to show. If empty or undefined, all columns are visible.",
11024
+ "type": "array"
11025
+ },
11015
11026
  {
11016
11027
  "name": "subscribe",
11017
11028
  "attribute": "subscribe",
@@ -3,13 +3,9 @@ import { Grid, PluginPosition } from 'gridjs';
3
3
  import { pbMixin, waitOnce } from './pb-mixin.js';
4
4
  import { resolveURL } from './utils.js';
5
5
  import { importStyles, loadStylesheets, themableMixin } from './theming.js';
6
- import '@polymer/paper-input/paper-input';
7
- import '@polymer/iron-icons';
8
- import '@polymer/iron-form';
9
- import '@polymer/paper-icon-button';
10
6
  import './pb-table-column.js';
11
7
  import { registry } from './urls.js';
12
- import { translate } from './pb-i18n.js';
8
+ import { get as i18n, translate } from './pb-i18n.js';
13
9
 
14
10
  /**
15
11
  * A table grid based on [gridjs](https://gridjs.io/), which loads its data from a server endpoint
@@ -86,6 +82,13 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
86
82
  type: Boolean,
87
83
  attribute: 'pagination-top',
88
84
  },
85
+ /**
86
+ * Optional list of column ids/properties to show. If empty or undefined, all columns are visible.
87
+ */
88
+ visibleColumns: {
89
+ type: Array,
90
+ attribute: 'visible-columns',
91
+ },
89
92
  _params: {
90
93
  type: Object,
91
94
  },
@@ -103,10 +106,31 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
103
106
  this.perPage = 10;
104
107
  this.height = null;
105
108
  this.fixedHeader = false;
109
+ this.visibleColumns = null;
110
+ this._pbColumns = [];
111
+ this._columns = [];
112
+ this._selectedRow = null;
113
+ this._gridI18nInitialized = false;
114
+ this._onTableClick = this._onTableClick.bind(this);
115
+ this._onDocumentClick = this._onDocumentClick.bind(this);
116
+ }
117
+
118
+ _applyPaginationPosition() {
119
+ if (!this.grid || !this.grid.plugin) {
120
+ return;
121
+ }
122
+ const paginationPlugin = this.grid.plugin.get('pagination');
123
+ if (!paginationPlugin) {
124
+ return;
125
+ }
126
+ paginationPlugin.position = this.paginationTop
127
+ ? PluginPosition.Header
128
+ : PluginPosition.Footer;
106
129
  }
107
130
 
108
131
  async connectedCallback() {
109
132
  super.connectedCallback();
133
+ document.addEventListener('click', this._onDocumentClick);
110
134
 
111
135
  this.subscribeTo('pb-search-resubmit', ev => {
112
136
  this._submit();
@@ -120,10 +144,15 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
120
144
  this.subscribeTo(
121
145
  'pb-i18n-update',
122
146
  ev => {
123
- const needsRefresh = this.language && this.language !== ev.detail.language;
147
+ const needsRefresh = this.language !== ev.detail.language;
124
148
  this.language = ev.detail.language;
125
- if (needsRefresh) {
126
- this._submit();
149
+ const needsInitialI18nRefresh = !this._gridI18nInitialized;
150
+ if ((needsRefresh || needsInitialI18nRefresh) && this.grid) {
151
+ this._gridI18nInitialized = true;
152
+ if (needsRefresh) {
153
+ this._applyPaginationPosition();
154
+ this._submit();
155
+ }
127
156
  }
128
157
  },
129
158
  [],
@@ -151,12 +180,17 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
151
180
  this.shadowRoot.adoptedStyleSheets = sheets;
152
181
  }
153
182
 
183
+ disconnectedCallback() {
184
+ document.removeEventListener('click', this._onDocumentClick);
185
+ super.disconnectedCallback();
186
+ }
187
+
154
188
  firstUpdated() {
155
189
  const table = this.shadowRoot.getElementById('table');
190
+ table.addEventListener('click', this._onTableClick);
156
191
 
157
- const pbColumns = this.querySelectorAll('pb-table-column');
158
- const columns = [];
159
- pbColumns.forEach(column => columns.push(column.data()));
192
+ this._pbColumns = Array.from(this.querySelectorAll('pb-table-column'));
193
+ this._columns = this._getColumnsConfig();
160
194
  waitOnce('pb-page-ready', data => {
161
195
  if (data && data.language) {
162
196
  this.language = data.language;
@@ -166,7 +200,8 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
166
200
  const config = {
167
201
  height: this.height,
168
202
  fixedHeader: true,
169
- columns,
203
+ columns: this._columns,
204
+ language: this._gridLanguageConfig(),
170
205
  resizable: this.resizable,
171
206
  server: {
172
207
  url,
@@ -181,7 +216,7 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
181
216
  if (!cols.length) return prev;
182
217
  const col = cols[0];
183
218
  return `${prev}${prev.indexOf('?') > -1 ? '&' : '?'}order=${
184
- columns[col.index].id
219
+ this._columns[col.index].id
185
220
  }&dir=${col.direction === 1 ? 'asc' : 'desc'}`;
186
221
  },
187
222
  },
@@ -191,9 +226,9 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
191
226
  limit: this.perPage,
192
227
  server: {
193
228
  url: (prev, page, limit) => {
194
- const form = this.shadowRoot.getElementById('form');
229
+ const form = this.shadowRoot.getElementById('search-form');
195
230
  if (form) {
196
- Object.assign(this._params, form.serializeForm());
231
+ Object.assign(this._params, this._serializeForm(form));
197
232
  }
198
233
  this._params = this._paramsFromSubforms(this._params);
199
234
  this._params.limit = limit;
@@ -219,23 +254,131 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
219
254
  };
220
255
 
221
256
  this.grid = new Grid(config);
222
- if (this.paginationTop) {
223
- this.grid.plugin.get('pagination').position = PluginPosition.Header;
224
- }
257
+ this._applyPaginationPosition();
225
258
  this.grid.on('load', () => {
259
+ this._clearRowSelection();
260
+ if (this.paginationTop) {
261
+ // `forceRender()` can reset GridJS plugin state; re-apply after each load.
262
+ this.grid.plugin.get('pagination').position = PluginPosition.Header;
263
+ }
226
264
  this.emitTo('pb-results-received', {
227
265
  params: this._params,
228
266
  });
267
+ this._applyColumnVisibilityToDom();
229
268
  });
230
269
 
231
270
  this.grid.render(table);
232
271
  });
233
272
  }
234
273
 
274
+ updated(changedProperties) {
275
+ if (changedProperties.has('visibleColumns') && this.grid) {
276
+ this._columns = this._getColumnsConfig();
277
+ this._applyColumnVisibilityToDom();
278
+ }
279
+ }
280
+
281
+ _visibleColumnsAsSet() {
282
+ return Array.isArray(this.visibleColumns) ? new Set(this.visibleColumns) : null;
283
+ }
284
+
285
+ _columnId(pbColumn, config) {
286
+ return config.id || pbColumn.property || pbColumn.label;
287
+ }
288
+
289
+ _getColumnsConfig() {
290
+ const visibleSet = this._visibleColumnsAsSet();
291
+ return this._pbColumns.map(pbColumn => {
292
+ const config = pbColumn.data();
293
+ const id = this._columnId(pbColumn, config);
294
+ return visibleSet ? { ...config, hidden: !visibleSet.has(id) } : config;
295
+ });
296
+ }
297
+
298
+ _applyColumnVisibilityToDom() {
299
+ const visibleSet = this._visibleColumnsAsSet();
300
+ const table = this.shadowRoot.querySelector('.gridjs-table');
301
+ if (!table) {
302
+ return;
303
+ }
304
+ this._columns.forEach((column, index) => {
305
+ const id = column.id || this._pbColumns[index]?.property || this._pbColumns[index]?.label;
306
+ const hidden = visibleSet ? !visibleSet.has(id) : false;
307
+ const cells = table.querySelectorAll(`th:nth-child(${index + 1}), td:nth-child(${index + 1})`);
308
+ cells.forEach(cell => {
309
+ cell.style.display = hidden ? 'none' : '';
310
+ });
311
+ });
312
+ }
313
+
314
+ _onTableClick(event) {
315
+ const row = event.target.closest('tbody tr');
316
+ if (!row) {
317
+ return;
318
+ }
319
+ this._toggleRowSelection(row);
320
+ }
321
+
322
+ _onDocumentClick(event) {
323
+ const path = event.composedPath();
324
+ if (!path.includes(this)) {
325
+ this._clearRowSelection();
326
+ }
327
+ }
328
+
329
+ _toggleRowSelection(row) {
330
+ if (this._selectedRow === row) {
331
+ this._clearRowSelection();
332
+ return;
333
+ }
334
+ this._clearRowSelection();
335
+ this._selectedRow = row;
336
+ this._selectedRow.classList.add('grid-row-selected');
337
+ }
338
+
339
+ _clearRowSelection() {
340
+ if (this._selectedRow) {
341
+ this._selectedRow.classList.remove('grid-row-selected');
342
+ this._selectedRow = null;
343
+ }
344
+ }
345
+
235
346
  _submit() {
236
347
  this.grid.forceRender();
237
348
  }
238
349
 
350
+ _gridLanguageConfig() {
351
+ return {
352
+ search: {
353
+ placeholder: () => i18n('tableGrid.searchPlaceholder'),
354
+ },
355
+ sort: {
356
+ sortAsc: () => i18n('tableGrid.sortAsc'),
357
+ sortDesc: () => i18n('tableGrid.sortDesc'),
358
+ },
359
+ pagination: {
360
+ previous: () => i18n('tableGrid.previous'),
361
+ next: () => i18n('tableGrid.next'),
362
+ navigate: (page, pages) =>
363
+ i18n('tableGrid.navigate', {
364
+ page,
365
+ pages,
366
+ }),
367
+ page: page =>
368
+ i18n('tableGrid.page', {
369
+ page,
370
+ }),
371
+ showing: () => i18n('tableGrid.showing'),
372
+ to: () => i18n('tableGrid.to'),
373
+ of: () => i18n('tableGrid.of'),
374
+ results: () => i18n('tableGrid.results'),
375
+ },
376
+ loading: () => i18n('tableGrid.loading'),
377
+ noRecordsFound: () => i18n('tableGrid.noRecordsFound'),
378
+ error: () => i18n('tableGrid.error'),
379
+ };
380
+ }
381
+
239
382
  _paramsFromSubforms(params) {
240
383
  if (this.subforms) {
241
384
  document.querySelectorAll(this.subforms).forEach(form => {
@@ -247,27 +390,33 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
247
390
  return params;
248
391
  }
249
392
 
393
+ _serializeForm(form) {
394
+ const data = {};
395
+ const formData = new FormData(form);
396
+ formData.forEach((value, key) => {
397
+ data[key] = value;
398
+ });
399
+ return data;
400
+ }
401
+
250
402
  render() {
251
403
  return html`
252
404
  ${this.search
253
405
  ? html`
254
- <iron-form id="form">
255
- <form action="">
256
- <paper-input
257
- id="search"
258
- name="search"
259
- label="${translate('search.search')}"
260
- value="${this._params.search || ''}"
261
- @keyup="${e => (e.keyCode === 13 ? this._submit() : null)}"
262
- >
263
- <paper-icon-button
264
- icon="search"
265
- @click="${this._submit}"
266
- slot="suffix"
267
- ></paper-icon-button>
268
- </paper-input>
269
- </form>
270
- </iron-form>
406
+ <form id="search-form" action="">
407
+ <input
408
+ id="search"
409
+ name="search"
410
+ type="search"
411
+ .value="${this._params.search || ''}"
412
+ aria-label="${translate('tableGrid.search')}"
413
+ placeholder="${translate('tableGrid.searchPlaceholder')}"
414
+ @keyup="${e => (e.key === 'Enter' ? this._submit() : null)}"
415
+ />
416
+ <button type="button" @click="${this._submit}">
417
+ ${translate('tableGrid.search')}
418
+ </button>
419
+ </form>
271
420
  `
272
421
  : null}
273
422
  <div id="table"></div>
@@ -279,6 +428,9 @@ export class PbTableGrid extends themableMixin(pbMixin(LitElement)) {
279
428
  :host {
280
429
  display: block;
281
430
  }
431
+ .grid-row-selected td.gridjs-td {
432
+ background-color: var(--pb-table-grid-selected-row-background-color, #e8f0fe);
433
+ }
282
434
  button {
283
435
  border: 0;
284
436
  }
@@ -1,42 +0,0 @@
1
- name: Release npm package
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- - next-3
8
-
9
- permissions:
10
- contents: read # for checkout
11
-
12
- jobs:
13
- release:
14
- name: Release
15
- runs-on: ubuntu-latest
16
- permissions:
17
- contents: write # to be able to publish a GitHub release
18
- issues: write # to be able to comment on released issues
19
- pull-requests: write # to be able to comment on released pull requests
20
- id-token: write # to enable use of OIDC for trusted publishing and npm provenance
21
- steps:
22
- - uses: actions/checkout@v5
23
- - uses: actions/setup-node@v5
24
- with:
25
- node-version: "22"
26
- - name: npm install and build
27
- run:
28
- npm ci
29
- npm run build:production
30
- - name: Build docker image
31
- run: docker build -t exist-db -f Dockerfile .
32
- - name: Start docker image
33
- run: docker run --publish 8080:8080 --detach exist-db
34
- - name: Wait for eXist
35
- uses: iFaxity/wait-on-action@v1
36
- with:
37
- resource: http-get://localhost:8080/exist/apps/tei-publisher/api/version
38
- - run: npm test
39
- - run: npx semantic-release
40
- env:
41
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42
- NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}