@jungherz-de/notionkit-elements 1.12.0 → 1.14.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/CHANGELOG.md +30 -0
- package/README.md +11 -11
- package/SKILL.md +113 -54
- package/dist/components/nk-gallery-view.js +114 -0
- package/dist/components/nk-page-cover.js +2 -0
- package/dist/components/nk-page.js +17 -9
- package/dist/notionkit-elements.esm.js +131 -13
- package/dist/notionkit-elements.js +131 -12
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/data/nk-gallery-view.js +111 -0
- package/src/components/page/nk-page-cover.js +2 -0
- package/src/components/page/nk-page.js +17 -9
- package/src/index.js +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { NkElement } from './base.js';
|
|
2
|
+
import { r as renderPropertyCell } from './shared/property-cell-DKvv6TIG.js';
|
|
3
|
+
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
+
import './shared/avatar-CzPZN3uP.js';
|
|
5
|
+
|
|
6
|
+
// <nk-gallery-view name="gallery" label="🖼 Gallery" meta-keys="status,due" new-row></nk-gallery-view>
|
|
7
|
+
// The fifth database view (NotionKit 1.14.0): the rows as cards with a
|
|
8
|
+
// picture on top, in a grid that fills the row – Notion's gallery, for a
|
|
9
|
+
// course catalog or a reading list. `cover-key` names the row field with the
|
|
10
|
+
// picture's URL (default: cover); a row without one shows the cover
|
|
11
|
+
// gradient, and `no-cover` leaves the pictures out. `size` small | medium |
|
|
12
|
+
// large sets the card size, `fit` shows a picture whole instead of cropped.
|
|
13
|
+
// Cards show the title column and the `meta-keys` (default: select and date
|
|
14
|
+
// columns). Standalone: view.columns = […]; view.rows = […]. Inside
|
|
15
|
+
// <nk-database> the database pushes the data. A card fires nk-select
|
|
16
|
+
// { row, id, value } on a click, Enter or Space, like a row of the table;
|
|
17
|
+
// the add card nk-action { action: 'new-row' }.
|
|
18
|
+
// → <div class="nk-gallery" role="list"><div class="nk-card" role="listitem" tabindex="0">
|
|
19
|
+
// <div class="nk-cover"><img src="…" alt=""></div><div class="card-title">🧭 …</div><div class="card-meta">…</div></div>
|
|
20
|
+
// <div class="nk-new-row">+ New page</div></div>
|
|
21
|
+
const SIZES = ['small', 'large'];
|
|
22
|
+
|
|
23
|
+
class NkGalleryView extends NkElement {
|
|
24
|
+
static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'cover-key', 'no-cover', 'size', 'fit', 'title-key', 'meta-keys', 'new-row', 'new-row-label']; }
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
this._grid = this.createElement('div', ['nk-gallery'], { role: 'list' });
|
|
28
|
+
this._wrapper.appendChild(this._grid);
|
|
29
|
+
this._columns ??= []; this._rows ??= [];
|
|
30
|
+
this._render();
|
|
31
|
+
this.closest('nk-database')?.requestSync?.();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_titleColumn() {
|
|
35
|
+
const key = this.getAttribute('title-key');
|
|
36
|
+
return this._columns.find(c => c.key === key) || this._columns.find(c => c.title) || this._columns[0] || null;
|
|
37
|
+
}
|
|
38
|
+
_metaColumns() {
|
|
39
|
+
const keys = (this.getAttribute('meta-keys') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
40
|
+
if (keys.length) return keys.map(k => this._columns.find(c => c.key === k)).filter(Boolean);
|
|
41
|
+
return this._columns.filter(c => c.type === 'select' || c.type === 'date');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_render() {
|
|
45
|
+
if (!this._grid) return;
|
|
46
|
+
const size = this.getAttribute('size');
|
|
47
|
+
this._grid.className = ['nk-gallery', SIZES.includes(size) && size, this.getBoolAttr('fit') && 'fit'].filter(Boolean).join(' ');
|
|
48
|
+
const title = this._titleColumn(), meta = this._metaColumns();
|
|
49
|
+
const coverKey = this.getAttribute('cover-key') || 'cover', covers = !this.getBoolAttr('no-cover');
|
|
50
|
+
this._grid.replaceChildren();
|
|
51
|
+
for (const row of this._rows) {
|
|
52
|
+
const card = this.createElement('div', ['nk-card'], { role: 'listitem', tabindex: '0', 'data-id': row.id ?? '' });
|
|
53
|
+
if (covers) {
|
|
54
|
+
const cover = this.createElement('div', ['nk-cover']);
|
|
55
|
+
if (row[coverKey]) cover.appendChild(this.createElement('img', [], { src: row[coverKey], alt: '' }));
|
|
56
|
+
card.appendChild(cover);
|
|
57
|
+
}
|
|
58
|
+
const t = this.createElement('div', ['card-title']);
|
|
59
|
+
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? row[title.key] ?? '' : ''}`;
|
|
60
|
+
const m = this.createElement('div', ['card-meta']);
|
|
61
|
+
for (const c of meta) {
|
|
62
|
+
const v = row[c.key];
|
|
63
|
+
if (v === undefined || v === null || v === '') continue;
|
|
64
|
+
const s = document.createElement('span');
|
|
65
|
+
if (c.type === 'progress') s.textContent = `▰ ${v}%`;
|
|
66
|
+
else if (c.type === 'date') s.textContent = `📅 ${v}`;
|
|
67
|
+
else s.appendChild(renderPropertyCell(c, v, row));
|
|
68
|
+
m.appendChild(s);
|
|
69
|
+
}
|
|
70
|
+
card.append(t, m);
|
|
71
|
+
this._grid.appendChild(card);
|
|
72
|
+
}
|
|
73
|
+
if (this.getBoolAttr('new-row')) {
|
|
74
|
+
const add = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0', 'data-add': '' });
|
|
75
|
+
add.textContent = this.getAttribute('new-row-label') || '+ New page';
|
|
76
|
+
this._grid.appendChild(add);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setupEvents() {
|
|
81
|
+
this._onClick = (e) => {
|
|
82
|
+
if (e.target.closest('[data-add]')) { this.emit('nk-action', { action: 'new-row' }); return; }
|
|
83
|
+
const card = e.target.closest('.nk-card');
|
|
84
|
+
if (card) { const row = this._rowById(card.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
|
|
85
|
+
};
|
|
86
|
+
this._onKey = (e) => {
|
|
87
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
88
|
+
if (e.target.matches?.('.nk-card, [data-add]')) { e.preventDefault(); e.target.click(); }
|
|
89
|
+
};
|
|
90
|
+
this._grid.addEventListener('click', this._onClick);
|
|
91
|
+
this._grid.addEventListener('keydown', this._onKey);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
teardownEvents() {
|
|
95
|
+
this._grid?.removeEventListener('click', this._onClick);
|
|
96
|
+
this._grid?.removeEventListener('keydown', this._onKey);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
|
|
100
|
+
onAttributeChanged() { this._render(); }
|
|
101
|
+
|
|
102
|
+
get name() { return this.getAttribute('name') || 'gallery'; }
|
|
103
|
+
get columns() { return this._columns; }
|
|
104
|
+
set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
|
|
105
|
+
get rows() { return this._rows; }
|
|
106
|
+
set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
|
|
107
|
+
set data({ columns, rows }) { this.setData(columns, rows); }
|
|
108
|
+
setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
|
|
109
|
+
refresh() { this._render(); }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
customElements.define('nk-gallery-view', NkGalleryView);
|
|
113
|
+
|
|
114
|
+
export { NkGalleryView };
|
|
@@ -6,6 +6,8 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
6
6
|
// → <div class="nk-cover"><img src="cover.jpg" alt=""></div>: the picture fills
|
|
7
7
|
// the band, cropped rather than stretched; `position` moves the crop, as
|
|
8
8
|
// Notion's "Reposition" does. The gradient stays underneath while it loads.
|
|
9
|
+
// The band is as tall as the token --nk-cover-height (200px), which reaches
|
|
10
|
+
// the shadow root: style="--nk-cover-height: 30vh" sets one cover.
|
|
9
11
|
class NkPageCover extends NkElement {
|
|
10
12
|
static get observedAttributes() { return ['src', 'position']; }
|
|
11
13
|
|
|
@@ -21,9 +21,9 @@ class NkPage extends NkElement {
|
|
|
21
21
|
render() {
|
|
22
22
|
this._page = this.createElement('div', ['nk-page']);
|
|
23
23
|
this._icon = this.createElement('div', ['nk-page-icon'], { role: 'button', title: 'Change icon' });
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this._page.appendChild(
|
|
24
|
+
this._iconSlot = this.createElement('slot', [], { name: 'icon' });
|
|
25
|
+
this._iconSlot.appendChild(this._icon);
|
|
26
|
+
this._page.appendChild(this._iconSlot);
|
|
27
27
|
this._page.appendChild(document.createElement('slot'));
|
|
28
28
|
this._cover = this.createElement('div', ['nk-cover']);
|
|
29
29
|
this._coverSlot = this.createElement('slot', [], { name: 'cover' });
|
|
@@ -49,11 +49,14 @@ class NkPage extends NkElement {
|
|
|
49
49
|
if (this._icon.textContent !== icon) this._icon.textContent = icon;
|
|
50
50
|
this._icon.style.display = icon ? '' : 'none';
|
|
51
51
|
this._cover.style.display = this.getBoolAttr('cover') ? '' : 'none';
|
|
52
|
-
// `covered` is the stylesheet's twin of `.nk-cover + .nk-page
|
|
53
|
-
// overlaps the cover and the page drops its top padding.
|
|
54
|
-
// (attribute or a slotted nk-page-cover) the icon sits in
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
// `covered` is the stylesheet's twin of `.nk-cover + .nk-page` with an
|
|
53
|
+
// icon: the icon overlaps the cover and the page drops its top padding.
|
|
54
|
+
// Without a cover (attribute or a slotted nk-page-cover) the icon sits in
|
|
55
|
+
// the padding; without an icon (attribute or slot="icon") the page keeps
|
|
56
|
+
// it, so the title never touches the cover (NotionKit 1.13.0).
|
|
57
|
+
const filled = slot => slot.assignedNodes().some(n => n.nodeType === Node.ELEMENT_NODE || n.data.trim());
|
|
58
|
+
const covered = this.getBoolAttr('cover') || filled(this._coverSlot), iconed = !!icon || filled(this._iconSlot);
|
|
59
|
+
this._page.classList.toggle('covered', covered && iconed);
|
|
57
60
|
this._page.classList.toggle('full', this.getBoolAttr('full'));
|
|
58
61
|
this._page.classList.toggle('small', this.getBoolAttr('small'));
|
|
59
62
|
}
|
|
@@ -63,10 +66,15 @@ class NkPage extends NkElement {
|
|
|
63
66
|
this._onCover = () => this._sync();
|
|
64
67
|
this._icon.addEventListener('click', this._onIcon);
|
|
65
68
|
this._coverSlot.addEventListener('slotchange', this._onCover);
|
|
69
|
+
this._iconSlot.addEventListener('slotchange', this._onCover);
|
|
66
70
|
this._sync();
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
teardownEvents() {
|
|
73
|
+
teardownEvents() {
|
|
74
|
+
this._icon?.removeEventListener('click', this._onIcon);
|
|
75
|
+
this._coverSlot?.removeEventListener('slotchange', this._onCover);
|
|
76
|
+
this._iconSlot?.removeEventListener('slotchange', this._onCover);
|
|
77
|
+
}
|
|
70
78
|
|
|
71
79
|
onAttributeChanged(name) {
|
|
72
80
|
if (name === 'narrow') this._build();
|