livereload-morph 0.1.4 → 0.1.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/dist/livereload-morph.esm.js +1464 -0
- package/dist/livereload-morph.js +1308 -1275
- package/dist/livereload-morph.min.js +1 -1
- package/package.json +7 -5
package/dist/livereload-morph.js
CHANGED
|
@@ -1,1464 +1,1497 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
var
|
|
1
|
+
(() => {
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
4
29
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
30
|
+
// src/index.js
|
|
31
|
+
var exports_src = {};
|
|
32
|
+
__export(exports_src, {
|
|
33
|
+
default: () => src_default
|
|
34
|
+
});
|
|
10
35
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
// src/protocol.js
|
|
37
|
+
var PROTOCOL_6 = "http://livereload.com/protocols/official-6";
|
|
38
|
+
var PROTOCOL_7 = "http://livereload.com/protocols/official-7";
|
|
39
|
+
|
|
40
|
+
class ProtocolError {
|
|
41
|
+
constructor(reason, data) {
|
|
42
|
+
this.message = `LiveReload protocol error (${reason}) after receiving data: "${data}".`;
|
|
43
|
+
}
|
|
18
44
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
|
|
46
|
+
class Parser {
|
|
47
|
+
constructor(handlers) {
|
|
48
|
+
this.handlers = handlers;
|
|
49
|
+
this.reset();
|
|
50
|
+
}
|
|
51
|
+
reset() {
|
|
52
|
+
this.protocol = null;
|
|
53
|
+
}
|
|
54
|
+
process(data) {
|
|
55
|
+
try {
|
|
56
|
+
let message;
|
|
57
|
+
if (!this.protocol) {
|
|
58
|
+
if (data.match(new RegExp("^!!ver:([\\d.]+)$"))) {
|
|
31
59
|
this.protocol = 6;
|
|
32
|
-
} else {
|
|
33
|
-
|
|
60
|
+
} else if (message = this._parseMessage(data, ["hello"])) {
|
|
61
|
+
if (!message.protocols.length) {
|
|
62
|
+
throw new ProtocolError("no protocols specified in handshake message");
|
|
63
|
+
} else if (Array.from(message.protocols).includes(PROTOCOL_7)) {
|
|
64
|
+
this.protocol = 7;
|
|
65
|
+
} else if (Array.from(message.protocols).includes(PROTOCOL_6)) {
|
|
66
|
+
this.protocol = 6;
|
|
67
|
+
} else {
|
|
68
|
+
throw new ProtocolError("no supported protocols found");
|
|
69
|
+
}
|
|
34
70
|
}
|
|
71
|
+
return this.handlers.connected(this.protocol);
|
|
35
72
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
if (this.protocol === 6) {
|
|
74
|
+
message = JSON.parse(data);
|
|
75
|
+
if (!message.length) {
|
|
76
|
+
throw new ProtocolError("protocol 6 messages must be arrays");
|
|
77
|
+
}
|
|
78
|
+
const [command, options] = Array.from(message);
|
|
79
|
+
if (command !== "refresh") {
|
|
80
|
+
throw new ProtocolError("unknown protocol 6 command");
|
|
81
|
+
}
|
|
82
|
+
return this.handlers.message({
|
|
83
|
+
command: "reload",
|
|
84
|
+
path: options.path,
|
|
85
|
+
liveCSS: options.apply_css_live != null ? options.apply_css_live : true
|
|
86
|
+
});
|
|
42
87
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
88
|
+
message = this._parseMessage(data, ["reload", "alert"]);
|
|
89
|
+
return this.handlers.message(message);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (e instanceof ProtocolError) {
|
|
92
|
+
return this.handlers.error(e);
|
|
46
93
|
}
|
|
47
|
-
|
|
48
|
-
command: "reload",
|
|
49
|
-
path: options.path,
|
|
50
|
-
liveCSS: options.apply_css_live != null ? options.apply_css_live : true
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
message = this._parseMessage(data, ["reload", "alert"]);
|
|
54
|
-
return this.handlers.message(message);
|
|
55
|
-
} catch (e) {
|
|
56
|
-
if (e instanceof ProtocolError) {
|
|
57
|
-
return this.handlers.error(e);
|
|
94
|
+
throw e;
|
|
58
95
|
}
|
|
59
|
-
throw e;
|
|
60
96
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
_parseMessage(data, validCommands) {
|
|
98
|
+
let message;
|
|
99
|
+
try {
|
|
100
|
+
message = JSON.parse(data);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
throw new ProtocolError("unparsable JSON", data);
|
|
103
|
+
}
|
|
104
|
+
if (!message.command) {
|
|
105
|
+
throw new ProtocolError('missing "command" key', data);
|
|
106
|
+
}
|
|
107
|
+
if (!validCommands.includes(message.command)) {
|
|
108
|
+
throw new ProtocolError(`invalid command '${message.command}', only valid commands are: ${validCommands.join(", ")})`, data);
|
|
109
|
+
}
|
|
110
|
+
return message;
|
|
74
111
|
}
|
|
75
|
-
return message;
|
|
76
112
|
}
|
|
77
|
-
}
|
|
78
113
|
|
|
79
|
-
// src/connector.js
|
|
80
|
-
var VERSION = "1.0.0";
|
|
114
|
+
// src/connector.js
|
|
115
|
+
var VERSION = "1.0.0";
|
|
81
116
|
|
|
82
|
-
class Connector {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
class Connector {
|
|
118
|
+
constructor(options, WebSocket, Timer, handlers) {
|
|
119
|
+
this.options = options;
|
|
120
|
+
this.WebSocket = WebSocket;
|
|
121
|
+
this.Timer = Timer;
|
|
122
|
+
this.handlers = handlers;
|
|
123
|
+
const path = this.options.path ? `${this.options.path}` : "livereload";
|
|
124
|
+
const port = this.options.port ? `:${this.options.port}` : "";
|
|
125
|
+
this._uri = `ws${this.options.https ? "s" : ""}://${this.options.host}${port}/${path}`;
|
|
126
|
+
this._nextDelay = this.options.mindelay;
|
|
127
|
+
this._connectionDesired = false;
|
|
128
|
+
this.protocol = 0;
|
|
129
|
+
this.protocolParser = new Parser({
|
|
130
|
+
connected: (protocol) => {
|
|
131
|
+
this.protocol = protocol;
|
|
132
|
+
this._handshakeTimeout.stop();
|
|
133
|
+
this._nextDelay = this.options.mindelay;
|
|
134
|
+
this._disconnectionReason = "broken";
|
|
135
|
+
return this.handlers.connected(this.protocol);
|
|
136
|
+
},
|
|
137
|
+
error: (e) => {
|
|
138
|
+
this.handlers.error(e);
|
|
139
|
+
return this._closeOnError();
|
|
140
|
+
},
|
|
141
|
+
message: (message) => {
|
|
142
|
+
return this.handlers.message(message);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
this._handshakeTimeout = new this.Timer(() => {
|
|
146
|
+
if (!this._isSocketConnected()) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this._disconnectionReason = "handshake-timeout";
|
|
150
|
+
return this.socket.close();
|
|
151
|
+
});
|
|
152
|
+
this._reconnectTimer = new this.Timer(() => {
|
|
153
|
+
if (!this._connectionDesired) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
return this.connect();
|
|
157
|
+
});
|
|
158
|
+
this.connect();
|
|
159
|
+
}
|
|
160
|
+
_isSocketConnected() {
|
|
161
|
+
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
|
|
162
|
+
}
|
|
163
|
+
connect() {
|
|
164
|
+
this._connectionDesired = true;
|
|
165
|
+
if (this._isSocketConnected()) {
|
|
166
|
+
return;
|
|
108
167
|
}
|
|
109
|
-
|
|
110
|
-
|
|
168
|
+
this._reconnectTimer.stop();
|
|
169
|
+
this._disconnectionReason = "cannot-connect";
|
|
170
|
+
this.protocolParser.reset();
|
|
171
|
+
this.handlers.connecting();
|
|
172
|
+
this.socket = new this.WebSocket(this._uri);
|
|
173
|
+
this.socket.onopen = (e) => this._onopen(e);
|
|
174
|
+
this.socket.onclose = (e) => this._onclose(e);
|
|
175
|
+
this.socket.onmessage = (e) => this._onmessage(e);
|
|
176
|
+
this.socket.onerror = (e) => this._onerror(e);
|
|
177
|
+
}
|
|
178
|
+
disconnect() {
|
|
179
|
+
this._connectionDesired = false;
|
|
180
|
+
this._reconnectTimer.stop();
|
|
111
181
|
if (!this._isSocketConnected()) {
|
|
112
182
|
return;
|
|
113
183
|
}
|
|
114
|
-
this._disconnectionReason = "
|
|
184
|
+
this._disconnectionReason = "manual";
|
|
115
185
|
return this.socket.close();
|
|
116
|
-
}
|
|
117
|
-
|
|
186
|
+
}
|
|
187
|
+
_scheduleReconnection() {
|
|
118
188
|
if (!this._connectionDesired) {
|
|
119
189
|
return;
|
|
120
190
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
_isSocketConnected() {
|
|
126
|
-
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
|
|
127
|
-
}
|
|
128
|
-
connect() {
|
|
129
|
-
this._connectionDesired = true;
|
|
130
|
-
if (this._isSocketConnected()) {
|
|
131
|
-
return;
|
|
191
|
+
if (!this._reconnectTimer.running) {
|
|
192
|
+
this._reconnectTimer.start(this._nextDelay);
|
|
193
|
+
this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
|
|
194
|
+
}
|
|
132
195
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.socket.onopen = (e) => this._onopen(e);
|
|
139
|
-
this.socket.onclose = (e) => this._onclose(e);
|
|
140
|
-
this.socket.onmessage = (e) => this._onmessage(e);
|
|
141
|
-
this.socket.onerror = (e) => this._onerror(e);
|
|
142
|
-
}
|
|
143
|
-
disconnect() {
|
|
144
|
-
this._connectionDesired = false;
|
|
145
|
-
this._reconnectTimer.stop();
|
|
146
|
-
if (!this._isSocketConnected()) {
|
|
147
|
-
return;
|
|
196
|
+
sendCommand(command) {
|
|
197
|
+
if (!this.protocol) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
return this._sendCommand(command);
|
|
148
201
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
_scheduleReconnection() {
|
|
153
|
-
if (!this._connectionDesired) {
|
|
154
|
-
return;
|
|
202
|
+
_sendCommand(command) {
|
|
203
|
+
return this.socket.send(JSON.stringify(command));
|
|
155
204
|
}
|
|
156
|
-
|
|
157
|
-
this.
|
|
158
|
-
this.
|
|
205
|
+
_closeOnError() {
|
|
206
|
+
this._handshakeTimeout.stop();
|
|
207
|
+
this._disconnectionReason = "error";
|
|
208
|
+
return this.socket.close();
|
|
159
209
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
210
|
+
_onopen(e) {
|
|
211
|
+
this.handlers.socketConnected();
|
|
212
|
+
this._disconnectionReason = "handshake-failed";
|
|
213
|
+
const hello = {
|
|
214
|
+
command: "hello",
|
|
215
|
+
protocols: [PROTOCOL_6, PROTOCOL_7]
|
|
216
|
+
};
|
|
217
|
+
hello.ver = VERSION;
|
|
218
|
+
this._sendCommand(hello);
|
|
219
|
+
return this._handshakeTimeout.start(this.options.handshake_timeout);
|
|
164
220
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return this.socket.close();
|
|
174
|
-
}
|
|
175
|
-
_onopen(e) {
|
|
176
|
-
this.handlers.socketConnected();
|
|
177
|
-
this._disconnectionReason = "handshake-failed";
|
|
178
|
-
const hello = {
|
|
179
|
-
command: "hello",
|
|
180
|
-
protocols: [PROTOCOL_6, PROTOCOL_7]
|
|
181
|
-
};
|
|
182
|
-
hello.ver = VERSION;
|
|
183
|
-
this._sendCommand(hello);
|
|
184
|
-
return this._handshakeTimeout.start(this.options.handshake_timeout);
|
|
185
|
-
}
|
|
186
|
-
_onclose(e) {
|
|
187
|
-
this.protocol = 0;
|
|
188
|
-
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
|
|
189
|
-
return this._scheduleReconnection();
|
|
190
|
-
}
|
|
191
|
-
_onerror(e) {}
|
|
192
|
-
_onmessage(e) {
|
|
193
|
-
return this.protocolParser.process(e.data);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// src/timer.js
|
|
198
|
-
class Timer {
|
|
199
|
-
constructor(func) {
|
|
200
|
-
this.func = func;
|
|
201
|
-
this.running = false;
|
|
202
|
-
this.id = null;
|
|
203
|
-
this._handler = () => {
|
|
204
|
-
this.running = false;
|
|
205
|
-
this.id = null;
|
|
206
|
-
return this.func();
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
start(timeout) {
|
|
210
|
-
if (this.running) {
|
|
211
|
-
clearTimeout(this.id);
|
|
221
|
+
_onclose(e) {
|
|
222
|
+
this.protocol = 0;
|
|
223
|
+
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
|
|
224
|
+
return this._scheduleReconnection();
|
|
225
|
+
}
|
|
226
|
+
_onerror(e) {}
|
|
227
|
+
_onmessage(e) {
|
|
228
|
+
return this.protocolParser.process(e.data);
|
|
212
229
|
}
|
|
213
|
-
this.id = setTimeout(this._handler, timeout);
|
|
214
|
-
this.running = true;
|
|
215
230
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
|
|
232
|
+
// src/timer.js
|
|
233
|
+
class Timer {
|
|
234
|
+
constructor(func) {
|
|
235
|
+
this.func = func;
|
|
219
236
|
this.running = false;
|
|
220
237
|
this.id = null;
|
|
238
|
+
this._handler = () => {
|
|
239
|
+
this.running = false;
|
|
240
|
+
this.id = null;
|
|
241
|
+
return this.func();
|
|
242
|
+
};
|
|
221
243
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// src/options.js
|
|
227
|
-
class Options {
|
|
228
|
-
constructor() {
|
|
229
|
-
this.https = false;
|
|
230
|
-
this.host = null;
|
|
231
|
-
let port = 35729;
|
|
232
|
-
Object.defineProperty(this, "port", {
|
|
233
|
-
get() {
|
|
234
|
-
return port;
|
|
235
|
-
},
|
|
236
|
-
set(v) {
|
|
237
|
-
port = v ? isNaN(v) ? v : +v : "";
|
|
244
|
+
start(timeout) {
|
|
245
|
+
if (this.running) {
|
|
246
|
+
clearTimeout(this.id);
|
|
238
247
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.maxdelay = 60000;
|
|
242
|
-
this.handshake_timeout = 5000;
|
|
243
|
-
this.morphHTML = true;
|
|
244
|
-
this.verbose = false;
|
|
245
|
-
this.importCacheWaitPeriod = 200;
|
|
246
|
-
}
|
|
247
|
-
set(name, value) {
|
|
248
|
-
if (typeof value === "undefined") {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (!isNaN(+value)) {
|
|
252
|
-
value = +value;
|
|
248
|
+
this.id = setTimeout(this._handler, timeout);
|
|
249
|
+
this.running = true;
|
|
253
250
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
251
|
+
stop() {
|
|
252
|
+
if (this.running) {
|
|
253
|
+
clearTimeout(this.id);
|
|
254
|
+
this.running = false;
|
|
255
|
+
this.id = null;
|
|
256
|
+
}
|
|
258
257
|
}
|
|
259
|
-
this[name] = value;
|
|
260
258
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
259
|
+
Timer.start = (timeout, func) => setTimeout(func, timeout);
|
|
260
|
+
|
|
261
|
+
// src/options.js
|
|
262
|
+
class Options {
|
|
263
|
+
constructor() {
|
|
264
|
+
this.https = false;
|
|
265
|
+
this.host = null;
|
|
266
|
+
let port = 35729;
|
|
267
|
+
Object.defineProperty(this, "port", {
|
|
268
|
+
get() {
|
|
269
|
+
return port;
|
|
270
|
+
},
|
|
271
|
+
set(v) {
|
|
272
|
+
port = v ? isNaN(v) ? v : +v : "";
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
this.mindelay = 1000;
|
|
276
|
+
this.maxdelay = 60000;
|
|
277
|
+
this.handshake_timeout = 5000;
|
|
278
|
+
this.morphHTML = true;
|
|
279
|
+
this.verbose = false;
|
|
280
|
+
this.importCacheWaitPeriod = 200;
|
|
281
|
+
}
|
|
282
|
+
set(name, value) {
|
|
283
|
+
if (typeof value === "undefined") {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!isNaN(+value)) {
|
|
287
|
+
value = +value;
|
|
288
|
+
}
|
|
289
|
+
if (value === "true") {
|
|
290
|
+
value = true;
|
|
291
|
+
} else if (value === "false") {
|
|
292
|
+
value = false;
|
|
293
|
+
}
|
|
294
|
+
this[name] = value;
|
|
268
295
|
}
|
|
269
|
-
return options;
|
|
270
296
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (host) {
|
|
297
|
+
Options.extract = function(document2) {
|
|
298
|
+
const win = document2.defaultView || window;
|
|
299
|
+
if (win && win.LiveMorphOptions) {
|
|
275
300
|
const options = new Options;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
options.port = parseInt(port, 10);
|
|
280
|
-
const verbose = script.getAttribute("data-livereload-morph-verbose");
|
|
281
|
-
if (verbose !== null)
|
|
282
|
-
options.verbose = verbose === "true";
|
|
301
|
+
for (const [key, value] of Object.entries(win.LiveMorphOptions)) {
|
|
302
|
+
options.set(key, value);
|
|
303
|
+
}
|
|
283
304
|
return options;
|
|
284
305
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
beforeAttributeUpdated: noOp
|
|
302
|
-
},
|
|
303
|
-
head: {
|
|
304
|
-
style: "merge",
|
|
305
|
-
shouldPreserve: (elt) => elt.getAttribute("im-preserve") === "true",
|
|
306
|
-
shouldReAppend: (elt) => elt.getAttribute("im-re-append") === "true",
|
|
307
|
-
shouldRemove: noOp,
|
|
308
|
-
afterHeadMorphed: noOp
|
|
309
|
-
},
|
|
310
|
-
restoreFocus: true
|
|
306
|
+
const scripts = Array.from(document2.getElementsByTagName("script"));
|
|
307
|
+
for (const script of scripts) {
|
|
308
|
+
const host = script.getAttribute("data-livereload-morph-host");
|
|
309
|
+
if (host) {
|
|
310
|
+
const options = new Options;
|
|
311
|
+
options.host = host;
|
|
312
|
+
const port = script.getAttribute("data-livereload-morph-port");
|
|
313
|
+
if (port)
|
|
314
|
+
options.port = parseInt(port, 10);
|
|
315
|
+
const verbose = script.getAttribute("data-livereload-morph-verbose");
|
|
316
|
+
if (verbose !== null)
|
|
317
|
+
options.verbose = verbose === "true";
|
|
318
|
+
return options;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
311
322
|
};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
|
|
324
|
+
// node_modules/idiomorph/dist/idiomorph.esm.js
|
|
325
|
+
var Idiomorph = function() {
|
|
326
|
+
const noOp = () => {};
|
|
327
|
+
const defaults = {
|
|
328
|
+
morphStyle: "outerHTML",
|
|
329
|
+
callbacks: {
|
|
330
|
+
beforeNodeAdded: noOp,
|
|
331
|
+
afterNodeAdded: noOp,
|
|
332
|
+
beforeNodeMorphed: noOp,
|
|
333
|
+
afterNodeMorphed: noOp,
|
|
334
|
+
beforeNodeRemoved: noOp,
|
|
335
|
+
afterNodeRemoved: noOp,
|
|
336
|
+
beforeAttributeUpdated: noOp
|
|
337
|
+
},
|
|
338
|
+
head: {
|
|
339
|
+
style: "merge",
|
|
340
|
+
shouldPreserve: (elt) => elt.getAttribute("im-preserve") === "true",
|
|
341
|
+
shouldReAppend: (elt) => elt.getAttribute("im-re-append") === "true",
|
|
342
|
+
shouldRemove: noOp,
|
|
343
|
+
afterHeadMorphed: noOp
|
|
344
|
+
},
|
|
345
|
+
restoreFocus: true
|
|
346
|
+
};
|
|
347
|
+
function morph(oldNode, newContent, config = {}) {
|
|
348
|
+
oldNode = normalizeElement(oldNode);
|
|
349
|
+
const newNode = normalizeParent(newContent);
|
|
350
|
+
const ctx = createMorphContext(oldNode, newNode, config);
|
|
351
|
+
const morphedNodes = saveAndRestoreFocus(ctx, () => {
|
|
352
|
+
return withHeadBlocking(ctx, oldNode, newNode, (ctx2) => {
|
|
353
|
+
if (ctx2.morphStyle === "innerHTML") {
|
|
354
|
+
morphChildren(ctx2, oldNode, newNode);
|
|
355
|
+
return Array.from(oldNode.childNodes);
|
|
356
|
+
} else {
|
|
357
|
+
return morphOuterHTML(ctx2, oldNode, newNode);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
324
360
|
});
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
361
|
+
ctx.pantry.remove();
|
|
362
|
+
return morphedNodes;
|
|
363
|
+
}
|
|
364
|
+
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
365
|
+
const oldParent = normalizeParent(oldNode);
|
|
366
|
+
morphChildren(ctx, oldParent, newNode, oldNode, oldNode.nextSibling);
|
|
367
|
+
return Array.from(oldParent.childNodes);
|
|
368
|
+
}
|
|
369
|
+
function saveAndRestoreFocus(ctx, fn) {
|
|
370
|
+
if (!ctx.config.restoreFocus)
|
|
371
|
+
return fn();
|
|
372
|
+
let activeElement = document.activeElement;
|
|
373
|
+
if (!(activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement)) {
|
|
374
|
+
return fn();
|
|
375
|
+
}
|
|
376
|
+
const { id: activeElementId, selectionStart, selectionEnd } = activeElement;
|
|
377
|
+
const results = fn();
|
|
378
|
+
if (activeElementId && activeElementId !== document.activeElement?.getAttribute("id")) {
|
|
379
|
+
activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
|
|
380
|
+
activeElement?.focus();
|
|
381
|
+
}
|
|
382
|
+
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
383
|
+
activeElement.setSelectionRange(selectionStart, selectionEnd);
|
|
384
|
+
}
|
|
385
|
+
return results;
|
|
386
|
+
}
|
|
387
|
+
const morphChildren = function() {
|
|
388
|
+
function morphChildren2(ctx, oldParent, newParent, insertionPoint = null, endPoint = null) {
|
|
389
|
+
if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
|
|
390
|
+
oldParent = oldParent.content;
|
|
391
|
+
newParent = newParent.content;
|
|
392
|
+
}
|
|
393
|
+
insertionPoint ||= oldParent.firstChild;
|
|
394
|
+
for (const newChild of newParent.childNodes) {
|
|
395
|
+
if (insertionPoint && insertionPoint != endPoint) {
|
|
396
|
+
const bestMatch = findBestMatch(ctx, newChild, insertionPoint, endPoint);
|
|
397
|
+
if (bestMatch) {
|
|
398
|
+
if (bestMatch !== insertionPoint) {
|
|
399
|
+
removeNodesBetween(ctx, insertionPoint, bestMatch);
|
|
400
|
+
}
|
|
401
|
+
morphNode(bestMatch, newChild, ctx);
|
|
402
|
+
insertionPoint = bestMatch.nextSibling;
|
|
403
|
+
continue;
|
|
365
404
|
}
|
|
366
|
-
morphNode(bestMatch, newChild, ctx);
|
|
367
|
-
insertionPoint = bestMatch.nextSibling;
|
|
368
|
-
continue;
|
|
369
405
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
406
|
+
if (newChild instanceof Element) {
|
|
407
|
+
const newChildId = newChild.getAttribute("id");
|
|
408
|
+
if (ctx.persistentIds.has(newChildId)) {
|
|
409
|
+
const movedChild = moveBeforeById(oldParent, newChildId, insertionPoint, ctx);
|
|
410
|
+
morphNode(movedChild, newChild, ctx);
|
|
411
|
+
insertionPoint = movedChild.nextSibling;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const insertedNode = createNode(oldParent, newChild, insertionPoint, ctx);
|
|
416
|
+
if (insertedNode) {
|
|
417
|
+
insertionPoint = insertedNode.nextSibling;
|
|
378
418
|
}
|
|
379
419
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
insertionPoint =
|
|
420
|
+
while (insertionPoint && insertionPoint != endPoint) {
|
|
421
|
+
const tempNode = insertionPoint;
|
|
422
|
+
insertionPoint = insertionPoint.nextSibling;
|
|
423
|
+
removeNode(ctx, tempNode);
|
|
383
424
|
}
|
|
384
425
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
} else {
|
|
401
|
-
const newClonedChild = document.importNode(newChild, true);
|
|
402
|
-
oldParent.insertBefore(newClonedChild, insertionPoint);
|
|
403
|
-
ctx.callbacks.afterNodeAdded(newClonedChild);
|
|
404
|
-
return newClonedChild;
|
|
426
|
+
function createNode(oldParent, newChild, insertionPoint, ctx) {
|
|
427
|
+
if (ctx.callbacks.beforeNodeAdded(newChild) === false)
|
|
428
|
+
return null;
|
|
429
|
+
if (ctx.idMap.has(newChild)) {
|
|
430
|
+
const newEmptyChild = document.createElement(newChild.tagName);
|
|
431
|
+
oldParent.insertBefore(newEmptyChild, insertionPoint);
|
|
432
|
+
morphNode(newEmptyChild, newChild, ctx);
|
|
433
|
+
ctx.callbacks.afterNodeAdded(newEmptyChild);
|
|
434
|
+
return newEmptyChild;
|
|
435
|
+
} else {
|
|
436
|
+
const newClonedChild = document.importNode(newChild, true);
|
|
437
|
+
oldParent.insertBefore(newClonedChild, insertionPoint);
|
|
438
|
+
ctx.callbacks.afterNodeAdded(newClonedChild);
|
|
439
|
+
return newClonedChild;
|
|
440
|
+
}
|
|
405
441
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
442
|
+
const findBestMatch = function() {
|
|
443
|
+
function findBestMatch2(ctx, node, startPoint, endPoint) {
|
|
444
|
+
let softMatch = null;
|
|
445
|
+
let nextSibling = node.nextSibling;
|
|
446
|
+
let siblingSoftMatchCount = 0;
|
|
447
|
+
let cursor = startPoint;
|
|
448
|
+
while (cursor && cursor != endPoint) {
|
|
449
|
+
if (isSoftMatch(cursor, node)) {
|
|
450
|
+
if (isIdSetMatch(ctx, cursor, node)) {
|
|
451
|
+
return cursor;
|
|
452
|
+
}
|
|
453
|
+
if (softMatch === null) {
|
|
454
|
+
if (!ctx.idMap.has(cursor)) {
|
|
455
|
+
softMatch = cursor;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
417
458
|
}
|
|
418
|
-
if (softMatch === null) {
|
|
419
|
-
|
|
420
|
-
|
|
459
|
+
if (softMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {
|
|
460
|
+
siblingSoftMatchCount++;
|
|
461
|
+
nextSibling = nextSibling.nextSibling;
|
|
462
|
+
if (siblingSoftMatchCount >= 2) {
|
|
463
|
+
softMatch = undefined;
|
|
421
464
|
}
|
|
422
465
|
}
|
|
466
|
+
if (ctx.activeElementAndParents.includes(cursor))
|
|
467
|
+
break;
|
|
468
|
+
cursor = cursor.nextSibling;
|
|
423
469
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
470
|
+
return softMatch || null;
|
|
471
|
+
}
|
|
472
|
+
function isIdSetMatch(ctx, oldNode, newNode) {
|
|
473
|
+
let oldSet = ctx.idMap.get(oldNode);
|
|
474
|
+
let newSet = ctx.idMap.get(newNode);
|
|
475
|
+
if (!newSet || !oldSet)
|
|
476
|
+
return false;
|
|
477
|
+
for (const id of oldSet) {
|
|
478
|
+
if (newSet.has(id)) {
|
|
479
|
+
return true;
|
|
429
480
|
}
|
|
430
481
|
}
|
|
431
|
-
if (ctx.activeElementAndParents.includes(cursor))
|
|
432
|
-
break;
|
|
433
|
-
cursor = cursor.nextSibling;
|
|
434
|
-
}
|
|
435
|
-
return softMatch || null;
|
|
436
|
-
}
|
|
437
|
-
function isIdSetMatch(ctx, oldNode, newNode) {
|
|
438
|
-
let oldSet = ctx.idMap.get(oldNode);
|
|
439
|
-
let newSet = ctx.idMap.get(newNode);
|
|
440
|
-
if (!newSet || !oldSet)
|
|
441
482
|
return false;
|
|
442
|
-
for (const id of oldSet) {
|
|
443
|
-
if (newSet.has(id)) {
|
|
444
|
-
return true;
|
|
445
|
-
}
|
|
446
483
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
ctx.callbacks.afterNodeRemoved(node);
|
|
484
|
+
function isSoftMatch(oldNode, newNode) {
|
|
485
|
+
const oldElt = oldNode;
|
|
486
|
+
const newElt = newNode;
|
|
487
|
+
return oldElt.nodeType === newElt.nodeType && oldElt.tagName === newElt.tagName && (!oldElt.getAttribute?.("id") || oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"));
|
|
488
|
+
}
|
|
489
|
+
return findBestMatch2;
|
|
490
|
+
}();
|
|
491
|
+
function removeNode(ctx, node) {
|
|
492
|
+
if (ctx.idMap.has(node)) {
|
|
493
|
+
moveBefore(ctx.pantry, node, null);
|
|
494
|
+
} else {
|
|
495
|
+
if (ctx.callbacks.beforeNodeRemoved(node) === false)
|
|
496
|
+
return;
|
|
497
|
+
node.parentNode?.removeChild(node);
|
|
498
|
+
ctx.callbacks.afterNodeRemoved(node);
|
|
499
|
+
}
|
|
464
500
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
501
|
+
function removeNodesBetween(ctx, startInclusive, endExclusive) {
|
|
502
|
+
let cursor = startInclusive;
|
|
503
|
+
while (cursor && cursor !== endExclusive) {
|
|
504
|
+
let tempNode = cursor;
|
|
505
|
+
cursor = cursor.nextSibling;
|
|
506
|
+
removeNode(ctx, tempNode);
|
|
507
|
+
}
|
|
508
|
+
return cursor;
|
|
509
|
+
}
|
|
510
|
+
function moveBeforeById(parentNode, id, after, ctx) {
|
|
511
|
+
const target = ctx.target.getAttribute?.("id") === id && ctx.target || ctx.target.querySelector(`[id="${id}"]`) || ctx.pantry.querySelector(`[id="${id}"]`);
|
|
512
|
+
removeElementFromAncestorsIdMaps(target, ctx);
|
|
513
|
+
moveBefore(parentNode, target, after);
|
|
514
|
+
return target;
|
|
515
|
+
}
|
|
516
|
+
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
517
|
+
const id = element.getAttribute("id");
|
|
518
|
+
while (element = element.parentNode) {
|
|
519
|
+
let idSet = ctx.idMap.get(element);
|
|
520
|
+
if (idSet) {
|
|
521
|
+
idSet.delete(id);
|
|
522
|
+
if (!idSet.size) {
|
|
523
|
+
ctx.idMap.delete(element);
|
|
524
|
+
}
|
|
489
525
|
}
|
|
490
526
|
}
|
|
491
527
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
528
|
+
function moveBefore(parentNode, element, after) {
|
|
529
|
+
if (parentNode.moveBefore) {
|
|
530
|
+
try {
|
|
531
|
+
parentNode.moveBefore(element, after);
|
|
532
|
+
} catch (e) {
|
|
533
|
+
parentNode.insertBefore(element, after);
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
498
536
|
parentNode.insertBefore(element, after);
|
|
499
537
|
}
|
|
500
|
-
} else {
|
|
501
|
-
parentNode.insertBefore(element, after);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return morphChildren2;
|
|
505
|
-
}();
|
|
506
|
-
const morphNode = function() {
|
|
507
|
-
function morphNode2(oldNode, newContent, ctx) {
|
|
508
|
-
if (ctx.ignoreActive && oldNode === document.activeElement) {
|
|
509
|
-
return null;
|
|
510
538
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
morphAttributes(oldNode, newContent, ctx);
|
|
518
|
-
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
|
519
|
-
morphChildren(ctx, oldNode, newContent);
|
|
539
|
+
return morphChildren2;
|
|
540
|
+
}();
|
|
541
|
+
const morphNode = function() {
|
|
542
|
+
function morphNode2(oldNode, newContent, ctx) {
|
|
543
|
+
if (ctx.ignoreActive && oldNode === document.activeElement) {
|
|
544
|
+
return null;
|
|
520
545
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const oldAttributes = oldElt.attributes;
|
|
531
|
-
const newAttributes = newElt.attributes;
|
|
532
|
-
for (const newAttribute of newAttributes) {
|
|
533
|
-
if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
|
|
537
|
-
oldElt.setAttribute(newAttribute.name, newAttribute.value);
|
|
546
|
+
if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {
|
|
547
|
+
return oldNode;
|
|
548
|
+
}
|
|
549
|
+
if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) {} else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
|
|
550
|
+
handleHeadElement(oldNode, newContent, ctx);
|
|
551
|
+
} else {
|
|
552
|
+
morphAttributes(oldNode, newContent, ctx);
|
|
553
|
+
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
|
554
|
+
morphChildren(ctx, oldNode, newContent);
|
|
538
555
|
}
|
|
539
556
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
557
|
+
ctx.callbacks.afterNodeMorphed(oldNode, newContent);
|
|
558
|
+
return oldNode;
|
|
559
|
+
}
|
|
560
|
+
function morphAttributes(oldNode, newNode, ctx) {
|
|
561
|
+
let type = newNode.nodeType;
|
|
562
|
+
if (type === 1) {
|
|
563
|
+
const oldElt = oldNode;
|
|
564
|
+
const newElt = newNode;
|
|
565
|
+
const oldAttributes = oldElt.attributes;
|
|
566
|
+
const newAttributes = newElt.attributes;
|
|
567
|
+
for (const newAttribute of newAttributes) {
|
|
568
|
+
if (ignoreAttribute(newAttribute.name, oldElt, "update", ctx)) {
|
|
546
569
|
continue;
|
|
547
570
|
}
|
|
548
|
-
oldElt.
|
|
571
|
+
if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {
|
|
572
|
+
oldElt.setAttribute(newAttribute.name, newAttribute.value);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
for (let i = oldAttributes.length - 1;0 <= i; i--) {
|
|
576
|
+
const oldAttribute = oldAttributes[i];
|
|
577
|
+
if (!oldAttribute)
|
|
578
|
+
continue;
|
|
579
|
+
if (!newElt.hasAttribute(oldAttribute.name)) {
|
|
580
|
+
if (ignoreAttribute(oldAttribute.name, oldElt, "remove", ctx)) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
oldElt.removeAttribute(oldAttribute.name);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (!ignoreValueOfActiveElement(oldElt, ctx)) {
|
|
587
|
+
syncInputValue(oldElt, newElt, ctx);
|
|
549
588
|
}
|
|
550
589
|
}
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (type === 8 || type === 3) {
|
|
556
|
-
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
557
|
-
oldNode.nodeValue = newNode.nodeValue;
|
|
590
|
+
if (type === 8 || type === 3) {
|
|
591
|
+
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
592
|
+
oldNode.nodeValue = newNode.nodeValue;
|
|
593
|
+
}
|
|
558
594
|
}
|
|
559
595
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
596
|
+
function syncInputValue(oldElement, newElement, ctx) {
|
|
597
|
+
if (oldElement instanceof HTMLInputElement && newElement instanceof HTMLInputElement && newElement.type !== "file") {
|
|
598
|
+
let newValue = newElement.value;
|
|
599
|
+
let oldValue = oldElement.value;
|
|
600
|
+
syncBooleanAttribute(oldElement, newElement, "checked", ctx);
|
|
601
|
+
syncBooleanAttribute(oldElement, newElement, "disabled", ctx);
|
|
602
|
+
if (!newElement.hasAttribute("value")) {
|
|
603
|
+
if (!ignoreAttribute("value", oldElement, "remove", ctx)) {
|
|
604
|
+
oldElement.value = "";
|
|
605
|
+
oldElement.removeAttribute("value");
|
|
606
|
+
}
|
|
607
|
+
} else if (oldValue !== newValue) {
|
|
608
|
+
if (!ignoreAttribute("value", oldElement, "update", ctx)) {
|
|
609
|
+
oldElement.setAttribute("value", newValue);
|
|
610
|
+
oldElement.value = newValue;
|
|
611
|
+
}
|
|
571
612
|
}
|
|
572
|
-
} else if (
|
|
573
|
-
|
|
574
|
-
|
|
613
|
+
} else if (oldElement instanceof HTMLOptionElement && newElement instanceof HTMLOptionElement) {
|
|
614
|
+
syncBooleanAttribute(oldElement, newElement, "selected", ctx);
|
|
615
|
+
} else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
|
|
616
|
+
let newValue = newElement.value;
|
|
617
|
+
let oldValue = oldElement.value;
|
|
618
|
+
if (ignoreAttribute("value", oldElement, "update", ctx)) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (newValue !== oldValue) {
|
|
575
622
|
oldElement.value = newValue;
|
|
576
623
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
} else if (oldElement instanceof HTMLTextAreaElement && newElement instanceof HTMLTextAreaElement) {
|
|
581
|
-
let newValue = newElement.value;
|
|
582
|
-
let oldValue = oldElement.value;
|
|
583
|
-
if (ignoreAttribute("value", oldElement, "update", ctx)) {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
if (newValue !== oldValue) {
|
|
587
|
-
oldElement.value = newValue;
|
|
588
|
-
}
|
|
589
|
-
if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
|
|
590
|
-
oldElement.firstChild.nodeValue = newValue;
|
|
624
|
+
if (oldElement.firstChild && oldElement.firstChild.nodeValue !== newValue) {
|
|
625
|
+
oldElement.firstChild.nodeValue = newValue;
|
|
626
|
+
}
|
|
591
627
|
}
|
|
592
628
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
|
|
598
|
-
if (!ignoreUpdate) {
|
|
599
|
-
oldElement[attributeName] = newElement[attributeName];
|
|
600
|
-
}
|
|
601
|
-
if (newLiveValue) {
|
|
629
|
+
function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {
|
|
630
|
+
const newLiveValue = newElement[attributeName], oldLiveValue = oldElement[attributeName];
|
|
631
|
+
if (newLiveValue !== oldLiveValue) {
|
|
632
|
+
const ignoreUpdate = ignoreAttribute(attributeName, oldElement, "update", ctx);
|
|
602
633
|
if (!ignoreUpdate) {
|
|
603
|
-
oldElement
|
|
634
|
+
oldElement[attributeName] = newElement[attributeName];
|
|
604
635
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
636
|
+
if (newLiveValue) {
|
|
637
|
+
if (!ignoreUpdate) {
|
|
638
|
+
oldElement.setAttribute(attributeName, "");
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
if (!ignoreAttribute(attributeName, oldElement, "remove", ctx)) {
|
|
642
|
+
oldElement.removeAttribute(attributeName);
|
|
643
|
+
}
|
|
608
644
|
}
|
|
609
645
|
}
|
|
610
646
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
647
|
+
function ignoreAttribute(attr, element, updateType, ctx) {
|
|
648
|
+
if (attr === "value" && ctx.ignoreActiveValue && element === document.activeElement) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
return ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) === false;
|
|
615
652
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
|
619
|
-
return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
|
620
|
-
}
|
|
621
|
-
return morphNode2;
|
|
622
|
-
}();
|
|
623
|
-
function withHeadBlocking(ctx, oldNode, newNode, callback) {
|
|
624
|
-
if (ctx.head.block) {
|
|
625
|
-
const oldHead = oldNode.querySelector("head");
|
|
626
|
-
const newHead = newNode.querySelector("head");
|
|
627
|
-
if (oldHead && newHead) {
|
|
628
|
-
const promises = handleHeadElement(oldHead, newHead, ctx);
|
|
629
|
-
return Promise.all(promises).then(() => {
|
|
630
|
-
const newCtx = Object.assign(ctx, {
|
|
631
|
-
head: {
|
|
632
|
-
block: false,
|
|
633
|
-
ignore: true
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
return callback(newCtx);
|
|
637
|
-
});
|
|
653
|
+
function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
|
|
654
|
+
return !!ctx.ignoreActiveValue && possibleActiveElement === document.activeElement && possibleActiveElement !== document.body;
|
|
638
655
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (isReAppended) {
|
|
657
|
-
removed.push(currentHeadElt);
|
|
658
|
-
} else {
|
|
659
|
-
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
|
660
|
-
preserved.push(currentHeadElt);
|
|
656
|
+
return morphNode2;
|
|
657
|
+
}();
|
|
658
|
+
function withHeadBlocking(ctx, oldNode, newNode, callback) {
|
|
659
|
+
if (ctx.head.block) {
|
|
660
|
+
const oldHead = oldNode.querySelector("head");
|
|
661
|
+
const newHead = newNode.querySelector("head");
|
|
662
|
+
if (oldHead && newHead) {
|
|
663
|
+
const promises = handleHeadElement(oldHead, newHead, ctx);
|
|
664
|
+
return Promise.all(promises).then(() => {
|
|
665
|
+
const newCtx = Object.assign(ctx, {
|
|
666
|
+
head: {
|
|
667
|
+
block: false,
|
|
668
|
+
ignore: true
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
return callback(newCtx);
|
|
672
|
+
});
|
|
661
673
|
}
|
|
662
|
-
}
|
|
663
|
-
|
|
674
|
+
}
|
|
675
|
+
return callback(ctx);
|
|
676
|
+
}
|
|
677
|
+
function handleHeadElement(oldHead, newHead, ctx) {
|
|
678
|
+
let added = [];
|
|
679
|
+
let removed = [];
|
|
680
|
+
let preserved = [];
|
|
681
|
+
let nodesToAppend = [];
|
|
682
|
+
let srcToNewHeadNodes = new Map;
|
|
683
|
+
for (const newHeadChild of newHead.children) {
|
|
684
|
+
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
|
685
|
+
}
|
|
686
|
+
for (const currentHeadElt of oldHead.children) {
|
|
687
|
+
let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
|
688
|
+
let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
|
|
689
|
+
let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
|
|
690
|
+
if (inNewContent || isPreserved) {
|
|
664
691
|
if (isReAppended) {
|
|
665
692
|
removed.push(currentHeadElt);
|
|
666
|
-
|
|
693
|
+
} else {
|
|
694
|
+
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
|
695
|
+
preserved.push(currentHeadElt);
|
|
667
696
|
}
|
|
668
697
|
} else {
|
|
669
|
-
if (ctx.head.
|
|
670
|
-
|
|
698
|
+
if (ctx.head.style === "append") {
|
|
699
|
+
if (isReAppended) {
|
|
700
|
+
removed.push(currentHeadElt);
|
|
701
|
+
nodesToAppend.push(currentHeadElt);
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
if (ctx.head.shouldRemove(currentHeadElt) !== false) {
|
|
705
|
+
removed.push(currentHeadElt);
|
|
706
|
+
}
|
|
671
707
|
}
|
|
672
708
|
}
|
|
673
709
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
710
|
+
nodesToAppend.push(...srcToNewHeadNodes.values());
|
|
711
|
+
let promises = [];
|
|
712
|
+
for (const newNode of nodesToAppend) {
|
|
713
|
+
let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
|
|
714
|
+
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
|
715
|
+
if ("href" in newElt && newElt.href || "src" in newElt && newElt.src) {
|
|
716
|
+
let resolve;
|
|
717
|
+
let promise = new Promise(function(_resolve) {
|
|
718
|
+
resolve = _resolve;
|
|
719
|
+
});
|
|
720
|
+
newElt.addEventListener("load", function() {
|
|
721
|
+
resolve();
|
|
722
|
+
});
|
|
723
|
+
promises.push(promise);
|
|
724
|
+
}
|
|
725
|
+
oldHead.appendChild(newElt);
|
|
726
|
+
ctx.callbacks.afterNodeAdded(newElt);
|
|
727
|
+
added.push(newElt);
|
|
689
728
|
}
|
|
690
|
-
oldHead.appendChild(newElt);
|
|
691
|
-
ctx.callbacks.afterNodeAdded(newElt);
|
|
692
|
-
added.push(newElt);
|
|
693
729
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
ctx.callbacks.afterNodeRemoved(removedElement);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
ctx.head.afterHeadMorphed(oldHead, {
|
|
702
|
-
added,
|
|
703
|
-
kept: preserved,
|
|
704
|
-
removed
|
|
705
|
-
});
|
|
706
|
-
return promises;
|
|
707
|
-
}
|
|
708
|
-
const createMorphContext = function() {
|
|
709
|
-
function createMorphContext2(oldNode, newContent, config) {
|
|
710
|
-
const { persistentIds, idMap } = createIdMaps(oldNode, newContent);
|
|
711
|
-
const mergedConfig = mergeDefaults(config);
|
|
712
|
-
const morphStyle = mergedConfig.morphStyle || "outerHTML";
|
|
713
|
-
if (!["innerHTML", "outerHTML"].includes(morphStyle)) {
|
|
714
|
-
throw `Do not understand how to morph style ${morphStyle}`;
|
|
715
|
-
}
|
|
716
|
-
return {
|
|
717
|
-
target: oldNode,
|
|
718
|
-
newContent,
|
|
719
|
-
config: mergedConfig,
|
|
720
|
-
morphStyle,
|
|
721
|
-
ignoreActive: mergedConfig.ignoreActive,
|
|
722
|
-
ignoreActiveValue: mergedConfig.ignoreActiveValue,
|
|
723
|
-
restoreFocus: mergedConfig.restoreFocus,
|
|
724
|
-
idMap,
|
|
725
|
-
persistentIds,
|
|
726
|
-
pantry: createPantry(),
|
|
727
|
-
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
728
|
-
callbacks: mergedConfig.callbacks,
|
|
729
|
-
head: mergedConfig.head
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
function mergeDefaults(config) {
|
|
733
|
-
let finalConfig = Object.assign({}, defaults);
|
|
734
|
-
Object.assign(finalConfig, config);
|
|
735
|
-
finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
|
|
736
|
-
finalConfig.head = Object.assign({}, defaults.head, config.head);
|
|
737
|
-
return finalConfig;
|
|
738
|
-
}
|
|
739
|
-
function createPantry() {
|
|
740
|
-
const pantry = document.createElement("div");
|
|
741
|
-
pantry.hidden = true;
|
|
742
|
-
document.body.insertAdjacentElement("afterend", pantry);
|
|
743
|
-
return pantry;
|
|
744
|
-
}
|
|
745
|
-
function createActiveElementAndParents(oldNode) {
|
|
746
|
-
let activeElementAndParents = [];
|
|
747
|
-
let elt = document.activeElement;
|
|
748
|
-
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
749
|
-
while (elt) {
|
|
750
|
-
activeElementAndParents.push(elt);
|
|
751
|
-
if (elt === oldNode)
|
|
752
|
-
break;
|
|
753
|
-
elt = elt.parentElement;
|
|
730
|
+
for (const removedElement of removed) {
|
|
731
|
+
if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
|
|
732
|
+
oldHead.removeChild(removedElement);
|
|
733
|
+
ctx.callbacks.afterNodeRemoved(removedElement);
|
|
754
734
|
}
|
|
755
735
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
736
|
+
ctx.head.afterHeadMorphed(oldHead, {
|
|
737
|
+
added,
|
|
738
|
+
kept: preserved,
|
|
739
|
+
removed
|
|
740
|
+
});
|
|
741
|
+
return promises;
|
|
742
|
+
}
|
|
743
|
+
const createMorphContext = function() {
|
|
744
|
+
function createMorphContext2(oldNode, newContent, config) {
|
|
745
|
+
const { persistentIds, idMap } = createIdMaps(oldNode, newContent);
|
|
746
|
+
const mergedConfig = mergeDefaults(config);
|
|
747
|
+
const morphStyle = mergedConfig.morphStyle || "outerHTML";
|
|
748
|
+
if (!["innerHTML", "outerHTML"].includes(morphStyle)) {
|
|
749
|
+
throw `Do not understand how to morph style ${morphStyle}`;
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
target: oldNode,
|
|
753
|
+
newContent,
|
|
754
|
+
config: mergedConfig,
|
|
755
|
+
morphStyle,
|
|
756
|
+
ignoreActive: mergedConfig.ignoreActive,
|
|
757
|
+
ignoreActiveValue: mergedConfig.ignoreActiveValue,
|
|
758
|
+
restoreFocus: mergedConfig.restoreFocus,
|
|
759
|
+
idMap,
|
|
760
|
+
persistentIds,
|
|
761
|
+
pantry: createPantry(),
|
|
762
|
+
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
763
|
+
callbacks: mergedConfig.callbacks,
|
|
764
|
+
head: mergedConfig.head
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function mergeDefaults(config) {
|
|
768
|
+
let finalConfig = Object.assign({}, defaults);
|
|
769
|
+
Object.assign(finalConfig, config);
|
|
770
|
+
finalConfig.callbacks = Object.assign({}, defaults.callbacks, config.callbacks);
|
|
771
|
+
finalConfig.head = Object.assign({}, defaults.head, config.head);
|
|
772
|
+
return finalConfig;
|
|
773
|
+
}
|
|
774
|
+
function createPantry() {
|
|
775
|
+
const pantry = document.createElement("div");
|
|
776
|
+
pantry.hidden = true;
|
|
777
|
+
document.body.insertAdjacentElement("afterend", pantry);
|
|
778
|
+
return pantry;
|
|
779
|
+
}
|
|
780
|
+
function createActiveElementAndParents(oldNode) {
|
|
781
|
+
let activeElementAndParents = [];
|
|
782
|
+
let elt = document.activeElement;
|
|
783
|
+
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
784
|
+
while (elt) {
|
|
785
|
+
activeElementAndParents.push(elt);
|
|
786
|
+
if (elt === oldNode)
|
|
778
787
|
break;
|
|
779
|
-
|
|
788
|
+
elt = elt.parentElement;
|
|
780
789
|
}
|
|
781
790
|
}
|
|
791
|
+
return activeElementAndParents;
|
|
782
792
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const persistentIds = createPersistentIds(oldIdElements, newIdElements);
|
|
788
|
-
let idMap = new Map;
|
|
789
|
-
populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
|
|
790
|
-
const newRoot = newContent.__idiomorphRoot || newContent;
|
|
791
|
-
populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
|
|
792
|
-
return { persistentIds, idMap };
|
|
793
|
-
}
|
|
794
|
-
function createPersistentIds(oldIdElements, newIdElements) {
|
|
795
|
-
let duplicateIds = new Set;
|
|
796
|
-
let oldIdTagNameMap = new Map;
|
|
797
|
-
for (const { id, tagName } of oldIdElements) {
|
|
798
|
-
if (oldIdTagNameMap.has(id)) {
|
|
799
|
-
duplicateIds.add(id);
|
|
800
|
-
} else {
|
|
801
|
-
oldIdTagNameMap.set(id, tagName);
|
|
793
|
+
function findIdElements(root) {
|
|
794
|
+
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
795
|
+
if (root.getAttribute?.("id")) {
|
|
796
|
+
elements.push(root);
|
|
802
797
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
798
|
+
return elements;
|
|
799
|
+
}
|
|
800
|
+
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
801
|
+
for (const elt of elements) {
|
|
802
|
+
const id = elt.getAttribute("id");
|
|
803
|
+
if (persistentIds.has(id)) {
|
|
804
|
+
let current = elt;
|
|
805
|
+
while (current) {
|
|
806
|
+
let idSet = idMap.get(current);
|
|
807
|
+
if (idSet == null) {
|
|
808
|
+
idSet = new Set;
|
|
809
|
+
idMap.set(current, idSet);
|
|
810
|
+
}
|
|
811
|
+
idSet.add(id);
|
|
812
|
+
if (current === root)
|
|
813
|
+
break;
|
|
814
|
+
current = current.parentElement;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
810
817
|
}
|
|
811
818
|
}
|
|
812
|
-
|
|
813
|
-
|
|
819
|
+
function createIdMaps(oldContent, newContent) {
|
|
820
|
+
const oldIdElements = findIdElements(oldContent);
|
|
821
|
+
const newIdElements = findIdElements(newContent);
|
|
822
|
+
const persistentIds = createPersistentIds(oldIdElements, newIdElements);
|
|
823
|
+
let idMap = new Map;
|
|
824
|
+
populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);
|
|
825
|
+
const newRoot = newContent.__idiomorphRoot || newContent;
|
|
826
|
+
populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);
|
|
827
|
+
return { persistentIds, idMap };
|
|
828
|
+
}
|
|
829
|
+
function createPersistentIds(oldIdElements, newIdElements) {
|
|
830
|
+
let duplicateIds = new Set;
|
|
831
|
+
let oldIdTagNameMap = new Map;
|
|
832
|
+
for (const { id, tagName } of oldIdElements) {
|
|
833
|
+
if (oldIdTagNameMap.has(id)) {
|
|
834
|
+
duplicateIds.add(id);
|
|
835
|
+
} else {
|
|
836
|
+
oldIdTagNameMap.set(id, tagName);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
let persistentIds = new Set;
|
|
840
|
+
for (const { id, tagName } of newIdElements) {
|
|
841
|
+
if (persistentIds.has(id)) {
|
|
842
|
+
duplicateIds.add(id);
|
|
843
|
+
} else if (oldIdTagNameMap.get(id) === tagName) {
|
|
844
|
+
persistentIds.add(id);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
for (const id of duplicateIds) {
|
|
848
|
+
persistentIds.delete(id);
|
|
849
|
+
}
|
|
850
|
+
return persistentIds;
|
|
814
851
|
}
|
|
815
|
-
return
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return content;
|
|
852
|
+
return createMorphContext2;
|
|
853
|
+
}();
|
|
854
|
+
const { normalizeElement, normalizeParent } = function() {
|
|
855
|
+
const generatedByIdiomorph = new WeakSet;
|
|
856
|
+
function normalizeElement2(content) {
|
|
857
|
+
if (content instanceof Document) {
|
|
858
|
+
return content.documentElement;
|
|
859
|
+
} else {
|
|
860
|
+
return content;
|
|
861
|
+
}
|
|
826
862
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
863
|
+
function normalizeParent2(newContent) {
|
|
864
|
+
if (newContent == null) {
|
|
865
|
+
return document.createElement("div");
|
|
866
|
+
} else if (typeof newContent === "string") {
|
|
867
|
+
return normalizeParent2(parseContent(newContent));
|
|
868
|
+
} else if (generatedByIdiomorph.has(newContent)) {
|
|
869
|
+
return newContent;
|
|
870
|
+
} else if (newContent instanceof Node) {
|
|
871
|
+
if (newContent.parentNode) {
|
|
872
|
+
return new SlicedParentNode(newContent);
|
|
873
|
+
} else {
|
|
874
|
+
const dummyParent = document.createElement("div");
|
|
875
|
+
dummyParent.append(newContent);
|
|
876
|
+
return dummyParent;
|
|
877
|
+
}
|
|
838
878
|
} else {
|
|
839
879
|
const dummyParent = document.createElement("div");
|
|
840
|
-
|
|
880
|
+
for (const elt of [...newContent]) {
|
|
881
|
+
dummyParent.append(elt);
|
|
882
|
+
}
|
|
841
883
|
return dummyParent;
|
|
842
884
|
}
|
|
843
|
-
} else {
|
|
844
|
-
const dummyParent = document.createElement("div");
|
|
845
|
-
for (const elt of [...newContent]) {
|
|
846
|
-
dummyParent.append(elt);
|
|
847
|
-
}
|
|
848
|
-
return dummyParent;
|
|
849
885
|
}
|
|
850
|
-
}
|
|
851
886
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
887
|
+
class SlicedParentNode {
|
|
888
|
+
constructor(node) {
|
|
889
|
+
this.originalNode = node;
|
|
890
|
+
this.realParentNode = node.parentNode;
|
|
891
|
+
this.previousSibling = node.previousSibling;
|
|
892
|
+
this.nextSibling = node.nextSibling;
|
|
893
|
+
}
|
|
894
|
+
get childNodes() {
|
|
895
|
+
const nodes = [];
|
|
896
|
+
let cursor = this.previousSibling ? this.previousSibling.nextSibling : this.realParentNode.firstChild;
|
|
897
|
+
while (cursor && cursor != this.nextSibling) {
|
|
898
|
+
nodes.push(cursor);
|
|
899
|
+
cursor = cursor.nextSibling;
|
|
900
|
+
}
|
|
901
|
+
return nodes;
|
|
902
|
+
}
|
|
903
|
+
querySelectorAll(selector) {
|
|
904
|
+
return this.childNodes.reduce((results, node) => {
|
|
905
|
+
if (node instanceof Element) {
|
|
906
|
+
if (node.matches(selector))
|
|
907
|
+
results.push(node);
|
|
908
|
+
const nodeList = node.querySelectorAll(selector);
|
|
909
|
+
for (let i = 0;i < nodeList.length; i++) {
|
|
910
|
+
results.push(nodeList[i]);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return results;
|
|
914
|
+
}, []);
|
|
915
|
+
}
|
|
916
|
+
insertBefore(node, referenceNode) {
|
|
917
|
+
return this.realParentNode.insertBefore(node, referenceNode);
|
|
918
|
+
}
|
|
919
|
+
moveBefore(node, referenceNode) {
|
|
920
|
+
return this.realParentNode.moveBefore(node, referenceNode);
|
|
921
|
+
}
|
|
922
|
+
get __idiomorphRoot() {
|
|
923
|
+
return this.originalNode;
|
|
865
924
|
}
|
|
866
|
-
return nodes;
|
|
867
925
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
926
|
+
function parseContent(newContent) {
|
|
927
|
+
let parser = new DOMParser;
|
|
928
|
+
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
|
929
|
+
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
|
930
|
+
let content = parser.parseFromString(newContent, "text/html");
|
|
931
|
+
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
|
932
|
+
generatedByIdiomorph.add(content);
|
|
933
|
+
return content;
|
|
934
|
+
} else {
|
|
935
|
+
let htmlElement = content.firstChild;
|
|
936
|
+
if (htmlElement) {
|
|
937
|
+
generatedByIdiomorph.add(htmlElement);
|
|
876
938
|
}
|
|
939
|
+
return htmlElement;
|
|
877
940
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
insertBefore(node, referenceNode) {
|
|
882
|
-
return this.realParentNode.insertBefore(node, referenceNode);
|
|
883
|
-
}
|
|
884
|
-
moveBefore(node, referenceNode) {
|
|
885
|
-
return this.realParentNode.moveBefore(node, referenceNode);
|
|
886
|
-
}
|
|
887
|
-
get __idiomorphRoot() {
|
|
888
|
-
return this.originalNode;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
function parseContent(newContent) {
|
|
892
|
-
let parser = new DOMParser;
|
|
893
|
-
let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "");
|
|
894
|
-
if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
|
|
895
|
-
let content = parser.parseFromString(newContent, "text/html");
|
|
896
|
-
if (contentWithSvgsRemoved.match(/<\/html>/)) {
|
|
941
|
+
} else {
|
|
942
|
+
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
|
943
|
+
let content = responseDoc.body.querySelector("template").content;
|
|
897
944
|
generatedByIdiomorph.add(content);
|
|
898
945
|
return content;
|
|
899
|
-
} else {
|
|
900
|
-
let htmlElement = content.firstChild;
|
|
901
|
-
if (htmlElement) {
|
|
902
|
-
generatedByIdiomorph.add(htmlElement);
|
|
903
|
-
}
|
|
904
|
-
return htmlElement;
|
|
905
946
|
}
|
|
906
|
-
} else {
|
|
907
|
-
let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
|
|
908
|
-
let content = responseDoc.body.querySelector("template").content;
|
|
909
|
-
generatedByIdiomorph.add(content);
|
|
910
|
-
return content;
|
|
911
947
|
}
|
|
912
|
-
|
|
913
|
-
|
|
948
|
+
return { normalizeElement: normalizeElement2, normalizeParent: normalizeParent2 };
|
|
949
|
+
}();
|
|
950
|
+
return {
|
|
951
|
+
morph,
|
|
952
|
+
defaults
|
|
953
|
+
};
|
|
914
954
|
}();
|
|
915
|
-
return {
|
|
916
|
-
morph,
|
|
917
|
-
defaults
|
|
918
|
-
};
|
|
919
|
-
}();
|
|
920
955
|
|
|
921
|
-
// src/utils.js
|
|
922
|
-
function splitUrl(url) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
956
|
+
// src/utils.js
|
|
957
|
+
function splitUrl(url) {
|
|
958
|
+
let hash = "";
|
|
959
|
+
let params = "";
|
|
960
|
+
let index = url.indexOf("#");
|
|
961
|
+
if (index >= 0) {
|
|
962
|
+
hash = url.slice(index);
|
|
963
|
+
url = url.slice(0, index);
|
|
964
|
+
}
|
|
965
|
+
const comboSign = url.indexOf("??");
|
|
966
|
+
if (comboSign >= 0) {
|
|
967
|
+
if (comboSign + 1 !== url.lastIndexOf("?")) {
|
|
968
|
+
index = url.lastIndexOf("?");
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
index = url.indexOf("?");
|
|
934
972
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (index >= 0) {
|
|
939
|
-
params = url.slice(index);
|
|
940
|
-
url = url.slice(0, index);
|
|
941
|
-
}
|
|
942
|
-
return { url, params, hash };
|
|
943
|
-
}
|
|
944
|
-
function pathFromUrl(url) {
|
|
945
|
-
if (!url) {
|
|
946
|
-
return "";
|
|
947
|
-
}
|
|
948
|
-
let path;
|
|
949
|
-
({ url } = splitUrl(url));
|
|
950
|
-
if (url.indexOf("file://") === 0) {
|
|
951
|
-
path = url.replace(new RegExp("^file://(localhost)?"), "");
|
|
952
|
-
} else {
|
|
953
|
-
path = url.replace(new RegExp("^([^:]+:)?//([^:/]+)(:\\d*)?/"), "/");
|
|
954
|
-
}
|
|
955
|
-
return decodeURIComponent(path);
|
|
956
|
-
}
|
|
957
|
-
function numberOfMatchingSegments(left, right) {
|
|
958
|
-
left = left.replace(/^\/+/, "").toLowerCase();
|
|
959
|
-
right = right.replace(/^\/+/, "").toLowerCase();
|
|
960
|
-
if (left === right) {
|
|
961
|
-
return 1e4;
|
|
962
|
-
}
|
|
963
|
-
const comps1 = left.split(/\/|\\/).reverse();
|
|
964
|
-
const comps2 = right.split(/\/|\\/).reverse();
|
|
965
|
-
const len = Math.min(comps1.length, comps2.length);
|
|
966
|
-
let eqCount = 0;
|
|
967
|
-
while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
|
|
968
|
-
++eqCount;
|
|
969
|
-
}
|
|
970
|
-
return eqCount;
|
|
971
|
-
}
|
|
972
|
-
function pickBestMatch(path, objects, pathFunc = (s) => s) {
|
|
973
|
-
let bestMatch = { score: 0 };
|
|
974
|
-
for (const object of objects) {
|
|
975
|
-
const score = numberOfMatchingSegments(path, pathFunc(object));
|
|
976
|
-
if (score > bestMatch.score) {
|
|
977
|
-
bestMatch = { object, score };
|
|
973
|
+
if (index >= 0) {
|
|
974
|
+
params = url.slice(index);
|
|
975
|
+
url = url.slice(0, index);
|
|
978
976
|
}
|
|
977
|
+
return { url, params, hash };
|
|
979
978
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
979
|
+
function pathFromUrl(url) {
|
|
980
|
+
if (!url) {
|
|
981
|
+
return "";
|
|
982
|
+
}
|
|
983
|
+
let path;
|
|
984
|
+
({ url } = splitUrl(url));
|
|
985
|
+
if (url.indexOf("file://") === 0) {
|
|
986
|
+
path = url.replace(new RegExp("^file://(localhost)?"), "");
|
|
987
|
+
} else {
|
|
988
|
+
path = url.replace(new RegExp("^([^:]+:)?//([^:/]+)(:\\d*)?/"), "/");
|
|
989
|
+
}
|
|
990
|
+
return decodeURIComponent(path);
|
|
990
991
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
992
|
+
function numberOfMatchingSegments(left, right) {
|
|
993
|
+
left = left.replace(/^\/+/, "").toLowerCase();
|
|
994
|
+
right = right.replace(/^\/+/, "").toLowerCase();
|
|
995
|
+
if (left === right) {
|
|
996
|
+
return 1e4;
|
|
997
|
+
}
|
|
998
|
+
const comps1 = left.split(/\/|\\/).reverse();
|
|
999
|
+
const comps2 = right.split(/\/|\\/).reverse();
|
|
1000
|
+
const len = Math.min(comps1.length, comps2.length);
|
|
1001
|
+
let eqCount = 0;
|
|
1002
|
+
while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
|
|
1003
|
+
++eqCount;
|
|
1004
|
+
}
|
|
1005
|
+
return eqCount;
|
|
994
1006
|
}
|
|
995
|
-
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
if (resolved)
|
|
1002
|
-
return;
|
|
1003
|
-
resolved = true;
|
|
1004
|
-
resolve();
|
|
1005
|
-
};
|
|
1006
|
-
linkElement.onload = () => {
|
|
1007
|
-
finish();
|
|
1008
|
-
};
|
|
1009
|
-
const pollInterval = 50;
|
|
1010
|
-
const poll = () => {
|
|
1011
|
-
if (resolved)
|
|
1012
|
-
return;
|
|
1013
|
-
if (linkElement.sheet) {
|
|
1014
|
-
finish();
|
|
1015
|
-
return;
|
|
1007
|
+
function pickBestMatch(path, objects, pathFunc = (s) => s) {
|
|
1008
|
+
let bestMatch = { score: 0 };
|
|
1009
|
+
for (const object of objects) {
|
|
1010
|
+
const score = numberOfMatchingSegments(path, pathFunc(object));
|
|
1011
|
+
if (score > bestMatch.score) {
|
|
1012
|
+
bestMatch = { object, score };
|
|
1016
1013
|
}
|
|
1017
|
-
setTimeout(poll, pollInterval);
|
|
1018
|
-
};
|
|
1019
|
-
setTimeout(poll, pollInterval);
|
|
1020
|
-
setTimeout(finish, timeout);
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// src/morpher.js
|
|
1025
|
-
class Morpher {
|
|
1026
|
-
constructor(window2, console2, Timer2, importCacheWaitPeriod = 200) {
|
|
1027
|
-
this.window = window2;
|
|
1028
|
-
this.console = console2;
|
|
1029
|
-
this.Timer = Timer2;
|
|
1030
|
-
this.document = window2.document;
|
|
1031
|
-
this.importCacheWaitPeriod = importCacheWaitPeriod;
|
|
1032
|
-
}
|
|
1033
|
-
reload(path, options = {}) {
|
|
1034
|
-
const isCSSFile = path.match(/\.css(?:\.map)?$/i);
|
|
1035
|
-
const isImageFile = path.match(/\.(jpe?g|png|gif|svg|webp|ico)$/i);
|
|
1036
|
-
const isJSFile = path.match(/\.m?js$/i);
|
|
1037
|
-
if (isCSSFile && options.liveCSS) {
|
|
1038
|
-
return this.reloadStylesheet(path, options);
|
|
1039
1014
|
}
|
|
1040
|
-
if (
|
|
1041
|
-
return
|
|
1015
|
+
if (bestMatch.score === 0) {
|
|
1016
|
+
return null;
|
|
1042
1017
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1018
|
+
return bestMatch;
|
|
1019
|
+
}
|
|
1020
|
+
function generateCacheBustUrl(url) {
|
|
1021
|
+
const { url: cleanUrl, params, hash } = splitUrl(url);
|
|
1022
|
+
const expando = `livereload=${Date.now()}`;
|
|
1023
|
+
if (!params) {
|
|
1024
|
+
return `${cleanUrl}?${expando}${hash}`;
|
|
1045
1025
|
}
|
|
1046
|
-
if (
|
|
1047
|
-
|
|
1026
|
+
if (params.includes("livereload=")) {
|
|
1027
|
+
const newParams = params.replace(/([?&])livereload=\d+/, `$1${expando}`);
|
|
1028
|
+
return `${cleanUrl}${newParams}${hash}`;
|
|
1048
1029
|
}
|
|
1049
|
-
|
|
1030
|
+
return `${cleanUrl}${params}&${expando}${hash}`;
|
|
1050
1031
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1032
|
+
function waitForStylesheetLoad(linkElement, timeout = 15000) {
|
|
1033
|
+
return new Promise((resolve) => {
|
|
1034
|
+
let resolved = false;
|
|
1035
|
+
const finish = () => {
|
|
1036
|
+
if (resolved)
|
|
1037
|
+
return;
|
|
1038
|
+
resolved = true;
|
|
1039
|
+
resolve();
|
|
1040
|
+
};
|
|
1041
|
+
linkElement.onload = () => {
|
|
1042
|
+
finish();
|
|
1043
|
+
};
|
|
1044
|
+
const pollInterval = 50;
|
|
1045
|
+
const poll = () => {
|
|
1046
|
+
if (resolved)
|
|
1047
|
+
return;
|
|
1048
|
+
if (linkElement.sheet) {
|
|
1049
|
+
finish();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
setTimeout(poll, pollInterval);
|
|
1053
|
+
};
|
|
1054
|
+
setTimeout(poll, pollInterval);
|
|
1055
|
+
setTimeout(finish, timeout);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/morpher.js
|
|
1060
|
+
class Morpher {
|
|
1061
|
+
constructor(window2, console2, Timer2, importCacheWaitPeriod = 200) {
|
|
1062
|
+
this.window = window2;
|
|
1063
|
+
this.console = console2;
|
|
1064
|
+
this.Timer = Timer2;
|
|
1065
|
+
this.document = window2.document;
|
|
1066
|
+
this.importCacheWaitPeriod = importCacheWaitPeriod;
|
|
1067
|
+
}
|
|
1068
|
+
reload(path, options = {}) {
|
|
1069
|
+
const isCSSFile = path.match(/\.css(?:\.map)?$/i);
|
|
1070
|
+
const isImageFile = path.match(/\.(jpe?g|png|gif|svg|webp|ico)$/i);
|
|
1071
|
+
const isJSFile = path.match(/\.m?js$/i);
|
|
1072
|
+
if (isCSSFile && options.liveCSS) {
|
|
1073
|
+
return this.reloadStylesheet(path, options);
|
|
1074
|
+
}
|
|
1075
|
+
if (isImageFile && options.liveImg) {
|
|
1076
|
+
return this.reloadImages(path);
|
|
1077
|
+
}
|
|
1078
|
+
if (isJSFile) {
|
|
1079
|
+
return this.reloadPage();
|
|
1080
|
+
}
|
|
1081
|
+
if (options.morphHTML) {
|
|
1082
|
+
return this.morphHTML(path, options);
|
|
1083
|
+
}
|
|
1084
|
+
this.reloadPage();
|
|
1085
|
+
}
|
|
1086
|
+
async morphHTML(path, options = {}) {
|
|
1087
|
+
try {
|
|
1088
|
+
const response = await fetch(this.window.location.href, {
|
|
1089
|
+
cache: "no-cache",
|
|
1090
|
+
headers: { "X-Live-Morph": "true" }
|
|
1091
|
+
});
|
|
1092
|
+
if (!response.ok) {
|
|
1093
|
+
throw new Error(`Fetch failed: ${response.status} ${response.statusText}`);
|
|
1094
|
+
}
|
|
1095
|
+
let html = await response.text();
|
|
1096
|
+
html = html.replace(/<!DOCTYPE[^>]*>/i, "").trim();
|
|
1097
|
+
Idiomorph.morph(this.document.documentElement, html, {
|
|
1098
|
+
head: {
|
|
1099
|
+
style: "merge",
|
|
1100
|
+
shouldPreserve: (elt) => {
|
|
1101
|
+
if (elt.tagName === "SCRIPT" && elt.src) {
|
|
1102
|
+
return elt.src.toLowerCase().includes("livereload-morph");
|
|
1103
|
+
}
|
|
1104
|
+
return false;
|
|
1068
1105
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1106
|
+
},
|
|
1107
|
+
callbacks: {
|
|
1108
|
+
beforeAttributeUpdated: (attributeName, node, mutationType) => {
|
|
1109
|
+
if (node.tagName === "INPUT" || node.tagName === "TEXTAREA" || node.tagName === "SELECT") {
|
|
1110
|
+
if (attributeName === "value" || attributeName === "checked") {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (node.tagName === "DETAILS" && attributeName === "open") {
|
|
1076
1115
|
return false;
|
|
1077
1116
|
}
|
|
1117
|
+
return true;
|
|
1078
1118
|
}
|
|
1079
|
-
if (node.tagName === "DETAILS" && attributeName === "open") {
|
|
1080
|
-
return false;
|
|
1081
|
-
}
|
|
1082
|
-
return true;
|
|
1083
1119
|
}
|
|
1120
|
+
});
|
|
1121
|
+
this.console.log("HTML morphed successfully");
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
this.console.error(`Morph failed: ${error.message}`);
|
|
1124
|
+
if (options.fallbackToReload !== false) {
|
|
1125
|
+
this.console.log("Falling back to full page reload");
|
|
1126
|
+
this.reloadPage();
|
|
1084
1127
|
}
|
|
1085
|
-
});
|
|
1086
|
-
this.console.log("HTML morphed successfully");
|
|
1087
|
-
} catch (error) {
|
|
1088
|
-
this.console.error(`Morph failed: ${error.message}`);
|
|
1089
|
-
if (options.fallbackToReload !== false) {
|
|
1090
|
-
this.console.log("Falling back to full page reload");
|
|
1091
|
-
this.reloadPage();
|
|
1092
1128
|
}
|
|
1093
1129
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1130
|
+
async reloadStylesheet(path, options = {}) {
|
|
1131
|
+
try {
|
|
1132
|
+
const links = Array.from(this.document.getElementsByTagName("link")).filter((link) => link.rel && link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval);
|
|
1133
|
+
const imported = [];
|
|
1134
|
+
for (const style of Array.from(this.document.getElementsByTagName("style"))) {
|
|
1135
|
+
if (style.sheet) {
|
|
1136
|
+
this.collectImportedStylesheets(style, style.sheet, imported);
|
|
1137
|
+
}
|
|
1102
1138
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1139
|
+
for (const link of links) {
|
|
1140
|
+
if (link.sheet) {
|
|
1141
|
+
this.collectImportedStylesheets(link, link.sheet, imported);
|
|
1142
|
+
}
|
|
1107
1143
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1144
|
+
if (this.window.StyleFix && this.document.querySelectorAll) {
|
|
1145
|
+
for (const style of Array.from(this.document.querySelectorAll("style[data-href]"))) {
|
|
1146
|
+
links.push(style);
|
|
1147
|
+
}
|
|
1112
1148
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1149
|
+
this.console.log(`CSS reload: found ${links.length} LINKed stylesheets, ${imported.length} @imported stylesheets`);
|
|
1150
|
+
const match = pickBestMatch(path, links.concat(imported), (item) => pathFromUrl(item.href || this.linkHref(item)));
|
|
1151
|
+
if (!match) {
|
|
1152
|
+
if (options.reloadMissingCSS !== false) {
|
|
1153
|
+
this.console.log(`CSS reload: no match found for '${path}', reloading all stylesheets`);
|
|
1154
|
+
for (const link of links) {
|
|
1155
|
+
await this.reattachStylesheetLink(link);
|
|
1156
|
+
}
|
|
1157
|
+
} else {
|
|
1158
|
+
this.console.log(`CSS reload: no match found for '${path}', skipping (reloadMissingCSS=false)`);
|
|
1121
1159
|
}
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (match.object.rule) {
|
|
1163
|
+
this.console.log(`CSS reload: reloading @imported stylesheet: ${match.object.href}`);
|
|
1164
|
+
await this.reattachImportedRule(match.object);
|
|
1122
1165
|
} else {
|
|
1123
|
-
this.console.log(`CSS reload:
|
|
1166
|
+
this.console.log(`CSS reload: reloading stylesheet: ${this.linkHref(match.object)}`);
|
|
1167
|
+
await this.reattachStylesheetLink(match.object);
|
|
1124
1168
|
}
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
this.console.error(`Stylesheet reload failed: ${error.message}`);
|
|
1171
|
+
this.console.error("Stack:", error.stack);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async reattachStylesheetLink(link) {
|
|
1175
|
+
if (link.__LiveReload_pendingRemoval) {
|
|
1125
1176
|
return;
|
|
1126
1177
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1178
|
+
link.__LiveReload_pendingRemoval = true;
|
|
1179
|
+
let clone;
|
|
1180
|
+
if (link.tagName === "STYLE") {
|
|
1181
|
+
clone = this.document.createElement("link");
|
|
1182
|
+
clone.rel = "stylesheet";
|
|
1183
|
+
clone.media = link.media;
|
|
1184
|
+
clone.disabled = link.disabled;
|
|
1130
1185
|
} else {
|
|
1131
|
-
|
|
1132
|
-
await this.reattachStylesheetLink(match.object);
|
|
1186
|
+
clone = link.cloneNode(false);
|
|
1133
1187
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
if (link.__LiveReload_pendingRemoval) {
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
link.__LiveReload_pendingRemoval = true;
|
|
1144
|
-
let clone;
|
|
1145
|
-
if (link.tagName === "STYLE") {
|
|
1146
|
-
clone = this.document.createElement("link");
|
|
1147
|
-
clone.rel = "stylesheet";
|
|
1148
|
-
clone.media = link.media;
|
|
1149
|
-
clone.disabled = link.disabled;
|
|
1150
|
-
} else {
|
|
1151
|
-
clone = link.cloneNode(false);
|
|
1152
|
-
}
|
|
1153
|
-
clone.href = generateCacheBustUrl(this.linkHref(link));
|
|
1154
|
-
const parent = link.parentNode;
|
|
1155
|
-
if (parent.lastChild === link) {
|
|
1156
|
-
parent.appendChild(clone);
|
|
1157
|
-
} else {
|
|
1158
|
-
parent.insertBefore(clone, link.nextSibling);
|
|
1159
|
-
}
|
|
1160
|
-
await waitForStylesheetLoad(clone);
|
|
1161
|
-
const additionalWait = /AppleWebKit/.test(this.window.navigator.userAgent) ? 5 : 200;
|
|
1162
|
-
await new Promise((resolve) => this.Timer.start(additionalWait, resolve));
|
|
1163
|
-
if (link.parentNode) {
|
|
1164
|
-
link.parentNode.removeChild(link);
|
|
1165
|
-
}
|
|
1166
|
-
if (this.window.StyleFix) {
|
|
1167
|
-
this.window.StyleFix.link(clone);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
reloadPage() {
|
|
1171
|
-
this.window.location.reload();
|
|
1172
|
-
}
|
|
1173
|
-
reloadImages(path) {
|
|
1174
|
-
for (const img of Array.from(this.document.images)) {
|
|
1175
|
-
if (this.pathsMatch(path, pathFromUrl(img.src))) {
|
|
1176
|
-
img.src = generateCacheBustUrl(img.src);
|
|
1188
|
+
clone.href = generateCacheBustUrl(this.linkHref(link));
|
|
1189
|
+
const parent = link.parentNode;
|
|
1190
|
+
if (parent.lastChild === link) {
|
|
1191
|
+
parent.appendChild(clone);
|
|
1192
|
+
} else {
|
|
1193
|
+
parent.insertBefore(clone, link.nextSibling);
|
|
1177
1194
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1195
|
+
await waitForStylesheetLoad(clone);
|
|
1196
|
+
const additionalWait = /AppleWebKit/.test(this.window.navigator.userAgent) ? 5 : 200;
|
|
1197
|
+
await new Promise((resolve) => this.Timer.start(additionalWait, resolve));
|
|
1198
|
+
if (link.parentNode) {
|
|
1199
|
+
link.parentNode.removeChild(link);
|
|
1200
|
+
}
|
|
1201
|
+
if (this.window.StyleFix) {
|
|
1202
|
+
this.window.StyleFix.link(clone);
|
|
1184
1203
|
}
|
|
1185
1204
|
}
|
|
1186
|
-
|
|
1187
|
-
this.
|
|
1188
|
-
}
|
|
1189
|
-
this.console.log(`Image reload: ${path}`);
|
|
1190
|
-
}
|
|
1191
|
-
reloadStylesheetImages(styleSheet, path) {
|
|
1192
|
-
let rules;
|
|
1193
|
-
try {
|
|
1194
|
-
rules = (styleSheet || {}).cssRules;
|
|
1195
|
-
} catch (e) {
|
|
1196
|
-
return;
|
|
1205
|
+
reloadPage() {
|
|
1206
|
+
this.window.location.reload();
|
|
1197
1207
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
case CSSRule.IMPORT_RULE:
|
|
1204
|
-
this.reloadStylesheetImages(rule.styleSheet, path);
|
|
1205
|
-
break;
|
|
1206
|
-
case CSSRule.STYLE_RULE:
|
|
1207
|
-
this.reloadStyleImages(rule.style, bgStyleNames, path);
|
|
1208
|
-
break;
|
|
1209
|
-
case CSSRule.MEDIA_RULE:
|
|
1210
|
-
this.reloadStylesheetImages(rule, path);
|
|
1211
|
-
break;
|
|
1208
|
+
reloadImages(path) {
|
|
1209
|
+
for (const img of Array.from(this.document.images)) {
|
|
1210
|
+
if (this.pathsMatch(path, pathFromUrl(img.src))) {
|
|
1211
|
+
img.src = generateCacheBustUrl(img.src);
|
|
1212
|
+
}
|
|
1212
1213
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
if (typeof value === "string") {
|
|
1219
|
-
const newValue = value.replace(/\burl\s*\(([^)]*)\)/g, (match, src) => {
|
|
1220
|
-
const cleanSrc = src.replace(/^['"]|['"]$/g, "");
|
|
1221
|
-
if (this.pathsMatch(path, pathFromUrl(cleanSrc))) {
|
|
1222
|
-
return `url(${generateCacheBustUrl(cleanSrc)})`;
|
|
1223
|
-
}
|
|
1224
|
-
return match;
|
|
1225
|
-
});
|
|
1226
|
-
if (newValue !== value) {
|
|
1227
|
-
style[styleName] = newValue;
|
|
1214
|
+
const bgSelectors = ["background", "border"];
|
|
1215
|
+
const bgStyleNames = ["backgroundImage", "borderImage", "webkitBorderImage", "MozBorderImage"];
|
|
1216
|
+
for (const selector of bgSelectors) {
|
|
1217
|
+
for (const el of Array.from(this.document.querySelectorAll(`[style*=${selector}]`))) {
|
|
1218
|
+
this.reloadStyleImages(el.style, bgStyleNames, path);
|
|
1228
1219
|
}
|
|
1229
1220
|
}
|
|
1221
|
+
for (const sheet of Array.from(this.document.styleSheets)) {
|
|
1222
|
+
this.reloadStylesheetImages(sheet, path);
|
|
1223
|
+
}
|
|
1224
|
+
this.console.log(`Image reload: ${path}`);
|
|
1230
1225
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
linkHref(link) {
|
|
1243
|
-
return link.href || link.getAttribute && link.getAttribute("data-href");
|
|
1244
|
-
}
|
|
1245
|
-
collectImportedStylesheets(link, styleSheet, result) {
|
|
1246
|
-
let rules;
|
|
1247
|
-
try {
|
|
1248
|
-
rules = (styleSheet || {}).cssRules;
|
|
1249
|
-
} catch (e) {
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
if (rules && rules.length) {
|
|
1253
|
-
for (let index = 0;index < rules.length; index++) {
|
|
1254
|
-
const rule = rules[index];
|
|
1226
|
+
reloadStylesheetImages(styleSheet, path) {
|
|
1227
|
+
let rules;
|
|
1228
|
+
try {
|
|
1229
|
+
rules = (styleSheet || {}).cssRules;
|
|
1230
|
+
} catch (e) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (!rules)
|
|
1234
|
+
return;
|
|
1235
|
+
const bgStyleNames = ["backgroundImage", "borderImage", "webkitBorderImage", "MozBorderImage"];
|
|
1236
|
+
for (const rule of Array.from(rules)) {
|
|
1255
1237
|
switch (rule.type) {
|
|
1256
|
-
case CSSRule.CHARSET_RULE:
|
|
1257
|
-
continue;
|
|
1258
1238
|
case CSSRule.IMPORT_RULE:
|
|
1259
|
-
|
|
1260
|
-
|
|
1239
|
+
this.reloadStylesheetImages(rule.styleSheet, path);
|
|
1240
|
+
break;
|
|
1241
|
+
case CSSRule.STYLE_RULE:
|
|
1242
|
+
this.reloadStyleImages(rule.style, bgStyleNames, path);
|
|
1261
1243
|
break;
|
|
1262
|
-
|
|
1244
|
+
case CSSRule.MEDIA_RULE:
|
|
1245
|
+
this.reloadStylesheetImages(rule, path);
|
|
1263
1246
|
break;
|
|
1264
1247
|
}
|
|
1265
1248
|
}
|
|
1266
1249
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1250
|
+
reloadStyleImages(style, styleNames, path) {
|
|
1251
|
+
for (const styleName of styleNames) {
|
|
1252
|
+
const value = style[styleName];
|
|
1253
|
+
if (typeof value === "string") {
|
|
1254
|
+
const newValue = value.replace(/\burl\s*\(([^)]*)\)/g, (match, src) => {
|
|
1255
|
+
const cleanSrc = src.replace(/^['"]|['"]$/g, "");
|
|
1256
|
+
if (this.pathsMatch(path, pathFromUrl(cleanSrc))) {
|
|
1257
|
+
return `url(${generateCacheBustUrl(cleanSrc)})`;
|
|
1258
|
+
}
|
|
1259
|
+
return match;
|
|
1260
|
+
});
|
|
1261
|
+
if (newValue !== value) {
|
|
1262
|
+
style[styleName] = newValue;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1277
1265
|
}
|
|
1278
1266
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
if (link.parentNode) {
|
|
1287
|
-
link.parentNode.insertBefore(tempLink, link);
|
|
1288
|
-
}
|
|
1289
|
-
await new Promise((resolve) => this.Timer.start(this.importCacheWaitPeriod, resolve));
|
|
1290
|
-
if (tempLink.parentNode) {
|
|
1291
|
-
tempLink.parentNode.removeChild(tempLink);
|
|
1267
|
+
pathsMatch(path1, path2) {
|
|
1268
|
+
const segs1 = path1.replace(/^\//, "").split("/").reverse();
|
|
1269
|
+
const segs2 = path2.replace(/^\//, "").split("/").reverse();
|
|
1270
|
+
const len = Math.min(segs1.length, segs2.length);
|
|
1271
|
+
for (let i = 0;i < len; i++) {
|
|
1272
|
+
if (segs1[i] !== segs2[i])
|
|
1273
|
+
return false;
|
|
1292
1274
|
}
|
|
1293
|
-
|
|
1275
|
+
return len > 0;
|
|
1276
|
+
}
|
|
1277
|
+
linkHref(link) {
|
|
1278
|
+
return link.href || link.getAttribute && link.getAttribute("data-href");
|
|
1279
|
+
}
|
|
1280
|
+
collectImportedStylesheets(link, styleSheet, result) {
|
|
1281
|
+
let rules;
|
|
1282
|
+
try {
|
|
1283
|
+
rules = (styleSheet || {}).cssRules;
|
|
1284
|
+
} catch (e) {
|
|
1294
1285
|
return;
|
|
1295
1286
|
}
|
|
1287
|
+
if (rules && rules.length) {
|
|
1288
|
+
for (let index = 0;index < rules.length; index++) {
|
|
1289
|
+
const rule = rules[index];
|
|
1290
|
+
switch (rule.type) {
|
|
1291
|
+
case CSSRule.CHARSET_RULE:
|
|
1292
|
+
continue;
|
|
1293
|
+
case CSSRule.IMPORT_RULE:
|
|
1294
|
+
result.push({ link, rule, index, href: rule.href });
|
|
1295
|
+
this.collectImportedStylesheets(link, rule.styleSheet, result);
|
|
1296
|
+
break;
|
|
1297
|
+
default:
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1296
1302
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1303
|
+
async reattachImportedRule({ rule, index, link }) {
|
|
1304
|
+
const parent = rule.parentStyleSheet;
|
|
1305
|
+
const href = generateCacheBustUrl(rule.href);
|
|
1306
|
+
let media = "";
|
|
1307
|
+
try {
|
|
1308
|
+
media = rule.media.length ? [].join.call(rule.media, ", ") : "";
|
|
1309
|
+
} catch (e) {
|
|
1310
|
+
if (e.name !== "SecurityError") {
|
|
1311
|
+
this.console.error(`Unexpected error accessing @import media: ${e.name}: ${e.message}`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const newRule = `@import url("${href}") ${media};`;
|
|
1315
|
+
rule.__LiveReload_newHref = href;
|
|
1316
|
+
if (this.importCacheWaitPeriod > 0) {
|
|
1317
|
+
const tempLink = this.document.createElement("link");
|
|
1318
|
+
tempLink.rel = "stylesheet";
|
|
1319
|
+
tempLink.href = href;
|
|
1320
|
+
tempLink.__LiveReload_pendingRemoval = true;
|
|
1321
|
+
if (link.parentNode) {
|
|
1322
|
+
link.parentNode.insertBefore(tempLink, link);
|
|
1323
|
+
}
|
|
1324
|
+
await new Promise((resolve) => this.Timer.start(this.importCacheWaitPeriod, resolve));
|
|
1325
|
+
if (tempLink.parentNode) {
|
|
1326
|
+
tempLink.parentNode.removeChild(tempLink);
|
|
1327
|
+
}
|
|
1328
|
+
if (rule.__LiveReload_newHref !== href) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1305
1331
|
}
|
|
1306
1332
|
parent.insertRule(newRule, index);
|
|
1307
1333
|
parent.deleteRule(index + 1);
|
|
1334
|
+
if (this.importCacheWaitPeriod > 0) {
|
|
1335
|
+
const updatedRule = parent.cssRules[index];
|
|
1336
|
+
updatedRule.__LiveReload_newHref = href;
|
|
1337
|
+
await new Promise((resolve) => this.Timer.start(this.importCacheWaitPeriod, resolve));
|
|
1338
|
+
if (updatedRule.__LiveReload_newHref !== href) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
parent.insertRule(newRule, index);
|
|
1342
|
+
parent.deleteRule(index + 1);
|
|
1343
|
+
}
|
|
1308
1344
|
}
|
|
1309
1345
|
}
|
|
1310
|
-
}
|
|
1311
1346
|
|
|
1312
|
-
// src/live-morph.js
|
|
1313
|
-
class LiveMorph {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1347
|
+
// src/live-morph.js
|
|
1348
|
+
class LiveMorph {
|
|
1349
|
+
constructor(window2) {
|
|
1350
|
+
this.window = window2;
|
|
1351
|
+
this.listeners = {};
|
|
1352
|
+
if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
|
|
1353
|
+
console.error("[LiveMorph] Disabled because the browser does not support WebSockets");
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
this.options = Options.extract(this.window.document);
|
|
1357
|
+
if (!this.options) {
|
|
1358
|
+
console.error("[LiveMorph] Disabled - no configuration found");
|
|
1359
|
+
console.error('[LiveMorph] Set window.LiveMorphOptions = { host: "localhost", port: 35729 }');
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
console.log("[LiveMorph] Options loaded:", JSON.stringify({
|
|
1363
|
+
host: this.options.host,
|
|
1364
|
+
port: this.options.port,
|
|
1365
|
+
morphHTML: this.options.morphHTML,
|
|
1366
|
+
verbose: this.options.verbose
|
|
1367
|
+
}));
|
|
1368
|
+
this.console = this._setupConsole();
|
|
1369
|
+
this.morpher = new Morpher(this.window, this.console, Timer, this.options.importCacheWaitPeriod);
|
|
1370
|
+
this.connector = new Connector(this.options, this.WebSocket, Timer, {
|
|
1371
|
+
connecting: () => {},
|
|
1372
|
+
socketConnected: () => {},
|
|
1373
|
+
connected: (protocol) => {
|
|
1374
|
+
if (typeof this.listeners.connect === "function") {
|
|
1375
|
+
this.listeners.connect();
|
|
1376
|
+
}
|
|
1377
|
+
const { host } = this.options;
|
|
1378
|
+
const port = this.options.port ? `:${this.options.port}` : "";
|
|
1379
|
+
this.log(`Connected to ${host}${port} (protocol v${protocol})`);
|
|
1380
|
+
return this.sendInfo();
|
|
1381
|
+
},
|
|
1382
|
+
error: (e) => {
|
|
1383
|
+
if (e instanceof ProtocolError) {
|
|
1384
|
+
return console.log(`[LiveMorph] ${e.message}`);
|
|
1385
|
+
} else {
|
|
1386
|
+
return console.log(`[LiveMorph] Internal error: ${e.message}`);
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
disconnected: (reason, nextDelay) => {
|
|
1390
|
+
if (typeof this.listeners.disconnect === "function") {
|
|
1391
|
+
this.listeners.disconnect();
|
|
1392
|
+
}
|
|
1393
|
+
const { host } = this.options;
|
|
1394
|
+
const port = this.options.port ? `:${this.options.port}` : "";
|
|
1395
|
+
const delaySec = (nextDelay / 1000).toFixed(0);
|
|
1396
|
+
switch (reason) {
|
|
1397
|
+
case "cannot-connect":
|
|
1398
|
+
return this.log(`Cannot connect to ${host}${port}, will retry in ${delaySec} sec`);
|
|
1399
|
+
case "broken":
|
|
1400
|
+
return this.log(`Disconnected from ${host}${port}, reconnecting in ${delaySec} sec`);
|
|
1401
|
+
case "handshake-timeout":
|
|
1402
|
+
return this.log(`Cannot connect to ${host}${port} (handshake timeout), will retry in ${delaySec} sec`);
|
|
1403
|
+
case "handshake-failed":
|
|
1404
|
+
return this.log(`Cannot connect to ${host}${port} (handshake failed), will retry in ${delaySec} sec`);
|
|
1405
|
+
case "manual":
|
|
1406
|
+
case "error":
|
|
1407
|
+
default:
|
|
1408
|
+
return this.log(`Disconnected from ${host}${port} (${reason}), reconnecting in ${delaySec} sec`);
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
message: (message) => {
|
|
1412
|
+
switch (message.command) {
|
|
1413
|
+
case "reload":
|
|
1414
|
+
return this.performReload(message);
|
|
1415
|
+
case "alert":
|
|
1416
|
+
return this.performAlert(message);
|
|
1417
|
+
}
|
|
1382
1418
|
}
|
|
1419
|
+
});
|
|
1420
|
+
this.initialized = true;
|
|
1421
|
+
}
|
|
1422
|
+
_setupConsole() {
|
|
1423
|
+
const hasConsole = this.window.console && this.window.console.log && this.window.console.error;
|
|
1424
|
+
if (!hasConsole) {
|
|
1425
|
+
return { log() {}, error() {} };
|
|
1383
1426
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1427
|
+
if (this.options.verbose) {
|
|
1428
|
+
return this.window.console;
|
|
1429
|
+
}
|
|
1430
|
+
return {
|
|
1431
|
+
log() {},
|
|
1432
|
+
error: this.window.console.error.bind(this.window.console)
|
|
1433
|
+
};
|
|
1391
1434
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1435
|
+
on(eventName, handler) {
|
|
1436
|
+
this.listeners[eventName] = handler;
|
|
1394
1437
|
}
|
|
1395
|
-
|
|
1396
|
-
log(
|
|
1397
|
-
error: this.window.console.error.bind(this.window.console)
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
on(eventName, handler) {
|
|
1401
|
-
this.listeners[eventName] = handler;
|
|
1402
|
-
}
|
|
1403
|
-
log(message) {
|
|
1404
|
-
return this.console.log(`[LiveMorph] ${message}`);
|
|
1405
|
-
}
|
|
1406
|
-
performReload(message) {
|
|
1407
|
-
this.log(`Received reload request for: ${message.path}`);
|
|
1408
|
-
const options = {
|
|
1409
|
-
liveCSS: message.liveCSS != null ? message.liveCSS : true,
|
|
1410
|
-
liveImg: message.liveImg != null ? message.liveImg : true,
|
|
1411
|
-
reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true,
|
|
1412
|
-
morphHTML: this.options.morphHTML
|
|
1413
|
-
};
|
|
1414
|
-
this.log(`Reload options: ${JSON.stringify(options)}`);
|
|
1415
|
-
return this.morpher.reload(message.path, options);
|
|
1416
|
-
}
|
|
1417
|
-
performAlert(message) {
|
|
1418
|
-
return alert(message.message);
|
|
1419
|
-
}
|
|
1420
|
-
sendInfo() {
|
|
1421
|
-
if (!this.initialized) {
|
|
1422
|
-
return;
|
|
1438
|
+
log(message) {
|
|
1439
|
+
return this.console.log(`[LiveMorph] ${message}`);
|
|
1423
1440
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1441
|
+
performReload(message) {
|
|
1442
|
+
this.log(`Received reload request for: ${message.path}`);
|
|
1443
|
+
const options = {
|
|
1444
|
+
liveCSS: message.liveCSS != null ? message.liveCSS : true,
|
|
1445
|
+
liveImg: message.liveImg != null ? message.liveImg : true,
|
|
1446
|
+
reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true,
|
|
1447
|
+
morphHTML: this.options.morphHTML
|
|
1448
|
+
};
|
|
1449
|
+
this.log(`Reload options: ${JSON.stringify(options)}`);
|
|
1450
|
+
return this.morpher.reload(message.path, options);
|
|
1426
1451
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1452
|
+
performAlert(message) {
|
|
1453
|
+
return alert(message.message);
|
|
1454
|
+
}
|
|
1455
|
+
sendInfo() {
|
|
1456
|
+
if (!this.initialized) {
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
if (!(this.connector.protocol >= 7)) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
this.connector.sendCommand({
|
|
1463
|
+
command: "info",
|
|
1464
|
+
plugins: {},
|
|
1465
|
+
url: this.window.location.href
|
|
1466
|
+
});
|
|
1436
1467
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1468
|
+
shutDown() {
|
|
1469
|
+
if (!this.initialized) {
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
this.connector.disconnect();
|
|
1473
|
+
this.log("Disconnected");
|
|
1474
|
+
if (typeof this.listeners.shutdown === "function") {
|
|
1475
|
+
this.listeners.shutdown();
|
|
1476
|
+
}
|
|
1441
1477
|
}
|
|
1442
1478
|
}
|
|
1443
|
-
}
|
|
1444
1479
|
|
|
1445
|
-
// src/index.js
|
|
1446
|
-
var liveMorph = new LiveMorph(window);
|
|
1447
|
-
window.LiveMorph = liveMorph;
|
|
1448
|
-
if (typeof document !== "undefined") {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
var src_default = liveMorph;
|
|
1462
|
-
|
|
1463
|
-
src_default as default
|
|
1464
|
-
};
|
|
1480
|
+
// src/index.js
|
|
1481
|
+
var liveMorph = new LiveMorph(window);
|
|
1482
|
+
window.LiveMorph = liveMorph;
|
|
1483
|
+
if (typeof document !== "undefined") {
|
|
1484
|
+
document.addEventListener("LiveMorphShutDown", () => {
|
|
1485
|
+
liveMorph.shutDown();
|
|
1486
|
+
});
|
|
1487
|
+
liveMorph.on("connect", () => {
|
|
1488
|
+
const event = new CustomEvent("LiveMorphConnect");
|
|
1489
|
+
document.dispatchEvent(event);
|
|
1490
|
+
});
|
|
1491
|
+
liveMorph.on("disconnect", () => {
|
|
1492
|
+
const event = new CustomEvent("LiveMorphDisconnect");
|
|
1493
|
+
document.dispatchEvent(event);
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
var src_default = liveMorph;
|
|
1497
|
+
})();
|