taskunity 2026.1__py3-none-any.whl
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.
- taskunity/__init__.py +1 -0
- taskunity/app.py +1268 -0
- taskunity/cli.py +43 -0
- taskunity/models.py +103 -0
- taskunity/render.py +371 -0
- taskunity/static/app.css +1394 -0
- taskunity/static/chart.umd.min.js +14 -0
- taskunity/static/chartjs-adapter-date-fns.bundle.min.js +7 -0
- taskunity/static/htmx.min.js +68 -0
- taskunity/task_store.py +598 -0
- taskunity/templates/base.html +397 -0
- taskunity/templates/index.html +4 -0
- taskunity/templates/partials/board.html +24 -0
- taskunity/templates/partials/calendar.html +25 -0
- taskunity/templates/partials/main.html +283 -0
- taskunity/templates/partials/milestone_banner.html +34 -0
- taskunity/templates/partials/milestone_panel.html +222 -0
- taskunity/templates/partials/milestones.html +55 -0
- taskunity/templates/partials/projects.html +41 -0
- taskunity/templates/partials/task_list.html +52 -0
- taskunity/templates/partials/task_panel.html +310 -0
- taskunity/templates/partials/timeline.html +24 -0
- taskunity-2026.1.dist-info/METADATA +238 -0
- taskunity-2026.1.dist-info/RECORD +27 -0
- taskunity-2026.1.dist-info/WHEEL +5 -0
- taskunity-2026.1.dist-info/entry_points.txt +2 -0
- taskunity-2026.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,397 @@
|
|
|
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">
|
|
6
|
+
<title>{{ app_name }} · {{ workspace_name }}</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="/static/app.css">
|
|
11
|
+
<script src="/static/chart.umd.min.js"></script>
|
|
12
|
+
<script src="/static/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
13
|
+
<script src="/static/htmx.min.js"></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<header class="topbar">
|
|
17
|
+
<div class="brand">
|
|
18
|
+
<span class="brand-mark">{{ app_name[:1]|upper }}</span>
|
|
19
|
+
<div>
|
|
20
|
+
<h1>{{ app_name }}</h1>
|
|
21
|
+
<p>{{ workspace_name }}</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="settings-anchor">
|
|
25
|
+
<button class="settings-btn" id="settings-btn" aria-label="Settings" title="Settings">⚙</button>
|
|
26
|
+
<div class="settings-popup" id="settings-popup" hidden>
|
|
27
|
+
<div class="settings-popup-title">Theme</div>
|
|
28
|
+
<div class="theme-options">
|
|
29
|
+
<button class="theme-option" data-theme-opt="system" title="Follow system preference">
|
|
30
|
+
<span>💻</span>System
|
|
31
|
+
</button>
|
|
32
|
+
<button class="theme-option" data-theme-opt="light" title="Light mode">
|
|
33
|
+
<span>☀️</span>Light
|
|
34
|
+
</button>
|
|
35
|
+
<button class="theme-option" data-theme-opt="dark" title="Dark mode">
|
|
36
|
+
<span>🌙</span>Dark
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</header>
|
|
42
|
+
{% block body %}{% endblock %}
|
|
43
|
+
<script>
|
|
44
|
+
(function () {
|
|
45
|
+
// --- App-wide localStorage key constants ---
|
|
46
|
+
var THEME_KEY = 'taskunity.theme';
|
|
47
|
+
var PANE_KEY = 'taskunity.sidePaneWidth';
|
|
48
|
+
|
|
49
|
+
// --- Theme ---
|
|
50
|
+
(function () {
|
|
51
|
+
function applyTheme(theme) {
|
|
52
|
+
if (theme === 'dark') {
|
|
53
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
54
|
+
} else if (theme === 'light') {
|
|
55
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
56
|
+
} else {
|
|
57
|
+
document.documentElement.removeAttribute('data-theme');
|
|
58
|
+
}
|
|
59
|
+
document.querySelectorAll('.theme-option').forEach(function (btn) {
|
|
60
|
+
btn.classList.toggle('active', btn.dataset.themeOpt === (theme || 'system'));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
var saved = localStorage.getItem(THEME_KEY) || 'system';
|
|
64
|
+
applyTheme(saved);
|
|
65
|
+
document.addEventListener('click', function (e) {
|
|
66
|
+
var opt = e.target.closest('[data-theme-opt]');
|
|
67
|
+
if (opt) {
|
|
68
|
+
var t = opt.dataset.themeOpt;
|
|
69
|
+
localStorage.setItem(THEME_KEY, t);
|
|
70
|
+
applyTheme(t);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
var btn = e.target.closest('#settings-btn');
|
|
74
|
+
if (btn) {
|
|
75
|
+
var popup = document.getElementById('settings-popup');
|
|
76
|
+
if (popup) popup.hidden = !popup.hidden;
|
|
77
|
+
if (popup && !popup.hidden) {
|
|
78
|
+
var saved2 = localStorage.getItem(THEME_KEY) || 'system';
|
|
79
|
+
document.querySelectorAll('.theme-option').forEach(function (b) {
|
|
80
|
+
b.classList.toggle('active', b.dataset.themeOpt === saved2);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
var popup2 = document.getElementById('settings-popup');
|
|
86
|
+
if (popup2 && !e.target.closest('.settings-anchor')) popup2.hidden = true;
|
|
87
|
+
});
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
function setProject(picker, value, color) {
|
|
91
|
+
var hidden = picker.querySelector('input[name="project"]');
|
|
92
|
+
var current = picker.querySelector('.project-dd-current');
|
|
93
|
+
if (hidden) hidden.value = value;
|
|
94
|
+
if (current) {
|
|
95
|
+
if (color) {
|
|
96
|
+
current.innerHTML = '<span class="swatch" style="background:' + color + '"></span>';
|
|
97
|
+
current.appendChild(document.createTextNode(value));
|
|
98
|
+
} else {
|
|
99
|
+
current.textContent = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
picker.querySelectorAll('.project-dd-item').forEach(function (b) {
|
|
103
|
+
b.classList.toggle('selected', b.dataset.value === value);
|
|
104
|
+
});
|
|
105
|
+
var dd = picker.querySelector('details');
|
|
106
|
+
if (dd) dd.open = false;
|
|
107
|
+
}
|
|
108
|
+
document.addEventListener('click', function (e) {
|
|
109
|
+
var item = e.target.closest('.project-dd-item');
|
|
110
|
+
if (item) {
|
|
111
|
+
var picker = item.closest('[data-project-picker]');
|
|
112
|
+
if (picker) setProject(picker, item.dataset.value, item.dataset.color);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
document.querySelectorAll('.project-dd[open]').forEach(function (dd) {
|
|
116
|
+
if (!dd.contains(e.target)) dd.open = false;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
document.addEventListener('keydown', function (e) {
|
|
120
|
+
if (e.target.classList && e.target.classList.contains('project-dd-input') && e.key === 'Enter') {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
var val = e.target.value.trim();
|
|
123
|
+
if (!val) return;
|
|
124
|
+
var picker = e.target.closest('[data-project-picker]');
|
|
125
|
+
if (picker) setProject(picker, val, '');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function depRefresh(picker) {
|
|
130
|
+
var hidden = picker.querySelector('input[name="depends_on"]');
|
|
131
|
+
var ids = Array.prototype.map.call(picker.querySelectorAll('.dep-chip'), function (c) { return c.dataset.id; });
|
|
132
|
+
if (hidden) hidden.value = ids.join(', ');
|
|
133
|
+
}
|
|
134
|
+
function depFilter(picker) {
|
|
135
|
+
var input = picker.querySelector('.dep-search-input');
|
|
136
|
+
var menu = picker.querySelector('.dep-menu');
|
|
137
|
+
var empty = picker.querySelector('.dep-empty');
|
|
138
|
+
var q = (input.value || '').trim().toLowerCase();
|
|
139
|
+
if (q === '') { menu.hidden = true; if (empty) empty.hidden = true; return; }
|
|
140
|
+
var shown = 0;
|
|
141
|
+
picker.querySelectorAll('.dep-option').forEach(function (o) {
|
|
142
|
+
var already = !!picker.querySelector('.dep-chip[data-id="' + o.dataset.id + '"]');
|
|
143
|
+
var match = o.dataset.search.indexOf(q) !== -1;
|
|
144
|
+
var show = match && !already;
|
|
145
|
+
o.hidden = !show;
|
|
146
|
+
if (show) shown++;
|
|
147
|
+
});
|
|
148
|
+
if (empty) empty.hidden = shown !== 0;
|
|
149
|
+
menu.hidden = false;
|
|
150
|
+
}
|
|
151
|
+
function depAdd(picker, id, title) {
|
|
152
|
+
if (picker.querySelector('.dep-chip[data-id="' + id + '"]')) return;
|
|
153
|
+
var chips = picker.querySelector('.dep-chips');
|
|
154
|
+
var chip = document.createElement('span');
|
|
155
|
+
chip.className = 'dep-chip';
|
|
156
|
+
chip.dataset.id = id;
|
|
157
|
+
chip.appendChild(document.createTextNode(title));
|
|
158
|
+
var x = document.createElement('button');
|
|
159
|
+
x.type = 'button';
|
|
160
|
+
x.className = 'dep-chip-x';
|
|
161
|
+
x.setAttribute('data-dep-remove', '');
|
|
162
|
+
x.setAttribute('aria-label', 'Remove dependency');
|
|
163
|
+
x.textContent = '×';
|
|
164
|
+
chip.appendChild(x);
|
|
165
|
+
chips.appendChild(chip);
|
|
166
|
+
depRefresh(picker);
|
|
167
|
+
var input = picker.querySelector('.dep-search-input');
|
|
168
|
+
if (input) input.value = '';
|
|
169
|
+
var menu = picker.querySelector('.dep-menu');
|
|
170
|
+
if (menu) menu.hidden = true;
|
|
171
|
+
}
|
|
172
|
+
document.addEventListener('input', function (e) {
|
|
173
|
+
if (e.target.classList && e.target.classList.contains('dep-search-input')) {
|
|
174
|
+
var picker = e.target.closest('[data-dep-picker]');
|
|
175
|
+
if (picker) depFilter(picker);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
document.addEventListener('focusin', function (e) {
|
|
179
|
+
if (e.target.classList && e.target.classList.contains('dep-search-input')) {
|
|
180
|
+
var picker = e.target.closest('[data-dep-picker]');
|
|
181
|
+
if (picker) depFilter(picker);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
document.addEventListener('click', function (e) {
|
|
185
|
+
var opt = e.target.closest('.dep-option');
|
|
186
|
+
if (opt) {
|
|
187
|
+
var picker = opt.closest('[data-dep-picker]');
|
|
188
|
+
var titleEl = opt.querySelector('.dep-option-title');
|
|
189
|
+
if (picker) depAdd(picker, opt.dataset.id, titleEl ? titleEl.textContent : opt.dataset.id);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
var rm = e.target.closest('[data-dep-remove]');
|
|
193
|
+
if (rm) {
|
|
194
|
+
var picker2 = rm.closest('[data-dep-picker]');
|
|
195
|
+
var chip = rm.closest('.dep-chip');
|
|
196
|
+
if (chip) chip.remove();
|
|
197
|
+
if (picker2) depRefresh(picker2);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
document.querySelectorAll('[data-dep-picker]').forEach(function (p) {
|
|
201
|
+
if (!p.contains(e.target)) {
|
|
202
|
+
var m = p.querySelector('.dep-menu');
|
|
203
|
+
if (m) m.hidden = true;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
function msAddFilter(box) {
|
|
209
|
+
var input = box.querySelector('.ms-add-input');
|
|
210
|
+
var menu = box.querySelector('.ms-add-menu');
|
|
211
|
+
var empty = box.querySelector('.dep-empty');
|
|
212
|
+
var q = (input.value || '').trim().toLowerCase();
|
|
213
|
+
var shown = 0;
|
|
214
|
+
box.querySelectorAll('.ms-add-option').forEach(function (o) {
|
|
215
|
+
var match = q === '' ? true : o.dataset.search.indexOf(q) !== -1;
|
|
216
|
+
o.hidden = !match;
|
|
217
|
+
if (match) shown++;
|
|
218
|
+
});
|
|
219
|
+
if (empty) empty.hidden = shown !== 0;
|
|
220
|
+
if (menu) menu.hidden = false;
|
|
221
|
+
}
|
|
222
|
+
document.addEventListener('input', function (e) {
|
|
223
|
+
if (e.target.classList && e.target.classList.contains('ms-add-input')) {
|
|
224
|
+
var box = e.target.closest('[data-ms-add]');
|
|
225
|
+
if (box) msAddFilter(box);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
document.addEventListener('focusin', function (e) {
|
|
229
|
+
if (e.target.classList && e.target.classList.contains('ms-add-input')) {
|
|
230
|
+
var box = e.target.closest('[data-ms-add]');
|
|
231
|
+
if (box) msAddFilter(box);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
document.addEventListener('click', function (e) {
|
|
235
|
+
if (e.target.closest('.ms-add-option')) return;
|
|
236
|
+
document.querySelectorAll('[data-ms-add]').forEach(function (box) {
|
|
237
|
+
if (!box.contains(e.target)) {
|
|
238
|
+
var m = box.querySelector('.ms-add-menu');
|
|
239
|
+
if (m) m.hidden = true;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Milestone project chips: add via select, remove via x
|
|
245
|
+
document.addEventListener('change', function (e) {
|
|
246
|
+
if (!(e.target.classList && e.target.classList.contains('ms-proj-select'))) return;
|
|
247
|
+
var sel = e.target;
|
|
248
|
+
var name = sel.value;
|
|
249
|
+
if (!name) return;
|
|
250
|
+
var box = sel.closest('[data-ms-projects]');
|
|
251
|
+
if (!box) return;
|
|
252
|
+
var chips = box.querySelector('.ms-proj-chips');
|
|
253
|
+
if (chips.querySelector('.ms-proj-chip[data-name="' + name + '"]')) { sel.value = ''; return; }
|
|
254
|
+
var color = sel.options[sel.selectedIndex].dataset.color || '#2e6fd8';
|
|
255
|
+
var chip = document.createElement('span');
|
|
256
|
+
chip.className = 'ms-proj-chip';
|
|
257
|
+
chip.dataset.name = name;
|
|
258
|
+
var sw = document.createElement('span');
|
|
259
|
+
sw.className = 'swatch';
|
|
260
|
+
sw.style.background = color;
|
|
261
|
+
chip.appendChild(sw);
|
|
262
|
+
chip.appendChild(document.createTextNode(name));
|
|
263
|
+
var hid = document.createElement('input');
|
|
264
|
+
hid.type = 'hidden';
|
|
265
|
+
hid.name = 'projects';
|
|
266
|
+
hid.value = name;
|
|
267
|
+
chip.appendChild(hid);
|
|
268
|
+
var x = document.createElement('button');
|
|
269
|
+
x.type = 'button';
|
|
270
|
+
x.className = 'ms-proj-x';
|
|
271
|
+
x.setAttribute('data-ms-proj-remove', '');
|
|
272
|
+
x.setAttribute('aria-label', 'Remove project');
|
|
273
|
+
x.textContent = '×';
|
|
274
|
+
chip.appendChild(x);
|
|
275
|
+
chips.appendChild(chip);
|
|
276
|
+
sel.remove(sel.selectedIndex);
|
|
277
|
+
sel.value = '';
|
|
278
|
+
});
|
|
279
|
+
document.addEventListener('click', function (e) {
|
|
280
|
+
var pr = e.target.closest('[data-ms-proj-remove]');
|
|
281
|
+
if (!pr) return;
|
|
282
|
+
var chip = pr.closest('.ms-proj-chip');
|
|
283
|
+
var box = pr.closest('[data-ms-projects]');
|
|
284
|
+
if (!chip || !box) return;
|
|
285
|
+
var name = chip.dataset.name;
|
|
286
|
+
var sel = box.querySelector('.ms-proj-select');
|
|
287
|
+
chip.remove();
|
|
288
|
+
if (sel && !Array.prototype.some.call(sel.options, function (o) { return o.value === name; })) {
|
|
289
|
+
var o = document.createElement('option');
|
|
290
|
+
o.value = name;
|
|
291
|
+
o.textContent = name;
|
|
292
|
+
sel.appendChild(o);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
document.addEventListener('change', function (e) {
|
|
297
|
+
if (!(e.target.classList && e.target.classList.contains('hide-old-toggle'))) return;
|
|
298
|
+
var form = e.target.closest('form');
|
|
299
|
+
if (!form) return;
|
|
300
|
+
var hidden = form.querySelector('[data-show-closed-hidden]');
|
|
301
|
+
if (hidden) hidden.value = e.target.checked ? '1' : '0';
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
function setComposerTab(root, name) {
|
|
305
|
+
if (!root) return;
|
|
306
|
+
root.querySelectorAll('[data-activity-tab]').forEach(function (btn) {
|
|
307
|
+
btn.classList.toggle('active', btn.dataset.activityTab === name);
|
|
308
|
+
});
|
|
309
|
+
root.querySelectorAll('[data-activity-pane]').forEach(function (pane) {
|
|
310
|
+
pane.hidden = pane.dataset.activityPane !== name;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
document.addEventListener('click', function (e) {
|
|
314
|
+
var tab = e.target.closest('[data-activity-tab]');
|
|
315
|
+
if (!tab) return;
|
|
316
|
+
var root = tab.closest('[data-activity-composer]');
|
|
317
|
+
if (root) setComposerTab(root, tab.dataset.activityTab);
|
|
318
|
+
});
|
|
319
|
+
document.querySelectorAll('[data-activity-composer]').forEach(function (root) {
|
|
320
|
+
setComposerTab(root, 'note');
|
|
321
|
+
});
|
|
322
|
+
document.body.addEventListener('htmx:afterSwap', function () {
|
|
323
|
+
document.querySelectorAll('[data-activity-composer]').forEach(function (root) {
|
|
324
|
+
var active = root.querySelector('[data-activity-tab].active');
|
|
325
|
+
setComposerTab(root, active ? active.dataset.activityTab : 'note');
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
function paneBounds() {
|
|
330
|
+
var max = Math.min(Math.floor(window.innerWidth * 0.62), 820);
|
|
331
|
+
var min = 300;
|
|
332
|
+
if (max < min) max = min;
|
|
333
|
+
return { min: min, max: max };
|
|
334
|
+
}
|
|
335
|
+
function setPaneWidth(layout, px) {
|
|
336
|
+
if (!layout) return;
|
|
337
|
+
var b = paneBounds();
|
|
338
|
+
var clamped = Math.max(b.min, Math.min(b.max, Math.round(px)));
|
|
339
|
+
layout.style.setProperty('--side-pane-width', clamped + 'px');
|
|
340
|
+
}
|
|
341
|
+
function initPaneResizer(root) {
|
|
342
|
+
var layout = (root || document).querySelector('[data-resizable-layout]');
|
|
343
|
+
var resizer = (root || document).querySelector('[data-pane-resizer]');
|
|
344
|
+
if (!layout || !resizer) return;
|
|
345
|
+
var saved = parseInt(localStorage.getItem(PANE_KEY) || '', 10);
|
|
346
|
+
if (!Number.isNaN(saved)) setPaneWidth(layout, saved);
|
|
347
|
+
var dragging = false;
|
|
348
|
+
function widthFromPointer(clientX) {
|
|
349
|
+
var rect = layout.getBoundingClientRect();
|
|
350
|
+
return rect.right - clientX;
|
|
351
|
+
}
|
|
352
|
+
function commit(px) {
|
|
353
|
+
setPaneWidth(layout, px);
|
|
354
|
+
var current = parseInt(getComputedStyle(layout).getPropertyValue('--side-pane-width'), 10);
|
|
355
|
+
if (!Number.isNaN(current)) localStorage.setItem(PANE_KEY, String(current));
|
|
356
|
+
}
|
|
357
|
+
resizer.addEventListener('mousedown', function (e) {
|
|
358
|
+
dragging = true;
|
|
359
|
+
layout.classList.add('resizing');
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
});
|
|
362
|
+
document.addEventListener('mousemove', function (e) {
|
|
363
|
+
if (!dragging) return;
|
|
364
|
+
commit(widthFromPointer(e.clientX));
|
|
365
|
+
});
|
|
366
|
+
document.addEventListener('mouseup', function () {
|
|
367
|
+
if (!dragging) return;
|
|
368
|
+
dragging = false;
|
|
369
|
+
layout.classList.remove('resizing');
|
|
370
|
+
});
|
|
371
|
+
resizer.addEventListener('dblclick', function () {
|
|
372
|
+
localStorage.removeItem(PANE_KEY);
|
|
373
|
+
layout.style.removeProperty('--side-pane-width');
|
|
374
|
+
});
|
|
375
|
+
resizer.addEventListener('keydown', function (e) {
|
|
376
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
|
|
377
|
+
var step = e.shiftKey ? 40 : 16;
|
|
378
|
+
var current = parseInt(getComputedStyle(layout).getPropertyValue('--side-pane-width'), 10);
|
|
379
|
+
if (Number.isNaN(current)) current = 430;
|
|
380
|
+
if (e.key === 'ArrowLeft') current += step;
|
|
381
|
+
if (e.key === 'ArrowRight') current -= step;
|
|
382
|
+
commit(current);
|
|
383
|
+
e.preventDefault();
|
|
384
|
+
});
|
|
385
|
+
window.addEventListener('resize', function () {
|
|
386
|
+
var current = parseInt(getComputedStyle(layout).getPropertyValue('--side-pane-width'), 10);
|
|
387
|
+
if (!Number.isNaN(current)) setPaneWidth(layout, current);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
initPaneResizer(document);
|
|
391
|
+
document.body.addEventListener('htmx:afterSwap', function () {
|
|
392
|
+
initPaneResizer(document);
|
|
393
|
+
});
|
|
394
|
+
})();
|
|
395
|
+
</script>
|
|
396
|
+
</body>
|
|
397
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<section class="panel">
|
|
2
|
+
<div class="panel-heading"><h2>Board</h2></div>
|
|
3
|
+
<div class="board">
|
|
4
|
+
{% for status in statuses %}
|
|
5
|
+
<div class="column">
|
|
6
|
+
<h3>{{ status|capitalize }} <span>{{ model.by_status.get(status, [])|length }}</span></h3>
|
|
7
|
+
{% for task in model.by_status.get(status, []) %}
|
|
8
|
+
<button class="task-card {{ task.status }}"{% if task.project %} style="border-left-color: {{ project_colors.get(task.project, '#3567e0') }}"{% endif %} hx-get="/tasks/{{ task.id }}/panel?view={{ filters.view }}{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#task-panel" hx-swap="innerHTML">
|
|
9
|
+
<strong>{{ task.title }}</strong>
|
|
10
|
+
{% if task.due_date %}<small>due {{ task.due_date }}</small>{% endif %}
|
|
11
|
+
{% if task.summary %}<p>{{ task.summary }}</p>{% endif %}
|
|
12
|
+
<div class="meta">
|
|
13
|
+
<span class="priority-tag {{ task.priority }}">{{ task.priority }}</span>
|
|
14
|
+
{% if task.project %}<span class="project" style="background: {{ project_colors.get(task.project, '#2e6fd8') }}22; color: {{ project_colors.get(task.project, '#2e6fd8') }}">{{ task.project }}</span>{% endif %}
|
|
15
|
+
</div>
|
|
16
|
+
<div class="mini-progress" title="{{ task.percent_complete }}% complete">
|
|
17
|
+
<div style="width: {{ task.percent_complete }}%"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</button>
|
|
20
|
+
{% endfor %}
|
|
21
|
+
</div>
|
|
22
|
+
{% endfor %}
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<div class="schedule-body">
|
|
2
|
+
<div class="calendar-headline">
|
|
3
|
+
<p class="schedule-range">{{ calendar.current_month_label }}</p>
|
|
4
|
+
<p class="calendar-subtle">Move by month with the arrows or jump controls.</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="calendar-carousel">
|
|
7
|
+
<div class="cal-head">
|
|
8
|
+
{% for label in calendar.weekdays %}<div>{{ label }}</div>{% endfor %}
|
|
9
|
+
</div>
|
|
10
|
+
{% for week in calendar.weeks %}
|
|
11
|
+
<div class="cal-week">
|
|
12
|
+
{% for day in week %}
|
|
13
|
+
<div class="cal-day{% if not day.in_month %} out{% endif %}{% if not day.in_range %} muted-day{% endif %}{% if day.today %} today{% endif %}">
|
|
14
|
+
<span class="cal-date">{{ day.date.day }}</span>
|
|
15
|
+
{% for task in day.tasks %}
|
|
16
|
+
<button class="cal-task {{ task.status }}"
|
|
17
|
+
hx-get="/tasks/{{ task.id }}/panel?view={{ filters.view }}{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#task-panel" hx-swap="innerHTML"
|
|
18
|
+
title="{{ task.title }}{% if task.project %} · {{ task.project }}{% endif %}">{{ task.title }}</button>
|
|
19
|
+
{% endfor %}
|
|
20
|
+
</div>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</div>
|
|
23
|
+
{% endfor %}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|