Skip to content

Commit 471515c

Browse files
authored
Merge pull request #124 from djedi/feat/note-templates
Feat/note templates
2 parents fbedae3 + 206cdcf commit 471515c

9 files changed

Lines changed: 209 additions & 25 deletions

File tree

app/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
DateTime,
99
LargeBinary,
1010
ForeignKey,
11+
Text,
1112
event,
1213
text,
1314
)
@@ -222,6 +223,8 @@ class User(Base):
222223
kanban_columns = Column(String(512), nullable=True, default='["todo", "done"]')
223224
week_start_monday = Column(Boolean, nullable=True, default=False)
224225
email_encrypted = Column("email", LargeBinary, nullable=True)
226+
daily_template = Column(Text, nullable=True) # Template for daily notes
227+
note_template = Column(Text, nullable=True) # Template for regular notes
225228
notes = relationship("Note", lazy="dynamic", cascade="all, delete, delete-orphan")
226229
meta = relationship("Meta", lazy="dynamic", cascade="all, delete, delete-orphan")
227230
external_calendars = relationship(

app/routes.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1549,9 +1549,11 @@ async def get_date():
15491549
if not user:
15501550
abort(400)
15511551

1552+
# Use user's custom daily template if set, otherwise use default
1553+
default_template = user.daily_template or "---\ntags: \nprojects: \n---\n\n"
15521554
ret_note = {
15531555
"title": date,
1554-
"data": "---\ntags: \nprojects: \n---\n\n",
1556+
"data": default_template,
15551557
"is_date": True,
15561558
"user_id": user.uuid,
15571559
}
@@ -1636,6 +1638,8 @@ async def sidebar_data():
16361638
kanban_enabled=kanban_enabled,
16371639
kanban_columns=kanban_columns,
16381640
week_start_monday=week_start_monday,
1641+
daily_template=user.daily_template,
1642+
note_template=user.note_template,
16391643
),
16401644
200,
16411645
)
@@ -1720,6 +1724,8 @@ async def get_settings():
17201724
kanban_enabled=user.kanban_enabled or False,
17211725
kanban_columns=kanban_columns,
17221726
week_start_monday=user.week_start_monday or False,
1727+
daily_template=user.daily_template,
1728+
note_template=user.note_template,
17231729
),
17241730
200,
17251731
)
@@ -1764,6 +1770,12 @@ async def update_settings():
17641770
if "week_start_monday" in req:
17651771
user.week_start_monday = req["week_start_monday"]
17661772

1773+
if "daily_template" in req:
1774+
user.daily_template = req["daily_template"] or None
1775+
1776+
if "note_template" in req:
1777+
user.note_template = req["note_template"] or None
1778+
17671779
db.session.add(user)
17681780
db.session.flush()
17691781
db.session.commit()
@@ -1783,6 +1795,8 @@ async def update_settings():
17831795
kanban_enabled=user.kanban_enabled or False,
17841796
kanban_columns=kanban_columns,
17851797
week_start_monday=user.week_start_monday or False,
1798+
daily_template=user.daily_template,
1799+
note_template=user.note_template,
17861800
),
17871801
200,
17881802
)

client/src/components/Calendar.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ onMounted(() => {
4040
4141
const changeDate = async (value: Date | null) => {
4242
if (value) {
43+
// Blur any focused element to prevent aria-hidden warning from Buefy datepicker
44+
if (document.activeElement instanceof HTMLElement) {
45+
document.activeElement.blur();
46+
}
47+
4348
const previousRoute = route.params.id;
4449
const previousDate = sidebar.date;
4550

client/src/components/Settings.vue

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,49 @@
314314
</div>
315315
</div>
316316

317+
<div class="settings-card templates-card">
318+
<p class="section-title">Templates</p>
319+
<p class="section-hint">Set default content for new notes.</p>
320+
321+
<div class="template-section">
322+
<div class="template-header">
323+
<span class="template-label">Daily note template</span>
324+
<b-button size="is-small" @click="saveDailyTemplate">Save</b-button>
325+
</div>
326+
<b-field>
327+
<b-input
328+
v-model="localDailyTemplate"
329+
type="textarea"
330+
placeholder="---&#10;tags: &#10;projects: &#10;---&#10;&#10;"
331+
rows="5"
332+
class="template-textarea"
333+
/>
334+
</b-field>
335+
<p class="setting-hint">
336+
Template used when creating a new daily note. Leave empty to use the default.
337+
</p>
338+
</div>
339+
340+
<div class="template-section">
341+
<div class="template-header">
342+
<span class="template-label">Other note template</span>
343+
<b-button size="is-small" @click="saveNoteTemplate">Save</b-button>
344+
</div>
345+
<b-field>
346+
<b-input
347+
v-model="localNoteTemplate"
348+
type="textarea"
349+
placeholder="---&#10;title: &#10;tags: &#10;projects: &#10;---&#10;&#10;"
350+
rows="5"
351+
class="template-textarea"
352+
/>
353+
</b-field>
354+
<p class="setting-hint">
355+
Template used when creating a new note (not daily). Leave empty to use the default.
356+
</p>
357+
</div>
358+
</div>
359+
317360
<div class="settings-card about-card">
318361
<p class="section-title">About</p>
319362
<div class="about-info">
@@ -357,6 +400,8 @@ const localDirection = ref<DirectionPreference>('ltr');
357400
const localKanbanEnabled = ref(false);
358401
const localKanbanColumns = ref<string[]>(['todo', 'done']);
359402
const localWeekStartMonday = ref(false);
403+
const localDailyTemplate = ref('');
404+
const localNoteTemplate = ref('');
360405
361406
// Email management
362407
const hasEmail = ref(false);
@@ -404,6 +449,8 @@ onMounted(() => {
404449
localKanbanEnabled.value = sidebar.kanbanEnabled;
405450
localKanbanColumns.value = [...sidebar.kanbanColumns];
406451
localWeekStartMonday.value = sidebar.weekStartMonday;
452+
localDailyTemplate.value = sidebar.dailyTemplate || '';
453+
localNoteTemplate.value = sidebar.noteTemplate || '';
407454
408455
fetchCalendarUrl();
409456
fetchExternalCalendars();
@@ -690,6 +737,26 @@ const saveColumns = () => {
690737
}
691738
};
692739
740+
const saveDailyTemplate = () => {
741+
const template = localDailyTemplate.value.trim() || null;
742+
sidebar.updateDailyTemplate(template);
743+
buefy?.toast.open({
744+
message: template ? 'Daily template saved' : 'Daily template reset to default',
745+
type: 'is-success',
746+
duration: 2000,
747+
});
748+
};
749+
750+
const saveNoteTemplate = () => {
751+
const template = localNoteTemplate.value.trim() || null;
752+
sidebar.updateNoteTemplate(template);
753+
buefy?.toast.open({
754+
message: template ? 'Note template saved' : 'Note template reset to default',
755+
type: 'is-success',
756+
duration: 2000,
757+
});
758+
};
759+
693760
const fetchCalendarUrl = async () => {
694761
calendarLoading.value = true;
695762
try {
@@ -1312,4 +1379,35 @@ const close = () => {
13121379
gap: 8px;
13131380
margin-top: 4px;
13141381
}
1382+
1383+
/* Templates section */
1384+
.templates-card {
1385+
margin-top: 16px;
1386+
}
1387+
1388+
.template-section {
1389+
margin-bottom: 20px;
1390+
}
1391+
1392+
.template-section:last-child {
1393+
margin-bottom: 0;
1394+
}
1395+
1396+
.template-header {
1397+
display: flex;
1398+
justify-content: space-between;
1399+
align-items: center;
1400+
margin-bottom: 8px;
1401+
}
1402+
1403+
.template-label {
1404+
color: var(--text-secondary);
1405+
font-weight: 500;
1406+
}
1407+
1408+
.template-textarea :deep(.textarea) {
1409+
font-family: 'Fira Code', monospace;
1410+
font-size: 13px;
1411+
resize: vertical;
1412+
}
13151413
</style>

client/src/services/sidebar.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class SidebarSerivce {
7676
public kanbanEnabled: boolean = false;
7777
public kanbanColumns: string[] = ['todo', 'done'];
7878
public weekStartMonday: boolean = false;
79+
public dailyTemplate: string | null = null;
80+
public noteTemplate: string | null = null;
7981
public currentNoteId: string | null = null;
8082
public date: Date | null = null;
8183
public sidebarLoading: boolean = false;
@@ -197,6 +199,8 @@ class SidebarSerivce {
197199
this.kanbanEnabled = res.data.kanban_enabled || false;
198200
this.kanbanColumns = res.data.kanban_columns || ['todo', 'done'];
199201
this.weekStartMonday = res.data.week_start_monday || false;
202+
this.dailyTemplate = res.data.daily_template || null;
203+
this.noteTemplate = res.data.note_template || null;
200204
}
201205

202206
if (this.selectedSearch.length && this.searchString.length) {
@@ -288,6 +292,20 @@ class SidebarSerivce {
288292
} catch (_e) {}
289293
}
290294

295+
public async updateDailyTemplate(template: string | null) {
296+
try {
297+
await Requests.put('/settings', { daily_template: template });
298+
this.dailyTemplate = template;
299+
} catch (_e) {}
300+
}
301+
302+
public async updateNoteTemplate(template: string | null) {
303+
try {
304+
await Requests.put('/settings', { note_template: template });
305+
this.noteTemplate = template;
306+
} catch (_e) {}
307+
}
308+
291309
public async updateTaskColumn(
292310
taskUuid: string,
293311
column: string

client/src/services/sse.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,15 @@ class SSEService {
5252
* Will automatically reconnect on disconnection.
5353
*/
5454
connect(): void {
55-
console.log('SSE: connect() called');
5655
if (this.abortController || this.isConnecting) {
57-
console.log('SSE: Already connected or connecting, skipping');
5856
return;
5957
}
6058

6159
const token = getToken();
6260
if (!token) {
63-
console.warn('SSE: No auth token, skipping connection');
6461
return;
6562
}
6663

67-
console.log('SSE: Initiating connection...');
6864
this.isConnecting = true;
6965

7066
// For SSE, we need to bypass the webpack-dev-server proxy which buffers responses.
@@ -91,7 +87,6 @@ class SSEService {
9187
// Create abort controller for this connection
9288
this.abortController = new AbortController();
9389

94-
console.log(`SSE: Fetching ${baseUrl}/events/stream`);
9590
const response = await fetch(`${baseUrl}/events/stream`, {
9691
method: 'GET',
9792
headers: {
@@ -101,7 +96,6 @@ class SSEService {
10196
signal: this.abortController.signal,
10297
});
10398

104-
console.log(`SSE: Response status ${response.status}`);
10599
if (!response.ok) {
106100
throw new Error(`SSE connection failed: ${response.status}`);
107101
}
@@ -114,8 +108,6 @@ class SSEService {
114108
this.reconnectAttempts = 0;
115109
this.reconnectDelay = 1000;
116110

117-
console.log('SSE: Connected');
118-
119111
const reader = response.body.getReader();
120112
const decoder = new TextDecoder();
121113
let buffer = '';
@@ -127,7 +119,6 @@ class SSEService {
127119
const { done, value } = await reader.read();
128120

129121
if (done) {
130-
console.log('SSE: Stream ended');
131122
this.handleDisconnect();
132123
return;
133124
}
@@ -156,15 +147,13 @@ class SSEService {
156147
}
157148
}
158149
}
159-
} catch (error) {
160-
console.error('SSE: Stream error', error);
150+
} catch (_error) {
161151
this.handleDisconnect();
162152
}
163153
};
164154

165155
processStream();
166-
} catch (error) {
167-
console.error('SSE: Connection error', error);
156+
} catch (_error) {
168157
this.isConnecting = false;
169158
this.handleDisconnect();
170159
}
@@ -192,11 +181,11 @@ class SSEService {
192181
eventHub.emit('sseTaskColumnUpdated', data);
193182
break;
194183
case 'connected':
195-
console.log('SSE: Server acknowledged connection');
184+
// Server acknowledged connection
196185
break;
197186
}
198-
} catch (error) {
199-
console.error('SSE: Failed to parse event data', error, dataStr);
187+
} catch (_error) {
188+
// Failed to parse event data
200189
}
201190
}
202191

@@ -207,16 +196,13 @@ class SSEService {
207196
if (this.reconnectAttempts < this.maxReconnectAttempts) {
208197
this.reconnectAttempts++;
209198
const delay = this.reconnectDelay * 2 ** (this.reconnectAttempts - 1);
210-
console.log(`SSE: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
211199

212200
setTimeout(() => {
213201
const token = getToken();
214202
if (token) {
215203
this.connect();
216204
}
217205
}, delay);
218-
} else {
219-
console.warn('SSE: Max reconnect attempts reached');
220206
}
221207
}
222208

@@ -229,7 +215,6 @@ class SSEService {
229215
this.abortController = null;
230216
}
231217
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent auto-reconnect
232-
console.log('SSE: Disconnected');
233218
}
234219

235220
/**

0 commit comments

Comments
 (0)