@caboodle-tech/node-simple-server 4.2.7 → 4.3.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/README.md +28 -24
- package/bin/nss.js +46 -28
- package/changelogs/v4.md +8 -0
- package/examples/controllers/prod-website.js +2 -6
- package/examples/controllers/website.js +2 -6
- package/examples/controllers/websocket.js +4 -13
- package/examples/www-production/js/main.js +2 -3
- package/examples/www-website/character-test.html +252 -0
- package/examples/www-website/index.html +4 -1
- package/examples/www-website/js/main.js +2 -3
- package/examples/www-websockets/js/main.js +9 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -83,8 +83,11 @@ const serverOptions = {
|
|
|
83
83
|
// Get a new instance of NSS.
|
|
84
84
|
const Server = new NodeSimpleServer(serverOptions);
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* A bare minimum callback to handle most development changes. You register only for
|
|
88
|
+
* the events you need; unhandled events (e.g. unlink/unlinkDir if not listed) are ignored.
|
|
89
|
+
*/
|
|
90
|
+
const watcherCallback = (event, path, statsOrDetails) => {
|
|
88
91
|
const extension = statsOrDetails.ext;
|
|
89
92
|
if (extension === 'css') {
|
|
90
93
|
Server.reloadAllStyles();
|
|
@@ -102,7 +105,11 @@ function watcherCallback(event, path, statsOrDetails) {
|
|
|
102
105
|
if (event === 'change') {
|
|
103
106
|
Server.reloadSinglePage(path);
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
// When a file or directory is removed, reload so the browser does not show stale content.
|
|
109
|
+
if (event === 'unlink' || event === 'unlinkDir') {
|
|
110
|
+
Server.reloadAllPages();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
106
113
|
|
|
107
114
|
/**
|
|
108
115
|
* A bare minimum callback to handle all websocket messages from the frontend. By
|
|
@@ -111,15 +118,12 @@ function watcherCallback(event, path, statsOrDetails) {
|
|
|
111
118
|
*
|
|
112
119
|
* NSS_WS.send([string|int|bool|object]) // Pathname lookup will be used.
|
|
113
120
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const datatype = messageObject.type
|
|
121
|
+
const websocketCallback = (messageObject, pageId) => {
|
|
122
|
+
const datatype = messageObject.type;
|
|
117
123
|
const data = messageObject.data;
|
|
118
|
-
console.log(`Received ${datatype} data from page ${pageId}: ${data}`)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Server.message(pageId, 'Messaged received!');
|
|
122
|
-
}
|
|
124
|
+
console.log(`Received ${datatype} data from page ${pageId}: ${data}`);
|
|
125
|
+
Server.message(pageId, 'Message received!');
|
|
126
|
+
};
|
|
123
127
|
Server.addWebsocketCallback('.*', websocketCallback);
|
|
124
128
|
|
|
125
129
|
/**
|
|
@@ -129,21 +133,21 @@ Server.addWebsocketCallback('.*', websocketCallback);
|
|
|
129
133
|
*
|
|
130
134
|
* NSS_WS.send([string|int|bool|object], [route string]) // Route lookup will be used.
|
|
131
135
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const datatype = messageObject.type
|
|
136
|
+
const websocketCallbackRoute = (messageObject, pageId) => {
|
|
137
|
+
const datatype = messageObject.type;
|
|
135
138
|
const data = messageObject.data;
|
|
136
|
-
console.log(`Route received ${datatype} data from page ${pageId}: ${data}`)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
Server.addWebsocketCallback('api/search', websocketCallback);
|
|
139
|
+
console.log(`Route received ${datatype} data from page ${pageId}: ${data}`);
|
|
140
|
+
Server.message(pageId, 'Route specific message received!');
|
|
141
|
+
};
|
|
142
|
+
Server.addWebsocketCallback('api/search', websocketCallbackRoute);
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Watcher options: the events you register are your intent. Only registered events
|
|
146
|
+
* are handled; others (e.g. unlink, unlinkDir) are ignored unless you add them.
|
|
147
|
+
*/
|
|
144
148
|
const watcherOptions = {
|
|
145
149
|
events: {
|
|
146
|
-
all: watcherCallback,
|
|
150
|
+
all: watcherCallback,
|
|
147
151
|
},
|
|
148
152
|
};
|
|
149
153
|
|
|
@@ -154,7 +158,7 @@ Server.start();
|
|
|
154
158
|
Server.watch(websiteRoot, watcherOptions);
|
|
155
159
|
```
|
|
156
160
|
|
|
157
|
-
The `options` object **required** by the `watch` method must include an `events` property with at least one watched event. The demo code above used `all` to capture any event. This object takes a lot of settings and is explained below in the **Watch Options** table.
|
|
161
|
+
The `options` object **required** by the `watch` method must include an `events` property with at least one watched event. The demo code above used `all` to capture any event. You express intent by which events you register: only those are handled; others are ignored. If your app has a build step (e.g. source → output), register for `unlink` and `unlinkDir` in `events` to react when files or directories are removed (e.g. to keep your output in sync). This object takes a lot of settings and is explained below in the **Watch Options** table.
|
|
158
162
|
|
|
159
163
|
NSS uses `process.cwd()` as the live servers root if omitted and is pre-configured with several additional default settings. You can change these by providing your own `options` object when instantiating the server. How this looks in code is shown below, the following table **Server Options** explains all available options.
|
|
160
164
|
|
|
@@ -210,7 +214,7 @@ const Server = new NodeSimpleServer(options);
|
|
|
210
214
|
|
|
211
215
|
#### **events**
|
|
212
216
|
|
|
213
|
-
- Set to an object that can have any combination of these properties: `all`, `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `ready`, `raw`, `error`. Any property set on `events` should point to a callback function that will handle that event.
|
|
217
|
+
- Set to an object that can have any combination of these properties: `all`, `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `ready`, `raw`, `error`. Any property set on `events` should point to a callback function that will handle that event. Only the events you register are handled; others are ignored. Use `unlink` and `unlinkDir` when you need to react to file or directory deletion (e.g. to sync your build output or trigger a reload).
|
|
214
218
|
|
|
215
219
|
#### **persistent** default: true
|
|
216
220
|
|
package/bin/nss.js
CHANGED
|
@@ -43,7 +43,7 @@ class NodeSimpleServer {
|
|
|
43
43
|
map: {}
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
#VERSION = '4.2.
|
|
46
|
+
#VERSION = '4.2.8';
|
|
47
47
|
|
|
48
48
|
#watching = [];
|
|
49
49
|
|
|
@@ -130,9 +130,11 @@ class NodeSimpleServer {
|
|
|
130
130
|
* @return {Array} An array of loop back ip addresses and LAN addresses to this server.
|
|
131
131
|
*/
|
|
132
132
|
getAddresses(port) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
/*
|
|
134
|
+
* Use provided port, or fall back to portInUse (actual port server is using),
|
|
135
|
+
* or finally to the configured port. Check for undefined/null specifically to
|
|
136
|
+
* allow 0 if explicitly provided (though unlikely).
|
|
137
|
+
*/
|
|
136
138
|
const actualPort = port !== undefined && port !== null ? port : this.#OPS.portInUse || this.#OPS.port;
|
|
137
139
|
const locals = this.#getLocalAddresses();
|
|
138
140
|
const addresses = [
|
|
@@ -495,8 +497,8 @@ class NodeSimpleServer {
|
|
|
495
497
|
pattern = this.makeRegex(pattern);
|
|
496
498
|
}
|
|
497
499
|
// See if the pattern is a page id first.
|
|
498
|
-
if (this.#sockets.map[original]) {
|
|
499
|
-
this.#sockets.map[original].send('reload');
|
|
500
|
+
if (this.#sockets.map[`S${original}`]) {
|
|
501
|
+
this.#sockets.map[`S${original}`].send('reload');
|
|
500
502
|
return;
|
|
501
503
|
}
|
|
502
504
|
// See if the pattern matches a specific URL and reload all those pages.
|
|
@@ -541,8 +543,8 @@ class NodeSimpleServer {
|
|
|
541
543
|
pattern = this.makeRegex(pattern) || original;
|
|
542
544
|
}
|
|
543
545
|
// See if the pattern is a page id first.
|
|
544
|
-
if (this.#sockets.map[original]) {
|
|
545
|
-
this.#sockets.map[original].send('refreshCSS');
|
|
546
|
+
if (this.#sockets.map[`S${original}`]) {
|
|
547
|
+
this.#sockets.map[`S${original}`].send('refreshCSS');
|
|
546
548
|
return;
|
|
547
549
|
}
|
|
548
550
|
// See if the pattern matches a specific URL and reload all those pages.
|
|
@@ -682,29 +684,40 @@ class NodeSimpleServer {
|
|
|
682
684
|
return;
|
|
683
685
|
}
|
|
684
686
|
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
contentType
|
|
691
|
-
|
|
692
|
-
|
|
687
|
+
const contentType = ContentTypes[ext] || this.#OPS.contentType;
|
|
688
|
+
const isText = contentType.startsWith('text/') ||
|
|
689
|
+
contentType === 'application/json' ||
|
|
690
|
+
contentType === 'application/ld+json' ||
|
|
691
|
+
contentType === 'application/manifest+json' ||
|
|
692
|
+
contentType === 'application/xhtml+xml';
|
|
693
|
+
|
|
694
|
+
// Read with no encoding: returns Buffer of raw bytes (Node.js fs docs). Sending that
|
|
695
|
+
// Buffer preserves the file bytes; do not use 'binary' (alias for latin1) which
|
|
696
|
+
// misinterprets UTF-8 multi-byte sequences.
|
|
697
|
+
const file = Fs.readFileSync(systemPath);
|
|
698
|
+
|
|
699
|
+
// Output the file to the browser. Only set charset for text types.
|
|
700
|
+
const headerSettings = { contentType, file: systemPath };
|
|
701
|
+
if (isText) {
|
|
702
|
+
headerSettings.charset = 'utf-8';
|
|
703
|
+
}
|
|
704
|
+
resp.writeHead(HTTPStatus.ok, this.#getHeaders(headerSettings));
|
|
693
705
|
|
|
694
|
-
// If needed inject NSS's WebSocket at the end of the page.
|
|
706
|
+
// If needed inject NSS's WebSocket at the end of the page, decode to string, modify, re-encode.
|
|
695
707
|
if (this.#reload.includes(ext)) {
|
|
696
|
-
|
|
708
|
+
const html = file.toString('utf8');
|
|
697
709
|
const last = html.lastIndexOf('</body>');
|
|
710
|
+
let body;
|
|
698
711
|
if (last && last > 0) {
|
|
699
712
|
const start = html.substring(0, last);
|
|
700
713
|
const end = html.substring(last);
|
|
701
|
-
|
|
702
|
-
resp.write(html, 'utf-8');
|
|
714
|
+
body = Buffer.from(start + this.#handlers.liveReloading + end, 'utf8');
|
|
703
715
|
} else {
|
|
704
|
-
|
|
716
|
+
body = Buffer.from(html, 'utf8');
|
|
705
717
|
}
|
|
718
|
+
resp.write(body);
|
|
706
719
|
} else {
|
|
707
|
-
resp.write(file
|
|
720
|
+
resp.write(file);
|
|
708
721
|
}
|
|
709
722
|
resp.end();
|
|
710
723
|
}
|
|
@@ -879,7 +892,7 @@ class NodeSimpleServer {
|
|
|
879
892
|
for (let i = 0; i < connections.length; i++) {
|
|
880
893
|
if (connections[i].nssUid === pageId) {
|
|
881
894
|
connections.splice(i, 1);
|
|
882
|
-
delete this.#sockets.map[pageId];
|
|
895
|
+
delete this.#sockets.map[`S${pageId}`];
|
|
883
896
|
break;
|
|
884
897
|
}
|
|
885
898
|
}
|
|
@@ -923,9 +936,11 @@ class NodeSimpleServer {
|
|
|
923
936
|
return;
|
|
924
937
|
}
|
|
925
938
|
|
|
926
|
-
|
|
939
|
+
/*
|
|
940
|
+
* Create the HTTP server. Capture connection upgrade requests so we don't
|
|
941
|
+
* break WebSocket connections.
|
|
942
|
+
*/
|
|
927
943
|
this.#server = Http.createServer(this.#serverListener.bind(this));
|
|
928
|
-
// Capture connection upgrade requests so we don't break WebSocket connections.
|
|
929
944
|
// eslint-disable-next-line no-unused-vars
|
|
930
945
|
this.#server.on('upgrade', (request, socket) => {
|
|
931
946
|
/*
|
|
@@ -944,9 +959,11 @@ class NodeSimpleServer {
|
|
|
944
959
|
return;
|
|
945
960
|
}
|
|
946
961
|
});
|
|
947
|
-
|
|
962
|
+
/*
|
|
963
|
+
* Capture server errors and respond as needed. The port we tried to use is
|
|
964
|
+
* taken, increment and try to start again.
|
|
965
|
+
*/
|
|
948
966
|
this.#server.on('error', (error) => {
|
|
949
|
-
// The port we tried to use is taken, increment and try to start again.
|
|
950
967
|
if (error.code === 'EADDRINUSE') {
|
|
951
968
|
if (port) {
|
|
952
969
|
// Stop trying new ports after 100 attempts.
|
|
@@ -968,7 +985,7 @@ class NodeSimpleServer {
|
|
|
968
985
|
Print.error(`Server Error:\n${error}`);
|
|
969
986
|
});
|
|
970
987
|
|
|
971
|
-
|
|
988
|
+
/* Attempt to start the server now. */
|
|
972
989
|
// eslint-disable-next-line no-param-reassign
|
|
973
990
|
port = port || this.#OPS.port;
|
|
974
991
|
this.#server.listen(port, () => {
|
|
@@ -1091,6 +1108,7 @@ class NodeSimpleServer {
|
|
|
1091
1108
|
// Start watching the path(s).
|
|
1092
1109
|
const watcher = Chokidar.watch(paths, options);
|
|
1093
1110
|
this.#watching.push(watcher);
|
|
1111
|
+
|
|
1094
1112
|
// Prepare to modify some of the standard Chokidar listeners.
|
|
1095
1113
|
const alterAddUpdates = ['add', 'addDir', 'change'];
|
|
1096
1114
|
const alterCatchAlls = ['all', 'raw'];
|
package/changelogs/v4.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
### NSS 4.2.8 (unreleased)
|
|
2
|
+
|
|
3
|
+
- fix: Socket map key on close and in reloadSinglePage/reloadSingleStyles use `S${pageId}` consistently.
|
|
4
|
+
- fix: Serve file bodies as raw bytes (read with no encoding); only decode/re-encode when injecting live-reload script. Fixes UTF-8 mojibake (e.g. entities, em dashes, Arabic, CJK) that occurred when using `encoding: 'binary'` (latin1).
|
|
5
|
+
- fix: Example controllers no longer call printListeningAddresses() after start(); start() already prints once.
|
|
6
|
+
- refactor: README and example code use const arrow functions instead of function declarations; watcher examples clarify intent via events (e.g. unlink/unlinkDir).
|
|
7
|
+
- docs: Example www-website includes character-test.html demo (entities, emojis, RTL/LTR, UTF-8).
|
|
8
|
+
|
|
1
9
|
### NSS 4.2.7 (5 January 2026)
|
|
2
10
|
|
|
3
11
|
- build: Migrate ESLint configuration to flat config format
|
|
@@ -25,8 +25,7 @@ const WebsiteDemo = () => {
|
|
|
25
25
|
// Start the server.
|
|
26
26
|
server.start();
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
function callback(event, path, ext) {
|
|
28
|
+
const callback = (event, path, ext) => {
|
|
30
29
|
console.log(event, path, ext);
|
|
31
30
|
if (ext === 'css') {
|
|
32
31
|
server.reloadAllStyles();
|
|
@@ -39,7 +38,7 @@ const WebsiteDemo = () => {
|
|
|
39
38
|
if (event === 'change') {
|
|
40
39
|
server.reloadSinglePage(path);
|
|
41
40
|
}
|
|
42
|
-
}
|
|
41
|
+
};
|
|
43
42
|
|
|
44
43
|
// Build a bare minimum watcher options object.
|
|
45
44
|
const watcherOptions = {
|
|
@@ -57,9 +56,6 @@ const WebsiteDemo = () => {
|
|
|
57
56
|
* setting up `watch`.
|
|
58
57
|
*/
|
|
59
58
|
server.watch(websiteRoot, watcherOptions);
|
|
60
|
-
|
|
61
|
-
// Log the server's address so we can access it.
|
|
62
|
-
server.printListeningAddresses();
|
|
63
59
|
};
|
|
64
60
|
|
|
65
61
|
export default WebsiteDemo;
|
|
@@ -23,8 +23,7 @@ const WebsiteDemo = () => {
|
|
|
23
23
|
// Start the server.
|
|
24
24
|
server.start();
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
function callback(event, path, ext) {
|
|
26
|
+
const callback = (event, path, ext) => {
|
|
28
27
|
console.log(event, path, ext);
|
|
29
28
|
if (ext === 'css') {
|
|
30
29
|
server.reloadAllStyles();
|
|
@@ -37,7 +36,7 @@ const WebsiteDemo = () => {
|
|
|
37
36
|
if (event === 'change') {
|
|
38
37
|
server.reloadSinglePage(path);
|
|
39
38
|
}
|
|
40
|
-
}
|
|
39
|
+
};
|
|
41
40
|
|
|
42
41
|
// Build a bare minimum watcher options object.
|
|
43
42
|
const watcherOptions = {
|
|
@@ -55,9 +54,6 @@ const WebsiteDemo = () => {
|
|
|
55
54
|
* setting up `watch`.
|
|
56
55
|
*/
|
|
57
56
|
server.watch(websiteRoot, watcherOptions);
|
|
58
|
-
|
|
59
|
-
// Log the server's address so we can access it.
|
|
60
|
-
server.printListeningAddresses();
|
|
61
57
|
};
|
|
62
58
|
|
|
63
59
|
export default WebsiteDemo;
|
|
@@ -23,8 +23,7 @@ const WebsocketDemo = () => {
|
|
|
23
23
|
// Start the server.
|
|
24
24
|
server.start();
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
function callback(event, path, ext) {
|
|
26
|
+
const callback = (event, path, ext) => {
|
|
28
27
|
if (ext === 'css') {
|
|
29
28
|
server.reloadAllStyles();
|
|
30
29
|
return;
|
|
@@ -36,7 +35,7 @@ const WebsocketDemo = () => {
|
|
|
36
35
|
if (event === 'change') {
|
|
37
36
|
server.reloadSinglePage(path);
|
|
38
37
|
}
|
|
39
|
-
}
|
|
38
|
+
};
|
|
40
39
|
|
|
41
40
|
// Build a bare minimum watcher options object.
|
|
42
41
|
const watcherOptions = {
|
|
@@ -58,28 +57,20 @@ const WebsocketDemo = () => {
|
|
|
58
57
|
// Keep a reply count so we can distinguish replies.
|
|
59
58
|
const replyCount = {};
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
function websocketHandler(message, pageId) {
|
|
63
|
-
// Our demo only sends stings so ignore anything else.
|
|
60
|
+
const websocketHandler = (message, pageId) => {
|
|
64
61
|
if (message.type === 'string') {
|
|
65
|
-
// We record reply counts by page so make sure we have a record for this page.
|
|
66
62
|
if (!replyCount[pageId]) { replyCount[pageId] = 0; }
|
|
67
|
-
// Display the users message in the servers (NSS's) terminal.
|
|
68
63
|
console.log(`[websocket:${pageId}] Message from frontend --> ${message.message}`);
|
|
69
|
-
// To demonstrate we can reply send a message back after a delay.
|
|
70
64
|
setTimeout(() => {
|
|
71
65
|
replyCount[pageId] += 1;
|
|
72
66
|
server.message(pageId, `Reply ${replyCount[pageId]} from backend to page with id: ${pageId}`);
|
|
73
67
|
}, 2000);
|
|
74
68
|
}
|
|
75
|
-
}
|
|
69
|
+
};
|
|
76
70
|
|
|
77
71
|
// Register our websocket handler to respond to only index pages.
|
|
78
72
|
server.addWebsocketCallback('index.html', websocketHandler);
|
|
79
73
|
|
|
80
|
-
// Log the server's address so we can access it.
|
|
81
|
-
server.printListeningAddresses();
|
|
82
|
-
|
|
83
74
|
// NOTE: We could add as many callbacks as we like for different pages or patterns.
|
|
84
75
|
};
|
|
85
76
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const loadFoxImage = () => {
|
|
3
2
|
const container = document.getElementById('fox-container');
|
|
4
3
|
if (!container) {
|
|
5
4
|
console.log('DEMO: Could not locate the image container for the fox image.');
|
|
@@ -23,7 +22,7 @@ function loadFoxImage() {
|
|
|
23
22
|
.catch((_) => {
|
|
24
23
|
console.log('DEMO: There was an error trying to load the fox image.');
|
|
25
24
|
});
|
|
26
|
-
}
|
|
25
|
+
};
|
|
27
26
|
|
|
28
27
|
document.addEventListener('DOMContentLoaded', () => {
|
|
29
28
|
console.log('DEMO: Loading a random image of a fox in 3 seconds...');
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Character Test Page</title>
|
|
7
|
+
<link rel="stylesheet" href="./css/normalize.css">
|
|
8
|
+
<link rel="stylesheet" href="./css/main.css">
|
|
9
|
+
<style>
|
|
10
|
+
.edge-section { margin: 2rem 0; }
|
|
11
|
+
.edge-section h2 { font-size: 1.8rem; margin-bottom: 0.75rem; border-bottom: 1px solid #ccc; }
|
|
12
|
+
.edge-section table { border-collapse: collapse; width: 100%; max-width: 640px; }
|
|
13
|
+
.edge-section th, .edge-section td { border: 1px solid #ccc; padding: 0.5rem 0.75rem; text-align: left; }
|
|
14
|
+
.edge-section th { background: #f5f5f5; }
|
|
15
|
+
.rtl-block { direction: rtl; text-align: right; }
|
|
16
|
+
.ltr-block { direction: ltr; text-align: left; }
|
|
17
|
+
.mixed-rtl-ltr { unicode-bidi: bidi-override; }
|
|
18
|
+
.back-link { display: inline-block; margin-bottom: 1rem; }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="page-container">
|
|
23
|
+
<main>
|
|
24
|
+
<div class="content">
|
|
25
|
+
<a class="back-link" href="./index.html">← Back to Website Demo</a>
|
|
26
|
+
<h1>Character Test Page</h1>
|
|
27
|
+
<p>
|
|
28
|
+
This page tests that the server and browser handle UTF-8 correctly: HTML entities,
|
|
29
|
+
emojis, RTL/LTR text, and mixed scripts.
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<section class="edge-section" id="entities">
|
|
33
|
+
<h2>HTML Entities (Table)</h2>
|
|
34
|
+
<p>Rendered via reference: character from entity, decimal, or hex. Rendered via literal: character typed directly in the source (UTF-8).</p>
|
|
35
|
+
<table>
|
|
36
|
+
<thead>
|
|
37
|
+
<tr>
|
|
38
|
+
<th>Name</th>
|
|
39
|
+
<th>Entity</th>
|
|
40
|
+
<th>Decimal</th>
|
|
41
|
+
<th>Hex</th>
|
|
42
|
+
<th>Via Reference</th>
|
|
43
|
+
<th>Via Literal</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody>
|
|
47
|
+
<tr>
|
|
48
|
+
<td>Ampersand</td>
|
|
49
|
+
<td><code>&amp;</code></td>
|
|
50
|
+
<td><code>&#38;</code></td>
|
|
51
|
+
<td><code>&#x26;</code></td>
|
|
52
|
+
<td>&</td>
|
|
53
|
+
<td>n/a</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<tr>
|
|
56
|
+
<td>Less than</td>
|
|
57
|
+
<td><code>&lt;</code></td>
|
|
58
|
+
<td><code>&#60;</code></td>
|
|
59
|
+
<td><code>&#x3C;</code></td>
|
|
60
|
+
<td><</td>
|
|
61
|
+
<td>n/a</td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td>Greater than</td>
|
|
65
|
+
<td><code>&gt;</code></td>
|
|
66
|
+
<td><code>&#62;</code></td>
|
|
67
|
+
<td><code>&#x3E;</code></td>
|
|
68
|
+
<td>></td>
|
|
69
|
+
<td>n/a</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td>Double quote</td>
|
|
73
|
+
<td><code>&quot;</code></td>
|
|
74
|
+
<td><code>&#34;</code></td>
|
|
75
|
+
<td><code>&#x22;</code></td>
|
|
76
|
+
<td>"</td>
|
|
77
|
+
<td>"</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td>Apostrophe</td>
|
|
81
|
+
<td><code>&apos;</code></td>
|
|
82
|
+
<td><code>&#39;</code></td>
|
|
83
|
+
<td><code>&#x27;</code></td>
|
|
84
|
+
<td>'</td>
|
|
85
|
+
<td>'</td>
|
|
86
|
+
</tr>
|
|
87
|
+
<tr>
|
|
88
|
+
<td>Non-breaking space</td>
|
|
89
|
+
<td><code>&nbsp;</code></td>
|
|
90
|
+
<td><code>&#160;</code></td>
|
|
91
|
+
<td><code>&#xA0;</code></td>
|
|
92
|
+
<td>[ ]</td>
|
|
93
|
+
<td>[ ]</td>
|
|
94
|
+
</tr>
|
|
95
|
+
<tr>
|
|
96
|
+
<td>Copyright</td>
|
|
97
|
+
<td><code>&copy;</code></td>
|
|
98
|
+
<td><code>&#169;</code></td>
|
|
99
|
+
<td><code>&#xA9;</code></td>
|
|
100
|
+
<td>©</td>
|
|
101
|
+
<td>©</td>
|
|
102
|
+
</tr>
|
|
103
|
+
<tr>
|
|
104
|
+
<td>En dash</td>
|
|
105
|
+
<td><code>&ndash;</code></td>
|
|
106
|
+
<td><code>&#8211;</code></td>
|
|
107
|
+
<td><code>&#x2013;</code></td>
|
|
108
|
+
<td>–</td>
|
|
109
|
+
<td>–</td>
|
|
110
|
+
</tr>
|
|
111
|
+
<tr>
|
|
112
|
+
<td>Em dash</td>
|
|
113
|
+
<td><code>&mdash;</code></td>
|
|
114
|
+
<td><code>&#8212;</code></td>
|
|
115
|
+
<td><code>&#x2014;</code></td>
|
|
116
|
+
<td>—</td>
|
|
117
|
+
<td>—</td>
|
|
118
|
+
</tr>
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</section>
|
|
122
|
+
|
|
123
|
+
<section class="edge-section" id="emojis">
|
|
124
|
+
<h2>Emojis & Symbols (Unicode)</h2>
|
|
125
|
+
<p>Via reference: numeric character reference (e.g. 😀). Via literal: character typed in source (UTF-8). Both render as Unicode.</p>
|
|
126
|
+
<table>
|
|
127
|
+
<thead>
|
|
128
|
+
<tr>
|
|
129
|
+
<th>Name</th>
|
|
130
|
+
<th>Unicode</th>
|
|
131
|
+
<th>Via Reference</th>
|
|
132
|
+
<th>Via Literal</th>
|
|
133
|
+
</tr>
|
|
134
|
+
</thead>
|
|
135
|
+
<tbody>
|
|
136
|
+
<tr>
|
|
137
|
+
<td>Grinning face</td>
|
|
138
|
+
<td><code>U+1F600</code></td>
|
|
139
|
+
<td>😀</td>
|
|
140
|
+
<td>😀</td>
|
|
141
|
+
</tr>
|
|
142
|
+
<tr>
|
|
143
|
+
<td>Party popper</td>
|
|
144
|
+
<td><code>U+1F389</code></td>
|
|
145
|
+
<td>🎉</td>
|
|
146
|
+
<td>🎉</td>
|
|
147
|
+
</tr>
|
|
148
|
+
<tr>
|
|
149
|
+
<td>Red heart</td>
|
|
150
|
+
<td><code>U+2764</code></td>
|
|
151
|
+
<td>❤</td>
|
|
152
|
+
<td>❤</td>
|
|
153
|
+
</tr>
|
|
154
|
+
<tr>
|
|
155
|
+
<td>Light bulb</td>
|
|
156
|
+
<td><code>U+1F4A1</code></td>
|
|
157
|
+
<td>💡</td>
|
|
158
|
+
<td>💡</td>
|
|
159
|
+
</tr>
|
|
160
|
+
<tr>
|
|
161
|
+
<td>Check mark</td>
|
|
162
|
+
<td><code>U+2714</code></td>
|
|
163
|
+
<td>✔</td>
|
|
164
|
+
<td>✔</td>
|
|
165
|
+
</tr>
|
|
166
|
+
<tr>
|
|
167
|
+
<td>Cross mark</td>
|
|
168
|
+
<td><code>U+274C</code></td>
|
|
169
|
+
<td>❌</td>
|
|
170
|
+
<td>❌</td>
|
|
171
|
+
</tr>
|
|
172
|
+
<tr>
|
|
173
|
+
<td>Star</td>
|
|
174
|
+
<td><code>U+2B50</code></td>
|
|
175
|
+
<td>⭐</td>
|
|
176
|
+
<td>⭐</td>
|
|
177
|
+
</tr>
|
|
178
|
+
<tr>
|
|
179
|
+
<td>Flag (United States)</td>
|
|
180
|
+
<td><code>U+1F1FA U+1F1F8</code></td>
|
|
181
|
+
<td>🇺🇸</td>
|
|
182
|
+
<td>🇺🇸</td>
|
|
183
|
+
</tr>
|
|
184
|
+
<tr>
|
|
185
|
+
<td>Flag (United Kingdom)</td>
|
|
186
|
+
<td><code>U+1F1EC U+1F1E7</code></td>
|
|
187
|
+
<td>🇬🇧</td>
|
|
188
|
+
<td>🇬🇧</td>
|
|
189
|
+
</tr>
|
|
190
|
+
<tr>
|
|
191
|
+
<td>Flag (Canada)</td>
|
|
192
|
+
<td><code>U+1F1E8 U+1F1E6</code></td>
|
|
193
|
+
<td>🇨🇦</td>
|
|
194
|
+
<td>🇨🇦</td>
|
|
195
|
+
</tr>
|
|
196
|
+
<tr>
|
|
197
|
+
<td>Flag (Japan)</td>
|
|
198
|
+
<td><code>U+1F1EF U+1F1F5</code></td>
|
|
199
|
+
<td>🇯🇵</td>
|
|
200
|
+
<td>🇯🇵</td>
|
|
201
|
+
</tr>
|
|
202
|
+
<tr>
|
|
203
|
+
<td>Flag (France)</td>
|
|
204
|
+
<td><code>U+1F1EB U+1F1F7</code></td>
|
|
205
|
+
<td>🇫🇷</td>
|
|
206
|
+
<td>🇫🇷</td>
|
|
207
|
+
</tr>
|
|
208
|
+
<tr>
|
|
209
|
+
<td>Flag (Australia)</td>
|
|
210
|
+
<td><code>U+1F1E6 U+1F1FA</code></td>
|
|
211
|
+
<td>🇦🇺</td>
|
|
212
|
+
<td>🇦🇺</td>
|
|
213
|
+
</tr>
|
|
214
|
+
</tbody>
|
|
215
|
+
</table>
|
|
216
|
+
</section>
|
|
217
|
+
|
|
218
|
+
<section class="edge-section" id="arabic">
|
|
219
|
+
<h2>Arabic (RTL)</h2>
|
|
220
|
+
<p class="rtl-block" dir="rtl" lang="ar">
|
|
221
|
+
النص العربي للاختبار. إذا ظهر هذا النص بشكل صحيح ومن اليمين إلى اليسار، فإن الترميز واتجاه النص يعملان بشكل سليم.
|
|
222
|
+
</p>
|
|
223
|
+
<p><em>Above: Arabic sample. Direction should be right-to-left.</em></p>
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<section class="edge-section" id="mandarin">
|
|
227
|
+
<h2>Mandarin / Chinese</h2>
|
|
228
|
+
<p class="ltr-block" lang="zh">
|
|
229
|
+
这是中文测试。简体字与繁体字:简体 = 简体字,繁体 = 繁體字。如果这些字符显示正确,说明 UTF-8 编码正常。
|
|
230
|
+
</p>
|
|
231
|
+
<p><em>Above: Mandarin (Simplified and Traditional). Direction LTR.</em></p>
|
|
232
|
+
</section>
|
|
233
|
+
|
|
234
|
+
<section class="edge-section" id="mixed">
|
|
235
|
+
<h2>Mixed RTL and LTR</h2>
|
|
236
|
+
<p>
|
|
237
|
+
English sentence with <span dir="rtl" lang="ar">كلمة عربية</span> in the middle, then more English.
|
|
238
|
+
</p>
|
|
239
|
+
<p dir="rtl">
|
|
240
|
+
جملة عربية <span dir="ltr" lang="en">with English in the middle</span> ثم تكملة عربية.
|
|
241
|
+
</p>
|
|
242
|
+
</section>
|
|
243
|
+
|
|
244
|
+
<section class="edge-section" id="raw-unicode">
|
|
245
|
+
<h2>Raw Unicode in Page (No Entities)</h2>
|
|
246
|
+
<p>Emoji in source: 😀 🎉 中文 العربية</p>
|
|
247
|
+
</section>
|
|
248
|
+
</div>
|
|
249
|
+
</main>
|
|
250
|
+
</div>
|
|
251
|
+
</body>
|
|
252
|
+
</html>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<div class="content">
|
|
20
20
|
<h1>Website Demo</h1>
|
|
21
21
|
<p>
|
|
22
|
-
This is a simple demo of using Node Simple Server (NSS) for static website (HTML, CSS, JS) development. Try editing the example files for this site and watch how the page auto reloads your changes. You can also open the developer console to see helpful messages being printed by the demo.
|
|
22
|
+
This is a simple demo of using Node Simple Server (NSS) for static website (HTML, CSS, JS) development. Try editing the example files for this site and watch how the page auto reloads your changes. You can also open the developer console to see helpful messages being printed by the demo. You should also view your terminal to see the messages being printed by the demo.
|
|
23
23
|
</p>
|
|
24
24
|
<div id="fox-container">
|
|
25
25
|
<svg class="loader" version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
|
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
</div>
|
|
39
39
|
</main>
|
|
40
40
|
<footer>
|
|
41
|
+
<p>
|
|
42
|
+
<a href="./character-test.html">Character test page</a> for complex text: HTML entities, emojis, RTL and LTR, multilingual content.
|
|
43
|
+
</p>
|
|
41
44
|
<div class="content">
|
|
42
45
|
Made with <svg class="heart-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M18 1l-6 4-6-4-6 5v7l12 10 12-10v-7z"/></svg> by Caboodle Tech
|
|
43
46
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const loadFoxImage = () => {
|
|
3
2
|
const container = document.getElementById('fox-container');
|
|
4
3
|
if (!container) {
|
|
5
4
|
console.log('DEMO: Could not locate the image container for the fox image.');
|
|
@@ -23,7 +22,7 @@ function loadFoxImage() {
|
|
|
23
22
|
.catch((_) => {
|
|
24
23
|
console.log('DEMO: There was an error trying to load the fox image.');
|
|
25
24
|
});
|
|
26
|
-
}
|
|
25
|
+
};
|
|
27
26
|
|
|
28
27
|
document.addEventListener('DOMContentLoaded', () => {
|
|
29
28
|
console.log('DEMO: Loading a random image of a fox in 3 seconds...');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const setupChatApp = () => {
|
|
2
2
|
// Make sure the basic elements are present on the page.
|
|
3
3
|
const chatContainer = document.getElementById('chat');
|
|
4
4
|
if (!chatContainer) { return; }
|
|
@@ -28,9 +28,11 @@ function setupChatApp() {
|
|
|
28
28
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/*
|
|
32
|
+
* Handle websocket messages from the server. NSS demos only use strings so
|
|
33
|
+
* check for that.
|
|
34
|
+
*/
|
|
32
35
|
NSS_WS.registerCallback((msgObj) => {
|
|
33
|
-
// NSS demos only use strings so check for that.
|
|
34
36
|
if (msgObj.type === 'string') {
|
|
35
37
|
// Add the servers response to the chat app.
|
|
36
38
|
const msgDiv = document.createElement('div');
|
|
@@ -43,19 +45,19 @@ function setupChatApp() {
|
|
|
43
45
|
|
|
44
46
|
// When the submit button (send icon) is pressed send a websocket message.
|
|
45
47
|
button.addEventListener('click', () => {
|
|
46
|
-
|
|
48
|
+
/*
|
|
49
|
+
* Send the message. Add the message to the chat app.
|
|
50
|
+
*/
|
|
47
51
|
NSS_WS.send(textarea.value.replace(/\n/g, ' '));
|
|
48
|
-
// Add the message to the chat app.
|
|
49
52
|
const msgDiv = document.createElement('div');
|
|
50
53
|
msgDiv.classList.add('msg');
|
|
51
54
|
msgDiv.classList.add('frontend');
|
|
52
55
|
msgDiv.innerHTML = textarea.value.replace(/\n/g, '<br>');
|
|
53
56
|
msgContainer.appendChild(msgDiv);
|
|
54
|
-
// Reset the chat message box.
|
|
55
57
|
textarea.value = '';
|
|
56
58
|
textarea.style.height = 'initial';
|
|
57
59
|
});
|
|
58
|
-
}
|
|
60
|
+
};
|
|
59
61
|
|
|
60
62
|
// Wait until the page is ready and then setup the demo.
|
|
61
63
|
document.addEventListener('DOMContentLoaded', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caboodle-tech/node-simple-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Node Simple Server (NSS): A small but effective node based server for development sites, customizable live reloading, and websocket support built-in.",
|
|
5
5
|
"main": "bin/nss.js",
|
|
6
6
|
"scripts": {
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@html-eslint/eslint-plugin": "^0.32.0",
|
|
48
48
|
"@html-eslint/parser": "^0.32.0",
|
|
49
|
-
"eslint": "^9.39.
|
|
50
|
-
"eslint-plugin-jsonc": "^2.21.
|
|
49
|
+
"eslint": "^9.39.4",
|
|
50
|
+
"eslint-plugin-jsonc": "^2.21.1",
|
|
51
51
|
"jsonc-eslint-parser": "^2.4.2"
|
|
52
52
|
}
|
|
53
53
|
}
|