node-turbo 1.0.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/.c8rc.json +5 -0
- package/.esdoc.json +83 -0
- package/.eslintrc.json +10 -0
- package/.mocharc.json +7 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/docs/API.md +857 -0
- package/lib/errors/index.js +36 -0
- package/lib/express/express-turbo-stream.js +41 -0
- package/lib/express/index.js +4 -0
- package/lib/express/turbocharge-express.js +108 -0
- package/lib/index.js +8 -0
- package/lib/koa/index.js +4 -0
- package/lib/koa/koa-turbo-stream.js +44 -0
- package/lib/koa/turbocharge-koa.js +122 -0
- package/lib/request-helpers.js +53 -0
- package/lib/sse/index.js +3 -0
- package/lib/sse/sse-turbo-stream.js +137 -0
- package/lib/turbo-element.js +71 -0
- package/lib/turbo-frame.js +79 -0
- package/lib/turbo-readable.js +125 -0
- package/lib/turbo-stream-element.js +67 -0
- package/lib/turbo-stream.js +350 -0
- package/lib/ws/index.js +4 -0
- package/lib/ws/ws-turbo-stream.js +112 -0
- package/package.json +75 -0
- package/test/hooks.js +46 -0
- package/test/integration/express.test.js +137 -0
- package/test/integration/koa.test.js +125 -0
- package/test/integration/sse.test.js +80 -0
- package/test/integration/ws.test.js +155 -0
- package/test/package.test.js +68 -0
- package/test/unit/core/request-helpers.test.js +97 -0
- package/test/unit/core/turbo-element.test.js +15 -0
- package/test/unit/core/turbo-frame.test.js +63 -0
- package/test/unit/core/turbo-readable.test.js +93 -0
- package/test/unit/core/turbo-stream-element.test.js +76 -0
- package/test/unit/core/turbo-stream.test.js +308 -0
- package/test/unit/express/express-turbo-stream.test.js +39 -0
- package/test/unit/express/turbocharge-express.test.js +123 -0
- package/test/unit/koa/koa-turbo-stream.test.js +56 -0
- package/test/unit/koa/turbocharge-koa.test.js +141 -0
- package/test/unit/sse/sse-turbo-stream.test.js +109 -0
- package/test/unit/ws/ws-turbo-stream.test.js +46 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
import { TurboElement } from './turbo-element.js';
|
|
3
|
+
import { AttributeMissingError, AttributeMalformedError } from '#errors';
|
|
4
|
+
import { isPlainObject } from 'is-plain-object';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This class represents a Turbo Frame message.
|
|
8
|
+
*
|
|
9
|
+
* @extends {TurboElement}
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
export class TurboFrame extends TurboElement {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The key which is added to the HTTP headers when the request
|
|
16
|
+
* is made by a Turbo Frame.
|
|
17
|
+
*
|
|
18
|
+
* @type {String}
|
|
19
|
+
* @static
|
|
20
|
+
*/
|
|
21
|
+
static HEADER_KEY = 'turbo-frame';
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* MIME type of a Turbo Frame HTTP response, which is just `text/html`.
|
|
26
|
+
*
|
|
27
|
+
* @type {String}
|
|
28
|
+
* @static
|
|
29
|
+
*/
|
|
30
|
+
static MIME_TYPE = 'text/html';
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {String|Object} idOrAttributes - Either the ID as string or an object
|
|
35
|
+
* containing all attributes (including `id`).
|
|
36
|
+
* @param {String} content - The HTML content of this Turbo Frame message.
|
|
37
|
+
*/
|
|
38
|
+
constructor(idOrAttributes, content) {
|
|
39
|
+
if (typeof idOrAttributes === 'string') {
|
|
40
|
+
super({ id: idOrAttributes }, content);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
super(idOrAttributes, content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validates the attributes. `attributes.id` is mandatory.
|
|
50
|
+
* Gets called automatically by the constructor.
|
|
51
|
+
*
|
|
52
|
+
* @throws {AttributeMissingError} when mandatory attributes are missing.
|
|
53
|
+
* @throws {AttributeMalformedError} when mandatory attributes are malformed.
|
|
54
|
+
*/
|
|
55
|
+
validate() {
|
|
56
|
+
if (typeof this.attributes === 'undefined' || !isPlainObject(this.attributes) || !('id' in this.attributes)) {
|
|
57
|
+
throw new AttributeMissingError('TurboFrame: Attribute "id" is missing.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof this.attributes.id !== 'string') {
|
|
61
|
+
throw new AttributeMalformedError('TurboFrame: Attribute "id" must be a string.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.attributes.id.length === 0) {
|
|
65
|
+
throw new AttributeMalformedError('TurboFrame: Attribute "id" must be a string with non-zero length.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Renders the Turbo Frame message as HTML string and returns it.
|
|
72
|
+
*
|
|
73
|
+
* @returns {String} The rendered HTML.
|
|
74
|
+
*/
|
|
75
|
+
render() {
|
|
76
|
+
return `<turbo-frame ${this.renderAttributesAsHtml()}>${this.content}</turbo-frame>`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
import { TurboStream } from '#core';
|
|
4
|
+
import db from 'debug';
|
|
5
|
+
const debug = db('node-turbo:readable');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This class represents a readable stream which reads
|
|
10
|
+
* messages/elements from a Turbo Stream instance.
|
|
11
|
+
*
|
|
12
|
+
* @extends {stream~Readable}
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
export class TurboReadable extends Readable {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Turbo Stream instance to create the readable stream for.
|
|
19
|
+
*
|
|
20
|
+
* @type {TurboStream}
|
|
21
|
+
*/
|
|
22
|
+
_turboStream;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates the readable stream instance.
|
|
27
|
+
* Updates the Turbo Stream's configuration to not buffer elements
|
|
28
|
+
* and adds an event listener for `element` events to it, which get handled
|
|
29
|
+
* by `_boundPush(el)`.
|
|
30
|
+
*
|
|
31
|
+
* If there are already buffered elements, they get pushed into into the read
|
|
32
|
+
* queue immediately and the buffer is cleared afterwards.
|
|
33
|
+
*
|
|
34
|
+
* @param {TurboStream} turboStream - The Turbo Stream instance to create the readable stream for.
|
|
35
|
+
* @param {Object} [opts] - The options for the readable stream.
|
|
36
|
+
* @throws {Error} if `turboStream` is not a TurboStream instance
|
|
37
|
+
*/
|
|
38
|
+
constructor(turboStream, opts) {
|
|
39
|
+
debug('new TurboReadable()', opts);
|
|
40
|
+
|
|
41
|
+
if (!(turboStream instanceof TurboStream)) {
|
|
42
|
+
throw new Error('TurboReadable(): Not a TurboStream instance.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
super(Object.assign({ encoding: 'utf8' }, opts));
|
|
46
|
+
|
|
47
|
+
this._turboStream = turboStream;
|
|
48
|
+
this._turboStream.updateConfig({ buffer: false });
|
|
49
|
+
|
|
50
|
+
// If we have Turbo Stream elements, push them immediately.
|
|
51
|
+
if (this._turboStream.length > 0) {
|
|
52
|
+
this._turboStream.elements.forEach(el => this._pushElement(el));
|
|
53
|
+
this._turboStream.clear();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this._turboStream.on('element', this._boundPush);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pushes a Turbo Stream element into the read queue.
|
|
62
|
+
*
|
|
63
|
+
* @param {TurboStreamElement} el - The Turbo Stream element.
|
|
64
|
+
*/
|
|
65
|
+
_pushElement(el) {
|
|
66
|
+
debug('_pushElement()');
|
|
67
|
+
this.push(el.render());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* This is the bound variant of `_pushElement(el)`. This function serves
|
|
73
|
+
* as handler for the `element` event.
|
|
74
|
+
*
|
|
75
|
+
* @type {Function}
|
|
76
|
+
*/
|
|
77
|
+
_boundPush = this._pushElement.bind(this);
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets called when data is available for reading.
|
|
82
|
+
* This implementation does nothing.
|
|
83
|
+
* (Normally, push data would be pushed into the read queue here.)
|
|
84
|
+
*
|
|
85
|
+
* @todo Do we need backpressure handling?
|
|
86
|
+
*/
|
|
87
|
+
_read() {
|
|
88
|
+
debug('_read()');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Gets called when the stream is being destroyed. The event listener
|
|
94
|
+
* for the event `element` is removed and the configuration restored.
|
|
95
|
+
*
|
|
96
|
+
* @param {Error} err - The error object, if thrown.
|
|
97
|
+
*/
|
|
98
|
+
_destroy(err) {
|
|
99
|
+
debug('_destroy()', err);
|
|
100
|
+
this._turboStream.removeListener('element', this._boundPush);
|
|
101
|
+
this._turboStream.updateConfig({ buffer: true });
|
|
102
|
+
|
|
103
|
+
// if (typeof callback === 'function') {
|
|
104
|
+
// if (err) {
|
|
105
|
+
// callback(err);
|
|
106
|
+
// }
|
|
107
|
+
// else {
|
|
108
|
+
// callback();
|
|
109
|
+
// }
|
|
110
|
+
// }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Pushes `null` to the readable buffer to signal the end of the input.
|
|
116
|
+
*
|
|
117
|
+
* @todo Should we call this end() or is this confusing because normally
|
|
118
|
+
* only writable streams have this function.
|
|
119
|
+
*/
|
|
120
|
+
done() {
|
|
121
|
+
debug('done()');
|
|
122
|
+
this.push(null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import { TurboElement } from './turbo-element.js';
|
|
3
|
+
import { isPlainObject } from 'is-plain-object';
|
|
4
|
+
import { AttributeMissingError, AttributeMalformedError, AttributeInvalidError } from '#errors';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Turbo Stream element. A Turbo Stream message consists of one or several Turbo Stream
|
|
8
|
+
* elements.
|
|
9
|
+
*
|
|
10
|
+
* @extends {TurboElement}
|
|
11
|
+
* @since 1.0.0
|
|
12
|
+
*/
|
|
13
|
+
export class TurboStreamElement extends TurboElement {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates the attributes. `attributes.target` (or `attributes.targets`) and `attributes.action`
|
|
17
|
+
* are mandatory. Gets called by the constructor.
|
|
18
|
+
*
|
|
19
|
+
* @throws {AttributeMissingError} when mandatory attributes are missing.
|
|
20
|
+
* @throws {AttributeMalformedError} when mandatory attributes are malformed.
|
|
21
|
+
* @throws {AttributeInvalidError} when attributes are invalid.
|
|
22
|
+
*/
|
|
23
|
+
validate() {
|
|
24
|
+
if (typeof this.attributes === 'undefined' || !isPlainObject(this.attributes)) {
|
|
25
|
+
throw new AttributeMissingError('TurboStream: Attributes are missing.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!('target' in this.attributes) && !('targets' in this.attributes)) {
|
|
29
|
+
throw new AttributeMissingError('TurboStream: Attribute "target" or "targets" is missing.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if ('target' in this.attributes && 'targets' in this.attributes) {
|
|
33
|
+
throw new AttributeInvalidError('TurboStream: Attributes "target" and "targets" exclude each other.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ((typeof this.attributes.target !== 'string' || this.attributes.target.length === 0) &&
|
|
37
|
+
(typeof this.attributes.targets !== 'string' || this.attributes.targets.length === 0)) {
|
|
38
|
+
throw new AttributeMalformedError('TurboStream: Attribute "target"/"targets" must be a string with non-zero length.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!('action' in this.attributes)) {
|
|
42
|
+
throw new AttributeMissingError('TurboStream: Attribute "action" is missing.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof this.attributes.action !== 'string' || this.attributes.action.length === 0) {
|
|
46
|
+
throw new AttributeMalformedError('TurboStream: Attribute "action" must be a string with non-zero length.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Renders this Turbo Stream element as HTML string. Omits `<template>[content]<template>` when the attribute
|
|
53
|
+
* `action` is 'remove'.
|
|
54
|
+
*
|
|
55
|
+
* @returns {String} The rendered HTML fragment.
|
|
56
|
+
* @see https://turbo.hotwired.dev/handbook/streams#stream-messages-and-actions
|
|
57
|
+
*/
|
|
58
|
+
render() {
|
|
59
|
+
if (this.attributes.action === 'remove') {
|
|
60
|
+
return `<turbo-stream ${this.renderAttributesAsHtml()}></turbo-stream>`;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return `<turbo-stream ${this.renderAttributesAsHtml()}><template>${this.content}</template></turbo-stream>`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { TurboStreamElement, TurboReadable } from '#core';
|
|
4
|
+
import { Writable, Readable } from 'node:stream';
|
|
5
|
+
import db from 'debug';
|
|
6
|
+
const debug = db('node-turbo:turbostream');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A Turbo Stream message.
|
|
11
|
+
*
|
|
12
|
+
* @extends {events~EventEmitter}
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
export class TurboStream extends EventEmitter {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default configuration.
|
|
19
|
+
*
|
|
20
|
+
* @type {Object<String, String>}
|
|
21
|
+
* @property {Boolean} buffer - Should elements be added to the buffer (default: true)?
|
|
22
|
+
*/
|
|
23
|
+
config = {
|
|
24
|
+
buffer: true
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Array of buffered elements. Gets filled if `config.buffer` is `true`.
|
|
30
|
+
*
|
|
31
|
+
* @type {Array}
|
|
32
|
+
*/
|
|
33
|
+
elements = [];
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* MIME type for Turbo Stream messages.
|
|
38
|
+
*
|
|
39
|
+
* @see https://turbo.hotwired.dev/handbook/streams#streaming-from-http-responses
|
|
40
|
+
* @type {String}
|
|
41
|
+
* @static
|
|
42
|
+
*/
|
|
43
|
+
static MIME_TYPE = 'text/vnd.turbo-stream.html';
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* List of all supported actions:
|
|
48
|
+
* 'append', 'prepend', 'replace', 'update', 'remove', 'before' and 'after'.
|
|
49
|
+
*
|
|
50
|
+
* @see https://turbo.hotwired.dev/handbook/streams#stream-messages-and-actions
|
|
51
|
+
* @type {Array}
|
|
52
|
+
* @static
|
|
53
|
+
*/
|
|
54
|
+
static ACTIONS = [
|
|
55
|
+
'append',
|
|
56
|
+
'prepend',
|
|
57
|
+
'replace',
|
|
58
|
+
'update',
|
|
59
|
+
'remove',
|
|
60
|
+
'before',
|
|
61
|
+
'after'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The number of buffered Turbo Stream elements.
|
|
67
|
+
*
|
|
68
|
+
* @type {Number}
|
|
69
|
+
*/
|
|
70
|
+
get length() {
|
|
71
|
+
return this.elements.length;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* If `attributes` and `content` are available, a Turbo Stream element is added to the buffer,
|
|
77
|
+
* pending validation.
|
|
78
|
+
*
|
|
79
|
+
* @param {Object<String, String>} [attributes] - The attributes of this element.
|
|
80
|
+
* @param {String} [content] - The HTML content of this element.
|
|
81
|
+
*/
|
|
82
|
+
constructor(attributes, content) {
|
|
83
|
+
super();
|
|
84
|
+
|
|
85
|
+
if (typeof attributes !== 'undefined') {
|
|
86
|
+
return this.addElement(attributes, content);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extends/Overwrites the configuration.
|
|
93
|
+
*
|
|
94
|
+
* @param {Object} config - New configuration.
|
|
95
|
+
* @emits config
|
|
96
|
+
* @returns {TurboStream} The instance for chaining.
|
|
97
|
+
*/
|
|
98
|
+
updateConfig(config) {
|
|
99
|
+
if (config) {
|
|
100
|
+
this.config = Object.assign(this.config, config);
|
|
101
|
+
this.emit('config', this.config);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Adds a Turbo Stream element to the message.
|
|
110
|
+
* Adds the element to the buffer, if config.buffer === true.
|
|
111
|
+
* Fires the event 'element' with the added element.
|
|
112
|
+
*
|
|
113
|
+
* @param {Object<String, String>|TurboFrameElement} attributesOrElement -
|
|
114
|
+
* @param {String} content - The HTML content of the element.
|
|
115
|
+
* @emits element
|
|
116
|
+
* @returns {TurboStream} The instance for chaining.
|
|
117
|
+
*/
|
|
118
|
+
addElement(attributesOrElement, content) {
|
|
119
|
+
let el;
|
|
120
|
+
|
|
121
|
+
if (attributesOrElement instanceof TurboStreamElement) {
|
|
122
|
+
el = attributesOrElement;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
el = new TurboStreamElement(attributesOrElement, content);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this?.config?.buffer === true) {
|
|
129
|
+
this.elements.push(el);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.emit('element', el);
|
|
133
|
+
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Clears the buffer.
|
|
140
|
+
*
|
|
141
|
+
* @emits clear
|
|
142
|
+
* @returns {TurboStream} The instance for chaining.
|
|
143
|
+
*/
|
|
144
|
+
clear() {
|
|
145
|
+
this.elements = [];
|
|
146
|
+
this.emit('clear');
|
|
147
|
+
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Renders this Turbo Stream message if there are buffered elements.
|
|
154
|
+
*
|
|
155
|
+
* @returns {String|null} The rendered Turbo Stream HTML fragment or null if there were no buffered elements.
|
|
156
|
+
* @emits render
|
|
157
|
+
*/
|
|
158
|
+
render() {
|
|
159
|
+
const arr = this.renderElements();
|
|
160
|
+
if (arr !== null) {
|
|
161
|
+
const html = arr.join('\n');
|
|
162
|
+
this.emit('render', html);
|
|
163
|
+
|
|
164
|
+
return html;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* If there are buffered elements, renders them and returns an array with the HTML fragments.
|
|
173
|
+
*
|
|
174
|
+
* @returns {Array|null} The rendered Turbo Stream HTML fragments as array or null if there were no buffered elements.
|
|
175
|
+
*/
|
|
176
|
+
renderElements() {
|
|
177
|
+
if (this.elements.length > 0) {
|
|
178
|
+
return this.elements.map(el => el.render());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Renders this Turbo Stream message and clears the buffer.
|
|
187
|
+
*
|
|
188
|
+
* @returns {String|null} The rendered Turbo Stream HTML fragment or null if there were no buffered elements.
|
|
189
|
+
* @emits {render}
|
|
190
|
+
* @emits {clear}
|
|
191
|
+
*/
|
|
192
|
+
flush() {
|
|
193
|
+
const html = this.render();
|
|
194
|
+
this.clear();
|
|
195
|
+
|
|
196
|
+
return html;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Adds a Turbo Stream Element with a custom action.
|
|
202
|
+
*
|
|
203
|
+
* @param {String} action - The name of the custom action.
|
|
204
|
+
* @param {String} target - The target ID.
|
|
205
|
+
* @param {String} content - The HTML content of the element.
|
|
206
|
+
* @returns {TurboStream} The instance for chaining.
|
|
207
|
+
*/
|
|
208
|
+
custom(action, target, content) {
|
|
209
|
+
return this.addElement({ action, target }, content);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Adds a Turbo Stream Element with a custom action, targeting multiple DOM elements.
|
|
215
|
+
*
|
|
216
|
+
* @param {String} action - The name of the custom action.
|
|
217
|
+
* @param {String} targets - The query string targeting multiple DOM elements.
|
|
218
|
+
* @param {String} content - The HTML content of the element.
|
|
219
|
+
* @returns {TurboStream} The instance for chaining.
|
|
220
|
+
*/
|
|
221
|
+
customAll(action, targets, content) {
|
|
222
|
+
return this.addElement({ action, targets }, content);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
*
|
|
228
|
+
*/
|
|
229
|
+
createReadableStream(opts, streamOptions = {}) {
|
|
230
|
+
opts = Object.assign({}, { continuous: true }, opts);
|
|
231
|
+
debug('createReadableStream()', opts, streamOptions);
|
|
232
|
+
|
|
233
|
+
if (opts.continuous === true) {
|
|
234
|
+
debug(' returns new TurboStreamReadable()');
|
|
235
|
+
return new TurboReadable(this, streamOptions);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
debug(' returns Readable.from()');
|
|
239
|
+
return Readable.from(this.length > 0 ? this.render().split('\n') : [], streamOptions);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
// Add convenience functions for all supported actions.
|
|
246
|
+
TurboStream.ACTIONS.forEach(action => {
|
|
247
|
+
TurboStream.prototype[action] = function(targetOrAttributes, content) {
|
|
248
|
+
if (typeof targetOrAttributes === 'string') {
|
|
249
|
+
return this.addElement({ action: action, target: targetOrAttributes }, content);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
return this.addElement(Object.assign({ action: action }, targetOrAttributes), content);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
TurboStream.prototype[`${action}All`] = function(targets, content) {
|
|
257
|
+
return this.addElement({ action: action, targets }, content);
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Dynamic functions:
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @name #append
|
|
265
|
+
* @function
|
|
266
|
+
* @description Adds a Turbo Stream element with the action 'append' to the message.
|
|
267
|
+
* @param {String|Object<String, String>} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
268
|
+
* @param {String} content - The HTML content of the element.
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @name #prepend
|
|
273
|
+
* @function
|
|
274
|
+
* @description Adds a Turbo Stream element with the action 'prepend' to the message.
|
|
275
|
+
* @param {(String|Object<String, String>)} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
276
|
+
* @param {String} content - The HTML content of the element.
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @name #replace
|
|
281
|
+
* @function
|
|
282
|
+
* @description Adds a Turbo Stream element with the action 'replace' to the message.
|
|
283
|
+
* @param {(String|Object<String, String>)} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
284
|
+
* @param {String} content - The HTML content of the element.
|
|
285
|
+
*/
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @name #update
|
|
289
|
+
* @function
|
|
290
|
+
* @description Adds a Turbo Stream element with the action 'update' to the message.
|
|
291
|
+
* @param {String|Object<String, String>} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
292
|
+
* @param {String} content - The HTML content of the element.
|
|
293
|
+
*/
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @name #remove
|
|
297
|
+
* @function
|
|
298
|
+
* @description Adds a Turbo Stream element with the action 'remove' to the message.
|
|
299
|
+
* @param {String|Object<String, String>} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
300
|
+
* @param {String} content - The HTML content of the element.
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @name #before
|
|
305
|
+
* @function
|
|
306
|
+
* @description Adds a Turbo Stream element with the action 'before' to the message.
|
|
307
|
+
* @param {String|Object<String, String>} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
308
|
+
* @param {String} content - The HTML content of the element.
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @name after
|
|
313
|
+
* @function
|
|
314
|
+
* @description Adds a Turbo Stream element with the action 'after' to the message.
|
|
315
|
+
* @param {String|Object<String, String>} targetOrAttributes - Either the target ID as string or all attributes as object.
|
|
316
|
+
* @param {String} content - The HTML content of the element.
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
// Events:
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Event element.
|
|
323
|
+
* Gets fired when a Turbo Stream element has been added to a message.
|
|
324
|
+
*
|
|
325
|
+
* @event element
|
|
326
|
+
* @param {TurboStreamElement} - The added element.
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Event render.
|
|
331
|
+
* Gets fired when a Turbo Stream message has been rendered.
|
|
332
|
+
*
|
|
333
|
+
* @event render
|
|
334
|
+
* @param {String} - The rendered HTML fragment.
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Event config.
|
|
339
|
+
* Gets fired when the config has been updated.
|
|
340
|
+
*
|
|
341
|
+
* @event config
|
|
342
|
+
* @param {Object} - The updated config object.
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Event clear.
|
|
347
|
+
* Gets fired when the buffer is cleared.
|
|
348
|
+
*
|
|
349
|
+
* @event clear
|
|
350
|
+
*/
|
package/lib/ws/index.js
ADDED