redweb 0.9.0 → 0.11.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +187 -23
  3. package/docs/LIVE_HTML.md +352 -0
  4. package/examples/live-html/cards.css +36 -0
  5. package/examples/live-html/cards.html +11 -0
  6. package/examples/live-html/cards.js +91 -0
  7. package/examples/live-html/cards.ts +35 -0
  8. package/examples/live-html/chatroom.css +156 -0
  9. package/examples/live-html/chatroom.js +268 -0
  10. package/examples/live-html/chatroom.ts +217 -0
  11. package/examples/live-html/components.css +7 -0
  12. package/examples/live-html/components.js +113 -0
  13. package/examples/live-html/components.ts +41 -0
  14. package/examples/live-html/counter.css +24 -0
  15. package/examples/live-html/counter.html +10 -0
  16. package/examples/live-html/counter.js +73 -0
  17. package/examples/live-html/counter.ts +21 -0
  18. package/examples/live-html/jsx-page.js +81 -0
  19. package/examples/live-html/jsx-page.tsx +41 -0
  20. package/examples/live-html/tsconfig.json +19 -0
  21. package/index.d.ts +219 -1
  22. package/index.js +18 -1
  23. package/jsx-dev-runtime.d.ts +13 -0
  24. package/jsx-dev-runtime.js +9 -0
  25. package/jsx-runtime.d.ts +28 -0
  26. package/jsx-runtime.js +5 -0
  27. package/package.json +47 -3
  28. package/src/htmx/Html.js +137 -0
  29. package/src/htmx/HtmlRenderer.js +88 -0
  30. package/src/htmx/HtmlSyntax.js +168 -0
  31. package/src/htmx/Jsx.js +86 -0
  32. package/src/htmx/LiveHtmlServer.js +91 -0
  33. package/src/htmx/LivePage.js +232 -0
  34. package/src/htmx/PageAssetLoader.js +34 -0
  35. package/src/htmx/PageManager.js +435 -0
  36. package/src/htmx/StaticExporter.js +78 -0
  37. package/src/htmx/StaticSite.js +182 -0
  38. package/src/htmx/TemplateRenderer.js +231 -0
  39. package/src/htmx/browserRuntime.js +97 -0
  40. package/src/htmx/index.js +10 -0
  41. package/src/htmx/metadata.js +349 -0
  42. package/src/htmx/sourceRoot.js +28 -0
  43. package/src/htmx/start.js +17 -0
  44. package/src/htmx/synchronous.js +9 -0
  45. package/src/http/BaseHttpServer.js +0 -35
  46. package/src/ws/BaseSocketServer.js +4 -0
  47. package/src/htmx/HtmxRenderer.js +0 -73
  48. package/src/htmx/RedWebHtmxComponent.js +0 -11
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head><meta charset="utf-8"><title>Redweb cards</title></head>
4
+ <body>
5
+ <main>
6
+ <h1>Server-rendered cards</h1>
7
+ <section class="card-grid" aria-live="polite" rw-each="cards"></section>
8
+ <button type="button" rw-click="add">Add a card</button>
9
+ </main>
10
+ </body>
11
+ </html>
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.CardsPage = void 0;
38
+ const redweb_1 = require('../..');
39
+ let CardsPage = (() => {
40
+ let _classDecorators = [(0, redweb_1.page)('/', { template: 'cards.html', css: 'cards.css', shared: true })];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _instanceExtraInitializers = [];
45
+ let _cards_decorators;
46
+ let _cards_initializers = [];
47
+ let _cards_extraInitializers = [];
48
+ let _card_decorators;
49
+ let _add_decorators;
50
+ var CardsPage = class {
51
+ static { _classThis = this; }
52
+ static {
53
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
54
+ _cards_decorators = [(0, redweb_1.state)()];
55
+ _card_decorators = [(0, redweb_1.view)('cards')];
56
+ _add_decorators = [(0, redweb_1.action)()];
57
+ __esDecorate(this, null, _card_decorators, { kind: "method", name: "card", static: false, private: false, access: { has: obj => "card" in obj, get: obj => obj.card }, metadata: _metadata }, null, _instanceExtraInitializers);
58
+ __esDecorate(this, null, _add_decorators, { kind: "method", name: "add", static: false, private: false, access: { has: obj => "add" in obj, get: obj => obj.add }, metadata: _metadata }, null, _instanceExtraInitializers);
59
+ __esDecorate(null, null, _cards_decorators, { kind: "field", name: "cards", static: false, private: false, access: { has: obj => "cards" in obj, get: obj => obj.cards, set: (obj, value) => { obj.cards = value; } }, metadata: _metadata }, _cards_initializers, _cards_extraInitializers);
60
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
61
+ CardsPage = _classThis = _classDescriptor.value;
62
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
63
+ __runInitializers(_classThis, _classExtraInitializers);
64
+ }
65
+ cards = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _cards_initializers, [
66
+ { title: 'Realtime', description: 'State changes arrive over the existing socket.' },
67
+ { title: 'Safe HTML', description: 'Card values are escaped by default.' },
68
+ ]));
69
+ card(card) {
70
+ return (0, redweb_1.html) `
71
+ <article class="card">
72
+ <h2>${card.title}</h2>
73
+ <p>${card.description}</p>
74
+ </article>
75
+ `;
76
+ }
77
+ add() {
78
+ this.cards = [...this.cards, {
79
+ title: `Card ${this.cards.length + 1}`,
80
+ description: 'Rendered on the server and synchronized without client code.',
81
+ }];
82
+ }
83
+ constructor() {
84
+ __runInitializers(this, _cards_extraInitializers);
85
+ }
86
+ };
87
+ return CardsPage = _classThis;
88
+ })();
89
+ exports.CardsPage = CardsPage;
90
+ if (require.main === module)
91
+ (0, redweb_1.start)(CardsPage, { port: 8080 });
@@ -0,0 +1,35 @@
1
+ import { action, html, page, start, state, view } from 'redweb';
2
+
3
+ interface Card {
4
+ title: string;
5
+ description: string;
6
+ }
7
+
8
+ @page('/', { template: 'cards.html', css: 'cards.css', shared: true })
9
+ export class CardsPage {
10
+ @state()
11
+ cards: Card[] = [
12
+ { title: 'Realtime', description: 'State changes arrive over the existing socket.' },
13
+ { title: 'Safe HTML', description: 'Card values are escaped by default.' },
14
+ ];
15
+
16
+ @view('cards')
17
+ card(card: Card) {
18
+ return html`
19
+ <article class="card">
20
+ <h2>${card.title}</h2>
21
+ <p>${card.description}</p>
22
+ </article>
23
+ `;
24
+ }
25
+
26
+ @action()
27
+ add() {
28
+ this.cards = [...this.cards, {
29
+ title: `Card ${this.cards.length + 1}`,
30
+ description: 'Rendered on the server and synchronized without client code.',
31
+ }];
32
+ }
33
+ }
34
+
35
+ if (require.main === module) start(CardsPage, { port: 8080 });
@@ -0,0 +1,156 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
4
+ background: #07111f;
5
+ color: #e5eef9;
6
+ }
7
+
8
+ * { box-sizing: border-box; }
9
+
10
+ body {
11
+ margin: 0;
12
+ min-height: 100vh;
13
+ background: radial-gradient(circle at top, #15304b 0, #07111f 45rem);
14
+ }
15
+
16
+ main {
17
+ width: min(68rem, calc(100% - 2rem));
18
+ margin: 0 auto;
19
+ padding: 3rem 0;
20
+ }
21
+
22
+ .chatroom,
23
+ .join-panel,
24
+ .conversation,
25
+ .presence {
26
+ border: 1px solid #27435e;
27
+ border-radius: 1rem;
28
+ background: rgba(9, 24, 40, .92);
29
+ box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, .28);
30
+ }
31
+
32
+ .chatroom { overflow: hidden; }
33
+
34
+ .join-panel {
35
+ max-width: 38rem;
36
+ margin: 8vh auto;
37
+ padding: 2.5rem;
38
+ }
39
+
40
+ .join-panel h1,
41
+ .room-header h1 { margin: .2rem 0 .75rem; }
42
+
43
+ .eyebrow {
44
+ margin: 0;
45
+ color: #67e8f9;
46
+ font-size: .75rem;
47
+ font-weight: 800;
48
+ letter-spacing: .12em;
49
+ text-transform: uppercase;
50
+ }
51
+
52
+ .join-form { margin-top: 2rem; }
53
+ .form-error { color: #fda4af; font-weight: 700; }
54
+
55
+ label { display: block; margin-bottom: .5rem; font-weight: 700; }
56
+
57
+ .input-row,
58
+ .composer { display: flex; gap: .75rem; }
59
+
60
+ input,
61
+ button { font: inherit; }
62
+
63
+ input {
64
+ min-width: 0;
65
+ flex: 1;
66
+ padding: .8rem 1rem;
67
+ border: 1px solid #365570;
68
+ border-radius: .55rem;
69
+ background: #07111f;
70
+ color: inherit;
71
+ }
72
+
73
+ button {
74
+ padding: .8rem 1.1rem;
75
+ border: 0;
76
+ border-radius: .55rem;
77
+ background: #22d3ee;
78
+ color: #083344;
79
+ cursor: pointer;
80
+ font-weight: 800;
81
+ }
82
+
83
+ .room-layout {
84
+ display: grid;
85
+ grid-template-columns: minmax(0, 1fr) 15rem;
86
+ min-height: 38rem;
87
+ }
88
+
89
+ .conversation,
90
+ .presence { border: 0; border-radius: 0; box-shadow: none; }
91
+
92
+ .conversation { display: grid; grid-template-rows: auto 1fr auto; }
93
+
94
+ .room-header {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ padding: 1.25rem 1.5rem;
99
+ border-bottom: 1px solid #27435e;
100
+ }
101
+
102
+ .quiet-button { background: #1b3349; color: #d8e8f6; }
103
+
104
+ .message-list {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: .75rem;
108
+ margin: 0;
109
+ padding: 1.5rem;
110
+ list-style: none;
111
+ overflow-wrap: anywhere;
112
+ }
113
+
114
+ .message-list li:not(.empty-message) {
115
+ max-width: 80%;
116
+ padding: .8rem 1rem;
117
+ border-radius: .75rem;
118
+ background: #142b40;
119
+ }
120
+
121
+ .message-list strong { color: #67e8f9; }
122
+ .message-list p { margin: .25rem 0 0; }
123
+ .empty-message { margin: auto; color: #8ca3b8; }
124
+
125
+ .composer { padding: 1rem 1.5rem 1.5rem; }
126
+
127
+ .presence {
128
+ padding: 1.5rem;
129
+ border-left: 1px solid #27435e;
130
+ background: rgba(5, 17, 29, .72);
131
+ }
132
+
133
+ .presence ul { padding: 0; list-style: none; }
134
+ .presence li { padding: .45rem 0; }
135
+ .presence li::before { content: '●'; margin-right: .5rem; color: #4ade80; }
136
+ .presence .more-members { color: #8ca3b8; }
137
+ .presence .more-members::before { content: ''; margin: 0; }
138
+
139
+ .sr-only {
140
+ position: absolute;
141
+ width: 1px;
142
+ height: 1px;
143
+ padding: 0;
144
+ margin: -1px;
145
+ overflow: hidden;
146
+ clip: rect(0, 0, 0, 0);
147
+ white-space: nowrap;
148
+ border: 0;
149
+ }
150
+
151
+ @media (max-width: 42rem) {
152
+ main { padding: 1rem 0; }
153
+ .room-layout { grid-template-columns: 1fr; }
154
+ .presence { border-top: 1px solid #27435e; border-left: 0; }
155
+ .input-row { flex-direction: column; }
156
+ }
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ChatroomComponent = void 0;
38
+ exports.createChatroomPage = createChatroomPage;
39
+ const redweb_1 = require('../..');
40
+ const MAX_VISIBLE_MEMBERS = 100;
41
+ const UNSAFE_TEXT = /[\p{Cc}\p{Cf}]/u;
42
+ function messageView(messages) {
43
+ return messages.length
44
+ ? (0, redweb_1.each)([...messages], entry => (0, redweb_1.html) `<li><strong>${entry.sender}</strong><p>${entry.text}</p></li>`)
45
+ : (0, redweb_1.html) `<li class="empty-message">No messages yet. Say hello.</li>`;
46
+ }
47
+ function presenceView(members) {
48
+ const visible = members.slice(0, MAX_VISIBLE_MEMBERS);
49
+ const remaining = members.length - visible.length;
50
+ return (0, redweb_1.html) `
51
+ <p class="eyebrow">Online · ${members.length}</p>
52
+ <ul>
53
+ ${(0, redweb_1.each)([...visible], member => (0, redweb_1.html) `<li>${member}</li>`)}
54
+ ${remaining ? (0, redweb_1.html) `<li class="more-members">+${remaining} more</li>` : (0, redweb_1.html) ``}
55
+ </ul>
56
+ `;
57
+ }
58
+ class ChatRoom {
59
+ history = [];
60
+ participants = new Set();
61
+ online = new Set();
62
+ join(participant) {
63
+ const name = participant.displayName.toLocaleLowerCase();
64
+ if ([...this.participants].some(member => member !== participant && member.displayName.toLocaleLowerCase() === name)) {
65
+ return false;
66
+ }
67
+ this.participants.add(participant);
68
+ this.online.add(participant);
69
+ participant.updateMessages(messageView(this.history));
70
+ this.publishPresence();
71
+ return true;
72
+ }
73
+ disconnect(participant) {
74
+ if (!this.online.delete(participant))
75
+ return;
76
+ this.publishPresence();
77
+ }
78
+ leave(participant) {
79
+ this.online.delete(participant);
80
+ if (!this.participants.delete(participant))
81
+ return;
82
+ this.publishPresence();
83
+ }
84
+ send(participant, text) {
85
+ if (!this.online.has(participant))
86
+ return false;
87
+ this.history = [...this.history, { sender: participant.displayName, text }].slice(-100);
88
+ const messages = messageView(this.history);
89
+ for (const member of this.participants)
90
+ member.updateMessages(messages);
91
+ return true;
92
+ }
93
+ publishPresence() {
94
+ const members = [...this.online].map(participant => participant.displayName);
95
+ const presence = presenceView(members);
96
+ for (const participant of this.participants)
97
+ participant.updatePresence(presence);
98
+ }
99
+ }
100
+ let ChatroomComponent = (() => {
101
+ let _classDecorators = [(0, redweb_1.component)()];
102
+ let _classDescriptor;
103
+ let _classExtraInitializers = [];
104
+ let _classThis;
105
+ let _instanceExtraInitializers = [];
106
+ let _screen_decorators;
107
+ let _screen_initializers = [];
108
+ let _screen_extraInitializers = [];
109
+ let _messages_decorators;
110
+ let _messages_initializers = [];
111
+ let _messages_extraInitializers = [];
112
+ let _presence_decorators;
113
+ let _presence_initializers = [];
114
+ let _presence_extraInitializers = [];
115
+ let _join_decorators;
116
+ let _send_decorators;
117
+ let _leave_decorators;
118
+ var ChatroomComponent = class {
119
+ static { _classThis = this; }
120
+ static {
121
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
122
+ _screen_decorators = [(0, redweb_1.state)()];
123
+ _messages_decorators = [(0, redweb_1.state)()];
124
+ _presence_decorators = [(0, redweb_1.state)()];
125
+ _join_decorators = [(0, redweb_1.action)()];
126
+ _send_decorators = [(0, redweb_1.action)()];
127
+ _leave_decorators = [(0, redweb_1.action)()];
128
+ __esDecorate(this, null, _join_decorators, { kind: "method", name: "join", static: false, private: false, access: { has: obj => "join" in obj, get: obj => obj.join }, metadata: _metadata }, null, _instanceExtraInitializers);
129
+ __esDecorate(this, null, _send_decorators, { kind: "method", name: "send", static: false, private: false, access: { has: obj => "send" in obj, get: obj => obj.send }, metadata: _metadata }, null, _instanceExtraInitializers);
130
+ __esDecorate(this, null, _leave_decorators, { kind: "method", name: "leave", static: false, private: false, access: { has: obj => "leave" in obj, get: obj => obj.leave }, metadata: _metadata }, null, _instanceExtraInitializers);
131
+ __esDecorate(null, null, _screen_decorators, { kind: "field", name: "screen", static: false, private: false, access: { has: obj => "screen" in obj, get: obj => obj.screen, set: (obj, value) => { obj.screen = value; } }, metadata: _metadata }, _screen_initializers, _screen_extraInitializers);
132
+ __esDecorate(null, null, _messages_decorators, { kind: "field", name: "messages", static: false, private: false, access: { has: obj => "messages" in obj, get: obj => obj.messages, set: (obj, value) => { obj.messages = value; } }, metadata: _metadata }, _messages_initializers, _messages_extraInitializers);
133
+ __esDecorate(null, null, _presence_decorators, { kind: "field", name: "presence", static: false, private: false, access: { has: obj => "presence" in obj, get: obj => obj.presence, set: (obj, value) => { obj.presence = value; } }, metadata: _metadata }, _presence_initializers, _presence_extraInitializers);
134
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
135
+ ChatroomComponent = _classThis = _classDescriptor.value;
136
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
137
+ __runInitializers(_classThis, _classExtraInitializers);
138
+ }
139
+ room = __runInitializers(this, _instanceExtraInitializers);
140
+ displayName = '';
141
+ screen = __runInitializers(this, _screen_initializers, this.joinScreen());
142
+ messages = (__runInitializers(this, _screen_extraInitializers), __runInitializers(this, _messages_initializers, messageView([])));
143
+ presence = (__runInitializers(this, _messages_extraInitializers), __runInitializers(this, _presence_initializers, presenceView([])));
144
+ constructor(room) {
145
+ __runInitializers(this, _presence_extraInitializers);
146
+ this.room = room;
147
+ }
148
+ connected() {
149
+ if (this.displayName)
150
+ this.room.join(this);
151
+ }
152
+ disconnected() {
153
+ this.room.disconnect(this);
154
+ }
155
+ disposed() {
156
+ this.room.leave(this);
157
+ }
158
+ join({ name }) {
159
+ if (this.displayName)
160
+ return false;
161
+ if (typeof name !== 'string') {
162
+ this.screen = this.joinScreen('Display name must be text.');
163
+ return false;
164
+ }
165
+ const displayName = name.normalize('NFKC').trim();
166
+ if (!displayName || displayName.length > 40 || UNSAFE_TEXT.test(displayName)) {
167
+ this.screen = this.joinScreen('Choose a visible display name of at most 40 characters.');
168
+ return false;
169
+ }
170
+ this.displayName = displayName;
171
+ if (this.room.join(this)) {
172
+ this.screen = this.roomScreen();
173
+ return true;
174
+ }
175
+ this.displayName = '';
176
+ this.screen = this.joinScreen('That display name is already in use.');
177
+ return false;
178
+ }
179
+ send({ message }) {
180
+ if (typeof message !== 'string')
181
+ return false;
182
+ const text = message.normalize('NFKC').trim();
183
+ if (!text || text.length > 500 || UNSAFE_TEXT.test(text))
184
+ return false;
185
+ return this.room.send(this, text);
186
+ }
187
+ leave() {
188
+ this.room.leave(this);
189
+ this.displayName = '';
190
+ this.screen = this.joinScreen();
191
+ }
192
+ updateMessages(messages) {
193
+ this.messages = messages;
194
+ }
195
+ updatePresence(presence) {
196
+ this.presence = presence;
197
+ }
198
+ render() {
199
+ return (0, redweb_1.html) `<section class="chatroom" data-rw-state="screen">${this.screen}</section>`;
200
+ }
201
+ joinScreen(error = '') {
202
+ const feedback = error ? (0, redweb_1.html) `<p class="form-error" role="alert">${error}</p>` : (0, redweb_1.html) ``;
203
+ return (0, redweb_1.html) `
204
+ <section class="join-panel">
205
+ <p class="eyebrow">Live room</p>
206
+ <h1>Join the chatroom</h1>
207
+ <p>Choose a name once, then chat in realtime with everyone currently in the room.</p>
208
+ ${feedback}
209
+ <form rw-submit="join" class="join-form">
210
+ <label for="display-name">Display name</label>
211
+ <div class="input-row">
212
+ <input id="display-name" name="name" maxlength="40" autocomplete="nickname" required autofocus>
213
+ <button type="submit">Join room</button>
214
+ </div>
215
+ </form>
216
+ </section>
217
+ `;
218
+ }
219
+ roomScreen() {
220
+ return (0, redweb_1.html) `
221
+ <div class="room-layout">
222
+ <section class="conversation">
223
+ <header class="room-header">
224
+ <div><p class="eyebrow">Connected as</p><h1>${this.displayName}</h1></div>
225
+ <button type="button" class="quiet-button" rw-click="leave">Leave</button>
226
+ </header>
227
+ <ol class="message-list" aria-live="polite" data-rw-state="messages">${this.messages}</ol>
228
+ <form rw-submit="send" class="composer">
229
+ <label class="sr-only" for="chat-message">Message</label>
230
+ <input id="chat-message" name="message" maxlength="500" autocomplete="off" placeholder="Message the room…" required autofocus>
231
+ <button type="submit">Send</button>
232
+ </form>
233
+ </section>
234
+ <aside class="presence" aria-label="People in the room" data-rw-state="presence">${this.presence}</aside>
235
+ </div>
236
+ `;
237
+ }
238
+ };
239
+ return ChatroomComponent = _classThis;
240
+ })();
241
+ exports.ChatroomComponent = ChatroomComponent;
242
+ function createChatroomPage() {
243
+ const room = new ChatRoom();
244
+ let ChatroomPage = (() => {
245
+ let _classDecorators = [(0, redweb_1.page)('/', { css: 'chatroom.css' })];
246
+ let _classDescriptor;
247
+ let _classExtraInitializers = [];
248
+ let _classThis;
249
+ var ChatroomPage = class {
250
+ static { _classThis = this; }
251
+ static {
252
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
253
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
254
+ ChatroomPage = _classThis = _classDescriptor.value;
255
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
256
+ __runInitializers(_classThis, _classExtraInitializers);
257
+ }
258
+ chat = new ChatroomComponent(room);
259
+ render() {
260
+ return (0, redweb_1.html) `<main>${this.chat}</main>`;
261
+ }
262
+ };
263
+ return ChatroomPage = _classThis;
264
+ })();
265
+ return ChatroomPage;
266
+ }
267
+ if (require.main === module)
268
+ (0, redweb_1.start)(createChatroomPage(), { port: 8080 });