@pageboard/html 0.12.4 → 0.12.6

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/elements/embed.js CHANGED
@@ -8,31 +8,49 @@ exports.embed = {
8
8
  description: 'The iframe src URL',
9
9
  nullable: true,
10
10
  type: 'string',
11
- format: 'uri-reference'
12
- // TODO plug embeds to href, but url-inspector makes it difficult for us right now
11
+ format: 'uri-reference',
12
+ $helper: {
13
+ name: 'href',
14
+ filter: {
15
+ type: ["embed"]
16
+ }
17
+ }
13
18
  },
14
- name: {
15
- title: 'Name',
19
+ linkable: {
20
+ title: 'Show hash link',
21
+ type: 'boolean',
22
+ default: false
23
+ },
24
+ id: {
25
+ nullable: true,
16
26
  type: 'string',
17
- format: 'id',
27
+ pattern: /^[a-z0-9-]*$/.source
28
+ },
29
+ query: {
30
+ title: 'Additional query parameters',
31
+ type: 'object',
18
32
  nullable: true
19
33
  }
20
34
  },
21
35
  group: "block",
22
- parse: function(dom) {
36
+ parse: function (dom) {
37
+ if (dom.matches('element-embed')) return;
23
38
  return {
24
- url: dom.dataset.src || dom.getAttribute('src')
39
+ url: dom.getAttribute('src')
25
40
  };
26
41
  },
27
42
  tag: 'iframe,element-embed',
28
- html: `<element-embed class="ui embed" data-src="[url]" id="[name|as:xid]"></element-embed>`,
43
+ html: `<element-embed data-src="[url|meta:source][query|as:query]" id="[id]" title="[url|meta:title]" style="padding-bottom:calc([url|meta:height] / [url|meta:width] * 100%)">
44
+ <a aria-hidden="true" class="linkable" href="[$loc.pathname][$loc.search][id|pre:%23]">[linkable|prune:*]#</a>
45
+ <iframe loading="lazy" allowfullscreen frameborder="0" scrolling="no"></iframe>
46
+ </element-embed>`,
29
47
  scripts: [
30
48
  '../ui/embed.js'
31
49
  ],
32
50
  stylesheets: [
33
51
  '../ui/loading.css',
34
- '../lib/components/embed.css',
35
- '../ui/embed.css'
52
+ '../ui/embed.css',
53
+ '../ui/linkable.css'
36
54
  ]
37
55
  };
38
-
56
+ exports.editor.scripts.push('../ui/embed-helper.js');
@@ -33,7 +33,6 @@ exports.heading = {
33
33
  },
34
34
  linkable: {
35
35
  title: 'Show hash link',
36
- description: 'On hover',
37
36
  type: 'boolean',
38
37
  default: false
39
38
  },
@@ -55,8 +54,8 @@ exports.heading = {
55
54
  group: "block",
56
55
  icon: '<i class="icon header"></i>',
57
56
  tag: 'h1,h2,h3,h4,h5,h6',
58
- html: `<h[level] class="ui [align|or:left] aligned header" is="h[level]-helper" id="[id][linkable|prune:-]" entitled="[entitled]">
59
- <a aria-hidden="true" href="[$loc.pathname][$loc.search][id|pre:%23]">[linkable|prune:*]#</a>
57
+ html: `<h[level] class="ui [align|or:left] aligned header" is="h[level]-helper" id="[id]" entitled="[entitled]">
58
+ <a aria-hidden="true" class="linkable" href="[$loc.pathname][$loc.search][id|pre:%23]">[linkable|prune:*]#</a>
60
59
  <div block-content="text">Heading</div>
61
60
  </hn>`,
62
61
  parse: function (dom) {
@@ -65,7 +64,8 @@ exports.heading = {
65
64
  };
66
65
  },
67
66
  stylesheets: [
68
- '../ui/heading.css'
67
+ '../ui/heading.css',
68
+ '../ui/linkable.css'
69
69
  ],
70
70
  scripts: [
71
71
  '../ui/heading.js'
package/elements/time.js CHANGED
@@ -158,7 +158,7 @@ exports.time = {
158
158
  const format = {};
159
159
  const list = (dom.dataset.format ?? "").split(':');
160
160
  for (const [key, schema] of Object.entries(this.properties.format.properties)) {
161
- for (const tok in list) {
161
+ for (const tok of list) {
162
162
  if (tok) {
163
163
  const item = schema.anyOf.find(item => item.const === tok);
164
164
  if (item) format[key] = item.const;
@@ -171,6 +171,7 @@ exports.time = {
171
171
  format
172
172
  };
173
173
  },
174
- html: `<time datetime="[datetime|now|isoDate]" data-format="[format|values|join:%3A]" data-timezone="[timezone]" is="element-time"></time>`,
174
+ tag: 'time',
175
+ html: `<time datetime="[datetime|or:now|date:iso]" data-format="[format|as:values|join:%3A]" data-timezone="[timezone]" is="element-time"></time>`,
175
176
  scripts: ['../ui/time.js']
176
177
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pageboard/html",
3
- "version": "0.12.4",
3
+ "version": "0.12.6",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -0,0 +1,10 @@
1
+ Page.extend('element-embed', class HTMLElementEmbedHelper {
2
+ patch(state) {
3
+ const { editor } = state.scope;
4
+ if (!editor) return;
5
+ const id = editor.slug(this.title).slice(0, 32);
6
+ if (id != this.id) {
7
+ editor.blocks.mutate(this, { id });
8
+ }
9
+ }
10
+ });
package/ui/embed.css CHANGED
@@ -1,8 +1,24 @@
1
1
  element-embed {
2
2
  display:block;
3
+ position:relative;
4
+ max-width:100%;
3
5
  width:100%;
6
+ background-color:#DDD;
7
+ height:0;
8
+ padding-bottom:56.25%;
4
9
  }
5
10
 
6
11
  [contenteditable] element-embed iframe {
7
12
  pointer-events:none;
8
13
  }
14
+
15
+ element-embed > iframe {
16
+ position: absolute;
17
+ border: none;
18
+ width: 100%;
19
+ height: 100%;
20
+ top: 0px;
21
+ left: 0px;
22
+ margin: 0em;
23
+ padding: 0em;
24
+ }
package/ui/embed.js CHANGED
@@ -4,72 +4,64 @@ class HTMLElementEmbed extends Page.Element {
4
4
  hash: null
5
5
  };
6
6
  static revealRatio = 0.2;
7
- #defer = new Deferred();
8
7
 
9
8
  reveal(state) {
10
9
  this.classList.add('waiting');
11
10
  state.consent(this);
12
- // return this.#defer; // do not hang the chain
11
+ }
12
+ get currentSrc() {
13
+ return this.querySelector('iframe')?.src ?? "about:blank";
13
14
  }
14
15
  consent(state) {
15
16
  const consent = state.scope.$consent;
16
17
  this.classList.toggle('denied', consent == "no");
17
18
  this.classList.toggle('waiting', consent == null);
18
19
 
19
- this.iframe = this.querySelector('iframe');
20
+ const iframe = this.querySelector('iframe');
20
21
  if (consent != "yes") {
21
- if (this.iframe) this.iframe.remove();
22
- this.#defer.resolve();
22
+ iframe.src = "";
23
23
  return;
24
24
  }
25
- if (!this.iframe) {
26
- this.innerHTML = `<iframe width="100%" height="100%" frameborder="0" scrolling="no" allow="autoplay; fullscreen; accelerometer; gyroscope"></iframe>`;
27
- this.iframe = this.firstElementChild;
28
- }
29
- if (!this.iframe.allow) this.iframe.allowFullscreen = true;
25
+ if (!iframe.allow) iframe.allowFullscreen = true;
30
26
 
31
27
  const opts = this.options;
32
- const prev = Page.parse(this.currentSrc || this.iframe.src || "about:blank");
28
+ const prev = Page.parse(this.currentSrc);
33
29
  const cur = Page.parse(opts.src || "about:blank");
34
30
  cur.hash = opts.hash;
35
- this.currentSrc = cur.toString();
31
+ const curSrc = cur.toString();
36
32
 
37
33
  if (cur.samePath(prev) == false) {
38
34
  this.classList.remove('error');
39
35
  this.classList.add('loading');
40
- this.iframe.setAttribute('src', this.currentSrc);
41
- } else {
42
- if (cur.hash != prev.hash) {
43
- try {
44
- this.iframe.contentWindow.location.replace(this.currentSrc);
45
- } catch (err) {
46
- this.iframe.setAttribute('src', this.currentSrc);
47
- }
36
+ if (!state.scope.$write) {
37
+ iframe.allow = "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture";
38
+ }
39
+ iframe.src = curSrc;
40
+ } else if (cur.hash != prev.hash) {
41
+ try {
42
+ iframe.contentWindow.location.replace(curSrc);
43
+ } catch (err) {
44
+ iframe.src = curSrc;
48
45
  }
49
- this.#defer.resolve();
50
46
  }
51
47
  }
52
48
  captureClick(e, state) {
53
49
  if (this.matches('.denied')) state.reconsent();
54
50
  }
55
51
  captureLoad() {
56
- this.#defer.resolve();
57
52
  this.classList.remove('loading');
58
53
  }
59
54
  captureError() {
60
- this.#defer.resolve();
61
55
  this.classList.add('error');
62
56
  }
63
57
  handleAllMessage(e, state) {
64
- if (!e.origin || !this.options.src) return;
65
- if (this.options.src.startsWith(e.origin) == false) return;
58
+ if (!e.origin || !this.options.source) return;
59
+ if (this.options.source.startsWith(e.origin) == false) return;
66
60
  if (this.receiveMessage) this.receiveMessage(e.data ?? {});
67
61
  }
68
62
  close() {
69
- if (this.iframe) {
70
- this.iframe.remove();
71
- delete this.iframe;
72
- }
63
+ const iframe = this.querySelector('iframe');
64
+ if (iframe) iframe.src = "";
73
65
  }
74
66
  }
75
67
 
@@ -54,8 +54,8 @@ class HTMLElementFieldsetList extends Page.Element {
54
54
  for (const node of inputs) {
55
55
  keys.add(node.name);
56
56
  }
57
- const splits = Array.from(keys).map(name => name.split('.'));
58
- const coms = [];
57
+ const splits = Array.from(keys).map(name => this.#parts(name));
58
+ const prefix = [];
59
59
  let pos = 0, com = null;
60
60
  while (splits.length && splits.every(list => {
61
61
  if (com == null) {
@@ -69,16 +69,16 @@ class HTMLElementFieldsetList extends Page.Element {
69
69
  return list[pos] == com;
70
70
  }
71
71
  })) {
72
- coms.push(com);
72
+ prefix.push(com);
73
73
  com = null;
74
74
  pos++;
75
75
  }
76
- if (coms.length) coms.push('');
77
- const prefix = coms.join('.');
78
76
  this.#prefix = prefix;
79
77
  const model = {};
80
78
  for (const key of keys) {
81
- if (key.startsWith(prefix)) model[key.substring(prefix.length)] = null;
79
+ if (this.#prefixed(key)) {
80
+ model[this.#parts(key).slice(prefix.length).join('.')] = null;
81
+ }
82
82
  }
83
83
  this.#model = model;
84
84
  }
@@ -109,6 +109,22 @@ class HTMLElementFieldsetList extends Page.Element {
109
109
  return `[block-type="fieldlist_button"][value="${name}"]`;
110
110
  }
111
111
 
112
+ #prefixed(key, p = this.#prefix) {
113
+ const parts = this.#parts(key);
114
+ for (let i = 0; i < p.length; i++) {
115
+ if (parts[i] != p[i]) return false;
116
+ }
117
+ return true;
118
+ }
119
+
120
+ #incrementkey(index, name) {
121
+ if (!this.#prefixed(name)) return null;
122
+ const parts = this.#prefix.slice();
123
+ parts.push(index);
124
+ parts.push(...this.#parts(name).slice(this.#prefix.length));
125
+ return parts.join('.');
126
+ }
127
+
112
128
  #resize(size, scope) {
113
129
  if (scope.$write) return;
114
130
  const len = Math.max(Number(this.dataset.size) || 0, size);
@@ -119,25 +135,17 @@ class HTMLElementFieldsetList extends Page.Element {
119
135
  return { index: i };
120
136
  });
121
137
  const inputs = tpl.querySelectorAll('[name]');
122
- const prefix = this.#prefix;
123
138
  for (const node of inputs) {
124
- if (node.name.startsWith(prefix)) {
125
- node.name = `${prefix}[fielditem.index].${node.name.substring(prefix.length)}`;
126
- if (node.id?.startsWith('for-' + prefix)) {
127
- node.id = `for-${prefix}[fielditem.index].${node.id.substring(4 + prefix.length)}`;
128
- }
139
+ const name = this.#incrementkey('[fielditem.index]', node.name);
140
+ if (name != null) {
141
+ node.name = name;
129
142
  }
130
143
  }
131
144
  const conditionalFieldsets = tpl.querySelectorAll('[is="element-fieldset"]');
132
145
  for (const node of conditionalFieldsets) {
133
- if (node.dataset.name?.startsWith(prefix)) {
134
- node.dataset.name = `${prefix}[fielditem.index].${node.dataset.name.substring(prefix.length)}`;
135
- }
136
- }
137
- const labels = tpl.querySelectorAll('label[for]');
138
- for (const node of labels) {
139
- if (node.htmlFor?.startsWith('for-' + prefix)) {
140
- node.htmlFor = `for-${prefix}[fielditem.index].${node.htmlFor.substring(4 + prefix.length)}`;
146
+ const name = this.#incrementkey('[fielditem.index]', node.dataset.name);
147
+ if (name != null) {
148
+ node.dataset.name = name;
141
149
  }
142
150
  }
143
151
 
@@ -159,7 +167,7 @@ class HTMLElementFieldsetList extends Page.Element {
159
167
  {
160
168
  const hidden = tpl.ownerDocument.createElement('input');
161
169
  hidden.type = "hidden";
162
- hidden.name = prefix.slice(0, -1);
170
+ hidden.name = this.#prefix.join('.');
163
171
  tpl.appendChild(hidden);
164
172
  }
165
173
  }
@@ -177,13 +185,16 @@ class HTMLElementFieldsetList extends Page.Element {
177
185
  });
178
186
  }
179
187
 
188
+ #parts(key) {
189
+ return key ? key.split(".") : [];
190
+ }
191
+
180
192
  #listFromValues(values) {
181
193
  const list = [];
182
- const prefix = this.#prefix;
183
194
  // just unflatten the array
184
195
  for (const [key, val] of Object.entries(values)) {
185
- if (!key.startsWith(prefix)) continue;
186
- const parts = key.slice(prefix.length).split(".");
196
+ if (!this.#prefixed(key)) continue;
197
+ const parts = this.#parts(key).slice(this.#prefix.length);
187
198
  const index = Number(parts.shift());
188
199
  if (!Number.isInteger(index)) continue;
189
200
  delete values[key];
@@ -195,11 +206,13 @@ class HTMLElementFieldsetList extends Page.Element {
195
206
  }
196
207
 
197
208
  #listToValues(values, list) {
198
- const prefix = this.#prefix;
199
209
  for (let i = 0; i < list.length; i++) {
200
210
  const obj = list[i];
201
211
  for (const [key, val] of Object.entries(obj)) {
202
- values[`${prefix}${i}.${key}`] = val;
212
+ const parts = this.#prefix.slice();
213
+ parts.push(i);
214
+ parts.push(...this.#parts(key));
215
+ values[parts.join('.')] = val;
203
216
  }
204
217
  }
205
218
  }
@@ -261,9 +274,10 @@ class HTMLElementFieldsetList extends Page.Element {
261
274
  }
262
275
 
263
276
  #parseName(name) {
264
- const prefix = this.prefix;
265
- if (!name?.startsWith(prefix)) return { index: -1 };
266
- const parts = name.substring(prefix.length).split('.');
277
+ if (!this.#prefixed(name)) {
278
+ return { index: -1 };
279
+ }
280
+ const parts = this.#parts(name).slice(this.#prefix.length);
267
281
  const index = Number(parts.shift());
268
282
  if (!Number.isInteger(index)) return { index: -1 };
269
283
  return { index, sub: parts.join('.') };
package/ui/fieldset.js CHANGED
@@ -19,14 +19,8 @@ class HTMLElementFieldSet extends Page.create(HTMLFieldSetElement) {
19
19
  this.fill(null, state.scope);
20
20
  state.finish(() => this.fill(null, state.scope));
21
21
  }
22
- setup() {
23
- this.form?.addEventListener('change', this);
24
- }
25
- close() {
26
- this.form?.removeEventListener('change', this);
27
- }
28
- handleEvent(e, state) {
29
- if (e.type == "change") {
22
+ handleAllChange(e, state) {
23
+ if (this.form.contains(e.target)) {
30
24
  this.fill(null, state.scope);
31
25
  }
32
26
  }
package/ui/heading.css CHANGED
@@ -1,12 +1,3 @@
1
- [block-type="heading"] > a[aria-hidden="true"] {
2
- display: block;
3
- position: absolute;
4
- visibility:hidden;
5
- left: -1ch;
6
- }
7
- [block-type="heading"]:hover > a[aria-hidden="true"] {
8
- visibility:visible;
9
- }
10
1
  h1,
11
2
  h2,
12
3
  h3,
@@ -0,0 +1,5 @@
1
+ [block-type][id] > a.linkable[aria-hidden="true"] {
2
+ display: block;
3
+ position: absolute;
4
+ left: -2ch;
5
+ }
package/ui/media.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const HTMLElementMediaConstructor = Superclass => class extends Superclass {
2
- #defer = new Deferred();
3
2
 
4
3
  patch(state) {
5
4
  this.classList.remove('error', 'loading');
@@ -15,17 +14,14 @@ const HTMLElementMediaConstructor = Superclass => class extends Superclass {
15
14
  this.setAttribute('src', curSrc);
16
15
  }
17
16
  if (state.scope.$write) this.pause();
18
- // return this.#defer; // do not hang chain
19
17
  }
20
18
  handleClick(e, state) {
21
19
  if (state.scope.$write) e.preventDefault();
22
20
  }
23
21
  captureLoad() {
24
- this.#defer.resolve();
25
22
  this.classList.remove('loading');
26
23
  }
27
24
  captureError() {
28
- this.#defer.resolve();
29
25
  this.classList.remove('loading');
30
26
  this.classList.add('error');
31
27
  }
package/ui/select.js CHANGED
@@ -169,7 +169,7 @@ class HTMLElementSelect extends Page.Element {
169
169
  }
170
170
 
171
171
  patch(state) {
172
- if (this.state.scope.$write) return;
172
+ if (state.scope.$write) return;
173
173
  if (this.children.length == 1) this.build(state);
174
174
 
175
175
  state.finish(() => {
package/ui/tab.css CHANGED
@@ -1,5 +1,6 @@
1
1
  element-tabs {
2
2
  display:block;
3
+ width: 100%;
3
4
  }
4
5
  .ui.tab.segment:not(.active) {
5
6
  display: none;
package/ui/tab.js CHANGED
@@ -11,11 +11,11 @@ class HTMLElementTabs extends Page.Element {
11
11
  }
12
12
  patch(state) {
13
13
  const pos = this.options.index;
14
- const id = this.id;
14
+ const key = `${this.id}.index`;
15
+ state.vars[key] = true;
15
16
 
16
17
  this.items.children.forEach((item, i) => {
17
18
  const query = { ...state.query };
18
- const key = `${id}.index`;
19
19
  if (i == 0) delete query[key];
20
20
  else query[key] = i;
21
21
  item.setAttribute('href', Page.format({
package/ui/time.js CHANGED
@@ -6,7 +6,7 @@ class HTMLElementTime extends Page.create(HTMLTimeElement) {
6
6
  };
7
7
 
8
8
  patch(state) {
9
- this.textContent = `[stamp|formatDate:[fmt]:[tz]]`.fuse({
9
+ this.textContent = `[stamp|or:now|date:[fmt]:[tz]]`.fuse({
10
10
  stamp: this.dateTime,
11
11
  fmt: this.dataset.format,
12
12
  tz: this.dataset.timezone