@navette-js/wire 1.0.0-beta.4 → 1.0.0-beta.5
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/THIRD-PARTY-NOTICES.md +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +29 -2
- package/lib/uploads.js +13 -0
- package/package.json +1 -1
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -7,6 +7,7 @@ Navette is an independent project, not affiliated with or endorsed by the Filame
|
|
|
7
7
|
| [Filament](https://github.com/filamentphp/filament) © Filament | HTML markup derived from its Blade views, compiled CSS and JavaScript (`@navette-js/panels/dist/filament`), English translations (`@navette-js/support/data`), image editor icons (`@navette-js/support/icons/filament`) | MIT | `packages/panels/dist/FILAMENT-LICENSE.md`, `packages/support/icons/filament/LICENSE.md` |
|
|
8
8
|
| [Livewire](https://github.com/livewire/livewire) © Caleb Porzio | Client script `livewire.js` (`@navette-js/wire/vendor`); the server runtime follows its protocol | MIT | `packages/wire/vendor/LIVEWIRE-LICENSE.md` |
|
|
9
9
|
| [Heroicons](https://github.com/tailwindlabs/heroicons) | SVG icons (`@navette-js/support/icons`) | MIT | `packages/support/icons/HEROICONS-LICENSE.md` |
|
|
10
|
+
| [QR Code generator library](https://www.nayuki.io/page/qr-code-generator-library) © Project Nayuki | Structure of the QR Code encoder (`@navette-js/panels`, `qr-code.ts`) | MIT | header of `packages/panels/src/qr-code.ts` |
|
|
10
11
|
| [Inter](https://github.com/rsms/inter) © The Inter Project Authors | Default font files (`@navette-js/panels/dist/filament/fonts`) | SIL Open Font License 1.1 | `packages/panels/dist/filament/fonts/INTER-LICENSE.txt` |
|
|
11
12
|
|
|
12
13
|
Runtime dependencies installed from npm (Express, Knex, Objection, better-sqlite3, Tiptap, EJS, Busboy…) keep their own licenses.
|
package/lib/index.d.ts
CHANGED
|
@@ -76,6 +76,11 @@ export declare abstract class Component {
|
|
|
76
76
|
redirect(url: string, navigate?: boolean): void;
|
|
77
77
|
skipRender(): void;
|
|
78
78
|
/** Partial render: only the `wire:partial="<name>"` fragment is sent back (effects.partials), the component is not re-rendered. */
|
|
79
|
+
/**
|
|
80
|
+
* JavaScript run once in the browser for this component, with `$wire` in scope (Livewire's `@script`). Call it while rendering;
|
|
81
|
+
* `key` identifies the script: it is sent again only if the browser has not run it yet.
|
|
82
|
+
*/
|
|
83
|
+
addScript(key: string, js: string): void;
|
|
79
84
|
renderPartial(name: string, html: string): void;
|
|
80
85
|
addError(path: string, message: string): void;
|
|
81
86
|
resetErrorBag(): void;
|
|
@@ -86,6 +91,8 @@ export declare abstract class Component {
|
|
|
86
91
|
* forged request and its return value sent back to the browser.
|
|
87
92
|
*/
|
|
88
93
|
export declare function callableMethods(component: Component): Set<string>;
|
|
94
|
+
/** Properties the browser may not change: those the class or any parent class lists in `static locked`. */
|
|
95
|
+
export declare function lockedProperties(component: Component): Set<string>;
|
|
89
96
|
export declare function publicProperties(component: Component): string[];
|
|
90
97
|
export declare function dehydrateValue(value: unknown): unknown;
|
|
91
98
|
export declare function hydrateValue(value: unknown): unknown;
|
package/lib/index.js
CHANGED
|
@@ -58,6 +58,11 @@ export class Component {
|
|
|
58
58
|
}
|
|
59
59
|
skipRender() { this._skipRender = true; }
|
|
60
60
|
/** Partial render: only the `wire:partial="<name>"` fragment is sent back (effects.partials), the component is not re-rendered. */
|
|
61
|
+
/**
|
|
62
|
+
* JavaScript run once in the browser for this component, with `$wire` in scope (Livewire's `@script`). Call it while rendering;
|
|
63
|
+
* `key` identifies the script: it is sent again only if the browser has not run it yet.
|
|
64
|
+
*/
|
|
65
|
+
addScript(key, js) { (this._effects.scripts ??= {})[key] = `<script>${js}</script>`; }
|
|
61
66
|
renderPartial(name, html) { (this._effects.partials ??= {})[name] = html; this._skipRender = true; }
|
|
62
67
|
addError(path, message) { (this._errors[path] ??= []).push(message); }
|
|
63
68
|
resetErrorBag() { this._errors = {}; }
|
|
@@ -81,6 +86,16 @@ export function callableMethods(component) {
|
|
|
81
86
|
}
|
|
82
87
|
return out;
|
|
83
88
|
}
|
|
89
|
+
/** Properties the browser may not change: those the class or any parent class lists in `static locked`. */
|
|
90
|
+
export function lockedProperties(component) {
|
|
91
|
+
const out = new Set();
|
|
92
|
+
for (let cls = component.constructor; cls && cls !== Component; cls = Object.getPrototypeOf(cls)) {
|
|
93
|
+
if (Object.prototype.hasOwnProperty.call(cls, 'locked') && Array.isArray(cls.locked))
|
|
94
|
+
for (const name of cls.locked)
|
|
95
|
+
out.add(name);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
84
99
|
export function publicProperties(component) {
|
|
85
100
|
return Object.keys(component).filter((k) => !k.startsWith('_'));
|
|
86
101
|
}
|
|
@@ -238,7 +253,7 @@ export class WireRuntime {
|
|
|
238
253
|
}
|
|
239
254
|
await component.dehydrate();
|
|
240
255
|
const snapshot = this.snapshot(component, name, {
|
|
241
|
-
path: request.path ?? '/', method: request.method ?? 'GET', release: 'a-a-a', children: Object.keys(component._children).length ? component._children : [], scripts:
|
|
256
|
+
path: request.path ?? '/', method: request.method ?? 'GET', release: 'a-a-a', children: Object.keys(component._children).length ? component._children : [], scripts: Object.keys(component._effects.scripts ?? {}), assets: [],
|
|
242
257
|
...(request.lazy ? { lazyLoaded: false, lazyIsolated: true } : {}), ...(reactiveProps(component).length ? { props: reactiveProps(component) } : {}), locale: this.options.locale ?? 'en', islands: [],
|
|
243
258
|
});
|
|
244
259
|
const { html: _h, ...effects } = component._effects;
|
|
@@ -285,12 +300,13 @@ export class WireRuntime {
|
|
|
285
300
|
component[prop] = passed[prop];
|
|
286
301
|
await component.boot();
|
|
287
302
|
await component.hydrate();
|
|
303
|
+
const locked = lockedProperties(component);
|
|
288
304
|
const applyUpdate = async (path, value) => {
|
|
289
305
|
const segments = path.split('.');
|
|
290
306
|
const prop = segments[0];
|
|
291
307
|
if (!known.has(prop))
|
|
292
308
|
throw new WireError(404, `Public property not found: ${prop}`);
|
|
293
|
-
if (
|
|
309
|
+
if (locked.has(prop))
|
|
294
310
|
throw new WireError(403, `Locked property: ${prop}`);
|
|
295
311
|
if (reactive.has(prop))
|
|
296
312
|
throw new WireError(403, `Reactive property, set by the parent component: ${prop}`);
|
|
@@ -395,6 +411,17 @@ export class WireRuntime {
|
|
|
395
411
|
memo.lazyLoaded = true;
|
|
396
412
|
delete memo.lazyIsolated;
|
|
397
413
|
}
|
|
414
|
+
// Scripts already run by the browser are not sent again.
|
|
415
|
+
if (effects.scripts) {
|
|
416
|
+
const known = new Set(Array.isArray(memo.scripts) ? memo.scripts : []);
|
|
417
|
+
const fresh = Object.fromEntries(Object.entries(effects.scripts).filter(([key]) => !known.has(key)));
|
|
418
|
+
if (Object.keys(fresh).length) {
|
|
419
|
+
effects.scripts = fresh;
|
|
420
|
+
memo.scripts = [...known, ...Object.keys(fresh)];
|
|
421
|
+
}
|
|
422
|
+
else
|
|
423
|
+
delete effects.scripts;
|
|
424
|
+
}
|
|
398
425
|
return { snapshot: this.snapshot(component, name, memo), effects };
|
|
399
426
|
}
|
|
400
427
|
/** HTTP entry point; body: `{ _token, components: [{ snapshot, updates, calls }] }`. */
|
package/lib/uploads.js
CHANGED
|
@@ -135,6 +135,19 @@ async function detect(file, filename) {
|
|
|
135
135
|
const { buffer, bytesRead } = await handle.read(Buffer.alloc(512), 0, 512, 0);
|
|
136
136
|
const head = buffer.subarray(0, bytesRead);
|
|
137
137
|
const known = SIGNATURES.find((signature) => signature.test(head));
|
|
138
|
+
if (known?.extension === 'zip') {
|
|
139
|
+
// Office documents are ZIP archives: their central directory (at the end) names their main part.
|
|
140
|
+
const { size } = await handle.stat();
|
|
141
|
+
const length = Math.min(size, 66 * 1024);
|
|
142
|
+
const { buffer: tail } = await handle.read(Buffer.alloc(length), 0, length, size - length);
|
|
143
|
+
const names = tail.toString('latin1');
|
|
144
|
+
if (names.includes('xl/workbook.xml'))
|
|
145
|
+
return { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', extension: 'xlsx' };
|
|
146
|
+
if (names.includes('word/document.xml'))
|
|
147
|
+
return { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', extension: 'docx' };
|
|
148
|
+
if (names.includes('ppt/presentation.xml'))
|
|
149
|
+
return { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', extension: 'pptx' };
|
|
150
|
+
}
|
|
138
151
|
if (known)
|
|
139
152
|
return { type: known.type, extension: known.extension };
|
|
140
153
|
const extension = path.extname(String(filename ?? '')).slice(1).toLowerCase();
|