create-wirejs-app 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bin.js +4 -6
  2. package/package.json +21 -20
  3. package/templates/default/api/index.js +99 -0
  4. package/templates/default/api/package.json +18 -0
  5. package/templates/default/api/prebuild.js +58 -0
  6. package/templates/default/gitignore +5 -0
  7. package/templates/default/package.json +25 -0
  8. package/templates/default/src/build_id.json +1 -0
  9. package/templates/default/src/components/account-menu.js +123 -0
  10. package/templates/default/src/components/authenticator.js +149 -0
  11. package/templates/default/src/components/countdown.js +36 -0
  12. package/templates/default/src/layouts/default.js +3 -0
  13. package/templates/default/src/lib/sample-lib.js +8 -0
  14. package/templates/default/src/package.json +10 -0
  15. package/templates/default/src/ssg/index.js +27 -0
  16. package/templates/default/src/ssg/todo-app.js +83 -0
  17. package/template/_gitignore +0 -4
  18. package/template/package.json +0 -16
  19. package/template/src/api/sample.js +0 -10
  20. package/template/src/api/todo.js +0 -76
  21. package/template/src/build_id.json +0 -1
  22. package/template/src/components/countdown.js +0 -33
  23. package/template/src/layouts/default.js +0 -10
  24. package/template/src/lib/sample-lib.js +0 -8
  25. package/template/src/routes/html.html +0 -8
  26. package/template/src/routes/index.md +0 -30
  27. /package/{template → templates/default}/src/layouts/bare.html +0 -0
  28. /package/{template → templates/default}/src/layouts/core.css +0 -0
  29. /package/{template → templates/default}/src/layouts/default.css +0 -0
  30. /package/{template → templates/default}/src/layouts/default.html +0 -0
  31. /package/{template → templates/default}/static/images/wirejs.svg +0 -0
package/bin.js CHANGED
@@ -15,11 +15,9 @@ const [
15
15
  fs.mkdirSync(projectName);
16
16
 
17
17
  console.log("Writing base package files ...");
18
- await copy(`${__dirname}/template`, `./${projectName}`);
19
- await copy(
20
- `${__dirname}/template/_gitignore`,
21
- `./${projectName}/.gitignore`
22
- );
18
+ await copy(`${__dirname}/templates/default`, `./${projectName}`);
19
+ await copy(`${__dirname}/templates/default/gitignore`, `./${projectName}/.gitignore`);
20
+ await fs.promises.unlink(`./${projectName}/gitignore`);
23
21
 
24
22
  const packageJson = await fs.readFileSync(`./${projectName}/package.json`);
25
23
  fs.writeFileSync(
@@ -31,7 +29,7 @@ const [
31
29
 
32
30
  console.log("Fetching dependencies ...");
33
31
  process.chdir(projectName);
34
- execSync('npm install', { stdio: [0, 1, 2] });
32
+ execSync('npm install');
35
33
 
36
34
  console.log(`
37
35
  Done creating ${projectName}!
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
- "name": "create-wirejs-app",
3
- "version": "1.0.3",
4
- "description": "Initializes a wirejs package.",
5
- "author": "Jon Wire",
6
- "license": "MIT",
7
- "bin": {
8
- "create-wirejs-app": "./bin.js"
9
- },
10
- "dependencies": {
11
- "recursive-copy": "^2.0.14"
12
- },
13
- "files": [
14
- "bin.js",
15
- "package.json",
16
- "template/src/*",
17
- "template/static/*",
18
- "template/_gitignore",
19
- "template/package.json*"
20
- ]
21
- }
2
+ "name": "create-wirejs-app",
3
+ "version": "2.0.0",
4
+ "description": "Initializes a wirejs package.",
5
+ "author": "Jon Wire",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "create-wirejs-app": "./bin.js"
9
+ },
10
+ "dependencies": {
11
+ "recursive-copy": "^2.0.14"
12
+ },
13
+ "scripts": {
14
+ "build": "echo \"Nothing to build\"",
15
+ "clean": "echo \"Nothing to clean\""
16
+ },
17
+ "files": [
18
+ "bin.js",
19
+ "package.json",
20
+ "templates/*"
21
+ ]
22
+ }
@@ -0,0 +1,99 @@
1
+ import { AuthenticationService, FileService, withContext } from 'wirejs-resources';
2
+ import { defaultGreeting } from '../src/lib/sample-lib.js';
3
+
4
+ const userTodos = new FileService('userTodoApp');
5
+ const wikiPages = new FileService('wikiPages');
6
+ const authService = new AuthenticationService('core-users');
7
+
8
+ export const auth = authService.buildApi();
9
+
10
+ async function currentUser(context) {
11
+ const { user } = await authService.getBaseState(context.cookies);
12
+ return user;
13
+ }
14
+
15
+ /**
16
+ * Given a name, this will return a friendly, personalized greeting.
17
+ * @param {string} name
18
+ * @returns {Promise<string>} A friendly greeting.
19
+ */
20
+ export const hello = withContext(context => async (name) => {
21
+ const user = await currentUser();
22
+ return `${defaultGreeting()}, ${user ? `<b>${user}</b>` : '<i>Anonymous</i>'}.`;
23
+ });
24
+
25
+ export const todos = withContext(context => ({
26
+ async read() {
27
+ const user = await currentUser(context);
28
+
29
+ console.log('current user', user);
30
+
31
+ if (!user) {
32
+ throw new Error("Unauthorized");
33
+ }
34
+
35
+ try {
36
+ const todos = await userTodos.read(`${user}/todos.json`);
37
+ return todos ? JSON.parse(todos) : [];
38
+ } catch (error) {
39
+ return [];
40
+ }
41
+ },
42
+ /**
43
+ * @param {string[]} todos
44
+ */
45
+ async write(todos) {
46
+ const user = await currentUser(context);
47
+
48
+ if (!user) {
49
+ throw new Error("Unauthorized");
50
+ }
51
+
52
+ if (!Array.isArray(todos)) {
53
+ throw new Error("Invalid todos!");
54
+ }
55
+
56
+ if (!todos.every(todo =>
57
+ typeof todo.id === 'string'
58
+ && typeof todo.text === 'string')
59
+ ) {
60
+ throw new Error("Invalid todos!");
61
+ }
62
+
63
+ const finalTodos = todos.map(todo => ({ id: todo.id, text: todo.text }));
64
+ await userTodos.write(`${user}/todos.json`, JSON.stringify(finalTodos));
65
+
66
+ return true;
67
+ }
68
+ }));
69
+
70
+ function normalizeWikiPageFilename(page) {
71
+ return page.replace(/[^-_a-zA-Z0-9/]/g, '-') + '.md';
72
+ }
73
+
74
+ export const wiki = withContext(context => ({
75
+ async read(page) {
76
+ const filename = normalizeWikiPageFilename(page);
77
+ try {
78
+ return await wikiPages.read(filename);
79
+ } catch (error) {
80
+ console.log("returning empty content");
81
+ return undefined;
82
+ }
83
+ },
84
+ /**
85
+ * @param {string[]} todos
86
+ */
87
+ async write(page, content) {
88
+ const user = await currentUser(context);
89
+
90
+ if (!user) {
91
+ throw new Error("Unauthorized");
92
+ }
93
+
94
+ const filename = normalizeWikiPageFilename(page);
95
+ await wikiPages.write(filename, content);
96
+
97
+ return true;
98
+ }
99
+ }));
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "my-api",
3
+ "private": "true",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "prestart": "node prebuild.js",
8
+ "start": "",
9
+ "prebuild": "node prebuild.js"
10
+ },
11
+ "devDependencies": {
12
+ "rimraf": "^6.0.1"
13
+ },
14
+ "exports": {
15
+ "wirejs:client": "./index.client.js",
16
+ "default": "./index.js"
17
+ }
18
+ }
@@ -0,0 +1,58 @@
1
+ import { writeFileSync } from 'fs';
2
+
3
+ const indexModule = await import('./index.js');
4
+
5
+ function dedent(tabs, text) {
6
+ const tabString = new Array(tabs).fill('\t').join('');
7
+ return text.trim().replace(new RegExp(`^${tabString}`, 'gm'), '');
8
+ }
9
+
10
+ const apiCode = Object.keys(indexModule)
11
+ .map(name => `export const ${name} = apiTree(${JSON.stringify([name])});`)
12
+ .join('\n')
13
+ ;
14
+
15
+ const baseClient = dedent(1, /* js */ `
16
+ async function wirejsCallApi(method, ...args) {
17
+ let cookieHeader = {};
18
+ if (typeof args[0]?.cookies?.getAll === 'function') {
19
+ const cookies = args[0]?.cookies?.getAll();
20
+ cookieHeader = typeof cookies === 'object'
21
+ ? {
22
+ Cookie: Object.entries(cookies).map(kv => kv.join('=')).join('; ')
23
+ }
24
+ : {};
25
+ }
26
+
27
+ const response = await fetch("/api", {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ ...cookieHeader
32
+ },
33
+ body: JSON.stringify([{method, args:[...args]}]),
34
+ });
35
+ const body = await response.json();
36
+
37
+ const error = body[0].error;
38
+ if (error) {
39
+ throw new Error(error);
40
+ }
41
+
42
+ const value = body[0].data;
43
+ return value;
44
+ };
45
+
46
+ function apiTree(path = []) {
47
+ return new Proxy(function() {}, {
48
+ apply(_target, _thisArg, args) {
49
+ return wirejsCallApi(path, ...args);
50
+ },
51
+ get(_target, prop) {
52
+ return apiTree([...path, prop]);
53
+ }
54
+ });
55
+ };
56
+ `);
57
+
58
+ writeFileSync('index.client.js', [baseClient, apiCode].join('\n\n'));
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ dist
3
+ temp
4
+ src/build_id.json
5
+ api/index.client.js
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "sample-app",
3
+ "version": "3.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "workspaces": [
7
+ "src",
8
+ "api"
9
+ ],
10
+ "dependencies": {
11
+ "wirejs-dom": "^1.0.34",
12
+ "wirejs-resources": "^0.1.1-alpha",
13
+ "dompurify": "^3.2.3",
14
+ "marked": "^15.0.6"
15
+ },
16
+ "devDependencies": {
17
+ "wirejs-scripts": "^3.0.0"
18
+ },
19
+ "scripts": {
20
+ "prebuild": "npm run prebuild --workspaces --if-present",
21
+ "prestart": "npm run prestart --workspaces --if-present",
22
+ "start": "wirejs-scripts start",
23
+ "build": "wirejs-scripts build"
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ "1731789421822"
@@ -0,0 +1,123 @@
1
+ import { attribute, html, node, text, id } from 'wirejs-dom/v2';
2
+ import { authenticator } from './authenticator.js';
3
+
4
+ /**
5
+ * @typedef {import('wirejs-services').AuthenticationService} AuthenticationService
6
+ * @typedef {ReturnType<AuthenticationService['buildApi']>} AuthStateApi
7
+ * @typedef {Awaited<ReturnType<AuthStateApi['getState']>>} AuthState
8
+ * @typedef {AuthState['actions'][string]} AuthStateAction
9
+ * @typedef {Parameters<AuthStateApi['setState']>[0]} AuthStateActionInput
10
+ * @typedef {import('wirejs-services').Context} Context
11
+ */
12
+
13
+ /**
14
+ * @param {AuthStateApi} api
15
+ */
16
+ export const accountMenu = (api) => {
17
+ const uiState = {
18
+ expanded: false
19
+ };
20
+
21
+ /**
22
+ * @type {Set<(state: AuthState) => any>}
23
+ */
24
+ const listeners = new Set();
25
+
26
+ const listenForClose = event => {
27
+ if (
28
+ (event.type === 'click' && !self.data.menu.contains(event.target))
29
+ || (event.type === 'keyup' && event.key === 'Escape')
30
+ ) {
31
+ close()
32
+ }
33
+ };
34
+
35
+ const close = () => {
36
+ uiState.expanded = false;
37
+ updateStyleToMatchState();
38
+ document.removeEventListener('click', listenForClose);
39
+ document.removeEventListener('keyup', listenForClose);
40
+ };
41
+
42
+ const removeListenForClose = () => {
43
+ document.removeEventListener('click', listenForClose);
44
+ document.removeEventListener('keyup', listenForClose);
45
+ };
46
+
47
+ const updateStyleToMatchState = () => {
48
+ self.data.menu.style.display = uiState.expanded ? '' : 'none';
49
+ const position = self.getBoundingClientRect();
50
+ self.data.menu.style.top = `${position.bottom + 1}px`;
51
+ self.data.menu.style.right = `${document.body.clientWidth - position.right + 16}px`;
52
+ };
53
+
54
+ const authenticatorNode = authenticator(api);
55
+ authenticatorNode.data.onchange(state => {
56
+ self.data.user = state.state.user || '';
57
+ close();
58
+ for (const listener of listeners) {
59
+ try {
60
+ listener(state);
61
+ } catch (error) {
62
+ console.error("Error calling auth state listener.");
63
+ }
64
+ }
65
+ });
66
+
67
+ const self = html`<accountmenu style='display: inline-block;'>
68
+ <div
69
+ style='display: inline-block;'
70
+ >${node('user', name => name ? html`<b>${name}</b>` : html`<i>Anonymous</i>`)}</div>
71
+ <div style='
72
+ display: inline-block;
73
+ border: 1px solid silver;
74
+ border-radius: 0.25rem;
75
+ cursor: pointer;
76
+ padding: 0 0.25em;
77
+ '
78
+ onclick=${() => {
79
+ uiState.expanded = !uiState.expanded;
80
+ updateStyleToMatchState();
81
+ if (uiState.expanded) {
82
+ authenticatorNode.data.focus();
83
+ setTimeout(() => {
84
+ document.addEventListener('click', listenForClose);
85
+ document.addEventListener('keyup', listenForClose);
86
+ }, 1);
87
+ } else {
88
+ removeListenForClose()
89
+ }
90
+ }}
91
+ >☰</div>
92
+ <div ${id('menu')} style='
93
+ display: none;
94
+ position: absolute;
95
+ border: 1px solid gray;
96
+ border-radius: 0.25rem;
97
+ background-color: white;
98
+ padding: 0.5rem;
99
+ box-shadow: -0.125rem 0.125rem 0.25rem lightgray;
100
+ '>${node('authenticator', authenticatorNode)}</div>
101
+ </accountmenu>`.onadd(async self => {
102
+ const state = await api.getState(true);
103
+ self.data.user = state.state.user || '';
104
+ }).extend(self => ({
105
+ data: {
106
+ /**
107
+ * @param {(state: AuthState) => any} callback
108
+ */
109
+ onchange: (callback) => {
110
+ listeners.add(callback);
111
+ },
112
+
113
+ /**
114
+ * @param {(state: AuthState) => any} callback
115
+ */
116
+ removeonchange: (callback) => {
117
+ listeners.delete(callback);
118
+ },
119
+ }
120
+ }));
121
+
122
+ return self;
123
+ };
@@ -0,0 +1,149 @@
1
+ import { attribute, html, node } from 'wirejs-dom/v2';
2
+
3
+ /**
4
+ * @typedef {import('wirejs-services').AuthenticationService} AuthenticationService
5
+ * @typedef {ReturnType<AuthenticationService['buildApi']>} AuthStateApi
6
+ * @typedef {Awaited<ReturnType<AuthStateApi['getState']>>} AuthState
7
+ * @typedef {AuthState['actions'][string]} AuthStateAction
8
+ * @typedef {Parameters<AuthStateApi['setState']>[0]} AuthStateActionInput
9
+ */
10
+
11
+ /**
12
+ * @param {AuthStateAction} action
13
+ * @param {(act: AuthStateActionInput) => void} act
14
+ */
15
+ export const authenticatoraction = (action, act) => {
16
+ const inputs = Object.entries(action.inputs || []).map(([name, { label, type }]) => {
17
+ const id = `input_${Math.floor(Math.random() * 1_000_000)}`;
18
+ const input = html`<div>
19
+ <label for=${id}>${label}</label>
20
+ <br />
21
+ <input
22
+ id=${id}
23
+ name=${name}
24
+ type=${type}
25
+ value=${attribute('value', '')}
26
+ style='width: calc(100% - 1rem); margin-bottom: 0.5rem;'
27
+ />
28
+ </div>`.extend(self => ({
29
+ data: { name }
30
+ }));
31
+ return input;
32
+ });
33
+
34
+ const buttons = action.buttons?.map(b => html`<p>
35
+ <button type='submit' value='${b}'>${b}</button>
36
+ </p>`);
37
+
38
+ const link = buttons ? undefined : [
39
+ html`<p><a
40
+ style='cursor: pointer; font-weight: bold;'
41
+ onclick=${() => act({ key: action.key })}
42
+ >${action.name}</a></p>`
43
+ ];
44
+
45
+ const actors = link ?? buttons;
46
+
47
+ if (action.inputs && Object.keys(action.inputs).length > 0) {
48
+ return html`<authenticatoraction>
49
+ <div>
50
+ <h4 style='margin-top: 1rem; margin-bottom: 0.5rem;'>${action.name}</h4>
51
+ <form
52
+ onsubmit=${evt => {
53
+ evt.preventDefault();
54
+ act({
55
+ key: action.key,
56
+ verb: evt.submitter?.value,
57
+ inputs: Object.fromEntries(inputs.map(input => ([
58
+ input.data.name,
59
+ input.data.value
60
+ ])))
61
+ });
62
+ }}
63
+ >
64
+ ${inputs}
65
+ ${actors}
66
+ </form>
67
+ <hr style='width: 33%; height: 1px; border: none; background: silver;' />
68
+ </div>
69
+ </authenticatoraction>`;
70
+ } else {
71
+ return html`<authenticatoraction>
72
+ ${actors}
73
+ </authenticatoraction>`;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @param {AuthStateApi} stateManager
79
+ * @returns
80
+ */
81
+ export const authenticator = (stateManager) => {
82
+ /**
83
+ * @type {Set<(state: AuthState) => any>}
84
+ */
85
+ const listeners = new Set();
86
+
87
+ /**
88
+ * @type {AuthState}
89
+ */
90
+ let lastKnownState = undefined;
91
+
92
+ const self = html`<authenticator style='display: block; min-width: 15em;'>
93
+ ${node('state', html`<span>Loading ...</span>`)}
94
+ </authenticator>`.extend(self => ({
95
+ /**
96
+ * @param {AuthState} state
97
+ */
98
+ renderState(state) {
99
+ lastKnownState = state;
100
+ if (state.errors) {
101
+ alert(state.errors.map(e => e.message).join("\n\n"));
102
+ } else {
103
+ self.data.state = html`<div>
104
+ <div>${state.message || ''}</div>
105
+ <div>${Object.entries(state.actions).map(([key, action]) => {
106
+ return authenticatoraction({key, ...action}, async act => {
107
+ self.renderState(await stateManager.setState(true, act));
108
+ });
109
+ })}</div>
110
+ </div>`;
111
+ }
112
+ for (const listener of listeners) {
113
+ try {
114
+ listener(state);
115
+ } catch (error) {
116
+ console.error("Error calling auth state listener.");
117
+ }
118
+ }
119
+ }
120
+ })).onadd(async (self) => {
121
+ self.renderState(await stateManager.getState(true))
122
+ }).extend(self => ({
123
+ data: {
124
+ /**
125
+ * @param {(state: AuthState) => any} callback
126
+ */
127
+ onchange: (callback) => {
128
+ listeners.add(callback);
129
+ },
130
+
131
+ /**
132
+ * @param {(state: AuthState) => any} callback
133
+ */
134
+ removeonchange: (callback) => {
135
+ listeners.delete(callback);
136
+ },
137
+
138
+ focus: () => {
139
+ [...self.getElementsByTagName('input')].shift()?.focus();
140
+ },
141
+
142
+ get lastKnownState() {
143
+ return lastKnownState;
144
+ }
145
+ }
146
+ }));
147
+
148
+ return self;
149
+ };
@@ -0,0 +1,36 @@
1
+ import { hello } from 'my-api';
2
+ import { html, text, node } from 'wirejs-dom/v2';
3
+
4
+ /**
5
+ * Counts down from a given time.
6
+ *
7
+ * @param {number} from - The time to count down "from".
8
+ * @returns
9
+ */
10
+ export async function Countdown(T = 10) {
11
+ return html`<div>
12
+ ${node('remaining', T, timeOrGreeting => {
13
+ if (typeof timeOrGreeting === 'string') {
14
+ return html`<div>${timeOrGreeting}</div>`;
15
+ } else if (timeOrGreeting === 0) {
16
+ return html`<div><b>ALL DONE!</b></div>`;
17
+ } else if (timeOrGreeting === 1) {
18
+ return html`<div><i>ONE second left!!!</i></div>`;
19
+ } else {
20
+ return html`<div>${timeOrGreeting} seconds remaining ...</div>`;
21
+ }
22
+ })}
23
+ </div>`.onadd(self => {
24
+ function tick() {
25
+ self.data.remaining = self.data.remaining - 1;
26
+ if (self.data.remaining > 0) {
27
+ setTimeout(() => tick(), 1000);
28
+ } else {
29
+ hello("So and so").then(r => self.data.remaining = r);
30
+ }
31
+ };
32
+ tick();
33
+ });
34
+ };
35
+
36
+ export default Countdown;
@@ -0,0 +1,3 @@
1
+ // common components
2
+ import Countdown from '../components/countdown.js';
3
+
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Just a small function to demonstrate that API's can `require()`
3
+ * things which get included and bundled correctly.
4
+ * @returns The string "Hello"
5
+ */
6
+ export function defaultGreeting() {
7
+ return "Hello"
8
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "src",
3
+ "version": "2.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "dependencies": {
7
+ "wirejs-dom": "*",
8
+ "my-api": "*"
9
+ }
10
+ }
@@ -0,0 +1,27 @@
1
+ import { html, hydrate } from 'wirejs-dom/v2';
2
+
3
+ async function App() {
4
+ return html`<div id='app'>
5
+ </div>`;
6
+ }
7
+
8
+ export async function generate() {
9
+ const page = html`
10
+ <!doctype html>
11
+ <html>
12
+ <head>
13
+ <title>Welcome!</title>
14
+ </head>
15
+ <body>
16
+ <h1>Welcome!</h1>
17
+ <p>This is your wirejs app!</p>
18
+ <p>It comes with some sample API methods and pages.</p>
19
+ <ul>
20
+ <li><a href='/todo-app.html'>Todo App</a></li>
21
+ <li><a href='/simple-wiki/index.html'>Simple Wiki</a></li>
22
+ </ul>
23
+ </body>
24
+ </html>
25
+ `;
26
+ return page;
27
+ }
@@ -0,0 +1,83 @@
1
+ import { html, node, list, attribute, hydrate } from 'wirejs-dom/v2';
2
+ import { accountMenu } from '../components/account-menu.js';
3
+ import { auth, todos } from 'my-api';
4
+
5
+ function Todos() {
6
+ const save = async () => {
7
+ try {
8
+ await todos.write(true, self.data.todos);
9
+ } catch (error) {
10
+ alert(error);
11
+ }
12
+ }
13
+
14
+ const remove = todo => {
15
+ self.data.todos = self.data.todos.filter(t => t.id !== todo.id);
16
+ save();
17
+ }
18
+
19
+ const newid = () => crypto.randomUUID();
20
+
21
+ const self = html`<div>
22
+ <h4>Your Todos</h4>
23
+ <ol>${list('todos', todo => html`<li>
24
+ ${todo.text} : <span
25
+ style='color: darkred; font-weight: bold; cursor: pointer;'
26
+ onclick=${() => remove(todo)}
27
+ >X</span>
28
+ </li>`)}</ol>
29
+ <div>
30
+ <form onsubmit=${event => {
31
+ event.preventDefault();
32
+ self.data.todos.push({ id: newid(), text: self.data.newTodo });
33
+ self.data.newTodo = '';
34
+ save();
35
+ }}>
36
+ <input type='text' value=${attribute('newTodo', '')} />
37
+ <input type='submit' value='Add' />
38
+ </form>
39
+ </div>
40
+ <div>`.onadd(async self => {
41
+ self.data.todos = await todos.read(true);
42
+ });
43
+ return self;
44
+ }
45
+
46
+ async function App() {
47
+ const accountMenuNode = accountMenu(auth);
48
+
49
+ accountMenuNode.data.onchange(async state => {
50
+ if (state.state.user) {
51
+ self.data.content = Todos();
52
+ } else {
53
+ self.data.content = html`<div>You need to sign in to add your todo list.</div>`;
54
+ }
55
+ });
56
+
57
+ const self = html`<div id='app'>
58
+ <div style='float: right;'>${accountMenuNode}</div>
59
+ ${node('content', html`<div>Loading ...</div>`)}
60
+ </div>`;
61
+
62
+ return self;
63
+ }
64
+
65
+ export async function generate() {
66
+ const page = html`
67
+ <!doctype html>
68
+ <html>
69
+ <head>
70
+ <title>Todo App</title>
71
+ </head>
72
+ <body>
73
+ <p><a href='/'>Home</a></p>
74
+ <h1>Todo App</h1>
75
+ ${await App()}
76
+ </body>
77
+ </html>
78
+ `;
79
+
80
+ return page;
81
+ }
82
+
83
+ hydrate('app', App);
@@ -1,4 +0,0 @@
1
- node_modules
2
- dist
3
- api
4
- src/build_id.json
@@ -1,16 +0,0 @@
1
- {
2
- "name": "project-name",
3
- "version": "2.0.0",
4
- "private": true,
5
- "dependencies": {
6
- "highlight.js": "^11.5.1",
7
- "wirejs-dom": "^1.0.7"
8
- },
9
- "devDependencies": {
10
- "wirejs-scripts": "^2.0.0"
11
- },
12
- "scripts": {
13
- "start": "wirejs-scripts start",
14
- "build": "wirejs-scripts build"
15
- }
16
- }
@@ -1,10 +0,0 @@
1
- const { defaultGreeting } = require('../lib/sample-lib');
2
-
3
- module.exports = {
4
- /**
5
- * Given a name, this will return a friendly, personalized greeting.
6
- * @param {string} name
7
- * @returns {string} A friendly greeting.
8
- */
9
- hello: async (name) => `${defaultGreeting()}, ${name}.`
10
- };
@@ -1,76 +0,0 @@
1
- // included from local template during development.
2
- // long term, maybe this goes into the wirejs-scripts repo, or possibly as a
3
- // dep thereof. but, the collection class provided may eventually depend on
4
- // which deployment target is specified.
5
- const { Collection } = require('../api-lib/collection');
6
-
7
- /**
8
- * Simple API example demonstrating interaction with a collection without auth.
9
- *
10
- * The `Note` class used shows how you might perform simple validation on data
11
- * as it enters the API boundary.
12
- */
13
-
14
-
15
- /**
16
- * A simple sample class we'll be saving and loading from the API.
17
- *
18
- * We could just as easily *not* use a class at all. (Maybe that would actually
19
- * be better for this example ...)
20
- */
21
- class Note {
22
- /**
23
- * Construct a new note with an id (optional), title, and body.
24
- *
25
- * If an id is not given, one will be generated.
26
- */
27
- constructor({id, title, body}) {
28
- this.id = id || this.makeId();
29
- this.title = title;
30
- this.body = body;
31
- this.validate();
32
- }
33
-
34
- /**
35
- * Semi-random, semi-increasing string. Date + rand.
36
- */
37
- makeId() {
38
- return Date.now() + '-' + String(Math.random()).substring(2, 10);
39
- }
40
-
41
- /**
42
- * Throw an error if the note doesn't contain all required fields with the
43
- * required constraints. In this case, id, title, and body must all be
44
- * "truthy" values.
45
- */
46
- validate() {
47
- if (!(this.id && this.title && this.body)) {
48
- throw new Error(
49
- "A note must contain a truthy id, title, and body!"
50
- );
51
- }
52
- }
53
- }
54
-
55
- /**
56
- * The actual API.
57
- */
58
- module.exports = {
59
- /**
60
- * Adds a new note.
61
- */
62
- add: async (note) => {
63
- },
64
-
65
- /*
66
- *
67
- */
68
- remove: async (id) => {
69
- },
70
-
71
- /*
72
- *
73
- */
74
- list: async () => {
75
- }
76
- };
@@ -1 +0,0 @@
1
- "1677445565514"
@@ -1,33 +0,0 @@
1
- const { DomClass } = require('wirejs-dom');
2
-
3
- const markup = `<sample:countdown>
4
- <h3>Limited time offer!</h3>
5
- <div data-id='countdown'>
6
- <b>
7
- <span data-id='remaining'></span>
8
- <span data-id='label'>seconds</span>
9
- </b> left!
10
- </div>
11
- </sample:countdown>`;
12
-
13
- const Countdown = DomClass(markup, function() {
14
- this.remaining = this.from || 60;
15
-
16
- const tick = () => {
17
- this.remaining = this.remaining - 1;
18
-
19
- if (this.remaining == 1) {
20
- this.label = 'second';
21
- } else if (this.remaining == 0) {
22
- this.countdown = '<b>ALL DONE! <i>You missed it!!!</i></b>';
23
- }
24
-
25
- if (this.remaining > 0) {
26
- setTimeout(() => tick(), 1000);
27
- }
28
- };
29
-
30
- tick();
31
- });
32
-
33
- module.exports = Countdown;
@@ -1,10 +0,0 @@
1
- // core deps
2
- const wirejs = require('wirejs-dom');
3
- require('highlight.js/styles/github.css');
4
- require('./default.css');
5
-
6
- // expose wirejs to inline scripts
7
- Object.assign(window, wirejs);
8
-
9
- // common components
10
- require('../components/countdown');
@@ -1,8 +0,0 @@
1
- module.exports = {
2
- /**
3
- * Just a small function to demonstrate that API's can `require()`
4
- * things which get included and bundled correctly.
5
- * @returns The string "Hello"
6
- */
7
- defaultGreeting: () => "Hello"
8
- }
@@ -1,8 +0,0 @@
1
- ${meta({
2
- title: "an html page"
3
- })}
4
- <p><code>html</code> pages use the default template by default too.</p>
5
- <p>And, it's important to remember that pages are parsed like
6
- <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals' target='_blank'>template literals</a>
7
- <b>at build time</b>.
8
- </p>
@@ -1,30 +0,0 @@
1
- ${meta({
2
- title: "First Page"
3
- })}
4
-
5
- # markdown!
6
-
7
- If all you need is markdown, you can do that!
8
-
9
- We can even do code blocks with syntax highlighting.
10
-
11
- ```js
12
- const result = getResult();
13
- ```
14
-
15
- Or mermaid diagrams:
16
-
17
- ```mermaid
18
- graph LR;
19
- x --> y(probably y)
20
- y --> z(definitely z)
21
- ```
22
-
23
- Of course, you can also build [normal html pages](html.html).
24
-
25
- And of course, you can also embed HTML with custom components directly in your
26
- markdown:
27
-
28
- <div>
29
- <sample:countdown from=10></sample:countdown>
30
- </div>