@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 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
- // A bare minimum callback to handle most development changes.
87
- function watcherCallback(event, path, statsOrDetails) {
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
- function websocketCallback(messageObject, pageId) {
115
- // Interpret and do what you need to with the message:
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
- // Respond to the page that sent the message if you like:
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
- function websocketCallback(messageObject, pageId) {
133
- // Interpret and do what you need to with the message:
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
- // Respond to the page that sent the message if you like:
139
- Server.message(pageId, 'Route specific messaged received!');
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
- // A bare minimum watcher options object; use for development, omit for production.
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, // Just send everything to a single function.
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.7';
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
- // Use provided port, or fall back to portInUse (actual port server is using),
134
- // or finally to the configured port
135
- // Check for undefined/null specifically to allow 0 if explicitly provided (though unlikely)
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
- // Get the file.
686
- const file = Fs.readFileSync(systemPath, { encoding: 'binary' });
687
-
688
- // Output the file to the browser.
689
- resp.writeHead(HTTPStatus.ok, this.#getHeaders({
690
- contentType: ContentTypes[ext] || this.#OPS.contentType,
691
- file: systemPath
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
- let html = file.toString();
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
- html = start + this.#handlers.liveReloading + end;
702
- resp.write(html, 'utf-8');
714
+ body = Buffer.from(start + this.#handlers.liveReloading + end, 'utf8');
703
715
  } else {
704
- resp.write(file, 'binary');
716
+ body = Buffer.from(html, 'utf8');
705
717
  }
718
+ resp.write(body);
706
719
  } else {
707
- resp.write(file, 'binary');
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
- // Create the HTTP server.
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
- // Capture server errors and respond as needed.
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
- // Attempt to start the server now.
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
- // A bare minimum callback to handle changes.
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
- // A bare minimum callback to handle changes.
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
- // A bare minimum callback to handle changes.
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
- // Build a simple websocket watcher (handler).
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
- function loadFoxImage() {
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;amp;</code></td>
50
+ <td><code>&amp;#38;</code></td>
51
+ <td><code>&amp;#x26;</code></td>
52
+ <td>&#38;</td>
53
+ <td>n/a</td>
54
+ </tr>
55
+ <tr>
56
+ <td>Less than</td>
57
+ <td><code>&amp;lt;</code></td>
58
+ <td><code>&amp;#60;</code></td>
59
+ <td><code>&amp;#x3C;</code></td>
60
+ <td>&#60;</td>
61
+ <td>n/a</td>
62
+ </tr>
63
+ <tr>
64
+ <td>Greater than</td>
65
+ <td><code>&amp;gt;</code></td>
66
+ <td><code>&amp;#62;</code></td>
67
+ <td><code>&amp;#x3E;</code></td>
68
+ <td>&#62;</td>
69
+ <td>n/a</td>
70
+ </tr>
71
+ <tr>
72
+ <td>Double quote</td>
73
+ <td><code>&amp;quot;</code></td>
74
+ <td><code>&amp;#34;</code></td>
75
+ <td><code>&amp;#x22;</code></td>
76
+ <td>"</td>
77
+ <td>"</td>
78
+ </tr>
79
+ <tr>
80
+ <td>Apostrophe</td>
81
+ <td><code>&amp;apos;</code></td>
82
+ <td><code>&amp;#39;</code></td>
83
+ <td><code>&amp;#x27;</code></td>
84
+ <td>'</td>
85
+ <td>'</td>
86
+ </tr>
87
+ <tr>
88
+ <td>Non-breaking space</td>
89
+ <td><code>&amp;nbsp;</code></td>
90
+ <td><code>&amp;#160;</code></td>
91
+ <td><code>&amp;#xA0;</code></td>
92
+ <td>[&#160;]</td>
93
+ <td>[&#160;]</td>
94
+ </tr>
95
+ <tr>
96
+ <td>Copyright</td>
97
+ <td><code>&amp;copy;</code></td>
98
+ <td><code>&amp;#169;</code></td>
99
+ <td><code>&amp;#xA9;</code></td>
100
+ <td>&#169;</td>
101
+ <td>©</td>
102
+ </tr>
103
+ <tr>
104
+ <td>En dash</td>
105
+ <td><code>&amp;ndash;</code></td>
106
+ <td><code>&amp;#8211;</code></td>
107
+ <td><code>&amp;#x2013;</code></td>
108
+ <td>&#8211;</td>
109
+ <td>–</td>
110
+ </tr>
111
+ <tr>
112
+ <td>Em dash</td>
113
+ <td><code>&amp;mdash;</code></td>
114
+ <td><code>&amp;#8212;</code></td>
115
+ <td><code>&amp;#x2014;</code></td>
116
+ <td>&#8212;</td>
117
+ <td>—</td>
118
+ </tr>
119
+ </tbody>
120
+ </table>
121
+ </section>
122
+
123
+ <section class="edge-section" id="emojis">
124
+ <h2>Emojis &amp; Symbols (Unicode)</h2>
125
+ <p>Via reference: numeric character reference (e.g. &#x1F600;). 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>&#x1F600;</td>
140
+ <td>😀</td>
141
+ </tr>
142
+ <tr>
143
+ <td>Party popper</td>
144
+ <td><code>U+1F389</code></td>
145
+ <td>&#x1F389;</td>
146
+ <td>🎉</td>
147
+ </tr>
148
+ <tr>
149
+ <td>Red heart</td>
150
+ <td><code>U+2764</code></td>
151
+ <td>&#x2764;</td>
152
+ <td>❤</td>
153
+ </tr>
154
+ <tr>
155
+ <td>Light bulb</td>
156
+ <td><code>U+1F4A1</code></td>
157
+ <td>&#x1F4A1;</td>
158
+ <td>💡</td>
159
+ </tr>
160
+ <tr>
161
+ <td>Check mark</td>
162
+ <td><code>U+2714</code></td>
163
+ <td>&#x2714;</td>
164
+ <td>✔</td>
165
+ </tr>
166
+ <tr>
167
+ <td>Cross mark</td>
168
+ <td><code>U+274C</code></td>
169
+ <td>&#x274C;</td>
170
+ <td>❌</td>
171
+ </tr>
172
+ <tr>
173
+ <td>Star</td>
174
+ <td><code>U+2B50</code></td>
175
+ <td>&#x2B50;</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>&#x1F1FA;&#x1F1F8;</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>&#x1F1EC;&#x1F1E7;</td>
188
+ <td>🇬🇧</td>
189
+ </tr>
190
+ <tr>
191
+ <td>Flag (Canada)</td>
192
+ <td><code>U+1F1E8 U+1F1E6</code></td>
193
+ <td>&#x1F1E8;&#x1F1E6;</td>
194
+ <td>🇨🇦</td>
195
+ </tr>
196
+ <tr>
197
+ <td>Flag (Japan)</td>
198
+ <td><code>U+1F1EF U+1F1F5</code></td>
199
+ <td>&#x1F1EF;&#x1F1F5;</td>
200
+ <td>🇯🇵</td>
201
+ </tr>
202
+ <tr>
203
+ <td>Flag (France)</td>
204
+ <td><code>U+1F1EB U+1F1F7</code></td>
205
+ <td>&#x1F1EB;&#x1F1F7;</td>
206
+ <td>🇫🇷</td>
207
+ </tr>
208
+ <tr>
209
+ <td>Flag (Australia)</td>
210
+ <td><code>U+1F1E6 U+1F1FA</code></td>
211
+ <td>&#x1F1E6;&#x1F1FA;</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
- function loadFoxImage() {
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
- function setupChatApp() {
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
- // Handle websocket messages from the server.
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
- // Send the message.
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.2.7",
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.2",
50
- "eslint-plugin-jsonc": "^2.21.0",
49
+ "eslint": "^9.39.4",
50
+ "eslint-plugin-jsonc": "^2.21.1",
51
51
  "jsonc-eslint-parser": "^2.4.2"
52
52
  }
53
53
  }