retold 4.0.1 → 4.0.2

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 (75) hide show
  1. package/.claude/settings.local.json +27 -1
  2. package/docs/README.md +7 -6
  3. package/docs/_sidebar.md +34 -21
  4. package/docs/_topbar.md +2 -2
  5. package/docs/architecture/module-architecture.md +234 -0
  6. package/docs/{modules.md → architecture/modules.md} +25 -22
  7. package/docs/cover.md +2 -2
  8. package/docs/css/docuserve.css +6 -6
  9. package/docs/examples/examples.md +71 -0
  10. package/docs/examples/todolist/todo-list-cli-client.md +178 -0
  11. package/docs/examples/todolist/todo-list-console-client.md +152 -0
  12. package/docs/examples/todolist/todo-list-model.md +114 -0
  13. package/docs/examples/todolist/todo-list-server.md +128 -0
  14. package/docs/examples/todolist/todo-list-web-client.md +177 -0
  15. package/docs/examples/todolist/todo-list.md +162 -0
  16. package/docs/getting-started.md +8 -7
  17. package/docs/index.html +4 -4
  18. package/docs/{meadow.md → modules/meadow.md} +4 -6
  19. package/docs/{orator.md → modules/orator.md} +1 -0
  20. package/docs/{pict.md → modules/pict.md} +30 -8
  21. package/docs/{utility.md → modules/utility.md} +0 -9
  22. package/docs/retold-catalog.json +896 -2317
  23. package/docs/retold-keyword-index.json +162327 -120227
  24. package/examples/todo-list/Dockerfile +45 -0
  25. package/examples/todo-list/README.md +394 -0
  26. package/examples/todo-list/cli-client/package-lock.json +418 -0
  27. package/examples/todo-list/cli-client/package.json +19 -0
  28. package/examples/todo-list/cli-client/source/TodoCLI-CLIProgram.js +30 -0
  29. package/examples/todo-list/cli-client/source/TodoCLI-Run.js +3 -0
  30. package/examples/todo-list/cli-client/source/commands/add/TodoCLI-Command-Add.js +74 -0
  31. package/examples/todo-list/cli-client/source/commands/complete/TodoCLI-Command-Complete.js +84 -0
  32. package/examples/todo-list/cli-client/source/commands/list/TodoCLI-Command-List.js +110 -0
  33. package/examples/todo-list/cli-client/source/commands/remove/TodoCLI-Command-Remove.js +49 -0
  34. package/examples/todo-list/cli-client/source/services/TodoCLI-Service-API.js +92 -0
  35. package/examples/todo-list/console-client/console-client.cjs +913 -0
  36. package/examples/todo-list/console-client/package-lock.json +426 -0
  37. package/examples/todo-list/console-client/package.json +19 -0
  38. package/examples/todo-list/console-client/views/PictView-TUI-Header.cjs +43 -0
  39. package/examples/todo-list/console-client/views/PictView-TUI-Layout.cjs +58 -0
  40. package/examples/todo-list/console-client/views/PictView-TUI-StatusBar.cjs +41 -0
  41. package/examples/todo-list/console-client/views/PictView-TUI-TaskList.cjs +104 -0
  42. package/examples/todo-list/docker-motd.sh +36 -0
  43. package/examples/todo-list/docker-run.sh +2 -0
  44. package/examples/todo-list/docker-shell.sh +2 -0
  45. package/examples/todo-list/model/MeadowSchema-Task.json +152 -0
  46. package/examples/todo-list/model/Task-Compiled.json +25 -0
  47. package/examples/todo-list/model/Task.mddl +15 -0
  48. package/examples/todo-list/model/data/seeded_todo_events.csv +1001 -0
  49. package/examples/todo-list/server/database-initialization-service.cjs +273 -0
  50. package/examples/todo-list/server/package-lock.json +6113 -0
  51. package/examples/todo-list/server/package.json +19 -0
  52. package/examples/todo-list/server/server.cjs +138 -0
  53. package/examples/todo-list/web-client/css/todolist-theme.css +235 -0
  54. package/examples/todo-list/web-client/generate-build-config.cjs +18 -0
  55. package/examples/todo-list/web-client/html/index.html +18 -0
  56. package/examples/todo-list/web-client/package-lock.json +12030 -0
  57. package/examples/todo-list/web-client/package.json +43 -0
  58. package/examples/todo-list/web-client/source/TodoList-Application-Config.json +12 -0
  59. package/examples/todo-list/web-client/source/TodoList-Application.cjs +383 -0
  60. package/examples/todo-list/web-client/source/providers/Provider-TaskData.cjs +243 -0
  61. package/examples/todo-list/web-client/source/providers/Router-Config.json +32 -0
  62. package/examples/todo-list/web-client/source/views/View-Layout.cjs +75 -0
  63. package/examples/todo-list/web-client/source/views/View-TaskForm.cjs +87 -0
  64. package/examples/todo-list/web-client/source/views/View-TaskList.cjs +127 -0
  65. package/examples/todo-list/web-client/source/views/calendar/View-MonthView.cjs +293 -0
  66. package/examples/todo-list/web-client/source/views/calendar/View-WeekView.cjs +149 -0
  67. package/examples/todo-list/web-client/source/views/calendar/View-YearView.cjs +226 -0
  68. package/modules/Include-Retold-Module-List.sh +2 -2
  69. package/package.json +5 -5
  70. package/docs/js/pict.min.js +0 -12
  71. package/docs/js/pict.min.js.map +0 -1
  72. package/docs/pict-docuserve.min.js +0 -58
  73. package/docs/pict-docuserve.min.js.map +0 -1
  74. /package/docs/{architecture.md → architecture/architecture.md} +0 -0
  75. /package/docs/{fable.md → modules/fable.md} +0 -0
@@ -0,0 +1,75 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const _ViewConfiguration =
4
+ {
5
+ ViewIdentifier: 'TodoList-Layout',
6
+ DefaultRenderable: 'TodoList-Layout-Shell',
7
+ DefaultDestinationAddress: '#TodoList-Container',
8
+ AutoRender: false,
9
+
10
+ CSS: /*css*/`
11
+ .tl-nav { display: flex; align-items: center; justify-content: space-between; background: #3D3229; color: #E8E0D4; padding: 0 1.5em; height: 54px; }
12
+ .tl-nav-brand { font-size: 1.25em; font-weight: 700; color: #E8E0D4; cursor: pointer; text-decoration: none; }
13
+ .tl-nav-links { display: flex; align-items: center; gap: 0.5em; }
14
+ .tl-nav-links a { color: #D4CCBE; text-decoration: none; padding: 0.4em 0.75em; cursor: pointer; border-radius: 4px; transition: color 0.15s; }
15
+ .tl-nav-links a:hover { color: #fff; }
16
+ .tl-nav-sep { color: #5E5549; margin: 0 0.15em; }
17
+ `,
18
+
19
+ Templates:
20
+ [
21
+ {
22
+ Hash: 'TodoList-Layout-Template',
23
+ Template: /*html*/`
24
+ <div class="tl-nav">
25
+ <a class="tl-nav-brand" onclick="{~P~}.PictApplication.navigateTo('/TaskList')">Todo List</a>
26
+ <div class="tl-nav-links">
27
+ <a onclick="{~P~}.PictApplication.navigateTo('/TaskList')">Tasks</a>
28
+ <span class="tl-nav-sep">|</span>
29
+ <a onclick="{~P~}.PictApplication.navigateTo('/WeekView')">Week</a>
30
+ <a onclick="{~P~}.PictApplication.navigateTo('/MonthView')">Month</a>
31
+ <a onclick="{~P~}.PictApplication.navigateTo('/YearView')">Year</a>
32
+ <span class="tl-nav-sep">|</span>
33
+ <a class="tl-btn tl-btn-success" onclick="{~P~}.PictApplication.addTask()">+ New Task</a>
34
+ </div>
35
+ </div>
36
+ <div id="TodoList-Content"></div>
37
+ `
38
+ }
39
+ ],
40
+
41
+ Renderables:
42
+ [
43
+ {
44
+ RenderableHash: 'TodoList-Layout-Shell',
45
+ TemplateHash: 'TodoList-Layout-Template',
46
+ DestinationAddress: '#TodoList-Container',
47
+ RenderMethod: 'replace'
48
+ }
49
+ ]
50
+ };
51
+
52
+ class TodoListLayoutView extends libPictView
53
+ {
54
+ constructor(pFable, pOptions, pServiceHash)
55
+ {
56
+ super(pFable, pOptions, pServiceHash);
57
+ }
58
+
59
+ onAfterRender()
60
+ {
61
+ // Render the task list as the default content view
62
+ this.pict.views['TodoList-TaskList'].render();
63
+ this.pict.CSSMap.injectCSS();
64
+
65
+ if (this.pict.providers.PictRouter)
66
+ {
67
+ this.pict.providers.PictRouter.resolve();
68
+ }
69
+
70
+ return super.onAfterRender();
71
+ }
72
+ }
73
+
74
+ module.exports = TodoListLayoutView;
75
+ module.exports.default_configuration = _ViewConfiguration;
@@ -0,0 +1,87 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const _ViewConfiguration =
4
+ {
5
+ ViewIdentifier: 'TodoList-TaskForm',
6
+ DefaultRenderable: 'TodoList-TaskForm-Content',
7
+ DefaultDestinationAddress: '#TodoList-Content',
8
+ DefaultTemplateRecordAddress: 'AppData.TodoList',
9
+ AutoRender: false,
10
+
11
+ Templates:
12
+ [
13
+ {
14
+ Hash: 'TodoList-TaskForm-Template',
15
+ Template: /*html*/`
16
+ <h2 class="tl-heading">{~D:Record.FormTitle~}</h2>
17
+ <div class="tl-form">
18
+ <div class="tl-form-group">
19
+ <label for="taskName">Name</label>
20
+ <input type="text" id="taskName" value="{~D:Record.SelectedTask.Name~}" placeholder="Task name" />
21
+ </div>
22
+ <div class="tl-form-group">
23
+ <label for="taskDescription">Description</label>
24
+ <textarea id="taskDescription" rows="3" placeholder="Task description">{~D:Record.SelectedTask.Description~}</textarea>
25
+ </div>
26
+ <div class="tl-form-group">
27
+ <label for="taskDueDate">Due Date</label>
28
+ <input type="date" id="taskDueDate" value="{~D:Record.SelectedTask.DueDate~}" />
29
+ </div>
30
+ <div class="tl-form-group">
31
+ <label for="taskHours">Estimated Hours</label>
32
+ <input type="number" id="taskHours" step="0.5" min="0" value="{~D:Record.SelectedTask.LengthInHours~}" />
33
+ </div>
34
+ <div class="tl-form-group">
35
+ <label for="taskStatus">Status</label>
36
+ <select id="taskStatus">
37
+ <option value="Pending">Pending</option>
38
+ <option value="In Progress">In Progress</option>
39
+ <option value="Complete">Complete</option>
40
+ </select>
41
+ </div>
42
+ <div class="tl-form-actions">
43
+ <button class="tl-btn tl-btn-primary" onclick="{~P~}.PictApplication.saveTask()">Save</button>
44
+ <button class="tl-btn tl-btn-default" onclick="{~P~}.PictApplication.navigateTo('/TaskList')">Cancel</button>
45
+ </div>
46
+ </div>
47
+ `
48
+ }
49
+ ],
50
+
51
+ Renderables:
52
+ [
53
+ {
54
+ RenderableHash: 'TodoList-TaskForm-Content',
55
+ TemplateHash: 'TodoList-TaskForm-Template',
56
+ DestinationAddress: '#TodoList-Content',
57
+ RenderMethod: 'replace'
58
+ }
59
+ ]
60
+ };
61
+
62
+ class TodoListTaskFormView extends libPictView
63
+ {
64
+ constructor(pFable, pOptions, pServiceHash)
65
+ {
66
+ super(pFable, pOptions, pServiceHash);
67
+ }
68
+
69
+ onAfterRender()
70
+ {
71
+ // Set the select element to match the current task status
72
+ let tmpSelectedTask = this.pict.AppData.TodoList.SelectedTask;
73
+ if (tmpSelectedTask && tmpSelectedTask.Status)
74
+ {
75
+ let tmpSelect = document.getElementById('taskStatus');
76
+ if (tmpSelect)
77
+ {
78
+ tmpSelect.value = tmpSelectedTask.Status;
79
+ }
80
+ }
81
+
82
+ return super.onAfterRender();
83
+ }
84
+ }
85
+
86
+ module.exports = TodoListTaskFormView;
87
+ module.exports.default_configuration = _ViewConfiguration;
@@ -0,0 +1,127 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const _ViewConfiguration =
4
+ {
5
+ ViewIdentifier: 'TodoList-TaskList',
6
+ DefaultRenderable: 'TodoList-TaskList-Content',
7
+ DefaultDestinationAddress: '#TodoList-Content',
8
+ AutoRender: false,
9
+
10
+ CSS: /*css*/`
11
+ .tl-toolbar .tl-btn { font-size: 0.8em; padding: 0.35em 0.65em; }
12
+ .tl-actions { white-space: nowrap; }
13
+ .tl-actions .tl-btn { margin-right: 0.35em; font-size: 0.8em; padding: 0.3em 0.7em; }
14
+ `,
15
+
16
+ Templates:
17
+ [
18
+ {
19
+ Hash: 'TodoList-TaskList-Template',
20
+ Template: /*html*/`
21
+ <h2 class="tl-heading">Tasks</h2>
22
+ {~T:TodoList-TaskList-Toolbar~}
23
+ <table class="tl-table">
24
+ <thead>
25
+ <tr>
26
+ <th>Name</th>
27
+ <th>Due Date</th>
28
+ <th>Hours</th>
29
+ <th>Status</th>
30
+ <th>Actions</th>
31
+ </tr>
32
+ </thead>
33
+ <tbody>
34
+ {~TemplateSet:TodoList-TaskRow:AppData.TodoList.Tasks~}
35
+ </tbody>
36
+ </table>
37
+ `
38
+ },
39
+ {
40
+ Hash: 'TodoList-TaskList-Toolbar',
41
+ Template: /*html*/`
42
+ <div class="tl-toolbar">
43
+ <div class="tl-toolbar-group">
44
+ <input type="text" id="tl-search-input" placeholder="Search name or description…" onkeydown="if(event.key==='Enter'){~P~}.PictApplication.searchTasks()" />
45
+ <button class="tl-btn tl-btn-primary" onclick="{~P~}.PictApplication.searchTasks()">Search</button>
46
+ <button class="tl-btn tl-btn-default" onclick="{~P~}.PictApplication.clearSearch()">Clear</button>
47
+ </div>
48
+ <div class="tl-toolbar-group">
49
+ <span class="tl-toolbar-label">Sort by</span>
50
+ <select id="tl-sort-order" onchange="{~P~}.PictApplication.changeSortOrder(this.value)">
51
+ <option value="DueDate~DESC">Due Date (newest first)</option>
52
+ <option value="DueDate~ASC">Due Date (oldest first)</option>
53
+ <option value="Name~ASC">Name (A–Z)</option>
54
+ <option value="Name~DESC">Name (Z–A)</option>
55
+ <option value="Status~ASC">Status (A–Z)</option>
56
+ <option value="Status~DESC">Status (Z–A)</option>
57
+ <option value="LengthInHours~DESC">Hours (most first)</option>
58
+ <option value="LengthInHours~ASC">Hours (least first)</option>
59
+ <option value="IDTask~DESC">Recently added</option>
60
+ <option value="IDTask~ASC">Oldest added</option>
61
+ </select>
62
+ </div>
63
+ <div class="tl-toolbar-group">
64
+ <span class="tl-record-count">{~D:AppData.TodoList.Tasks.length~} of {~D:AppData.TodoList.FilteredCount~} records</span>
65
+ </div>
66
+ </div>
67
+ `
68
+ },
69
+ {
70
+ Hash: 'TodoList-TaskRow',
71
+ Template: /*html*/`
72
+ <tr>
73
+ <td>{~D:Record.Name~}</td>
74
+ <td>{~D:Record.DueDate~}</td>
75
+ <td>{~D:Record.LengthInHours~}</td>
76
+ <td><span class="tl-status">{~D:Record.Status~}</span></td>
77
+ <td class="tl-actions">
78
+ <button class="tl-btn tl-btn-primary" onclick="{~P~}.PictApplication.editTask({~D:Record.IDTask~})">Edit</button>
79
+ <button class="tl-btn tl-btn-danger" onclick="{~P~}.PictApplication.deleteTask({~D:Record.IDTask~})">Delete</button>
80
+ </td>
81
+ </tr>
82
+ `
83
+ }
84
+ ],
85
+
86
+ Renderables:
87
+ [
88
+ {
89
+ RenderableHash: 'TodoList-TaskList-Content',
90
+ TemplateHash: 'TodoList-TaskList-Template',
91
+ DestinationAddress: '#TodoList-Content',
92
+ RenderMethod: 'replace'
93
+ }
94
+ ]
95
+ };
96
+
97
+ class TodoListTaskListView extends libPictView
98
+ {
99
+ constructor(pFable, pOptions, pServiceHash)
100
+ {
101
+ super(pFable, pOptions, pServiceHash);
102
+ }
103
+
104
+ onAfterRender()
105
+ {
106
+ let tmpListState = this.pict.AppData.TodoList.ListState;
107
+
108
+ // Restore the sort dropdown to reflect the current ListState
109
+ let tmpSortSelect = document.getElementById('tl-sort-order');
110
+ if (tmpSortSelect && tmpListState)
111
+ {
112
+ tmpSortSelect.value = tmpListState.SortColumn + '~' + tmpListState.SortDirection;
113
+ }
114
+
115
+ // Restore the search input text so it survives re-renders
116
+ let tmpSearchInput = document.getElementById('tl-search-input');
117
+ if (tmpSearchInput && tmpListState)
118
+ {
119
+ tmpSearchInput.value = tmpListState.SearchText || '';
120
+ }
121
+
122
+ return super.onAfterRender();
123
+ }
124
+ }
125
+
126
+ module.exports = TodoListTaskListView;
127
+ module.exports.default_configuration = _ViewConfiguration;
@@ -0,0 +1,293 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ const _ViewConfiguration =
4
+ {
5
+ ViewIdentifier: 'TodoList-MonthView',
6
+ DefaultRenderable: 'TodoList-MonthView-Content',
7
+ DefaultDestinationAddress: '#TodoList-Content',
8
+ AutoRender: false,
9
+
10
+ CSS: /*css*/`
11
+ .tl-month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; background: #DDD6CA; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
12
+ .tl-month-grid-header { background: #3D3229; color: #E8E0D4; text-align: center; padding: 0.6em 0.25em; font-weight: 600; font-size: 0.85em; }
13
+ .tl-month-cell { background: #fff; min-height: 5em; padding: 0.4em; display: flex; flex-direction: column; }
14
+ .tl-month-cell-outside { background: #EAE3D8; color: #B5ADA2; }
15
+ .tl-month-cell-today { background: #E0EDEB; }
16
+ .tl-month-day-num { font-size: 0.85em; font-weight: 600; margin-bottom: 0.3em; }
17
+ .tl-month-counts { display: flex; gap: 0.3em; flex-wrap: wrap; margin-top: auto; }
18
+ .tl-month-counts .tl-cal-badge { font-size: 0.75em; }
19
+ `,
20
+
21
+ Templates:
22
+ [
23
+ {
24
+ Hash: 'TodoList-MonthView-Template',
25
+ Template: /*html*/`
26
+ <div class="tl-cal-header">
27
+ <h2>Month View</h2>
28
+ <div class="tl-cal-nav">
29
+ <button class="tl-btn tl-btn-default" onclick="{~P~}.PictApplication.calendarNavigate('month', -1)">&larr; Prev</button>
30
+ <button class="tl-btn tl-btn-primary" onclick="{~P~}.PictApplication.calendarToday('month')">Today</button>
31
+ <button class="tl-btn tl-btn-default" onclick="{~P~}.PictApplication.calendarNavigate('month', 1)">Next &rarr;</button>
32
+ </div>
33
+ </div>
34
+ <p class="tl-cal-label">{~D:AppData.TodoList.CalendarState.MonthLabel~}</p>
35
+ <div id="TodoList-MonthGrid"></div>
36
+ <table class="tl-cal-table" style="margin-top:1.25em;">
37
+ <thead>
38
+ <tr>
39
+ <th>Week</th>
40
+ <th>Completed</th>
41
+ <th>Open</th>
42
+ <th>Total</th>
43
+ </tr>
44
+ </thead>
45
+ <tbody>
46
+ {~TemplateSet:TodoList-MonthWeekRow:AppData.TodoList.CalendarState.MonthRows~}
47
+ </tbody>
48
+ </table>
49
+ `
50
+ },
51
+ {
52
+ Hash: 'TodoList-MonthWeekRow',
53
+ Template: /*html*/`
54
+ <tr class="{~D:Record.TotalClass~}">
55
+ <td>{~D:Record.WeekLabel~}</td>
56
+ <td><span class="tl-cal-badge {~D:Record.CompleteBadge~}">{~D:Record.Complete~}</span></td>
57
+ <td><span class="tl-cal-badge {~D:Record.OpenBadge~}">{~D:Record.Open~}</span></td>
58
+ <td><span class="tl-cal-badge {~D:Record.TotalBadge~}">{~D:Record.Total~}</span></td>
59
+ </tr>
60
+ `
61
+ }
62
+ ],
63
+
64
+ Renderables:
65
+ [
66
+ {
67
+ RenderableHash: 'TodoList-MonthView-Content',
68
+ TemplateHash: 'TodoList-MonthView-Template',
69
+ DestinationAddress: '#TodoList-Content',
70
+ RenderMethod: 'replace'
71
+ }
72
+ ]
73
+ };
74
+
75
+ const _MonthNamesFull = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
76
+ const _DayAbbr = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
77
+ const _MonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
78
+
79
+ class TodoListMonthView extends libPictView
80
+ {
81
+ constructor(pFable, pOptions, pServiceHash)
82
+ {
83
+ super(pFable, pOptions, pServiceHash);
84
+ }
85
+
86
+ /**
87
+ * Build the task-count lookup and summary rows before the template renders.
88
+ */
89
+ onBeforeRender()
90
+ {
91
+ let tmpCal = this.pict.AppData.TodoList.CalendarState;
92
+ let tmpAnchor = new Date(tmpCal.AnchorDate + 'T00:00:00');
93
+ let tmpYear = tmpAnchor.getFullYear();
94
+ let tmpMonth = tmpAnchor.getMonth();
95
+
96
+ tmpCal.MonthLabel = _MonthNamesFull[tmpMonth] + ' ' + tmpYear;
97
+
98
+ // Build a date->counts map from all tasks
99
+ let tmpAllTasks = this.pict.AppData.TodoList.AllTasks;
100
+ let tmpTaskMap = {};
101
+ for (let i = 0; i < tmpAllTasks.length; i++)
102
+ {
103
+ let tmpDate = (tmpAllTasks[i].DueDate || '').substring(0, 10);
104
+ if (!tmpDate)
105
+ {
106
+ continue;
107
+ }
108
+ if (!tmpTaskMap[tmpDate])
109
+ {
110
+ tmpTaskMap[tmpDate] = { complete: 0, open: 0 };
111
+ }
112
+ if (tmpAllTasks[i].Status === 'Complete')
113
+ {
114
+ tmpTaskMap[tmpDate].complete++;
115
+ }
116
+ else
117
+ {
118
+ tmpTaskMap[tmpDate].open++;
119
+ }
120
+ }
121
+
122
+ // Store for use in onAfterRender (the grid is rendered imperatively)
123
+ this._taskMap = tmpTaskMap;
124
+ this._gridYear = tmpYear;
125
+ this._gridMonth = tmpMonth;
126
+
127
+ // Build weekly summary rows for the table
128
+ let tmpFirstDay = new Date(tmpYear, tmpMonth, 1);
129
+ let tmpLastDay = new Date(tmpYear, tmpMonth + 1, 0);
130
+
131
+ // Find the Monday on or before the 1st
132
+ let tmpStartDow = tmpFirstDay.getDay();
133
+ let tmpGridStart = new Date(tmpFirstDay);
134
+ tmpGridStart.setDate(tmpGridStart.getDate() - ((tmpStartDow + 6) % 7));
135
+
136
+ let tmpRows = [];
137
+ let tmpWeekStart = new Date(tmpGridStart);
138
+ let tmpTotalComplete = 0;
139
+ let tmpTotalOpen = 0;
140
+
141
+ while (tmpWeekStart <= tmpLastDay)
142
+ {
143
+ let tmpWeekComplete = 0;
144
+ let tmpWeekOpen = 0;
145
+
146
+ for (let d = 0; d < 7; d++)
147
+ {
148
+ let tmpDay = new Date(tmpWeekStart);
149
+ tmpDay.setDate(tmpWeekStart.getDate() + d);
150
+ let tmpDateStr = tmpDay.toISOString().substring(0, 10);
151
+
152
+ if (tmpDay.getMonth() === tmpMonth)
153
+ {
154
+ let tmpCounts = tmpTaskMap[tmpDateStr] || { complete: 0, open: 0 };
155
+ tmpWeekComplete += tmpCounts.complete;
156
+ tmpWeekOpen += tmpCounts.open;
157
+ }
158
+ }
159
+
160
+ let tmpWeekEnd = new Date(tmpWeekStart);
161
+ tmpWeekEnd.setDate(tmpWeekStart.getDate() + 6);
162
+ let tmpWeekTotal = tmpWeekComplete + tmpWeekOpen;
163
+
164
+ tmpRows.push(
165
+ {
166
+ WeekLabel: _MonthNames[tmpWeekStart.getMonth()] + ' ' + tmpWeekStart.getDate()
167
+ + ' – ' + _MonthNames[tmpWeekEnd.getMonth()] + ' ' + tmpWeekEnd.getDate(),
168
+ Complete: tmpWeekComplete,
169
+ Open: tmpWeekOpen,
170
+ Total: tmpWeekTotal,
171
+ CompleteBadge: tmpWeekComplete > 0 ? 'tl-cal-badge-complete' : 'tl-cal-badge-zero',
172
+ OpenBadge: tmpWeekOpen > 0 ? 'tl-cal-badge-open' : 'tl-cal-badge-zero',
173
+ TotalBadge: tmpWeekTotal > 0 ? 'tl-cal-badge-complete' : 'tl-cal-badge-zero',
174
+ TotalClass: ''
175
+ });
176
+
177
+ tmpTotalComplete += tmpWeekComplete;
178
+ tmpTotalOpen += tmpWeekOpen;
179
+
180
+ tmpWeekStart.setDate(tmpWeekStart.getDate() + 7);
181
+ }
182
+
183
+ // Add a totals row
184
+ let tmpGrandTotal = tmpTotalComplete + tmpTotalOpen;
185
+ tmpRows.push(
186
+ {
187
+ WeekLabel: 'Total',
188
+ Complete: tmpTotalComplete,
189
+ Open: tmpTotalOpen,
190
+ Total: tmpGrandTotal,
191
+ CompleteBadge: tmpTotalComplete > 0 ? 'tl-cal-badge-complete' : 'tl-cal-badge-zero',
192
+ OpenBadge: tmpTotalOpen > 0 ? 'tl-cal-badge-open' : 'tl-cal-badge-zero',
193
+ TotalBadge: tmpGrandTotal > 0 ? 'tl-cal-badge-complete' : 'tl-cal-badge-zero',
194
+ TotalClass: 'tl-cal-total'
195
+ });
196
+
197
+ tmpCal.MonthRows = tmpRows;
198
+
199
+ return super.onBeforeRender();
200
+ }
201
+
202
+ /**
203
+ * Render the calendar grid into the placeholder div after the template
204
+ * has been placed in the DOM.
205
+ */
206
+ onAfterRender()
207
+ {
208
+ let tmpContainer = document.getElementById('TodoList-MonthGrid');
209
+ if (!tmpContainer)
210
+ {
211
+ return super.onAfterRender();
212
+ }
213
+
214
+ let tmpYear = this._gridYear;
215
+ let tmpMonth = this._gridMonth;
216
+ let tmpTaskMap = this._taskMap;
217
+ let tmpToday = new Date().toISOString().substring(0, 10);
218
+
219
+ let tmpFirstDay = new Date(tmpYear, tmpMonth, 1);
220
+ let tmpLastDay = new Date(tmpYear, tmpMonth + 1, 0);
221
+
222
+ // Find the Monday on or before the 1st
223
+ let tmpStartDow = tmpFirstDay.getDay();
224
+ let tmpGridStart = new Date(tmpFirstDay);
225
+ tmpGridStart.setDate(tmpGridStart.getDate() - ((tmpStartDow + 6) % 7));
226
+
227
+ let tmpHTML = '<div class="tl-month-grid">';
228
+
229
+ // Day-of-week headers (Mon–Sun)
230
+ for (let h = 0; h < _DayAbbr.length; h++)
231
+ {
232
+ tmpHTML += '<div class="tl-month-grid-header">' + _DayAbbr[h] + '</div>';
233
+ }
234
+
235
+ // Calendar cells
236
+ let tmpCurrent = new Date(tmpGridStart);
237
+ let tmpSunday = new Date(tmpLastDay);
238
+ // Extend to fill the last row (go to next Sunday)
239
+ let tmpEndDow = tmpLastDay.getDay();
240
+ if (tmpEndDow !== 0)
241
+ {
242
+ tmpSunday.setDate(tmpLastDay.getDate() + (7 - tmpEndDow));
243
+ }
244
+
245
+ while (tmpCurrent <= tmpSunday)
246
+ {
247
+ let tmpDateStr = tmpCurrent.toISOString().substring(0, 10);
248
+ let tmpIsOutside = tmpCurrent.getMonth() !== tmpMonth;
249
+ let tmpIsToday = tmpDateStr === tmpToday;
250
+
251
+ let tmpCellClass = 'tl-month-cell';
252
+ if (tmpIsOutside)
253
+ {
254
+ tmpCellClass += ' tl-month-cell-outside';
255
+ }
256
+ if (tmpIsToday)
257
+ {
258
+ tmpCellClass += ' tl-month-cell-today';
259
+ }
260
+
261
+ let tmpCounts = tmpTaskMap[tmpDateStr] || { complete: 0, open: 0 };
262
+
263
+ tmpHTML += '<div class="' + tmpCellClass + '">';
264
+ tmpHTML += '<div class="tl-month-day-num">' + tmpCurrent.getDate() + '</div>';
265
+
266
+ if (!tmpIsOutside && (tmpCounts.complete > 0 || tmpCounts.open > 0))
267
+ {
268
+ tmpHTML += '<div class="tl-month-counts">';
269
+ if (tmpCounts.complete > 0)
270
+ {
271
+ tmpHTML += '<span class="tl-cal-badge tl-cal-badge-complete">' + tmpCounts.complete + '</span>';
272
+ }
273
+ if (tmpCounts.open > 0)
274
+ {
275
+ tmpHTML += '<span class="tl-cal-badge tl-cal-badge-open">' + tmpCounts.open + '</span>';
276
+ }
277
+ tmpHTML += '</div>';
278
+ }
279
+
280
+ tmpHTML += '</div>';
281
+
282
+ tmpCurrent.setDate(tmpCurrent.getDate() + 1);
283
+ }
284
+
285
+ tmpHTML += '</div>';
286
+ tmpContainer.innerHTML = tmpHTML;
287
+
288
+ return super.onAfterRender();
289
+ }
290
+ }
291
+
292
+ module.exports = TodoListMonthView;
293
+ module.exports.default_configuration = _ViewConfiguration;