mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-10-18 20:56:18 +08:00
Merge pull request #1579 from TriliumNext/calendar
Add week note and quarter note support
This commit is contained in:
commit
a92b040958
@ -14,7 +14,8 @@ UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'
|
|||||||
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
||||||
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
||||||
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
||||||
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
|
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'weekPattern', 'enableWeekNote', 'monthPattern',
|
||||||
|
'quarterPattern', 'yearPattern', 'enableQuarterNote', 'pageSize', 'viewType', 'mapRootNoteId',
|
||||||
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
||||||
'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
||||||
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
||||||
@ -31,7 +32,8 @@ UPDATE attributes SET name = 'name' WHERE type = 'relation'
|
|||||||
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
|
||||||
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
|
||||||
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
|
||||||
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
|
'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'weekPattern', 'enableWeekNote', 'monthPattern',
|
||||||
|
'quarterPattern', 'yearPattern', 'enableQuarterNote', 'pageSize', 'viewType', 'mapRootNoteId',
|
||||||
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
|
||||||
'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
'fullContentWidth', 'shareHiddenFromTree', 'shareExternalLink', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
|
||||||
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 2,
|
"formatVersion": 2,
|
||||||
"appVersion": "0.92.6",
|
"appVersion": "0.92.7",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"isClone": false,
|
"isClone": false,
|
||||||
@ -3511,58 +3511,51 @@
|
|||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "relation",
|
||||||
"name": "shareAlias",
|
"name": "internalLink",
|
||||||
"value": "search",
|
"value": "OR8WJ7Iz9K4U",
|
||||||
"isInheritable": false,
|
|
||||||
"position": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "label",
|
|
||||||
"name": "iconClass",
|
|
||||||
"value": "bx bx-search-alt-2",
|
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "xYmIYSP6wE3F",
|
"value": "wX4HbRucYSDD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "YtSN43OrfzaA",
|
"value": "ivYnonVFBxbQ",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "OR8WJ7Iz9K4U",
|
"value": "xYmIYSP6wE3F",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "9sRHySam5fXb",
|
"value": "YtSN43OrfzaA",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 70
|
"position": 70
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "m523cpzocqaD",
|
"value": "9sRHySam5fXb",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 80
|
"position": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "wX4HbRucYSDD",
|
"value": "m523cpzocqaD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 90
|
||||||
},
|
},
|
||||||
@ -3590,16 +3583,23 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "ivYnonVFBxbQ",
|
"value": "oPVyFC7WL2Lp",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 130
|
"position": 130
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "label",
|
||||||
"name": "internalLink",
|
"name": "shareAlias",
|
||||||
"value": "oPVyFC7WL2Lp",
|
"value": "search",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 140
|
"position": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "label",
|
||||||
|
"name": "iconClass",
|
||||||
|
"value": "bx bx-search-alt-2",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 30
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@ -3852,16 +3852,16 @@
|
|||||||
"mime": "text/html",
|
"mime": "text/html",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "relation",
|
||||||
"name": "iconClass",
|
"name": "internalLink",
|
||||||
"value": "bx bx-search-alt-2",
|
"value": "MI26XDLSAlCD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "8YBEPzcpUgxw",
|
"value": "iPIMuisry3hd",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
@ -3875,16 +3875,16 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "iPIMuisry3hd",
|
"value": "8YBEPzcpUgxw",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "label",
|
||||||
"name": "internalLink",
|
"name": "iconClass",
|
||||||
"value": "MI26XDLSAlCD",
|
"value": "bx bx-search-alt-2",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
|
@ -13,10 +13,14 @@ This pattern works well also because of [Cloning Notes](../../Basic%20Concepts%2
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can see the structure of day notes appearing under "Journal" note - there's a note for the whole year 2017, under it, you have "12 - December" which then contains "18 - Monday". This is our "day note" which contains some text in its content and also has some child notes (some of them are from [Task manager](Task%20Manager.md)).
|
You can see the structure of day notes appearing under "Journal" note - there's a note for the whole year 2025, under it, you have "03 - March" which then contains "09 - Monday". This is our "day note" which contains some text in its content and also has some child notes (some of them are from [Task manager](Task%20Manager.md)).
|
||||||
|
|
||||||
You can also notice how this day note has [promoted attribute](../Attributes/Promoted%20Attributes.md) "weight" where you can track your daily weight. This data is then used in [Weight tracker](Weight%20Tracker.md).
|
You can also notice how this day note has [promoted attribute](../Attributes/Promoted%20Attributes.md) "weight" where you can track your daily weight. This data is then used in [Weight tracker](Weight%20Tracker.md).
|
||||||
|
|
||||||
|
## Week Note and Quarter Note
|
||||||
|
|
||||||
|
Week and quarter notes are disabled by default, since it might be too much for some people. To enable them, you need to set `#enableWeekNotes` and `#enableQuarterNotes` attributes on the root calendar note, which is identified by `#calendarRoot` label. Week note is affected by the first week of year option. Be careful when you already have some week notes created, it will not automatically change the existing week notes and might lead to some duplicates.
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
Trilium provides [template](../Templates.md) functionality, and it could be used together with day notes.
|
Trilium provides [template](../Templates.md) functionality, and it could be used together with day notes.
|
||||||
@ -24,26 +28,48 @@ Trilium provides [template](../Templates.md) functionality, and it could be used
|
|||||||
You can define one of the following relations on the root of the journal (identified by `#calendarRoot` label):
|
You can define one of the following relations on the root of the journal (identified by `#calendarRoot` label):
|
||||||
|
|
||||||
* yearTemplate
|
* yearTemplate
|
||||||
|
* quarterTemplate (if `#enableQuarterNotes` is set)
|
||||||
* monthTemplate
|
* monthTemplate
|
||||||
|
* weekTemplate (if `#enableWeekNotes` is set)
|
||||||
* dateTemplate
|
* dateTemplate
|
||||||
|
|
||||||
All of these are relations. When Trilium creates a new note for year or month or date, it will take a look at the root and attach a corresponding `~template` relation to the newly created role. Using this, you can e.g. create your daily template with e.g. checkboxes for daily routine etc.
|
All of these are relations. When Trilium creates a new note for year or month or date, it will take a look at the root and attach a corresponding `~template` relation to the newly created role. Using this, you can e.g. create your daily template with e.g. checkboxes for daily routine etc.
|
||||||
|
|
||||||
## Date pattern
|
## Naming pattern
|
||||||
|
|
||||||
It's possible to customize the title of generated date notes by defining a `#datePattern` label on a root calendar note (identified by `#calendarRoot` label). Following are possible values:
|
You can customize the title of generated journal notes by defining a `#datePattern`, `#weekPattern`, `#monthPattern`, `#quarterPattern` and `#yearPattern` attribute on a root calendar note (identified by `#calendarRoot` label). The naming pattern replacements follow a level-up compatibility - each level can use replacements from itself and all levels above it. For example, `#monthPattern` can use month, quarter and year replacements, while `#weekPattern` can use week, month, quarter and year replacements. But it is not possible to use week replacements in `#monthPattern`.
|
||||||
|
|
||||||
* `{dayInMonthPadded} - {weekDay}` day notes are named e.g. "24 - Monday"
|
### Date pattern
|
||||||
* `{dayInMonthPadded}: {weekDay3}` day notes are named e.g. "24: Mon"
|
|
||||||
* `{dayInMonthPadded}: {weekDay2}` day notes are named e.g. "24: Mo"
|
It's possible to customize the title of generated date notes by defining a `#datePattern` attribute on a root calendar note (identified by `#calendarRoot` label). Following are possible values:
|
||||||
* `{isoDate} - {weekDay}` day notes are named e.g. "2020-12-24 - Monday"
|
|
||||||
|
* `{isoDate}` results in an ISO 8061 formatted date (e.g. "2025-03-09" for March 9, 2025)
|
||||||
|
* `{dateNumber}` results in a number like `9` for the 9th day of the month, `11` for the 11th day of the month
|
||||||
|
* `{dateNumberPadded}` results in a number like `09` for the 9th day of the month, `11` for the 11th day of the month
|
||||||
* `{ordinal}` is replaced with the ordinal date (e.g. 1st, 2nd, 3rd) etc.
|
* `{ordinal}` is replaced with the ordinal date (e.g. 1st, 2nd, 3rd) etc.
|
||||||
|
* `{weekDay}` results in the full day name (e.g. `Monday`)
|
||||||
|
* `{weekDay3}` is replaced with the first 3 letters of the day, e.g. Mon, Tue, etc.
|
||||||
|
* `{weekDay2}` is replaced with the first 2 letters of the day, e.g. Mo, Tu, etc.
|
||||||
|
|
||||||
## Month pattern
|
The default is `{dateNumberPadded} - {weekDay}`
|
||||||
|
|
||||||
It is also possible to customize the title of generated month notes through the `#monthPattern` attribute, much like `#datePattern`. The options are:
|
### Week pattern
|
||||||
|
|
||||||
|
It is also possible to customize the title of generated week notes through the `#weekPattern` attribute on the root calendar note. The options are:
|
||||||
|
|
||||||
|
* `{weekNumber}` results in a number like `9` for the 9th week of the year, `11` for the 11th week of the year
|
||||||
|
* `{weekNumberPadded}` results in a number like `09` for the 9th week of the year, `11` for the 11th week of the year
|
||||||
|
* `{shortWeek}` results in a short week string like `W9` for the 9th week of the year, `W11` for the 11th week of the year
|
||||||
|
* `{shortWeek3}` results in a short week string like `W09` for the 9th week of the year, `W11` for the 11th week of the year
|
||||||
|
|
||||||
|
The default is `Week {weekNumber}`
|
||||||
|
|
||||||
|
### Month pattern
|
||||||
|
|
||||||
|
It is also possible to customize the title of generated month notes through the `#monthPattern` attribute on the root calendar note. The options are:
|
||||||
|
|
||||||
* `{isoMonth}` results in an ISO 8061 formatted month (e.g. "2025-03" for March 2025)
|
* `{isoMonth}` results in an ISO 8061 formatted month (e.g. "2025-03" for March 2025)
|
||||||
|
* `{monthNumber}` results in a number like `9` for September, and `11` for November
|
||||||
* `{monthNumberPadded}` results in a number like `09` for September, and `11` for November
|
* `{monthNumberPadded}` results in a number like `09` for September, and `11` for November
|
||||||
* `{month}` results in the full month name (e.g. `September` or `October`)
|
* `{month}` results in the full month name (e.g. `September` or `October`)
|
||||||
* `{shortMonth3}` is replaced with the first 3 letters of the month, e.g. Jan, Feb, etc.
|
* `{shortMonth3}` is replaced with the first 3 letters of the month, e.g. Jan, Feb, etc.
|
||||||
@ -51,10 +77,27 @@ It is also possible to customize the title of generated month notes through the
|
|||||||
|
|
||||||
The default is `{monthNumberPadded} - {month}`
|
The default is `{monthNumberPadded} - {month}`
|
||||||
|
|
||||||
|
### Quarter pattern
|
||||||
|
|
||||||
|
It is also possible to customize the title of generated quarter notes through the `#quarterPattern` attribute on the root calendar note. The options are:
|
||||||
|
|
||||||
|
* `{quarterNumber}` results in a number like `1` for the 1st quarter of the year
|
||||||
|
* `{shortQuarter}` results in a short quarter string like `Q1` for the 1st quarter of the year
|
||||||
|
|
||||||
|
The default is `Quarter {quarterNumber}`
|
||||||
|
|
||||||
|
### Year pattern
|
||||||
|
|
||||||
|
It is also possible to customize the title of generated year notes through the `#yearPattern` attribute on the root calendar note. The options are:
|
||||||
|
|
||||||
|
* `{year}` results in the full year (e.g. `2025`)
|
||||||
|
|
||||||
|
The default is `{year}`
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
Trilium has some special support for day notes in the form of [backend Script API](https://triliumnext.github.io/Notes/backend_api/BackendScriptApi.html) - see e.g. getDayNote() function.
|
Trilium has some special support for day notes in the form of [backend Script API](https://triliumnext.github.io/Notes/backend_api/BackendScriptApi.html) - see e.g. getDayNote() function.
|
||||||
|
|
||||||
Day (and year, month) notes are created with a label - e.g. `#dateNote="2018-08-16"` this can then be used by other scripts to add new notes to day note etc.
|
Day (and year, month) notes are created with a label - e.g. `#dateNote="2025-03-09"` this can then be used by other scripts to add new notes to day note etc.
|
||||||
|
|
||||||
Journal also has relation `child:child:child:template=Day template` (see \[\[attribute inheritance\]\]) which effectively adds \[\[template\]\] to day notes (grand-grand-grand children of Journal).
|
Journal also has relation `child:child:child:template=Day template` (see \[\[attribute inheritance\]\]) which effectively adds \[\[template\]\] to day notes (grand-grand-grand children of Journal). Please note that, when you enable week notes or quarter notes, it will not automatically change the relation for the child level.
|
@ -4680,7 +4680,7 @@ otherwise (by e.g. createLink())
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h4 class="name" id="getWeekNote"><span class="type-signature"></span>getWeekNote<span class="signature">(date)</span><span class="type-signature"> → {Promise.<<a href="FNote.html">FNote</a>>}</span></h4>
|
<h4 class="name" id="getWeekFirstDayNote"><span class="type-signature"></span>getWeekFirstDayNote<span class="signature">(date)</span><span class="type-signature"> → {Promise.<<a href="FNote.html">FNote</a>>}</span></h4>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -563,7 +563,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
|||||||
* @param {string} date - e.g. "2019-04-29"
|
* @param {string} date - e.g. "2019-04-29"
|
||||||
* @returns {Promise<FNote>}
|
* @returns {Promise<FNote>}
|
||||||
*/
|
*/
|
||||||
this.getWeekNote = dateNotesService.getWeekNote;
|
this.getWeekFirstDayNote = dateNotesService.getWeekFirstDayNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns month-note. If it doesn't exist, it is automatically created.
|
* Returns month-note. If it doesn't exist, it is automatically created.
|
||||||
|
@ -694,7 +694,7 @@ paths:
|
|||||||
/calendar/weeks/{date}:
|
/calendar/weeks/{date}:
|
||||||
get:
|
get:
|
||||||
description: returns a week note for a given date. Gets created if doesn't exist.
|
description: returns a week note for a given date. Gets created if doesn't exist.
|
||||||
operationId: getWeekNote
|
operationId: getWeekFirstDayNote
|
||||||
parameters:
|
parameters:
|
||||||
- name: date
|
- name: date
|
||||||
in: path
|
in: path
|
||||||
|
@ -5,59 +5,72 @@ import mappers from "./mappers.js";
|
|||||||
import type { Router } from "express";
|
import type { Router } from "express";
|
||||||
|
|
||||||
const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
||||||
|
const getWeekInvalidError = (week: string) => new eu.EtapiError(400, "WEEK_INVALID", `Week "${week}" is not valid.`);
|
||||||
|
const getWeekNotFoundError = (week: string) => new eu.EtapiError(404, "WEEK_NOT_FOUND", `Week "${week}" not found. Check if week note is enabled.`);
|
||||||
const getMonthInvalidError = (month: string) => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
const getMonthInvalidError = (month: string) => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
||||||
const getYearInvalidError = (year: string) => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
const getYearInvalidError = (year: string) => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
||||||
|
|
||||||
function isValidDate(date: string) {
|
function isValidDate(date: string) {
|
||||||
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
|
return /[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date) && !!Date.parse(date);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!Date.parse(date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function register(router: Router) {
|
function register(router: Router) {
|
||||||
eu.route(router, "get", "/etapi/inbox/:date", (req, res, next) => {
|
eu.route(router, "get", "/etapi/inbox/:date", async (req, res, next) => {
|
||||||
|
const { date } = req.params;
|
||||||
|
|
||||||
|
if (!isValidDate(date)) {
|
||||||
|
throw getDateInvalidError(date);
|
||||||
|
}
|
||||||
|
const note = await specialNotesService.getInboxNote(date);
|
||||||
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
|
});
|
||||||
|
|
||||||
|
eu.route(router, "get", "/etapi/calendar/days/:date", async (req, res, next) => {
|
||||||
const { date } = req.params;
|
const { date } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(date);
|
throw getDateInvalidError(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = specialNotesService.getInboxNote(date);
|
const note = await dateNotesService.getDayNote(date);
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, "get", "/etapi/calendar/days/:date", (req, res, next) => {
|
eu.route(router, "get", "/etapi/calendar/week-first-day/:date", async (req, res, next) => {
|
||||||
const { date } = req.params;
|
const { date } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(date);
|
throw getDateInvalidError(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = dateNotesService.getDayNote(date);
|
const note = await dateNotesService.getWeekFirstDayNote(date);
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, "get", "/etapi/calendar/weeks/:date", (req, res, next) => {
|
eu.route(router, "get", "/etapi/calendar/weeks/:week", async (req, res, next) => {
|
||||||
const { date } = req.params;
|
const { week } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!/[0-9]{4}-W[0-9]{2}/.test(week)) {
|
||||||
throw getDateInvalidError(date);
|
throw getWeekInvalidError(week);
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = await dateNotesService.getWeekNote(week);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw getWeekNotFoundError(week);
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = dateNotesService.getWeekNote(date);
|
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, "get", "/etapi/calendar/months/:month", (req, res, next) => {
|
eu.route(router, "get", "/etapi/calendar/months/:month", async (req, res, next) => {
|
||||||
const { month } = req.params;
|
const { month } = req.params;
|
||||||
|
|
||||||
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
||||||
throw getMonthInvalidError(month);
|
throw getMonthInvalidError(month);
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = dateNotesService.getMonthNote(month);
|
const note = await dateNotesService.getMonthNote(month);
|
||||||
res.json(mappers.mapNoteToPojo(note));
|
res.json(mappers.mapNoteToPojo(note));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
62
src/public/app/doc_notes/en/User Guide/!!!meta.json
generated
62
src/public/app/doc_notes/en/User Guide/!!!meta.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 2,
|
"formatVersion": 2,
|
||||||
"appVersion": "0.92.6",
|
"appVersion": "0.92.7",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"isClone": false,
|
"isClone": false,
|
||||||
@ -3511,58 +3511,51 @@
|
|||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "relation",
|
||||||
"name": "shareAlias",
|
"name": "internalLink",
|
||||||
"value": "search",
|
"value": "OR8WJ7Iz9K4U",
|
||||||
"isInheritable": false,
|
|
||||||
"position": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "label",
|
|
||||||
"name": "iconClass",
|
|
||||||
"value": "bx bx-search-alt-2",
|
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "xYmIYSP6wE3F",
|
"value": "wX4HbRucYSDD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "YtSN43OrfzaA",
|
"value": "ivYnonVFBxbQ",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "OR8WJ7Iz9K4U",
|
"value": "xYmIYSP6wE3F",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "9sRHySam5fXb",
|
"value": "YtSN43OrfzaA",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 70
|
"position": 70
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "m523cpzocqaD",
|
"value": "9sRHySam5fXb",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 80
|
"position": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "wX4HbRucYSDD",
|
"value": "m523cpzocqaD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 90
|
||||||
},
|
},
|
||||||
@ -3590,16 +3583,23 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "ivYnonVFBxbQ",
|
"value": "oPVyFC7WL2Lp",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 130
|
"position": 130
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "label",
|
||||||
"name": "internalLink",
|
"name": "shareAlias",
|
||||||
"value": "oPVyFC7WL2Lp",
|
"value": "search",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 140
|
"position": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "label",
|
||||||
|
"name": "iconClass",
|
||||||
|
"value": "bx bx-search-alt-2",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 30
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "html",
|
"format": "html",
|
||||||
@ -3852,16 +3852,16 @@
|
|||||||
"mime": "text/html",
|
"mime": "text/html",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "relation",
|
||||||
"name": "iconClass",
|
"name": "internalLink",
|
||||||
"value": "bx bx-search-alt-2",
|
"value": "MI26XDLSAlCD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "8YBEPzcpUgxw",
|
"value": "iPIMuisry3hd",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
@ -3875,16 +3875,16 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "iPIMuisry3hd",
|
"value": "8YBEPzcpUgxw",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "label",
|
||||||
"name": "internalLink",
|
"name": "iconClass",
|
||||||
"value": "MI26XDLSAlCD",
|
"value": "bx bx-search-alt-2",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "html",
|
"format": "html",
|
||||||
|
@ -35,12 +35,19 @@
|
|||||||
<img src="Day Notes_image.png">
|
<img src="Day Notes_image.png">
|
||||||
</p>
|
</p>
|
||||||
<p>You can see the structure of day notes appearing under "Journal" note
|
<p>You can see the structure of day notes appearing under "Journal" note
|
||||||
- there's a note for the whole year 2017, under it, you have "12 - December"
|
- there's a note for the whole year 2025, under it, you have "03 - March"
|
||||||
which then contains "18 - Monday". This is our "day note" which contains
|
which then contains "09 - Monday". This is our "day note" which contains
|
||||||
some text in its content and also has some child notes (some of them are
|
some text in its content and also has some child notes (some of them are
|
||||||
from <a href="#root/_help_xYjQUYhpbUEW">Task manager</a>).</p>
|
from <a href="#root/_help_xYjQUYhpbUEW">Task manager</a>).</p>
|
||||||
<p>You can also notice how this day note has <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> "weight"
|
<p>You can also notice how this day note has <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> "weight"
|
||||||
where you can track your daily weight. This data is then used in <a href="#root/_help_R7abl2fc6Mxi">Weight tracker</a>.</p>
|
where you can track your daily weight. This data is then used in <a href="#root/_help_R7abl2fc6Mxi">Weight tracker</a>.</p>
|
||||||
|
<h2>Week Note and Quarter Note</h2>
|
||||||
|
<p>Week and quarter notes are disabled by default, since it might be too
|
||||||
|
much for some people. To enable them, you need to set <code>#enableWeekNotes</code> and <code>#enableQuarterNotes</code> attributes
|
||||||
|
on the root calendar note, which is identified by <code>#calendarRoot</code> label.
|
||||||
|
Week note is affected by the first week of year option. Be careful when
|
||||||
|
you already have some week notes created, it will not automatically change
|
||||||
|
the existing week notes and might lead to some duplicates.</p>
|
||||||
<h2>Templates</h2>
|
<h2>Templates</h2>
|
||||||
<p>Trilium provides <a href="#root/_help_KC1HB96bqqHX">template</a> functionality,
|
<p>Trilium provides <a href="#root/_help_KC1HB96bqqHX">template</a> functionality,
|
||||||
and it could be used together with day notes.</p>
|
and it could be used together with day notes.</p>
|
||||||
@ -48,36 +55,69 @@
|
|||||||
(identified by <code>#calendarRoot</code> label):</p>
|
(identified by <code>#calendarRoot</code> label):</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>yearTemplate</li>
|
<li>yearTemplate</li>
|
||||||
|
<li>quarterTemplate (if <code>#enableQuarterNotes</code> is set)</li>
|
||||||
<li>monthTemplate</li>
|
<li>monthTemplate</li>
|
||||||
|
<li>weekTemplate (if <code>#enableWeekNotes</code> is set)</li>
|
||||||
<li>dateTemplate</li>
|
<li>dateTemplate</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>All of these are relations. When Trilium creates a new note for year or
|
<p>All of these are relations. When Trilium creates a new note for year or
|
||||||
month or date, it will take a look at the root and attach a corresponding <code>~template</code> relation
|
month or date, it will take a look at the root and attach a corresponding <code>~template</code> relation
|
||||||
to the newly created role. Using this, you can e.g. create your daily template
|
to the newly created role. Using this, you can e.g. create your daily template
|
||||||
with e.g. checkboxes for daily routine etc.</p>
|
with e.g. checkboxes for daily routine etc.</p>
|
||||||
<h2>Date pattern</h2>
|
<h2>Naming pattern</h2>
|
||||||
|
<p>You can customize the title of generated journal notes by defining a <code>#datePattern</code>, <code>#weekPattern</code>, <code>#monthPattern</code>, <code>#quarterPattern</code> and <code>#yearPattern</code> attribute
|
||||||
|
on a root calendar note (identified by <code>#calendarRoot</code> label).
|
||||||
|
The naming pattern replacements follow a level-up compatibility - each
|
||||||
|
level can use replacements from itself and all levels above it. For example, <code>#monthPattern</code> can
|
||||||
|
use month, quarter and year replacements, while <code>#weekPattern</code> can
|
||||||
|
use week, month, quarter and year replacements. But it is not possible
|
||||||
|
to use week replacements in <code>#monthPattern</code>.</p>
|
||||||
|
<h3>Date pattern</h3>
|
||||||
<p>It's possible to customize the title of generated date notes by defining
|
<p>It's possible to customize the title of generated date notes by defining
|
||||||
a <code>#datePattern</code> label on a root calendar note (identified by <code>#calendarRoot</code> label).
|
a <code>#datePattern</code> attribute on a root calendar note (identified
|
||||||
Following are possible values:</p>
|
by <code>#calendarRoot</code> label). Following are possible values:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>{dayInMonthPadded} - {weekDay}</code> day notes are named e.g. "24
|
<li><code>{isoDate}</code> results in an ISO 8061 formatted date (e.g. "2025-03-09"
|
||||||
- Monday"</li>
|
for March 9, 2025)</li>
|
||||||
<li><code>{dayInMonthPadded}: {weekDay3}</code> day notes are named e.g. "24:
|
<li><code>{dateNumber}</code> results in a number like <code>9</code> for the
|
||||||
Mon"</li>
|
9th day of the month, <code>11</code> for the 11th day of the month</li>
|
||||||
<li><code>{dayInMonthPadded}: {weekDay2}</code> day notes are named e.g. "24:
|
<li><code>{dateNumberPadded}</code> results in a number like <code>09</code> for
|
||||||
Mo"</li>
|
the 9th day of the month, <code>11</code> for the 11th day of the month</li>
|
||||||
<li><code>{isoDate} - {weekDay}</code> day notes are named e.g. "2020-12-24
|
|
||||||
- Monday"</li>
|
|
||||||
<li><code>{ordinal}</code> is replaced with the ordinal date (e.g. 1st, 2nd,
|
<li><code>{ordinal}</code> is replaced with the ordinal date (e.g. 1st, 2nd,
|
||||||
3rd) etc.</li>
|
3rd) etc.</li>
|
||||||
|
<li><code>{weekDay}</code> results in the full day name (e.g. <code>Monday</code>)</li>
|
||||||
|
<li><code>{weekDay3}</code> is replaced with the first 3 letters of the day,
|
||||||
|
e.g. Mon, Tue, etc.</li>
|
||||||
|
<li><code>{weekDay2}</code> is replaced with the first 2 letters of the day,
|
||||||
|
e.g. Mo, Tu, etc.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Month pattern</h2>
|
<p>The default is <code>{dateNumberPadded} - {weekDay}</code>
|
||||||
|
</p>
|
||||||
|
<h3>Week pattern</h3>
|
||||||
|
<p>It is also possible to customize the title of generated week notes through
|
||||||
|
the <code>#weekPattern</code> attribute on the root calendar note. The options
|
||||||
|
are:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>{weekNumber}</code> results in a number like <code>9</code> for the
|
||||||
|
9th week of the year, <code>11</code> for the 11th week of the year</li>
|
||||||
|
<li><code>{weekNumberPadded}</code> results in a number like <code>09</code> for
|
||||||
|
the 9th week of the year, <code>11</code> for the 11th week of the year</li>
|
||||||
|
<li><code>{shortWeek}</code> results in a short week string like <code>W9</code> for
|
||||||
|
the 9th week of the year, <code>W11</code> for the 11th week of the year</li>
|
||||||
|
<li><code>{shortWeek3}</code> results in a short week string like <code>W09</code> for
|
||||||
|
the 9th week of the year, <code>W11</code> for the 11th week of the year</li>
|
||||||
|
</ul>
|
||||||
|
<p>The default is <code>Week {weekNumber}</code>
|
||||||
|
</p>
|
||||||
|
<h3>Month pattern</h3>
|
||||||
<p>It is also possible to customize the title of generated month notes through
|
<p>It is also possible to customize the title of generated month notes through
|
||||||
the <code>#monthPattern</code> attribute, much like <code>#datePattern</code>.
|
the <code>#monthPattern</code> attribute on the root calendar note. The options
|
||||||
The options are:</p>
|
are:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>{isoMonth}</code> results in an ISO 8061 formatted month (e.g. "2025-03"
|
<li><code>{isoMonth}</code> results in an ISO 8061 formatted month (e.g. "2025-03"
|
||||||
for March 2025)</li>
|
for March 2025)</li>
|
||||||
|
<li><code>{monthNumber}</code> results in a number like <code>9</code> for September,
|
||||||
|
and <code>11</code> for November</li>
|
||||||
<li><code>{monthNumberPadded}</code> results in a number like <code>09</code> for
|
<li><code>{monthNumberPadded}</code> results in a number like <code>09</code> for
|
||||||
September, and <code>11</code> for November</li>
|
September, and <code>11</code> for November</li>
|
||||||
<li><code>{month}</code> results in the full month name (e.g. <code>September</code> or <code>October</code>)</li>
|
<li><code>{month}</code> results in the full month name (e.g. <code>September</code> or <code>October</code>)</li>
|
||||||
@ -88,14 +128,37 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<p>The default is <code>{monthNumberPadded} - {month}</code>
|
<p>The default is <code>{monthNumberPadded} - {month}</code>
|
||||||
</p>
|
</p>
|
||||||
|
<h3>Quarter pattern</h3>
|
||||||
|
<p>It is also possible to customize the title of generated quarter notes
|
||||||
|
through the <code>#quarterPattern</code> attribute on the root calendar note.
|
||||||
|
The options are:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>{quarterNumber}</code> results in a number like <code>1</code> for
|
||||||
|
the 1st quarter of the year</li>
|
||||||
|
<li><code>{shortQuarter}</code> results in a short quarter string like <code>Q1</code> for
|
||||||
|
the 1st quarter of the year</li>
|
||||||
|
</ul>
|
||||||
|
<p>The default is <code>Quarter {quarterNumber}</code>
|
||||||
|
</p>
|
||||||
|
<h3>Year pattern</h3>
|
||||||
|
<p>It is also possible to customize the title of generated year notes through
|
||||||
|
the <code>#yearPattern</code> attribute on the root calendar note. The options
|
||||||
|
are:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>{year}</code> results in the full year (e.g. <code>2025</code>)</li>
|
||||||
|
</ul>
|
||||||
|
<p>The default is <code>{year}</code>
|
||||||
|
</p>
|
||||||
<h2>Implementation</h2>
|
<h2>Implementation</h2>
|
||||||
<p>Trilium has some special support for day notes in the form of <a href="https://triliumnext.github.io/Notes/backend_api/BackendScriptApi.html">backend Script API</a> -
|
<p>Trilium has some special support for day notes in the form of <a href="https://triliumnext.github.io/Notes/backend_api/BackendScriptApi.html">backend Script API</a> -
|
||||||
see e.g. getDayNote() function.</p>
|
see e.g. getDayNote() function.</p>
|
||||||
<p>Day (and year, month) notes are created with a label - e.g. <code>#dateNote="2018-08-16"</code> this
|
<p>Day (and year, month) notes are created with a label - e.g. <code>#dateNote="2025-03-09"</code> this
|
||||||
can then be used by other scripts to add new notes to day note etc.</p>
|
can then be used by other scripts to add new notes to day note etc.</p>
|
||||||
<p>Journal also has relation <code>child:child:child:template=Day template</code> (see
|
<p>Journal also has relation <code>child:child:child:template=Day template</code> (see
|
||||||
[[attribute inheritance]]) which effectively adds [[template]] to day notes
|
[[attribute inheritance]]) which effectively adds [[template]] to day notes
|
||||||
(grand-grand-grand children of Journal).</p>
|
(grand-grand-grand children of Journal). Please note that, when you enable
|
||||||
|
week notes or quarter notes, it will not automatically change the relation
|
||||||
|
for the child level.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -21,19 +21,18 @@
|
|||||||
<h2>Alternatives</h2>
|
<h2>Alternatives</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Pressing Ctrl+F while in a browser while not focused in a <a class="reference-link"
|
<li>Pressing Ctrl+F while in a browser while not focused in a <a class="reference-link"
|
||||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> or
|
href="#root/_help_iPIMuisry3hd">Text</a> or a <a class="reference-link"
|
||||||
a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> note
|
href="#root/_help_6f9hih2hXXZk">Code</a> note will trigger the browser's
|
||||||
will trigger the browser's native search. This will also find text that
|
native search. This will also find text that is part of Trilium's UI.</li>
|
||||||
is part of Trilium's UI.</li>
|
<li>Pressing Ctrl+F in a <a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a> note
|
||||||
<li>Pressing Ctrl+F in a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> note
|
will reveal <a class="reference-link" href="#root/_help_MI26XDLSAlCD">CKEditor</a>'s
|
||||||
will reveal <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/1YeN2MzFUluU/_help_MI26XDLSAlCD">CKEditor</a>'s
|
|
||||||
search functionality.</li>
|
search functionality.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Accessing the search</h2>
|
<h2>Accessing the search</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>On desktop, press<kbd>Ctrl</kbd> + <kbd>F</kbd>
|
<li>On desktop, press<kbd>Ctrl</kbd> + <kbd>F</kbd>
|
||||||
</li>
|
</li>
|
||||||
<li>From the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_8YBEPzcpUgxw">Note buttons</a>,
|
<li>From the <a class="reference-link" href="#root/_help_8YBEPzcpUgxw">Note buttons</a>,
|
||||||
look for the context menu and select <em>Search in note</em>.</li>
|
look for the context menu and select <em>Search in note</em>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Interaction</h2>
|
<h2>Interaction</h2>
|
||||||
|
@ -24,10 +24,10 @@
|
|||||||
results as sub-items.</p>
|
results as sub-items.</p>
|
||||||
<h2>Accessing the search</h2>
|
<h2>Accessing the search</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>From the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
<li>From the <a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
||||||
look for the dedicated search button.</li>
|
look for the dedicated search button.</li>
|
||||||
<li>To limit the search to a note and its children, select <em>Search from subtree</em> from
|
<li>To limit the search to a note and its children, select <em>Search from subtree</em> from
|
||||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_YtSN43OrfzaA">Note tree contextual menu</a> or
|
the <a class="reference-link" href="#root/_help_YtSN43OrfzaA">Note tree contextual menu</a> or
|
||||||
press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd>.</li>
|
press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Interaction</h2>
|
<h2>Interaction</h2>
|
||||||
@ -43,8 +43,8 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>To limit the search to a note and its sub-children, set a note in <em>Ancestor</em>.
|
<li>To limit the search to a note and its sub-children, set a note in <em>Ancestor</em>.
|
||||||
<ol>
|
<ol>
|
||||||
<li>This value is also pre-filled if the search is triggered from a <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_OR8WJ7Iz9K4U">hoisted note</a> or
|
<li>This value is also pre-filled if the search is triggered from a <a href="#root/_help_OR8WJ7Iz9K4U">hoisted note</a> or
|
||||||
a <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_9sRHySam5fXb">workspace</a>.</li>
|
a <a href="#root/_help_9sRHySam5fXb">workspace</a>.</li>
|
||||||
<li>To search the entire database, keep the value empty.</li>
|
<li>To search the entire database, keep the value empty.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<li>The <em>Search & Execute actions</em> button is only relevant if at
|
<li>The <em>Search & Execute actions</em> button is only relevant if at
|
||||||
least one action has been added (as described in the section below).</li>
|
least one action has been added (as described in the section below).</li>
|
||||||
<li>The <em>Save to note</em> will create a new note with the search configuration.
|
<li>The <em>Save to note</em> will create a new note with the search configuration.
|
||||||
For more information, see <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a>.</li>
|
For more information, see <a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>.</li>
|
||||||
</ol>
|
</ol>
|
||||||
<h2>Search options</h2>
|
<h2>Search options</h2>
|
||||||
<p>Click on which search option to apply from the Add search option section.</p>
|
<p>Click on which search option to apply from the Add search option section.</p>
|
||||||
@ -71,7 +71,7 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li>Search script
|
<li>Search script
|
||||||
<ol>
|
<ol>
|
||||||
<li>This feature allows writing a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> note
|
<li>This feature allows writing a <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> note
|
||||||
that will handle the search on its own.</li>
|
that will handle the search on its own.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
@ -79,12 +79,12 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li>The search will not look into the content of the notes, but it will still
|
<li>The search will not look into the content of the notes, but it will still
|
||||||
look into note titles and attributes, relations (based on the search query).</li>
|
look into note titles and attributes, relations (based on the search query).</li>
|
||||||
<li>This method can speed up the search considerably for large <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_wX4HbRucYSDD">databases</a>.</li>
|
<li>This method can speed up the search considerably for large <a href="#root/_help_wX4HbRucYSDD">databases</a>.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
<li>Include archived
|
<li>Include archived
|
||||||
<ol>
|
<ol>
|
||||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_MKmLg5x6xkor">Archived Notes</a> will
|
<li><a class="reference-link" href="#root/_help_MKmLg5x6xkor">Archived Notes</a> will
|
||||||
also be included in the results, whereas otherwise they would be ignored.</li>
|
also be included in the results, whereas otherwise they would be ignored.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
@ -107,7 +107,7 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li>This will print additional information in the server log (see
|
<li>This will print additional information in the server log (see
|
||||||
<a
|
<a
|
||||||
class="reference-link" href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_qzNzp9LYQyPT">Error logs</a>), regarding how the search expression was parsed.</li>
|
class="reference-link" href="#root/_help_qzNzp9LYQyPT">Error logs</a>), regarding how the search expression was parsed.</li>
|
||||||
<li>This function is especially useful after understanding the search functionality
|
<li>This function is especially useful after understanding the search functionality
|
||||||
in detail, in order to determine why a complex search query is not working
|
in detail, in order to determine why a complex search query is not working
|
||||||
as expected.</li>
|
as expected.</li>
|
||||||
@ -122,10 +122,9 @@
|
|||||||
action multiple times (i.e. in order to be able to apply multiple labels
|
action multiple times (i.e. in order to be able to apply multiple labels
|
||||||
to notes).</li>
|
to notes).</li>
|
||||||
<li>The actions given are the same as the ones in <a class="reference-link"
|
<li>The actions given are the same as the ones in <a class="reference-link"
|
||||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_ivYnonVFBxbQ">Bulk Actions</a>,
|
href="#root/_help_ivYnonVFBxbQ">Bulk Actions</a>, which is an alternative
|
||||||
which is an alternative for operating directly with notes within the
|
for operating directly with notes within the <a class="reference-link"
|
||||||
<a
|
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
|
||||||
<li>After defining the actions, first press <em>Search</em> to check the matched
|
<li>After defining the actions, first press <em>Search</em> to check the matched
|
||||||
notes and then press <em>Search & Execute actions</em> to trigger the
|
notes and then press <em>Search & Execute actions</em> to trigger the
|
||||||
actions.</li>
|
actions.</li>
|
||||||
|
@ -22,14 +22,22 @@ async function getDayNote(date: string) {
|
|||||||
return await froca.getNote(note.noteId);
|
return await froca.getNote(note.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWeekNote(date: string) {
|
async function getWeekFirstDayNote(date: string) {
|
||||||
const note = await server.get<FNoteRow>(`special-notes/weeks/${date}`, "date-note");
|
const note = await server.get<FNoteRow>(`special-notes/week-first-day/${date}`, "date-note");
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
return await froca.getNote(note.noteId);
|
return await froca.getNote(note.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getWeekNote(week: string) {
|
||||||
|
const note = await server.get<FNoteRow>(`special-notes/weeks/${week}`, "date-note");
|
||||||
|
|
||||||
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
|
return await froca.getNote(note?.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
async function getMonthNote(month: string) {
|
async function getMonthNote(month: string) {
|
||||||
const note = await server.get<FNoteRow>(`special-notes/months/${month}`, "date-note");
|
const note = await server.get<FNoteRow>(`special-notes/months/${month}`, "date-note");
|
||||||
|
|
||||||
@ -38,6 +46,14 @@ async function getMonthNote(month: string) {
|
|||||||
return await froca.getNote(note.noteId);
|
return await froca.getNote(note.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getQuarterNote(quarter: string) {
|
||||||
|
const note = await server.get<FNoteRow>(`special-notes/quarters/${quarter}`, "date-note");
|
||||||
|
|
||||||
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
|
return await froca.getNote(note.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
async function getYearNote(year: string) {
|
async function getYearNote(year: string) {
|
||||||
const note = await server.get<FNoteRow>(`special-notes/years/${year}`, "date-note");
|
const note = await server.get<FNoteRow>(`special-notes/years/${year}`, "date-note");
|
||||||
|
|
||||||
@ -66,7 +82,9 @@ export default {
|
|||||||
getInboxNote,
|
getInboxNote,
|
||||||
getTodayNote,
|
getTodayNote,
|
||||||
getDayNote,
|
getDayNote,
|
||||||
|
getWeekFirstDayNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
|
getQuarterNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
createSqlConsole,
|
createSqlConsole,
|
||||||
|
@ -363,6 +363,14 @@ interface Api {
|
|||||||
*
|
*
|
||||||
* @param date - e.g. "2019-04-29"
|
* @param date - e.g. "2019-04-29"
|
||||||
*/
|
*/
|
||||||
|
getWeekFirstDayNote: typeof dateNotesService.getWeekFirstDayNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns week note for given date. If such a note doesn't exist, it is automatically created.
|
||||||
|
*
|
||||||
|
* @param date in YYYY-MM-DD format
|
||||||
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
|
*/
|
||||||
getWeekNote: typeof dateNotesService.getWeekNote;
|
getWeekNote: typeof dateNotesService.getWeekNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,6 +380,14 @@ interface Api {
|
|||||||
*/
|
*/
|
||||||
getMonthNote: typeof dateNotesService.getMonthNote;
|
getMonthNote: typeof dateNotesService.getMonthNote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns quarter note for given date. If such a note doesn't exist, it is automatically created.
|
||||||
|
*
|
||||||
|
* @param date in YYYY-MM format
|
||||||
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
|
*/
|
||||||
|
getQuarterNote: typeof dateNotesService.getQuarterNote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns year-note. If it doesn't exist, it is automatically created.
|
* Returns year-note. If it doesn't exist, it is automatically created.
|
||||||
*
|
*
|
||||||
@ -651,8 +667,10 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
|
|||||||
|
|
||||||
this.getTodayNote = dateNotesService.getTodayNote;
|
this.getTodayNote = dateNotesService.getTodayNote;
|
||||||
this.getDayNote = dateNotesService.getDayNote;
|
this.getDayNote = dateNotesService.getDayNote;
|
||||||
|
this.getWeekFirstDayNote = dateNotesService.getWeekFirstDayNote;
|
||||||
this.getWeekNote = dateNotesService.getWeekNote;
|
this.getWeekNote = dateNotesService.getWeekNote;
|
||||||
this.getMonthNote = dateNotesService.getMonthNote;
|
this.getMonthNote = dateNotesService.getMonthNote;
|
||||||
|
this.getQuarterNote = dateNotesService.getQuarterNote;
|
||||||
this.getYearNote = dateNotesService.getYearNote;
|
this.getYearNote = dateNotesService.getYearNote;
|
||||||
|
|
||||||
this.setHoistedNoteId = (noteId) => {
|
this.setHoistedNoteId = (noteId) => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { t } from "../../services/i18n.js";
|
import { t } from "../../services/i18n.js";
|
||||||
import utils from "../../services/utils.js";
|
|
||||||
import dateNoteService from "../../services/date_notes.js";
|
import dateNoteService from "../../services/date_notes.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
@ -8,8 +7,15 @@ import toastService from "../../services/toast.js";
|
|||||||
import options from "../../services/options.js";
|
import options from "../../services/options.js";
|
||||||
import { Dropdown } from "bootstrap";
|
import { Dropdown } from "bootstrap";
|
||||||
import type { EventData } from "../../components/app_context.js";
|
import type { EventData } from "../../components/app_context.js";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc.js";
|
||||||
|
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||||
|
import type BAttribute from "../../../../becca/entities/battribute.js";
|
||||||
import "../../../stylesheets/calendar.css";
|
import "../../../stylesheets/calendar.css";
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(isSameOrAfter);
|
||||||
|
|
||||||
const MONTHS = [
|
const MONTHS = [
|
||||||
t("calendar.january"),
|
t("calendar.january"),
|
||||||
t("calendar.febuary"),
|
t("calendar.febuary"),
|
||||||
@ -29,7 +35,7 @@ const DROPDOWN_TPL = `
|
|||||||
<div class="calendar-dropdown-widget">
|
<div class="calendar-dropdown-widget">
|
||||||
<style>
|
<style>
|
||||||
.calendar-dropdown-widget {
|
.calendar-dropdown-widget {
|
||||||
width: 350px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -59,9 +65,7 @@ const DROPDOWN_TPL = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calendar-week">
|
<div class="calendar-week"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="calendar-body" data-calendar-area="month"></div>
|
<div class="calendar-body" data-calendar-area="month"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
@ -71,6 +75,11 @@ interface DateNotesForMonth {
|
|||||||
[date: string]: string;
|
[date: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WeekCalculationOptions {
|
||||||
|
firstWeekType: number;
|
||||||
|
minDaysInFirstWeek: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default class CalendarWidget extends RightDropdownButtonWidget {
|
export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||||
private $month!: JQuery<HTMLElement>;
|
private $month!: JQuery<HTMLElement>;
|
||||||
private $weekHeader!: JQuery<HTMLElement>;
|
private $weekHeader!: JQuery<HTMLElement>;
|
||||||
@ -82,9 +91,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
private $previousYear!: JQuery<HTMLElement>;
|
private $previousYear!: JQuery<HTMLElement>;
|
||||||
private monthDropdown!: Dropdown;
|
private monthDropdown!: Dropdown;
|
||||||
private firstDayOfWeek!: number;
|
private firstDayOfWeek!: number;
|
||||||
private activeDate: Date | null = null;
|
private weekCalculationOptions!: WeekCalculationOptions;
|
||||||
private todaysDate!: Date;
|
private activeDate: Dayjs | null = null;
|
||||||
private date!: Date;
|
private todaysDate!: Dayjs;
|
||||||
|
private date!: Dayjs;
|
||||||
|
private weekNoteEnable: boolean = false;
|
||||||
|
private weekNotes: string[] = [];
|
||||||
|
|
||||||
constructor(title: string = "", icon: string = "") {
|
constructor(title: string = "", icon: string = "") {
|
||||||
super(title, icon, DROPDOWN_TPL);
|
super(title, icon, DROPDOWN_TPL);
|
||||||
@ -97,6 +109,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
|
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
|
||||||
|
|
||||||
this.manageFirstDayOfWeek();
|
this.manageFirstDayOfWeek();
|
||||||
|
this.initWeekCalculation();
|
||||||
|
|
||||||
// Month navigation
|
// Month navigation
|
||||||
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
|
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
|
||||||
@ -109,18 +122,18 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
const value = target.dataset.value;
|
const value = target.dataset.value;
|
||||||
if (value) {
|
if (value) {
|
||||||
this.date.setMonth(parseInt(value));
|
this.date = this.date.month(parseInt(value));
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
|
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
|
||||||
this.$next.on("click", () => {
|
this.$next.on("click", () => {
|
||||||
this.date.setMonth(this.date.getMonth() + 1);
|
this.date = this.date.add(1, 'month');
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
});
|
});
|
||||||
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
||||||
this.$previous.on("click", () => {
|
this.$previous.on("click", () => {
|
||||||
this.date.setMonth(this.date.getMonth() - 1);
|
this.date = this.date.subtract(1, 'month');
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,17 +141,17 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
this.$yearSelect = this.$dropdownContent.find('[data-calendar-input="year"]');
|
this.$yearSelect = this.$dropdownContent.find('[data-calendar-input="year"]');
|
||||||
this.$yearSelect.on("input", (e) => {
|
this.$yearSelect.on("input", (e) => {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
this.date.setFullYear(parseInt(target.value));
|
this.date = this.date.year(parseInt(target.value));
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
});
|
});
|
||||||
this.$nextYear = this.$dropdownContent.find('[data-calendar-toggle="nextYear"]');
|
this.$nextYear = this.$dropdownContent.find('[data-calendar-toggle="nextYear"]');
|
||||||
this.$nextYear.on("click", () => {
|
this.$nextYear.on("click", () => {
|
||||||
this.date.setFullYear(this.date.getFullYear() + 1);
|
this.date = this.date.add(1, 'year');
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
});
|
});
|
||||||
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
||||||
this.$previousYear.on("click", () => {
|
this.$previousYear.on("click", () => {
|
||||||
this.date.setFullYear(this.date.getFullYear() - 1);
|
this.date = this.date.subtract(1, 'year');
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,6 +172,27 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$dropdownContent.on("click", ".calendar-week-number", async (ev) => {
|
||||||
|
if (!this.weekNoteEnable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const week = $(ev.target).closest(".calendar-week-number").attr("data-calendar-week-number");
|
||||||
|
|
||||||
|
if (week) {
|
||||||
|
const note = await dateNoteService.getWeekNote(week);
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
appContext.tabManager.getActiveContext()?.setNote(note.noteId);
|
||||||
|
this.dropdown?.hide();
|
||||||
|
} else {
|
||||||
|
toastService.showError(t("calendar.cannot_find_week_note"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
// Handle click events for the entire calendar widget
|
// Handle click events for the entire calendar widget
|
||||||
this.$dropdownContent.on("click", (e) => {
|
this.$dropdownContent.on("click", (e) => {
|
||||||
const $target = $(e.target);
|
const $target = $(e.target);
|
||||||
@ -177,57 +211,139 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getWeekNoteEnable() {
|
||||||
|
const noteId = await server.get<string[]>(`search/${encodeURIComponent('#calendarRoot')}`);
|
||||||
|
if (noteId.length === 0) {
|
||||||
|
this.weekNoteEnable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const noteAttributes = await server.get<BAttribute[]>(`notes/${noteId}/attributes`);
|
||||||
|
|
||||||
|
for (const attribute of noteAttributes) {
|
||||||
|
if (attribute.name === 'enableWeekNote') {
|
||||||
|
this.weekNoteEnable = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.weekNoteEnable = false;
|
||||||
|
}
|
||||||
|
|
||||||
manageFirstDayOfWeek() {
|
manageFirstDayOfWeek() {
|
||||||
this.firstDayOfWeek = options.getInt("firstDayOfWeek") || 0;
|
this.firstDayOfWeek = options.getInt("firstDayOfWeek") || 0;
|
||||||
|
|
||||||
// Generate the list of days of the week taking into consideration the user's selected first day of week.
|
// Generate the list of days of the week taking into consideration the user's selected first day of week.
|
||||||
let localeDaysOfWeek = [...DAYS_OF_WEEK];
|
let localeDaysOfWeek = [...DAYS_OF_WEEK];
|
||||||
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
|
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
|
||||||
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
|
localeDaysOfWeek = ['', ...localeDaysOfWeek, ...daysToBeAddedAtEnd];
|
||||||
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`).join(''));
|
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`).join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initWeekCalculation() {
|
||||||
|
this.weekCalculationOptions = {
|
||||||
|
firstWeekType: options.getInt("firstWeekOfYear") || 0,
|
||||||
|
minDaysInFirstWeek: options.getInt("minDaysInFirstWeek") || 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeekNumber(date: Dayjs): number {
|
||||||
|
const year = date.year();
|
||||||
|
const dayOfWeek = (day: number) => (day - this.firstDayOfWeek + 7) % 7;
|
||||||
|
|
||||||
|
// Get first day of the year and adjust to first week start
|
||||||
|
const jan1 = date.clone().year(year).month(0).date(1);
|
||||||
|
const jan1Weekday = jan1.day();
|
||||||
|
const dayOffset = dayOfWeek(jan1Weekday);
|
||||||
|
let firstWeekStart = jan1.clone().subtract(dayOffset, 'day');
|
||||||
|
|
||||||
|
// Adjust based on week rule
|
||||||
|
switch (this.weekCalculationOptions.firstWeekType) {
|
||||||
|
case 1: { // ISO 8601: first week contains Thursday
|
||||||
|
const thursday = firstWeekStart.clone().add(3, 'day'); // Monday + 3 = Thursday
|
||||||
|
if (thursday.year() < year) {
|
||||||
|
firstWeekStart = firstWeekStart.add(7, 'day');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: { // minDaysInFirstWeek rule
|
||||||
|
const daysInFirstWeek = 7 - dayOffset;
|
||||||
|
if (daysInFirstWeek < this.weekCalculationOptions.minDaysInFirstWeek) {
|
||||||
|
firstWeekStart = firstWeekStart.add(7, 'day');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// default case 0: week containing Jan 1 → already handled
|
||||||
|
}
|
||||||
|
|
||||||
|
const diffDays = date.startOf('day').diff(firstWeekStart.startOf('day'), 'day');
|
||||||
|
const weekNumber = Math.floor(diffDays / 7) + 1;
|
||||||
|
|
||||||
|
// Handle case when date is before first week start → belongs to last week of previous year
|
||||||
|
if (weekNumber <= 0) {
|
||||||
|
return this.getWeekNumber(date.subtract(1, 'day'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case when date belongs to first week of next year
|
||||||
|
const nextYear = year + 1;
|
||||||
|
const jan1Next = date.clone().year(nextYear).month(0).date(1);
|
||||||
|
const jan1WeekdayNext = jan1Next.day();
|
||||||
|
const offsetNext = dayOfWeek(jan1WeekdayNext);
|
||||||
|
let nextYearWeekStart = jan1Next.clone().subtract(offsetNext, 'day');
|
||||||
|
|
||||||
|
switch (this.weekCalculationOptions.firstWeekType) {
|
||||||
|
case 1: {
|
||||||
|
const thursday = nextYearWeekStart.clone().add(3, 'day');
|
||||||
|
if (thursday.year() < nextYear) {
|
||||||
|
nextYearWeekStart = nextYearWeekStart.add(7, 'day');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
const daysInFirstWeek = 7 - offsetNext;
|
||||||
|
if (daysInFirstWeek < this.weekCalculationOptions.minDaysInFirstWeek) {
|
||||||
|
nextYearWeekStart = nextYearWeekStart.add(7, 'day');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date.isSameOrAfter(nextYearWeekStart)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return weekNumber;
|
||||||
|
}
|
||||||
|
|
||||||
async dropdownShown() {
|
async dropdownShown() {
|
||||||
|
await this.getWeekNoteEnable();
|
||||||
|
this.weekNotes = await server.get<string[]>(`attribute-values/weekNote`);
|
||||||
this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null);
|
this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(activeDate: string | null) {
|
init(activeDate: string | null) {
|
||||||
// attaching time fixes local timezone handling
|
// attaching time fixes local timezone handling
|
||||||
this.activeDate = activeDate ? new Date(`${activeDate}T12:00:00`) : null;
|
this.activeDate = activeDate ? dayjs(`${activeDate}T12:00:00`) : null;
|
||||||
this.todaysDate = new Date();
|
this.todaysDate = dayjs();
|
||||||
this.date = new Date((this.activeDate || this.todaysDate).getTime());
|
this.date = dayjs(this.activeDate || this.todaysDate).startOf('month');
|
||||||
this.date.setDate(1);
|
|
||||||
|
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
}
|
}
|
||||||
|
|
||||||
createDay(dateNotesForMonth: DateNotesForMonth, num: number, day: number) {
|
createDay(dateNotesForMonth: DateNotesForMonth, num: number) {
|
||||||
const $newDay = $("<a>").addClass("calendar-date").attr("data-calendar-date", utils.formatDateISO(this.date));
|
const $newDay = $("<a>").addClass("calendar-date").attr("data-calendar-date", this.date.local().format('YYYY-MM-DD'));
|
||||||
const $date = $("<span>").html(String(num));
|
const $date = $("<span>").html(String(num));
|
||||||
|
|
||||||
// if it's the first day of the month
|
const dateNoteId = dateNotesForMonth[this.date.local().format('YYYY-MM-DD')];
|
||||||
if (num === 1) {
|
|
||||||
// 0 1 2 3 4 5 6
|
|
||||||
// Su Mo Tu We Th Fr Sa
|
|
||||||
// 1 2 3 4 5 6 0
|
|
||||||
// Mo Tu We Th Fr Sa Su
|
|
||||||
let dayOffset = day - this.firstDayOfWeek;
|
|
||||||
if (dayOffset < 0) dayOffset = 7 + dayOffset;
|
|
||||||
$newDay.css("marginLeft", dayOffset * 14.28 + "%");
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateNoteId = dateNotesForMonth[utils.formatDateISO(this.date)];
|
|
||||||
|
|
||||||
if (dateNoteId) {
|
if (dateNoteId) {
|
||||||
$newDay.addClass("calendar-date-exists");
|
$newDay.addClass("calendar-date-exists");
|
||||||
$newDay.attr("data-href", `#root/${dateNoteId}`);
|
$newDay.attr("data-href", `#root/${dateNoteId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isEqual(this.date, this.activeDate)) {
|
if (this.date.isSame(this.activeDate, 'day')) {
|
||||||
$newDay.addClass("calendar-date-active");
|
$newDay.addClass("calendar-date-active");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isEqual(this.date, this.todaysDate)) {
|
if (this.date.isSame(this.todaysDate, 'day')) {
|
||||||
$newDay.addClass("calendar-date-today");
|
$newDay.addClass("calendar-date-today");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,44 +351,140 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
|||||||
return $newDay;
|
return $newDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEqual(a: Date, b: Date | null) {
|
createWeekNumber(weekNumber: number) {
|
||||||
if ((!a && b) || (a && !b)) {
|
const weekNoteId = this.date.local().format('YYYY-') + 'W' + String(weekNumber).padStart(2, '0');
|
||||||
return false;
|
|
||||||
|
let $newWeekNumber;
|
||||||
|
if (this.weekNoteEnable) {
|
||||||
|
// Utilize the hover effect of calendar-date
|
||||||
|
$newWeekNumber = $("<a>").addClass("calendar-date");
|
||||||
|
|
||||||
|
if (this.weekNotes.includes(weekNoteId)) {
|
||||||
|
$newWeekNumber.addClass("calendar-date-exists");
|
||||||
|
$newWeekNumber.attr("data-href", `#root/${weekNoteId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$newWeekNumber = $("<span>").addClass("calendar-week-number-disabled");
|
||||||
|
}
|
||||||
|
$newWeekNumber.addClass("calendar-week-number").attr("data-calendar-week-number", weekNoteId);
|
||||||
|
$newWeekNumber.append($("<span>").html(String(weekNumber)));
|
||||||
|
|
||||||
|
return $newWeekNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPrevMonthDays(firstDayOfWeek: number): { weekNumber: number, dates: Dayjs[] } {
|
||||||
|
const prevMonthLastDay = this.date.subtract(1, 'month').endOf('month');
|
||||||
|
const daysToAdd = (firstDayOfWeek - this.firstDayOfWeek + 7) % 7;
|
||||||
|
const dates: Dayjs[] = [];
|
||||||
|
|
||||||
|
const firstDay = this.date.startOf('month');
|
||||||
|
const weekNumber = this.getWeekNumber(firstDay);
|
||||||
|
|
||||||
|
// Get dates from previous month
|
||||||
|
for (let i = daysToAdd - 1; i >= 0; i--) {
|
||||||
|
dates.push(prevMonthLastDay.subtract(i, 'day'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!b) return false;
|
return { weekNumber, dates };
|
||||||
|
}
|
||||||
|
|
||||||
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
private getNextMonthDays(lastDayOfWeek: number): Dayjs[] {
|
||||||
|
const nextMonthFirstDay = this.date.add(1, 'month').startOf('month');
|
||||||
|
const dates: Dayjs[] = [];
|
||||||
|
|
||||||
|
const lastDayOfUserWeek = (this.firstDayOfWeek + 6) % 7;
|
||||||
|
const daysToAdd = (lastDayOfUserWeek - lastDayOfWeek + 7) % 7;
|
||||||
|
|
||||||
|
// Get dates from next month
|
||||||
|
for (let i = 0; i < daysToAdd; i++) {
|
||||||
|
dates.push(nextMonthFirstDay.add(i, 'day'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMonth() {
|
async createMonth() {
|
||||||
const month = utils.formatDateISO(this.date).substr(0, 7);
|
const month = this.date.format('YYYY-MM');
|
||||||
const dateNotesForMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${month}`);
|
const dateNotesForMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${month}`);
|
||||||
|
|
||||||
this.$month.empty();
|
this.$month.empty();
|
||||||
|
|
||||||
const currentMonth = this.date.getMonth();
|
const firstDay = this.date.startOf('month');
|
||||||
while (this.date.getMonth() === currentMonth) {
|
const firstDayOfWeek = firstDay.day();
|
||||||
const $day = this.createDay(dateNotesForMonth, this.date.getDate(), this.date.getDay());
|
|
||||||
|
|
||||||
|
// Add dates from previous month
|
||||||
|
if (firstDayOfWeek !== this.firstDayOfWeek) {
|
||||||
|
const { weekNumber, dates } = this.getPrevMonthDays(firstDayOfWeek);
|
||||||
|
|
||||||
|
const prevMonth = this.date.subtract(1, 'month').format('YYYY-MM');
|
||||||
|
const dateNotesForPrevMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${prevMonth}`);
|
||||||
|
|
||||||
|
const $weekNumber = this.createWeekNumber(weekNumber);
|
||||||
|
this.$month.append($weekNumber);
|
||||||
|
|
||||||
|
dates.forEach(date => {
|
||||||
|
const tempDate = this.date;
|
||||||
|
this.date = date;
|
||||||
|
const $day = this.createDay(dateNotesForPrevMonth, date.date());
|
||||||
|
$day.addClass('calendar-date-prev-month');
|
||||||
|
this.$month.append($day);
|
||||||
|
this.date = tempDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMonth = this.date.month();
|
||||||
|
|
||||||
|
while (this.date.month() === currentMonth) {
|
||||||
|
const weekNumber = this.getWeekNumber(this.date);
|
||||||
|
|
||||||
|
// Add week number if it's first day of week
|
||||||
|
if (this.date.day() === this.firstDayOfWeek) {
|
||||||
|
const $weekNumber = this.createWeekNumber(weekNumber);
|
||||||
|
this.$month.append($weekNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
const $day = this.createDay(dateNotesForMonth, this.date.date());
|
||||||
this.$month.append($day);
|
this.$month.append($day);
|
||||||
|
|
||||||
this.date.setDate(this.date.getDate() + 1);
|
this.date = this.date.add(1, 'day');
|
||||||
}
|
}
|
||||||
// while loop trips over and day is at 30/31, bring it back
|
// while loop trips over and day is at 30/31, bring it back
|
||||||
this.date.setDate(1);
|
this.date = this.date.startOf('month').subtract(1, 'month');
|
||||||
this.date.setMonth(this.date.getMonth() - 1);
|
|
||||||
|
|
||||||
this.$monthSelect.text(MONTHS[this.date.getMonth()]);
|
// Add dates from next month
|
||||||
this.$yearSelect.val(this.date.getFullYear());
|
const lastDayOfMonth = this.date.endOf('month');
|
||||||
|
const lastDayOfWeek = lastDayOfMonth.day();
|
||||||
|
const lastDayOfUserWeek = (this.firstDayOfWeek + 6) % 7;
|
||||||
|
if (lastDayOfWeek !== lastDayOfUserWeek) {
|
||||||
|
const dates = this.getNextMonthDays(lastDayOfWeek);
|
||||||
|
|
||||||
|
const nextMonth = this.date.add(1, 'month').format('YYYY-MM');
|
||||||
|
const dateNotesForNextMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${nextMonth}`);
|
||||||
|
|
||||||
|
dates.forEach(date => {
|
||||||
|
const tempDate = this.date;
|
||||||
|
this.date = date;
|
||||||
|
const $day = this.createDay(dateNotesForNextMonth, date.date());
|
||||||
|
$day.addClass('calendar-date-next-month');
|
||||||
|
this.$month.append($day);
|
||||||
|
this.date = tempDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$monthSelect.text(MONTHS[this.date.month()]);
|
||||||
|
this.$yearSelect.val(this.date.year());
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
|
if (!loadResults.getOptionNames().includes("firstDayOfWeek") &&
|
||||||
|
!loadResults.getOptionNames().includes("firstWeekOfYear") &&
|
||||||
|
!loadResults.getOptionNames().includes("minDaysInFirstWeek")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.manageFirstDayOfWeek();
|
this.manageFirstDayOfWeek();
|
||||||
|
this.initWeekCalculation();
|
||||||
this.createMonth();
|
this.createMonth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,41 @@ const TPL = /*html*/`
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="option-row">
|
||||||
|
<label id="first-week-of-year-label">${t("i18n.first-week-of-the-year")}</label>
|
||||||
|
<div role="group" aria-labelledby="first-week-of-year-label">
|
||||||
|
<label class="tn-radio">
|
||||||
|
<input name="first-week-of-year" type="radio" value="0" />
|
||||||
|
${t("i18n.first-week-contains-first-day")}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="tn-radio">
|
||||||
|
<input name="first-week-of-year" type="radio" value="1" />
|
||||||
|
${t("i18n.first-week-contains-first-thursday")}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="tn-radio">
|
||||||
|
<input name="first-week-of-year" type="radio" value="2" />
|
||||||
|
${t("i18n.first-week-has-minimum-days")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="option-row min-days-row" style="display: none;">
|
||||||
|
<label for="min-days-in-first-week">${t("i18n.min-days-in-first-week")}</label>
|
||||||
|
<select id="min-days-in-first-week" class="form-select">
|
||||||
|
${Array.from({ length: 7 }, (_, i) => i + 1)
|
||||||
|
.map(num => `<option value="${num}">${num}</option>`)
|
||||||
|
.join('')}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="form-text">${t("i18n.first-week-info")}</p>
|
||||||
|
|
||||||
|
<div class="admonition warning" role="alert">
|
||||||
|
${t("i18n.first-week-warning")}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="option-row centered">
|
<div class="option-row centered">
|
||||||
<button class="btn btn-secondary btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button>
|
<button class="btn btn-secondary btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -64,6 +99,16 @@ const TPL = /*html*/`
|
|||||||
.locale-options-container .option-row.centered {
|
.locale-options-container .option-row.centered {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.locale-options-container .option-row [aria-labelledby="first-week-of-year-label"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-options-container .option-row [aria-labelledby="first-week-of-year-label"] .tn-radio {
|
||||||
|
margin-left: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -72,10 +117,13 @@ export default class LocalizationOptions extends OptionsWidget {
|
|||||||
|
|
||||||
private $localeSelect!: JQuery<HTMLElement>;
|
private $localeSelect!: JQuery<HTMLElement>;
|
||||||
private $formattingLocaleSelect!: JQuery<HTMLElement>;
|
private $formattingLocaleSelect!: JQuery<HTMLElement>;
|
||||||
|
private $minDaysRow!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
|
|
||||||
|
this.$minDaysRow = this.$widget.find(".min-days-row");
|
||||||
|
|
||||||
this.$localeSelect = this.$widget.find(".locale-select");
|
this.$localeSelect = this.$widget.find(".locale-select");
|
||||||
this.$localeSelect.on("change", async () => {
|
this.$localeSelect.on("change", async () => {
|
||||||
const newLocale = this.$localeSelect.val();
|
const newLocale = this.$localeSelect.val();
|
||||||
@ -92,6 +140,30 @@ export default class LocalizationOptions extends OptionsWidget {
|
|||||||
const firstDayOfWeek = String(this.$widget.find(`input[name="first-day-of-week"]:checked`).val());
|
const firstDayOfWeek = String(this.$widget.find(`input[name="first-day-of-week"]:checked`).val());
|
||||||
this.updateOption("firstDayOfWeek", firstDayOfWeek);
|
this.updateOption("firstDayOfWeek", firstDayOfWeek);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$widget.find('input[name="first-week-of-year"]').on('change', (e) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
const value = parseInt(target.value);
|
||||||
|
|
||||||
|
if (value === 2) {
|
||||||
|
this.$minDaysRow.show();
|
||||||
|
} else {
|
||||||
|
this.$minDaysRow.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateOption("firstWeekOfYear", value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentValue = this.$widget.find('input[name="first-week-of-year"]:checked').val();
|
||||||
|
if (currentValue === 2) {
|
||||||
|
this.$minDaysRow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$widget.find("#min-days-in-first-week").on("change", () => {
|
||||||
|
const minDays = this.$widget.find("#min-days-in-first-week").val();
|
||||||
|
this.updateOption("minDaysInFirstWeek", minDays);
|
||||||
|
});
|
||||||
|
|
||||||
this.$widget.find(".restart-app-button").on("click", utils.restartDesktopApp);
|
this.$widget.find(".restart-app-button").on("click", utils.restartDesktopApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +191,15 @@ export default class LocalizationOptions extends OptionsWidget {
|
|||||||
this.$formattingLocaleSelect.val(options.formattingLocale);
|
this.$formattingLocaleSelect.val(options.formattingLocale);
|
||||||
|
|
||||||
this.$widget.find(`input[name="first-day-of-week"][value="${options.firstDayOfWeek}"]`)
|
this.$widget.find(`input[name="first-day-of-week"][value="${options.firstDayOfWeek}"]`)
|
||||||
.prop("checked", "true");
|
.prop("checked", "true");
|
||||||
|
|
||||||
|
this.$widget.find(`input[name="first-week-of-year"][value="${options.firstWeekOfYear}"]`)
|
||||||
|
.prop("checked", "true");
|
||||||
|
|
||||||
|
if (parseInt(options.firstWeekOfYear) === 2) {
|
||||||
|
this.$minDaysRow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$widget.find("#min-days-in-first-week").val(options.minDaysInFirstWeek);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { FilterOptionsByType, OptionDefinitions, OptionMap, OptionNames } from "../../../../../services/options_interface.js";
|
import type { FilterOptionsByType, OptionMap, OptionNames } from "../../../../../services/options_interface.js";
|
||||||
import type { EventData, EventListener } from "../../../components/app_context.js";
|
import type { EventData, EventListener } from "../../../components/app_context.js";
|
||||||
import type FNote from "../../../entities/fnote.js";
|
import type FNote from "../../../entities/fnote.js";
|
||||||
import { t } from "../../../services/i18n.js";
|
import { t } from "../../../services/i18n.js";
|
||||||
@ -45,7 +45,7 @@ export default class OptionsWidget extends NoteContextAwareWidget implements Eve
|
|||||||
$checkbox.prop("checked", optionValue === "true");
|
$checkbox.prop("checked", optionValue === "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsLoaded(options: OptionMap) {}
|
optionsLoaded(options: OptionMap) { }
|
||||||
|
|
||||||
async refresh() {
|
async refresh() {
|
||||||
this.toggleInt(this.isEnabled());
|
this.toggleInt(this.isEnabled());
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
padding: 0 0.5rem 0.5rem 0.5rem;
|
padding: 0 0.5rem 0.5rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-dropdown-widget .calendar-header > div {
|
.calendar-dropdown-widget .calendar-header>div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -67,7 +67,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.calendar-dropdown-widget .calendar-header .dropdown-toggle::after {
|
.calendar-dropdown-widget .calendar-header .dropdown-toggle::after {
|
||||||
border: unset; /* Disable the dropdown arrow */
|
border: unset;
|
||||||
|
/* Disable the dropdown arrow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-dropdown-widget .calendar-week {
|
.calendar-dropdown-widget .calendar-week {
|
||||||
@ -77,10 +78,10 @@
|
|||||||
|
|
||||||
.calendar-dropdown-widget .calendar-week span {
|
.calendar-dropdown-widget .calendar-week span {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 0 0 14.28%;
|
flex: 0 0 12.5%;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
max-width: 14.28%;
|
max-width: 12.5%;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -92,13 +93,40 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown-widget .calendar-week-number {
|
||||||
|
color: var(--muted-text-color) !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown-widget .calendar-week-number::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: var(--main-border-color);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown-widget .calendar-week-number-disabled {
|
||||||
|
align-items: center;
|
||||||
|
color: var(--main-text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 0 0 12.5%;
|
||||||
|
max-width: 12.5%;
|
||||||
|
padding: 0.4rem 0;
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-dropdown-widget .calendar-date {
|
.calendar-dropdown-widget .calendar-date {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 0 0 14.28%;
|
flex: 0 0 12.5%;
|
||||||
max-width: 14.28%;
|
max-width: 12.5%;
|
||||||
padding: 0.4rem 0;
|
padding: 0.4rem 0;
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
@ -129,3 +157,17 @@
|
|||||||
.calendar-dropdown-widget .calendar-date:not(.calendar-date-active) {
|
.calendar-dropdown-widget .calendar-date:not(.calendar-date-active) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown-widget .calendar-date-prev-month,
|
||||||
|
.calendar-dropdown-widget .calendar-date-next-month {
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-dropdown-widget .calendar-date-prev-month:hover,
|
||||||
|
.calendar-dropdown-widget .calendar-date-next-month:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: var(--hover-item-background-color);
|
||||||
|
color: var(--hover-item-text-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
@ -334,7 +334,6 @@ body.layout-horizontal > .horizontal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.calendar-dropdown-widget .calendar-header {
|
.calendar-dropdown-widget .calendar-header {
|
||||||
padding: 8px 0 20px 0;
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,6 +588,7 @@
|
|||||||
"sat": "六",
|
"sat": "六",
|
||||||
"sun": "日",
|
"sun": "日",
|
||||||
"cannot_find_day_note": "无法找到日记",
|
"cannot_find_day_note": "无法找到日记",
|
||||||
|
"cannot_find_week_note": "无法找到周记",
|
||||||
"january": "一月",
|
"january": "一月",
|
||||||
"febuary": "二月",
|
"febuary": "二月",
|
||||||
"march": "三月",
|
"march": "三月",
|
||||||
@ -1231,7 +1232,15 @@
|
|||||||
"language": "语言",
|
"language": "语言",
|
||||||
"first-day-of-the-week": "一周的第一天",
|
"first-day-of-the-week": "一周的第一天",
|
||||||
"sunday": "周日",
|
"sunday": "周日",
|
||||||
"monday": "周一"
|
"monday": "周一",
|
||||||
|
"first-week-of-the-year": "一年的第一周",
|
||||||
|
"first-week-contains-first-day": "第一周包含一年的第一天",
|
||||||
|
"first-week-contains-first-thursday": "第一周包含一年的第一个周四",
|
||||||
|
"first-week-has-minimum-days": "第一周有最小天数",
|
||||||
|
"min-days-in-first-week": "第一周的最小天数",
|
||||||
|
"first-week-info": "第一周包含一年的第一个周四,基于 <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a> 标准。",
|
||||||
|
"first-week-warning": "更改第一周选项可能会导致与现有周笔记重复,已创建的周笔记将不会相应更新。",
|
||||||
|
"formatting-locale": "日期和数字格式"
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"automatic_backup": "自动备份",
|
"automatic_backup": "自动备份",
|
||||||
|
@ -588,6 +588,7 @@
|
|||||||
"sat": "Sat",
|
"sat": "Sat",
|
||||||
"sun": "Sun",
|
"sun": "Sun",
|
||||||
"cannot_find_day_note": "Cannot find day note",
|
"cannot_find_day_note": "Cannot find day note",
|
||||||
|
"cannot_find_week_note": "Cannot find week note",
|
||||||
"january": "January",
|
"january": "January",
|
||||||
"febuary": "February",
|
"febuary": "February",
|
||||||
"march": "March",
|
"march": "March",
|
||||||
@ -1242,6 +1243,13 @@
|
|||||||
"first-day-of-the-week": "First day of the week",
|
"first-day-of-the-week": "First day of the week",
|
||||||
"sunday": "Sunday",
|
"sunday": "Sunday",
|
||||||
"monday": "Monday",
|
"monday": "Monday",
|
||||||
|
"first-week-of-the-year": "First week of the year",
|
||||||
|
"first-week-contains-first-day": "First week contains first day of the year",
|
||||||
|
"first-week-contains-first-thursday": "First week contains first Thursday of the year",
|
||||||
|
"first-week-has-minimum-days": "First week has minimum days",
|
||||||
|
"min-days-in-first-week": "Minimum days in first week",
|
||||||
|
"first-week-info": "First week contains first Thursday of the year is based on <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a> standard.",
|
||||||
|
"first-week-warning": "Changing first week options may cause duplicate with existing Week Notes and the existing Week Notes will not be updated accordingly.",
|
||||||
"formatting-locale": "Date & number format"
|
"formatting-locale": "Date & number format"
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import type { Request } from "express";
|
import type { Request } from "express";
|
||||||
|
|
||||||
import attributeService from "../../services/attributes.js";
|
|
||||||
import cloneService from "../../services/cloning.js";
|
|
||||||
import noteService from "../../services/notes.js";
|
|
||||||
import dateNoteService from "../../services/date_notes.js";
|
|
||||||
import dateUtils from "../../services/date_utils.js";
|
|
||||||
import imageService from "../../services/image.js";
|
|
||||||
import appInfo from "../../services/app_info.js";
|
|
||||||
import ws from "../../services/ws.js";
|
|
||||||
import log from "../../services/log.js";
|
|
||||||
import utils from "../../services/utils.js";
|
|
||||||
import path from "path";
|
|
||||||
import htmlSanitizer from "../../services/html_sanitizer.js";
|
|
||||||
import attributeFormatter from "../../services/attribute_formatter.js";
|
|
||||||
import jsdom from "jsdom";
|
import jsdom from "jsdom";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
import type BNote from "../../becca/entities/bnote.js";
|
import type BNote from "../../becca/entities/bnote.js";
|
||||||
import ValidationError from "../../errors/validation_error.js";
|
import ValidationError from "../../errors/validation_error.js";
|
||||||
|
import appInfo from "../../services/app_info.js";
|
||||||
|
import attributeFormatter from "../../services/attribute_formatter.js";
|
||||||
|
import attributeService from "../../services/attributes.js";
|
||||||
|
import cloneService from "../../services/cloning.js";
|
||||||
|
import dateNoteService from "../../services/date_notes.js";
|
||||||
|
import dateUtils from "../../services/date_utils.js";
|
||||||
|
import htmlSanitizer from "../../services/html_sanitizer.js";
|
||||||
|
import imageService from "../../services/image.js";
|
||||||
|
import log from "../../services/log.js";
|
||||||
|
import noteService from "../../services/notes.js";
|
||||||
|
import utils from "../../services/utils.js";
|
||||||
|
import ws from "../../services/ws.js";
|
||||||
const { JSDOM } = jsdom;
|
const { JSDOM } = jsdom;
|
||||||
|
|
||||||
interface Image {
|
interface Image {
|
||||||
@ -26,14 +24,14 @@ interface Image {
|
|||||||
imageId: string;
|
imageId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClipping(req: Request) {
|
async function addClipping(req: Request) {
|
||||||
// if a note under the clipperInbox has the same 'pageUrl' attribute,
|
// if a note under the clipperInbox has the same 'pageUrl' attribute,
|
||||||
// add the content to that note and clone it under today's inbox
|
// add the content to that note and clone it under today's inbox
|
||||||
// otherwise just create a new note under today's inbox
|
// otherwise just create a new note under today's inbox
|
||||||
const { title, content, images } = req.body;
|
const { title, content, images } = req.body;
|
||||||
const clipType = "clippings";
|
const clipType = "clippings";
|
||||||
|
|
||||||
const clipperInbox = getClipperInboxNote();
|
const clipperInbox = await getClipperInboxNote();
|
||||||
|
|
||||||
const pageUrl = htmlSanitizer.sanitizeUrl(req.body.pageUrl);
|
const pageUrl = htmlSanitizer.sanitizeUrl(req.body.pageUrl);
|
||||||
let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
|
let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||||
@ -89,17 +87,17 @@ function findClippingNote(clipperInboxNote: BNote, pageUrl: string, clipType: st
|
|||||||
return clipType ? notes.find((note) => note.getOwnedLabelValue("clipType") === clipType) : notes[0];
|
return clipType ? notes.find((note) => note.getOwnedLabelValue("clipType") === clipType) : notes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClipperInboxNote() {
|
async function getClipperInboxNote() {
|
||||||
let clipperInbox = attributeService.getNoteWithLabel("clipperInbox");
|
let clipperInbox = attributeService.getNoteWithLabel("clipperInbox");
|
||||||
|
|
||||||
if (!clipperInbox) {
|
if (!clipperInbox) {
|
||||||
clipperInbox = dateNoteService.getDayNote(dateUtils.localNowDate());
|
clipperInbox = await dateNoteService.getDayNote(dateUtils.localNowDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
return clipperInbox;
|
return clipperInbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNote(req: Request) {
|
async function createNote(req: Request) {
|
||||||
const { content, images, labels } = req.body;
|
const { content, images, labels } = req.body;
|
||||||
|
|
||||||
const clipType = htmlSanitizer.sanitize(req.body.clipType);
|
const clipType = htmlSanitizer.sanitize(req.body.clipType);
|
||||||
@ -108,7 +106,7 @@ function createNote(req: Request) {
|
|||||||
const trimmedTitle = (typeof req.body.title === "string") ? req.body.title.trim() : "";
|
const trimmedTitle = (typeof req.body.title === "string") ? req.body.title.trim() : "";
|
||||||
const title = trimmedTitle || `Clipped note from ${pageUrl}`;
|
const title = trimmedTitle || `Clipped note from ${pageUrl}`;
|
||||||
|
|
||||||
const clipperInbox = getClipperInboxNote();
|
const clipperInbox = await getClipperInboxNote();
|
||||||
let note = findClippingNote(clipperInbox, pageUrl, clipType);
|
let note = findClippingNote(clipperInbox, pageUrl, clipType);
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
@ -215,9 +213,9 @@ function handshake() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNotesByUrl(req: Request) {
|
async function findNotesByUrl(req: Request) {
|
||||||
const pageUrl = req.params.noteUrl;
|
const pageUrl = req.params.noteUrl;
|
||||||
const clipperInbox = getClipperInboxNote();
|
const clipperInbox = await getClipperInboxNote();
|
||||||
const foundPage = findClippingNote(clipperInbox, pageUrl, null);
|
const foundPage = findClippingNote(clipperInbox, pageUrl, null);
|
||||||
return {
|
return {
|
||||||
noteId: foundPage ? foundPage.noteId : null
|
noteId: foundPage ? foundPage.noteId : null
|
||||||
|
@ -72,6 +72,8 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
|
|||||||
"locale",
|
"locale",
|
||||||
"formattingLocale",
|
"formattingLocale",
|
||||||
"firstDayOfWeek",
|
"firstDayOfWeek",
|
||||||
|
"firstWeekOfYear",
|
||||||
|
"minDaysInFirstWeek",
|
||||||
"languages",
|
"languages",
|
||||||
"textNoteEditorType",
|
"textNoteEditorType",
|
||||||
"textNoteEditorMultilineToolbar",
|
"textNoteEditorMultilineToolbar",
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
"use strict";
|
import type { Request } from "express";
|
||||||
|
|
||||||
import imageType from "image-type";
|
import imageType from "image-type";
|
||||||
|
|
||||||
import imageService from "../../services/image.js";
|
import imageService from "../../services/image.js";
|
||||||
import noteService from "../../services/notes.js";
|
import noteService from "../../services/notes.js";
|
||||||
import sanitizeAttributeName from "../../services/sanitize_attribute_name.js";
|
import sanitizeAttributeName from "../../services/sanitize_attribute_name.js";
|
||||||
import specialNotesService from "../../services/special_notes.js";
|
import specialNotesService from "../../services/special_notes.js";
|
||||||
import type { Request } from "express";
|
|
||||||
|
|
||||||
async function uploadImage(req: Request) {
|
async function uploadImage(req: Request) {
|
||||||
const file = req.file;
|
const file = req.file;
|
||||||
@ -34,7 +33,7 @@ async function uploadImage(req: Request) {
|
|||||||
return [400, "Invalid local date"];
|
return [400, "Invalid local date"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNote = specialNotesService.getInboxNote(req.headers["x-local-date"]);
|
const parentNote = await specialNotesService.getInboxNote(req.headers["x-local-date"]);
|
||||||
|
|
||||||
const { note, noteId } = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
const { note, noteId } = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
||||||
|
|
||||||
@ -55,12 +54,12 @@ async function uploadImage(req: Request) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveNote(req: Request) {
|
async function saveNote(req: Request) {
|
||||||
if (!req.headers["x-local-date"] || Array.isArray(req.headers["x-local-date"])) {
|
if (!req.headers["x-local-date"] || Array.isArray(req.headers["x-local-date"])) {
|
||||||
return [400, "Invalid local date"];
|
return [400, "Invalid local date"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNote = specialNotesService.getInboxNote(req.headers["x-local-date"]);
|
const parentNote = await specialNotesService.getInboxNote(req.headers["x-local-date"]);
|
||||||
|
|
||||||
const { note, branch } = noteService.createNewNote({
|
const { note, branch } = noteService.createNewNote({
|
||||||
parentNoteId: parentNote.noteId,
|
parentNoteId: parentNote.noteId,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import dateNoteService from "../../services/date_notes.js";
|
import dateNoteService from "../../services/date_notes.js";
|
||||||
import sql from "../../services/sql.js";
|
import sql from "../../services/sql.js";
|
||||||
import cls from "../../services/cls.js";
|
import cls from "../../services/cls.js";
|
||||||
@ -15,14 +13,22 @@ function getDayNote(req: Request) {
|
|||||||
return dateNoteService.getDayNote(req.params.date);
|
return dateNoteService.getDayNote(req.params.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWeekFirstDayNote(req: Request) {
|
||||||
|
return dateNoteService.getWeekFirstDayNote(req.params.date);
|
||||||
|
}
|
||||||
|
|
||||||
function getWeekNote(req: Request) {
|
function getWeekNote(req: Request) {
|
||||||
return dateNoteService.getWeekNote(req.params.date);
|
return dateNoteService.getWeekNote(req.params.week);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMonthNote(req: Request) {
|
function getMonthNote(req: Request) {
|
||||||
return dateNoteService.getMonthNote(req.params.month);
|
return dateNoteService.getMonthNote(req.params.month);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getQuarterNote(req: Request) {
|
||||||
|
return dateNoteService.getQuarterNote(req.params.quarter);
|
||||||
|
}
|
||||||
|
|
||||||
function getYearNote(req: Request) {
|
function getYearNote(req: Request) {
|
||||||
return dateNoteService.getYearNote(req.params.year);
|
return dateNoteService.getYearNote(req.params.year);
|
||||||
}
|
}
|
||||||
@ -58,8 +64,8 @@ function getDayNotesForMonth(req: Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSqlConsole(req: Request) {
|
async function saveSqlConsole(req: Request) {
|
||||||
return specialNotesService.saveSqlConsole(req.body.sqlConsoleNoteId);
|
return await specialNotesService.saveSqlConsole(req.body.sqlConsoleNoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSqlConsole() {
|
function createSqlConsole() {
|
||||||
@ -101,8 +107,10 @@ function createOrUpdateScriptLauncherFromApi(req: Request) {
|
|||||||
export default {
|
export default {
|
||||||
getInboxNote,
|
getInboxNote,
|
||||||
getDayNote,
|
getDayNote,
|
||||||
|
getWeekFirstDayNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
|
getQuarterNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
getDayNotesForMonth,
|
getDayNotesForMonth,
|
||||||
createSqlConsole,
|
createSqlConsole,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { isElectron, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
import { isElectron, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import log from "../services/log.js";
|
import log from "../services/log.js";
|
||||||
@ -308,8 +306,10 @@ function register(app: express.Application) {
|
|||||||
|
|
||||||
apiRoute(GET, "/api/special-notes/inbox/:date", specialNotesRoute.getInboxNote);
|
apiRoute(GET, "/api/special-notes/inbox/:date", specialNotesRoute.getInboxNote);
|
||||||
apiRoute(GET, "/api/special-notes/days/:date", specialNotesRoute.getDayNote);
|
apiRoute(GET, "/api/special-notes/days/:date", specialNotesRoute.getDayNote);
|
||||||
apiRoute(GET, "/api/special-notes/weeks/:date", specialNotesRoute.getWeekNote);
|
apiRoute(GET, "/api/special-notes/week-first-day/:date", specialNotesRoute.getWeekFirstDayNote);
|
||||||
|
apiRoute(GET, "/api/special-notes/weeks/:week", specialNotesRoute.getWeekNote);
|
||||||
apiRoute(GET, "/api/special-notes/months/:month", specialNotesRoute.getMonthNote);
|
apiRoute(GET, "/api/special-notes/months/:month", specialNotesRoute.getMonthNote);
|
||||||
|
apiRoute(GET, "/api/special-notes/quarters/:quarter", specialNotesRoute.getQuarterNote);
|
||||||
apiRoute(GET, "/api/special-notes/years/:year", specialNotesRoute.getYearNote);
|
apiRoute(GET, "/api/special-notes/years/:year", specialNotesRoute.getYearNote);
|
||||||
apiRoute(GET, "/api/special-notes/notes-for-month/:month", specialNotesRoute.getDayNotesForMonth);
|
apiRoute(GET, "/api/special-notes/notes-for-month/:month", specialNotesRoute.getDayNotesForMonth);
|
||||||
apiRoute(PST, "/api/special-notes/sql-console", specialNotesRoute.createSqlConsole);
|
apiRoute(PST, "/api/special-notes/sql-console", specialNotesRoute.createSqlConsole);
|
||||||
|
@ -224,14 +224,14 @@ interface Api {
|
|||||||
* @param date in YYYY-MM-DD format
|
* @param date in YYYY-MM-DD format
|
||||||
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
*/
|
*/
|
||||||
getDayNote(date: string, rootNote?: BNote): BNote | null;
|
getDayNote(date: string, rootNote?: BNote): Promise<BNote | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns today's day note. If such note doesn't exist, it is created.
|
* Returns today's day note. If such note doesn't exist, it is created.
|
||||||
*
|
*
|
||||||
* @param rootNote specify calendar root note, normally leave empty to use the default calendar
|
* @param rootNote specify calendar root note, normally leave empty to use the default calendar
|
||||||
*/
|
*/
|
||||||
getTodayNote(rootNote?: BNote): BNote | null;
|
getTodayNote(rootNote?: BNote): Promise<BNote | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns note for the first date of the week of the given date.
|
* Returns note for the first date of the week of the given date.
|
||||||
@ -239,15 +239,15 @@ interface Api {
|
|||||||
* @param date in YYYY-MM-DD format
|
* @param date in YYYY-MM-DD format
|
||||||
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
*/
|
*/
|
||||||
getWeekNote(
|
getWeekFirstDayNote(date: string, rootNote: BNote): Promise<BNote | null>;
|
||||||
date: string,
|
|
||||||
options: {
|
/**
|
||||||
// TODO: Deduplicate type with date_notes.ts once ES modules are added.
|
* Returns week note for given date. If such a note doesn't exist, it is created.
|
||||||
/** either "monday" (default) or "sunday" */
|
*
|
||||||
startOfTheWeek: "monday" | "sunday";
|
* @param date in YYYY-MM-DD format
|
||||||
},
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
rootNote: BNote
|
*/
|
||||||
): BNote | null;
|
getWeekNote(date: string, rootNote: BNote): Promise<BNote | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns month note for given date. If such a note doesn't exist, it is created.
|
* Returns month note for given date. If such a note doesn't exist, it is created.
|
||||||
@ -255,7 +255,15 @@ interface Api {
|
|||||||
* @param date in YYYY-MM format
|
* @param date in YYYY-MM format
|
||||||
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
*/
|
*/
|
||||||
getMonthNote(date: string, rootNote: BNote): BNote | null;
|
getMonthNote(date: string, rootNote: BNote): Promise<BNote | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns quarter note for given date. If such a note doesn't exist, it is created.
|
||||||
|
*
|
||||||
|
* @param date in YYYY-MM format
|
||||||
|
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
|
||||||
|
*/
|
||||||
|
getQuarterNote(date: string, rootNote: BNote): Promise<BNote | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns year note for given year. If such a note doesn't exist, it is created.
|
* Returns year note for given year. If such a note doesn't exist, it is created.
|
||||||
@ -552,8 +560,10 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
|
|||||||
this.getRootCalendarNote = dateNoteService.getRootCalendarNote;
|
this.getRootCalendarNote = dateNoteService.getRootCalendarNote;
|
||||||
this.getDayNote = dateNoteService.getDayNote;
|
this.getDayNote = dateNoteService.getDayNote;
|
||||||
this.getTodayNote = dateNoteService.getTodayNote;
|
this.getTodayNote = dateNoteService.getTodayNote;
|
||||||
|
this.getWeekFirstDayNote = dateNoteService.getWeekFirstDayNote;
|
||||||
this.getWeekNote = dateNoteService.getWeekNote;
|
this.getWeekNote = dateNoteService.getWeekNote;
|
||||||
this.getMonthNote = dateNoteService.getMonthNote;
|
this.getMonthNote = dateNoteService.getMonthNote;
|
||||||
|
this.getQuarterNote = dateNoteService.getQuarterNote;
|
||||||
this.getYearNote = dateNoteService.getYearNote;
|
this.getYearNote = dateNoteService.getYearNote;
|
||||||
|
|
||||||
this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes(parentNoteId, sortConfig.sortBy || "title", !!sortConfig.reverse, !!sortConfig.foldersFirst);
|
this.sortNotes = (parentNoteId, sortConfig = {}) => treeService.sortNotes(parentNoteId, sortConfig.sortBy || "title", !!sortConfig.reverse, !!sortConfig.foldersFirst);
|
||||||
@ -685,5 +695,5 @@ function BackendScriptApi(this: Api, currentNote: BNote, apiParams: ApiParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default BackendScriptApi as any as {
|
export default BackendScriptApi as any as {
|
||||||
new (currentNote: BNote, apiParams: ApiParams): Api;
|
new(currentNote: BNote, apiParams: ApiParams): Api;
|
||||||
};
|
};
|
||||||
|
@ -36,6 +36,12 @@ export default [
|
|||||||
{ type: "label", name: "workspaceSearchHome" },
|
{ type: "label", name: "workspaceSearchHome" },
|
||||||
{ type: "label", name: "sqlConsoleHome" },
|
{ type: "label", name: "sqlConsoleHome" },
|
||||||
{ type: "label", name: "datePattern" },
|
{ type: "label", name: "datePattern" },
|
||||||
|
{ type: "label", name: "weekPattern" },
|
||||||
|
{ type: "label", name: "enableWeekNote" },
|
||||||
|
{ type: "label", name: "monthPattern" },
|
||||||
|
{ type: "label", name: "quarterPattern" },
|
||||||
|
{ type: "label", name: "yearPattern" },
|
||||||
|
{ type: "label", name: "enableQuarterNote" },
|
||||||
{ type: "label", name: "pageSize" },
|
{ type: "label", name: "pageSize" },
|
||||||
{ type: "label", name: "viewType" },
|
{ type: "label", name: "viewType" },
|
||||||
{ type: "label", name: "mapRootNoteId" },
|
{ type: "label", name: "mapRootNoteId" },
|
||||||
|
114
src/services/date_notes.spec.ts
Normal file
114
src/services/date_notes.spec.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { beforeAll,describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import type BNote from "../becca/entities/bnote.js";
|
||||||
|
import dateNotesService from "./date_notes.js";
|
||||||
|
|
||||||
|
// Mock becca_loader
|
||||||
|
vi.mock("../becca/becca_loader.js", () => ({
|
||||||
|
default: {
|
||||||
|
load: vi.fn(),
|
||||||
|
loaded: Promise.resolve()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock SQL init
|
||||||
|
vi.mock("../services/sql.js", () => ({
|
||||||
|
default: {
|
||||||
|
dbReady: Promise.resolve(),
|
||||||
|
transactional: vi.fn((callback) => callback())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// Mock BNote
|
||||||
|
const mockRootNote = {
|
||||||
|
getOwnedLabelValue: (key: string) => {
|
||||||
|
const patterns: Record<string, string> = {
|
||||||
|
"yearPattern": "{year}",
|
||||||
|
"quarterPattern": "Quarter {quarterNumber}",
|
||||||
|
"monthPattern": "{monthNumberPadded} - {month}",
|
||||||
|
"weekPattern": "Week {weekNumber}",
|
||||||
|
"datePattern": "{dateNumberPadded} - {weekDay}"
|
||||||
|
};
|
||||||
|
return patterns[key] || null;
|
||||||
|
}
|
||||||
|
} as unknown as BNote;
|
||||||
|
|
||||||
|
describe("date_notes", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await i18next.init({
|
||||||
|
lng: "en",
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
translation: {
|
||||||
|
"months.march": "March",
|
||||||
|
"weekdays.saturday": "Saturday"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getJournalNoteTitle", () => {
|
||||||
|
const testDate = dayjs("2025-03-15"); // Saturday
|
||||||
|
|
||||||
|
it("should generate year note title", async () => {
|
||||||
|
const title = await dateNotesService.getJournalNoteTitle(mockRootNote, "year", testDate, 2025);
|
||||||
|
expect(title).toBe("2025");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate quarter note title", async () => {
|
||||||
|
const title = await dateNotesService.getJournalNoteTitle(mockRootNote, "quarter", testDate, 1);
|
||||||
|
expect(title).toBe("Quarter 1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate month note title", async () => {
|
||||||
|
const title = await dateNotesService.getJournalNoteTitle(mockRootNote, "month", testDate, 3);
|
||||||
|
expect(title).toBe("03 - March");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate week note title", async () => {
|
||||||
|
const title = await dateNotesService.getJournalNoteTitle(mockRootNote, "week", testDate, 11);
|
||||||
|
expect(title).toBe("Week 11");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate day note title", async () => {
|
||||||
|
const title = await dateNotesService.getJournalNoteTitle(mockRootNote, "day", testDate, 15);
|
||||||
|
expect(title).toBe("15 - Saturday");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should respect custom patterns", async () => {
|
||||||
|
const customRootNote = {
|
||||||
|
getOwnedLabelValue: (key: string) => {
|
||||||
|
const patterns: Record<string, string> = {
|
||||||
|
"yearPattern": "{year}",
|
||||||
|
"quarterPattern": "{quarterNumber} {shortQuarter}",
|
||||||
|
"monthPattern": "{isoMonth} {monthNumber} {monthNumberPadded} {month} {shortMonth3} {shortMonth4}",
|
||||||
|
"weekPattern": "{weekNumber} {weekNumberPadded} {shortWeek} {shortWeek3}",
|
||||||
|
"datePattern": "{isoDate} {dateNumber} {dateNumberPadded} {ordinal} {weekDay} {weekDay3} {weekDay2}"
|
||||||
|
};
|
||||||
|
return patterns[key] || null;
|
||||||
|
}
|
||||||
|
} as unknown as BNote;
|
||||||
|
|
||||||
|
const testDate = dayjs("2025-03-01"); // Saturday
|
||||||
|
|
||||||
|
const yearTitle = await dateNotesService.getJournalNoteTitle(customRootNote, "year", testDate, 2025);
|
||||||
|
expect(yearTitle).toBe("2025");
|
||||||
|
|
||||||
|
const quarterTitle = await dateNotesService.getJournalNoteTitle(customRootNote, "quarter", testDate, 1);
|
||||||
|
expect(quarterTitle).toBe("1 Q1");
|
||||||
|
|
||||||
|
const monthTitle = await dateNotesService.getJournalNoteTitle(customRootNote, "month", testDate, 3);
|
||||||
|
expect(monthTitle).toBe("2025-03 3 03 March Mar Marc");
|
||||||
|
|
||||||
|
const weekTitle = await dateNotesService.getJournalNoteTitle(customRootNote, "week", testDate, 9);
|
||||||
|
expect(weekTitle).toBe("9 09 W9 W09");
|
||||||
|
|
||||||
|
const dayTitle = await dateNotesService.getJournalNoteTitle(customRootNote, "day", testDate, 1);
|
||||||
|
expect(dayTitle).toBe("2025-03-01 1 01 1st Saturday Sat Sa");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,22 +1,38 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import noteService from "./notes.js";
|
|
||||||
import attributeService from "./attributes.js";
|
|
||||||
import dateUtils from "./date_utils.js";
|
|
||||||
import sql from "./sql.js";
|
|
||||||
import protectedSessionService from "./protected_session.js";
|
|
||||||
import searchService from "../services/search/services/search.js";
|
|
||||||
import SearchContext from "../services/search/search_context.js";
|
|
||||||
import hoistedNoteService from "./hoisted_note.js";
|
|
||||||
import type BNote from "../becca/entities/bnote.js";
|
import type BNote from "../becca/entities/bnote.js";
|
||||||
|
import type { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import advancedFormat from "dayjs/plugin/advancedFormat.js";
|
||||||
|
import attributeService from "./attributes.js";
|
||||||
|
import cloningService from "./cloning.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import hoistedNoteService from "./hoisted_note.js";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||||
|
import noteService from "./notes.js";
|
||||||
|
import optionService from "./options.js";
|
||||||
|
import protectedSessionService from "./protected_session.js";
|
||||||
|
import quarterOfYear from "dayjs/plugin/quarterOfYear.js";
|
||||||
|
import searchContext from "../services/search/search_context.js";
|
||||||
|
import searchService from "../services/search/services/search.js";
|
||||||
|
import sql from "./sql.js";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
|
||||||
|
dayjs.extend(isSameOrAfter);
|
||||||
|
dayjs.extend(quarterOfYear);
|
||||||
|
dayjs.extend(advancedFormat);
|
||||||
|
|
||||||
const CALENDAR_ROOT_LABEL = "calendarRoot";
|
const CALENDAR_ROOT_LABEL = "calendarRoot";
|
||||||
const YEAR_LABEL = "yearNote";
|
const YEAR_LABEL = "yearNote";
|
||||||
|
const QUARTER_LABEL = "quarterNote";
|
||||||
const MONTH_LABEL = "monthNote";
|
const MONTH_LABEL = "monthNote";
|
||||||
|
const WEEK_LABEL = "weekNote";
|
||||||
const DATE_LABEL = "dateNote";
|
const DATE_LABEL = "dateNote";
|
||||||
|
|
||||||
const WEEKDAY_TRANSLATION_IDS = ["weekdays.sunday", "weekdays.monday", "weekdays.tuesday", "weekdays.wednesday", "weekdays.thursday", "weekdays.friday", "weekdays.saturday", "weekdays.sunday"];
|
const WEEKDAY_TRANSLATION_IDS = [
|
||||||
|
"weekdays.sunday", "weekdays.monday", "weekdays.tuesday",
|
||||||
|
"weekdays.wednesday", "weekdays.thursday", "weekdays.friday",
|
||||||
|
"weekdays.saturday", "weekdays.sunday"
|
||||||
|
];
|
||||||
|
|
||||||
const MONTH_TRANSLATION_IDS = [
|
const MONTH_TRANSLATION_IDS = [
|
||||||
"months.january",
|
"months.january",
|
||||||
@ -33,14 +49,114 @@ const MONTH_TRANSLATION_IDS = [
|
|||||||
"months.december"
|
"months.december"
|
||||||
];
|
];
|
||||||
|
|
||||||
type StartOfWeek = "monday" | "sunday";
|
type TimeUnit = "year" | "quarter" | "month" | "week" | "day";
|
||||||
|
|
||||||
|
const baseReplacements = {
|
||||||
|
year: [ "year" ],
|
||||||
|
quarter: [ "quarterNumber", "shortQuarter" ],
|
||||||
|
month: [ "isoMonth", "monthNumber", "monthNumberPadded",
|
||||||
|
"month", "shortMonth3", "shortMonth4" ],
|
||||||
|
week: [ "weekNumber", "weekNumberPadded", "shortWeek", "shortWeek3" ],
|
||||||
|
day: [ "isoDate", "dateNumber", "dateNumberPadded",
|
||||||
|
"ordinal", "weekDay", "weekDay3", "weekDay2" ]
|
||||||
|
};
|
||||||
|
|
||||||
|
function getTimeUnitReplacements(timeUnit: TimeUnit): string[] {
|
||||||
|
const units: TimeUnit[] = [ "year", "quarter", "month", "week", "day" ];
|
||||||
|
const index = units.indexOf(timeUnit);
|
||||||
|
return units.slice(0, index + 1).flatMap(unit => baseReplacements[unit]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ordinal(date: Dayjs, lng: string) {
|
||||||
|
const localeMap: Record<string, string> = {
|
||||||
|
cn: "zh-cn",
|
||||||
|
tw: "zh-tw"
|
||||||
|
};
|
||||||
|
|
||||||
|
const dayjsLocale = localeMap[lng] || lng;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await import(`dayjs/locale/${dayjsLocale}.js`);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Could not load locale ${dayjsLocale}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dayjs(date).locale(dayjsLocale).format("Do");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJournalNoteTitle(
|
||||||
|
rootNote: BNote,
|
||||||
|
timeUnit: TimeUnit,
|
||||||
|
dateObj: Dayjs,
|
||||||
|
number: number
|
||||||
|
) {
|
||||||
|
const patterns = {
|
||||||
|
year: rootNote.getOwnedLabelValue("yearPattern") || "{year}",
|
||||||
|
quarter: rootNote.getOwnedLabelValue("quarterPattern") || t("quarterNumber"),
|
||||||
|
month: rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}",
|
||||||
|
week: rootNote.getOwnedLabelValue("weekPattern") || t("weekdayNumber"),
|
||||||
|
day: rootNote.getOwnedLabelValue("datePattern") || "{dateNumberPadded} - {weekDay}"
|
||||||
|
};
|
||||||
|
|
||||||
|
const pattern = patterns[timeUnit];
|
||||||
|
const monthName = t(MONTH_TRANSLATION_IDS[dateObj.month()]);
|
||||||
|
const weekDay = t(WEEKDAY_TRANSLATION_IDS[dateObj.day()]);
|
||||||
|
const numberStr = number.toString();
|
||||||
|
const ordinalStr = await ordinal(dateObj, i18next.language);
|
||||||
|
|
||||||
|
const allReplacements: Record<string, string> = {
|
||||||
|
// Common date formats
|
||||||
|
"{year}": dateObj.format("YYYY"),
|
||||||
|
|
||||||
|
// Month related
|
||||||
|
"{isoMonth}": dateObj.format("YYYY-MM"),
|
||||||
|
"{monthNumber}": numberStr,
|
||||||
|
"{monthNumberPadded}": numberStr.padStart(2, "0"),
|
||||||
|
"{month}": monthName,
|
||||||
|
"{shortMonth3}": monthName.slice(0, 3),
|
||||||
|
"{shortMonth4}": monthName.slice(0, 4),
|
||||||
|
|
||||||
|
// Quarter related
|
||||||
|
"{quarterNumber}": numberStr,
|
||||||
|
"{shortQuarter}": `Q${numberStr}`,
|
||||||
|
|
||||||
|
// Week related
|
||||||
|
"{weekNumber}": numberStr,
|
||||||
|
"{weekNumberPadded}": numberStr.padStart(2, "0"),
|
||||||
|
"{shortWeek}": `W${numberStr}`,
|
||||||
|
"{shortWeek3}": `W${numberStr.padStart(2, "0")}`,
|
||||||
|
|
||||||
|
// Day related
|
||||||
|
"{isoDate}": dateObj.format("YYYY-MM-DD"),
|
||||||
|
"{dateNumber}": numberStr,
|
||||||
|
"{dateNumberPadded}": numberStr.padStart(2, "0"),
|
||||||
|
"{ordinal}": ordinalStr,
|
||||||
|
"{weekDay}": weekDay,
|
||||||
|
"{weekDay3}": weekDay.substring(0, 3),
|
||||||
|
"{weekDay2}": weekDay.substring(0, 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
const allowedReplacements = Object.entries(allReplacements).reduce((acc, [ key, value ]) => {
|
||||||
|
const replacementKey = key.slice(1, -1);
|
||||||
|
if (getTimeUnitReplacements(timeUnit).includes(replacementKey)) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
|
return Object.entries(allowedReplacements).reduce(
|
||||||
|
(title, [ key, value ]) => title.replace(new RegExp(key, "g"), value),
|
||||||
|
pattern
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createNote(parentNote: BNote, noteTitle: string) {
|
function createNote(parentNote: BNote, noteTitle: string) {
|
||||||
return noteService.createNewNote({
|
return noteService.createNewNote({
|
||||||
parentNoteId: parentNote.noteId,
|
parentNoteId: parentNote.noteId,
|
||||||
title: noteTitle,
|
title: noteTitle,
|
||||||
content: "",
|
content: "",
|
||||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
isProtected: parentNote.isProtected &&
|
||||||
|
protectedSessionService.isProtectedSessionAvailable(),
|
||||||
type: "text"
|
type: "text"
|
||||||
}).note;
|
}).note;
|
||||||
}
|
}
|
||||||
@ -51,7 +167,9 @@ function getRootCalendarNote(): BNote {
|
|||||||
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
||||||
|
|
||||||
if (!workspaceNote || !workspaceNote.isRoot()) {
|
if (!workspaceNote || !workspaceNote.isRoot()) {
|
||||||
rootNote = searchService.findFirstNoteWithQuery("#workspaceCalendarRoot", new SearchContext({ ignoreHoistedNote: false }));
|
rootNote = searchService.findFirstNoteWithQuery(
|
||||||
|
"#workspaceCalendarRoot", new searchContext({ ignoreHoistedNote: false })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rootNote) {
|
if (!rootNote) {
|
||||||
@ -80,9 +198,11 @@ function getRootCalendarNote(): BNote {
|
|||||||
function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
||||||
const rootNote = _rootNote || getRootCalendarNote();
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
|
|
||||||
const yearStr = dateStr.trim().substr(0, 4);
|
const yearStr = dateStr.trim().substring(0, 4);
|
||||||
|
|
||||||
let yearNote = searchService.findFirstNoteWithQuery(`#${YEAR_LABEL}="${yearStr}"`, new SearchContext({ ancestorNoteId: rootNote.noteId }));
|
let yearNote = searchService.findFirstNoteWithQuery(
|
||||||
|
`#${YEAR_LABEL}="${yearStr}"`, new searchContext({ ancestorNoteId: rootNote.noteId })
|
||||||
|
);
|
||||||
|
|
||||||
if (yearNote) {
|
if (yearNote) {
|
||||||
return yearNote;
|
return yearNote;
|
||||||
@ -104,38 +224,79 @@ function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
|||||||
return yearNote as unknown as BNote;
|
return yearNote as unknown as BNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMonthNoteTitle(rootNote: BNote, monthNumber: string, dateObj: Date) {
|
function getQuarterNumberStr(date: Dayjs) {
|
||||||
const pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
|
return `${date.year()}-Q${date.quarter()}`;
|
||||||
const monthName = t(MONTH_TRANSLATION_IDS[dateObj.getMonth()]);
|
|
||||||
|
|
||||||
return pattern
|
|
||||||
.replace(/{shortMonth3}/g, monthName.slice(0, 3))
|
|
||||||
.replace(/{shortMonth4}/g, monthName.slice(0, 4))
|
|
||||||
.replace(/{isoMonth}/g, dateUtils.utcDateStr(dateObj).slice(0, 7))
|
|
||||||
.replace(/{monthNumberPadded}/g, monthNumber)
|
|
||||||
.replace(/{month}/g, monthName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
async function getQuarterNote(quarterStr: string, _rootNote: BNote | null = null): Promise<BNote> {
|
||||||
const rootNote = _rootNote || getRootCalendarNote();
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
|
|
||||||
const monthStr = dateStr.substr(0, 7);
|
quarterStr = quarterStr.trim().substring(0, 7);
|
||||||
const monthNumber = dateStr.substr(5, 2);
|
|
||||||
|
|
||||||
let monthNote = searchService.findFirstNoteWithQuery(`#${MONTH_LABEL}="${monthStr}"`, new SearchContext({ ancestorNoteId: rootNote.noteId }));
|
let quarterNote = searchService.findFirstNoteWithQuery(
|
||||||
|
`#${QUARTER_LABEL}="${quarterStr}"`, new searchContext({ ancestorNoteId: rootNote.noteId })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (quarterNote) {
|
||||||
|
return quarterNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ yearStr, quarterNumberStr ] = quarterStr.trim().split("-Q");
|
||||||
|
const quarterNumber = parseInt(quarterNumberStr);
|
||||||
|
const firstMonth = (quarterNumber - 1) * 3;
|
||||||
|
const quarterStartDate = dayjs().year(parseInt(yearStr)).month(firstMonth).date(1);
|
||||||
|
|
||||||
|
const yearNote = getYearNote(yearStr, rootNote);
|
||||||
|
const noteTitle = await getJournalNoteTitle(
|
||||||
|
rootNote, "quarter", quarterStartDate, quarterNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
sql.transactional(() => {
|
||||||
|
quarterNote = createNote(yearNote, noteTitle);
|
||||||
|
|
||||||
|
attributeService.createLabel(quarterNote.noteId, QUARTER_LABEL, quarterStr);
|
||||||
|
attributeService.createLabel(quarterNote.noteId, "sorted");
|
||||||
|
|
||||||
|
const quarterTemplateAttr = rootNote.getOwnedAttribute("relation", "quarterTemplate");
|
||||||
|
|
||||||
|
if (quarterTemplateAttr) {
|
||||||
|
attributeService.createRelation(
|
||||||
|
quarterNote.noteId, "template", quarterTemplateAttr.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return quarterNote as unknown as BNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMonthNote(dateStr: string, _rootNote: BNote | null = null): Promise<BNote> {
|
||||||
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
|
|
||||||
|
const monthStr = dateStr.substring(0, 7);
|
||||||
|
const monthNumber = dateStr.substring(5, 7);
|
||||||
|
|
||||||
|
let monthNote = searchService.findFirstNoteWithQuery(
|
||||||
|
`#${MONTH_LABEL}="${monthStr}"`, new searchContext({ ancestorNoteId: rootNote.noteId })
|
||||||
|
);
|
||||||
|
|
||||||
if (monthNote) {
|
if (monthNote) {
|
||||||
return monthNote;
|
return monthNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateObj = dateUtils.parseLocalDate(dateStr);
|
let monthParentNote;
|
||||||
|
|
||||||
const noteTitle = getMonthNoteTitle(rootNote, monthNumber, dateObj);
|
if (rootNote.hasLabel("enableQuarterNote")) {
|
||||||
|
monthParentNote = await getQuarterNote(getQuarterNumberStr(dayjs(dateStr)), rootNote);
|
||||||
|
} else {
|
||||||
|
monthParentNote = getYearNote(dateStr, rootNote);
|
||||||
|
}
|
||||||
|
|
||||||
const yearNote = getYearNote(dateStr, rootNote);
|
const noteTitle = await getJournalNoteTitle(
|
||||||
|
rootNote, "month", dayjs(dateStr), parseInt(monthNumber)
|
||||||
|
);
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
monthNote = createNote(yearNote, noteTitle);
|
monthNote = createNote(monthParentNote, noteTitle);
|
||||||
|
|
||||||
attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
||||||
attributeService.createLabel(monthNote.noteId, "sorted");
|
attributeService.createLabel(monthNote.noteId, "sorted");
|
||||||
@ -150,49 +311,178 @@ function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
|||||||
return monthNote as unknown as BNote;
|
return monthNote as unknown as BNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDayNoteTitle(rootNote: BNote, dayNumber: string, dateObj: Date) {
|
function getWeekStartDate(date: Dayjs): Dayjs {
|
||||||
const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
|
const day = date.day();
|
||||||
const weekDay = t(WEEKDAY_TRANSLATION_IDS[dateObj.getDay()]);
|
let diff;
|
||||||
|
|
||||||
return pattern
|
if (optionService.getOption("firstDayOfWeek") === "0") { // Sunday
|
||||||
.replace(/{ordinal}/g, ordinal(parseInt(dayNumber)))
|
diff = date.date() - day + (day === 0 ? -6 : 1); // adjust when day is sunday
|
||||||
.replace(/{dayInMonthPadded}/g, dayNumber)
|
} else { // Monday
|
||||||
.replace(/{isoDate}/g, dateUtils.utcDateStr(dateObj))
|
diff = date.date() - day;
|
||||||
.replace(/{weekDay}/g, weekDay)
|
}
|
||||||
.replace(/{weekDay3}/g, weekDay.substr(0, 3))
|
|
||||||
.replace(/{weekDay2}/g, weekDay.substr(0, 2));
|
const startDate = date.clone().date(diff);
|
||||||
|
return startDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** produces 1st, 2nd, 3rd, 4th, 21st, 31st for 1, 2, 3, 4, 21, 31 */
|
// TODO: Duplicated with getWeekNumber in src/public/app/widgets/buttons/calendar.ts
|
||||||
function ordinal(dayNumber: number) {
|
// Maybe can be merged later in monorepo setup
|
||||||
const suffixes = ["th", "st", "nd", "rd"];
|
function getWeekNumberStr(date: Dayjs): string {
|
||||||
const suffix = suffixes[(dayNumber - 20) % 10] || suffixes[dayNumber] || suffixes[0];
|
const year = date.year();
|
||||||
|
const dayOfWeek = (day: number) =>
|
||||||
|
(day - parseInt(optionService.getOption("firstDayOfWeek")) + 7) % 7;
|
||||||
|
|
||||||
return `${dayNumber}${suffix}`;
|
// Get first day of the year and adjust to first week start
|
||||||
|
const jan1 = date.clone().year(year).month(0).date(1);
|
||||||
|
const jan1Weekday = jan1.day();
|
||||||
|
const dayOffset = dayOfWeek(jan1Weekday);
|
||||||
|
let firstWeekStart = jan1.clone().subtract(dayOffset, "day");
|
||||||
|
|
||||||
|
// Adjust based on week rule
|
||||||
|
switch (parseInt(optionService.getOption("firstWeekOfYear"))) {
|
||||||
|
case 1: { // ISO 8601: first week contains Thursday
|
||||||
|
const thursday = firstWeekStart.clone().add(3, "day"); // Monday + 3 = Thursday
|
||||||
|
if (thursday.year() < year) {
|
||||||
|
firstWeekStart = firstWeekStart.add(7, "day");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: { // minDaysInFirstWeek rule
|
||||||
|
const daysInFirstWeek = 7 - dayOffset;
|
||||||
|
if (daysInFirstWeek < parseInt(optionService.getOption("minDaysInFirstWeek"))) {
|
||||||
|
firstWeekStart = firstWeekStart.add(7, "day");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// default case 0: week containing Jan 1 → already handled
|
||||||
|
}
|
||||||
|
|
||||||
|
const diffDays = date.startOf("day").diff(firstWeekStart.startOf("day"), "day");
|
||||||
|
const weekNumber = Math.floor(diffDays / 7) + 1;
|
||||||
|
|
||||||
|
// Handle case when date is before first week start → belongs to last week of previous year
|
||||||
|
if (weekNumber <= 0) {
|
||||||
|
return getWeekNumberStr(date.subtract(1, "day"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case when date belongs to first week of next year
|
||||||
|
const nextYear = year + 1;
|
||||||
|
const jan1Next = date.clone().year(nextYear).month(0).date(1);
|
||||||
|
const jan1WeekdayNext = jan1Next.day();
|
||||||
|
const offsetNext = dayOfWeek(jan1WeekdayNext);
|
||||||
|
let nextYearWeekStart = jan1Next.clone().subtract(offsetNext, "day");
|
||||||
|
|
||||||
|
switch (parseInt(optionService.getOption("firstWeekOfYear"))) {
|
||||||
|
case 1: {
|
||||||
|
const thursday = nextYearWeekStart.clone().add(3, "day");
|
||||||
|
if (thursday.year() < nextYear) {
|
||||||
|
nextYearWeekStart = nextYearWeekStart.add(7, "day");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
const daysInFirstWeek = 7 - offsetNext;
|
||||||
|
if (daysInFirstWeek < parseInt(optionService.getOption("minDaysInFirstWeek"))) {
|
||||||
|
nextYearWeekStart = nextYearWeekStart.add(7, "day");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date.isSameOrAfter(nextYearWeekStart)) {
|
||||||
|
return `${nextYear}-W01`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${year}-W${weekNumber.toString().padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDayNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
function getWeekFirstDayNote(dateStr: string, rootNote: BNote | null = null) {
|
||||||
|
const weekStartDate = getWeekStartDate(dayjs(dateStr));
|
||||||
|
return getDayNote(weekStartDate.format("YYYY-MM-DD"), rootNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWeekNote(weekStr: string, _rootNote: BNote | null = null): Promise<BNote | null> {
|
||||||
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
|
if (!rootNote.hasLabel("enableWeekNote")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
weekStr = weekStr.trim().substring(0, 8);
|
||||||
|
|
||||||
|
let weekNote = searchService.findFirstNoteWithQuery(
|
||||||
|
`#${WEEK_LABEL}="${weekStr}"`, new searchContext({ ancestorNoteId: rootNote.noteId })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (weekNote) {
|
||||||
|
return weekNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ yearStr, weekNumStr ] = weekStr.trim().split("-W");
|
||||||
|
const weekNumber = parseInt(weekNumStr);
|
||||||
|
|
||||||
|
const firstDayOfYear = dayjs().year(parseInt(yearStr)).month(0).date(1);
|
||||||
|
const weekStartDate = firstDayOfYear.add(weekNumber - 1, "week");
|
||||||
|
const startDate = getWeekStartDate(weekStartDate);
|
||||||
|
const endDate = dayjs(startDate).add(6, "day");
|
||||||
|
|
||||||
|
const startMonth = startDate.month();
|
||||||
|
const endMonth = endDate.month();
|
||||||
|
|
||||||
|
const monthNote = await getMonthNote(startDate.format("YYYY-MM-DD"), rootNote);
|
||||||
|
const noteTitle = await getJournalNoteTitle(rootNote, "week", startDate, weekNumber);
|
||||||
|
|
||||||
|
sql.transactional(async () => {
|
||||||
|
weekNote = createNote(monthNote, noteTitle);
|
||||||
|
|
||||||
|
attributeService.createLabel(weekNote.noteId, WEEK_LABEL, weekStr);
|
||||||
|
attributeService.createLabel(weekNote.noteId, "sorted");
|
||||||
|
|
||||||
|
const weekTemplateAttr = rootNote.getOwnedAttribute("relation", "weekTemplate");
|
||||||
|
|
||||||
|
if (weekTemplateAttr) {
|
||||||
|
attributeService.createRelation(weekNote.noteId, "template", weekTemplateAttr.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the week spans different months, clone the week note in the other month as well
|
||||||
|
if (startMonth !== endMonth) {
|
||||||
|
const secondMonthNote = await getMonthNote(endDate.format("YYYY-MM-DD"), rootNote);
|
||||||
|
cloningService.cloneNoteToParentNote(weekNote.noteId, secondMonthNote.noteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return weekNote as unknown as BNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDayNote(dateStr: string, _rootNote: BNote | null = null): Promise<BNote> {
|
||||||
const rootNote = _rootNote || getRootCalendarNote();
|
const rootNote = _rootNote || getRootCalendarNote();
|
||||||
|
|
||||||
dateStr = dateStr.trim().substr(0, 10);
|
dateStr = dateStr.trim().substring(0, 10);
|
||||||
|
|
||||||
let dateNote = searchService.findFirstNoteWithQuery(`#${DATE_LABEL}="${dateStr}"`, new SearchContext({ ancestorNoteId: rootNote.noteId }));
|
let dateNote = searchService.findFirstNoteWithQuery(
|
||||||
|
`#${DATE_LABEL}="${dateStr}"`, new searchContext({ ancestorNoteId: rootNote.noteId })
|
||||||
|
);
|
||||||
|
|
||||||
if (dateNote) {
|
if (dateNote) {
|
||||||
return dateNote;
|
return dateNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
const monthNote = getMonthNote(dateStr, rootNote);
|
let dateParentNote;
|
||||||
const dayNumber = dateStr.substr(8, 2);
|
|
||||||
|
|
||||||
const dateObj = dateUtils.parseLocalDate(dateStr);
|
if (rootNote.hasLabel("enableWeekNote")) {
|
||||||
|
dateParentNote = await getWeekNote(getWeekNumberStr(dayjs(dateStr)), rootNote);
|
||||||
|
} else {
|
||||||
|
dateParentNote = await getMonthNote(dateStr, rootNote);
|
||||||
|
}
|
||||||
|
|
||||||
const noteTitle = getDayNoteTitle(rootNote, dayNumber, dateObj);
|
const dayNumber = dateStr.substring(8, 10);
|
||||||
|
const noteTitle = await getJournalNoteTitle(
|
||||||
|
rootNote, "day", dayjs(dateStr), parseInt(dayNumber)
|
||||||
|
);
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
dateNote = createNote(monthNote, noteTitle);
|
dateNote = createNote(dateParentNote as BNote, noteTitle);
|
||||||
|
|
||||||
attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substr(0, 10));
|
attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substring(0, 10));
|
||||||
|
|
||||||
const dateTemplateAttr = rootNote.getOwnedAttribute("relation", "dateTemplate");
|
const dateTemplateAttr = rootNote.getOwnedAttribute("relation", "dateTemplate");
|
||||||
|
|
||||||
@ -205,43 +495,17 @@ function getDayNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTodayNote(rootNote: BNote | null = null) {
|
function getTodayNote(rootNote: BNote | null = null) {
|
||||||
return getDayNote(dateUtils.localNowDate(), rootNote);
|
return getDayNote(dayjs().format("YYYY-MM-DD"), rootNote);
|
||||||
}
|
|
||||||
|
|
||||||
function getStartOfTheWeek(date: Date, startOfTheWeek: StartOfWeek) {
|
|
||||||
const day = date.getDay();
|
|
||||||
let diff;
|
|
||||||
|
|
||||||
if (startOfTheWeek === "monday") {
|
|
||||||
diff = date.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday
|
|
||||||
} else if (startOfTheWeek === "sunday") {
|
|
||||||
diff = date.getDate() - day;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unrecognized start of the week ${startOfTheWeek}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(date.setDate(diff));
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WeekNoteOpts {
|
|
||||||
startOfTheWeek?: StartOfWeek;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWeekNote(dateStr: string, options: WeekNoteOpts = {}, rootNote: BNote | null = null) {
|
|
||||||
const startOfTheWeek = options.startOfTheWeek || "monday";
|
|
||||||
|
|
||||||
const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek);
|
|
||||||
|
|
||||||
dateStr = dateUtils.utcDateTimeStr(dateObj);
|
|
||||||
|
|
||||||
return getDayNote(dateStr, rootNote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getRootCalendarNote,
|
getRootCalendarNote,
|
||||||
getYearNote,
|
getYearNote,
|
||||||
|
getQuarterNote,
|
||||||
getMonthNote,
|
getMonthNote,
|
||||||
getWeekNote,
|
getWeekNote,
|
||||||
|
getWeekFirstDayNote,
|
||||||
getDayNote,
|
getDayNote,
|
||||||
getTodayNote
|
getTodayNote,
|
||||||
|
getJournalNoteTitle
|
||||||
};
|
};
|
||||||
|
@ -143,6 +143,8 @@ const defaultOptions: DefaultOption[] = [
|
|||||||
{ name: "locale", value: "en", isSynced: true },
|
{ name: "locale", value: "en", isSynced: true },
|
||||||
{ name: "formattingLocale", value: "en", isSynced: true },
|
{ name: "formattingLocale", value: "en", isSynced: true },
|
||||||
{ name: "firstDayOfWeek", value: "1", isSynced: true },
|
{ name: "firstDayOfWeek", value: "1", isSynced: true },
|
||||||
|
{ name: "firstWeekOfYear", value: "0", isSynced: true },
|
||||||
|
{ name: "minDaysInFirstWeek", value: "4", isSynced: true },
|
||||||
{ name: "languages", value: "[]", isSynced: true },
|
{ name: "languages", value: "[]", isSynced: true },
|
||||||
|
|
||||||
// Code block configuration
|
// Code block configuration
|
||||||
|
@ -85,6 +85,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
|||||||
eraseUnusedAttachmentsAfterSeconds: number;
|
eraseUnusedAttachmentsAfterSeconds: number;
|
||||||
eraseUnusedAttachmentsAfterTimeScale: number;
|
eraseUnusedAttachmentsAfterTimeScale: number;
|
||||||
firstDayOfWeek: number;
|
firstDayOfWeek: number;
|
||||||
|
firstWeekOfYear: number;
|
||||||
|
minDaysInFirstWeek: number;
|
||||||
languages: string;
|
languages: string;
|
||||||
|
|
||||||
// Appearance
|
// Appearance
|
||||||
|
@ -51,12 +51,12 @@ function createSqlConsole() {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSqlConsole(sqlConsoleNoteId: string) {
|
async function saveSqlConsole(sqlConsoleNoteId: string) {
|
||||||
const sqlConsoleNote = becca.getNote(sqlConsoleNoteId);
|
const sqlConsoleNote = becca.getNote(sqlConsoleNoteId);
|
||||||
if (!sqlConsoleNote) throw new Error(`Unable to find SQL console note ID: ${sqlConsoleNoteId}`);
|
if (!sqlConsoleNote) throw new Error(`Unable to find SQL console note ID: ${sqlConsoleNoteId}`);
|
||||||
const today = dateUtils.localNowDate();
|
const today = dateUtils.localNowDate();
|
||||||
|
|
||||||
const sqlConsoleHome = attributeService.getNoteWithLabel("sqlConsoleHome") || dateNoteService.getDayNote(today);
|
const sqlConsoleHome = attributeService.getNoteWithLabel("sqlConsoleHome") || await dateNoteService.getDayNote(today);
|
||||||
|
|
||||||
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
||||||
|
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import { Menu, Tray, BrowserWindow } from "electron";
|
import { BrowserWindow,Menu, Tray } from "electron";
|
||||||
import path from "path";
|
|
||||||
import windowService from "./window.js";
|
|
||||||
import optionService from "./options.js";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import type { KeyboardActionNames } from "./keyboard_actions_interface.js";
|
|
||||||
import date_notes from "./date_notes.js";
|
|
||||||
import type BNote from "../becca/entities/bnote.js";
|
|
||||||
import becca from "../becca/becca.js";
|
|
||||||
import becca_service from "../becca/becca_service.js";
|
|
||||||
import type BRecentNote from "../becca/entities/brecent_note.js";
|
|
||||||
import { ipcMain, nativeTheme } from "electron/main";
|
import { ipcMain, nativeTheme } from "electron/main";
|
||||||
import { default as i18next, t } from "i18next";
|
import { default as i18next, t } from "i18next";
|
||||||
import { isDev, isMac } from "./utils.js";
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
import becca from "../becca/becca.js";
|
||||||
|
import becca_service from "../becca/becca_service.js";
|
||||||
|
import type BNote from "../becca/entities/bnote.js";
|
||||||
|
import type BRecentNote from "../becca/entities/brecent_note.js";
|
||||||
import cls from "./cls.js";
|
import cls from "./cls.js";
|
||||||
|
import date_notes from "./date_notes.js";
|
||||||
|
import type { KeyboardActionNames } from "./keyboard_actions_interface.js";
|
||||||
|
import optionService from "./options.js";
|
||||||
|
import { isDev, isMac } from "./utils.js";
|
||||||
|
import windowService from "./window.js";
|
||||||
|
|
||||||
let tray: Tray;
|
let tray: Tray;
|
||||||
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
// `mainWindow.isVisible` doesn't work with `mainWindow.show` and `mainWindow.hide` - it returns `false` when the window
|
||||||
// is minimized
|
// is minimized
|
||||||
let windowVisibilityMap: Record<number, boolean> = {};; // Dictionary for storing window ID and its visibility status
|
const windowVisibilityMap: Record<number, boolean> = {};; // Dictionary for storing window ID and its visibility status
|
||||||
|
|
||||||
function getTrayIconPath() {
|
function getTrayIconPath() {
|
||||||
let name: string;
|
let name: string;
|
||||||
@ -75,7 +76,7 @@ function updateWindowVisibilityMap(allWindows: BrowserWindow[]) {
|
|||||||
const currentWindowIds: number[] = allWindows.map(window => window.id);
|
const currentWindowIds: number[] = allWindows.map(window => window.id);
|
||||||
|
|
||||||
// Deleting closed windows from windowVisibilityMap
|
// Deleting closed windows from windowVisibilityMap
|
||||||
for (const [id, visibility] of Object.entries(windowVisibilityMap)) {
|
for (const [id, _] of Object.entries(windowVisibilityMap)) {
|
||||||
const windowId = Number(id);
|
const windowId = Number(id);
|
||||||
if (!currentWindowIds.includes(windowId)) {
|
if (!currentWindowIds.includes(windowId)) {
|
||||||
delete windowVisibilityMap[windowId];
|
delete windowVisibilityMap[windowId];
|
||||||
@ -133,7 +134,7 @@ function updateTrayMenu() {
|
|||||||
const parentNote = becca.getNoteOrThrow("_lbBookmarks");
|
const parentNote = becca.getNoteOrThrow("_lbBookmarks");
|
||||||
const menuItems: Electron.MenuItemConstructorOptions[] = [];
|
const menuItems: Electron.MenuItemConstructorOptions[] = [];
|
||||||
|
|
||||||
for (const bookmarkNote of parentNote?.children) {
|
for (const bookmarkNote of parentNote?.children ?? []) {
|
||||||
if (bookmarkNote.isLabelTruthy("bookmarkFolder")) {
|
if (bookmarkNote.isLabelTruthy("bookmarkFolder")) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
label: bookmarkNote.title,
|
label: bookmarkNote.title,
|
||||||
@ -194,7 +195,7 @@ function updateTrayMenu() {
|
|||||||
|
|
||||||
for (const idStr in windowVisibilityMap) {
|
for (const idStr in windowVisibilityMap) {
|
||||||
const id = parseInt(idStr, 10); // Get the ID of the window and make sure it is a number
|
const id = parseInt(idStr, 10); // Get the ID of the window and make sure it is a number
|
||||||
const isVisible = windowVisibilityMap[id];
|
const isVisible = windowVisibilityMap[id];
|
||||||
const win = allWindows.find(w => w.id === id);
|
const win = allWindows.find(w => w.id === id);
|
||||||
if (!win) {
|
if (!win) {
|
||||||
continue;
|
continue;
|
||||||
@ -214,10 +215,10 @@ function updateTrayMenu() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
...windowVisibilityMenuItems,
|
...windowVisibilityMenuItems,
|
||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: t("tray.open_new_window"),
|
label: t("tray.open_new_window"),
|
||||||
@ -235,7 +236,7 @@ function updateTrayMenu() {
|
|||||||
label: t("tray.today"),
|
label: t("tray.today"),
|
||||||
type: "normal",
|
type: "normal",
|
||||||
icon: getIconPath("today"),
|
icon: getIconPath("today"),
|
||||||
click: cls.wrap(() => openInSameTab(date_notes.getTodayNote()))
|
click: cls.wrap(async () => openInSameTab(await date_notes.getTodayNote()))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("tray.bookmarks"),
|
label: t("tray.bookmarks"),
|
||||||
@ -268,7 +269,7 @@ function updateTrayMenu() {
|
|||||||
|
|
||||||
function changeVisibility() {
|
function changeVisibility() {
|
||||||
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
||||||
|
|
||||||
if (!lastFocusedWindow) {
|
if (!lastFocusedWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
"saturday": "周六",
|
"saturday": "周六",
|
||||||
"sunday": "周日"
|
"sunday": "周日"
|
||||||
},
|
},
|
||||||
|
"weekdayNumber": "第 {weekNumber} 周",
|
||||||
"months": {
|
"months": {
|
||||||
"january": "一月",
|
"january": "一月",
|
||||||
"february": "二月",
|
"february": "二月",
|
||||||
@ -188,6 +189,7 @@
|
|||||||
"november": "十一月",
|
"november": "十一月",
|
||||||
"december": "十二月"
|
"december": "十二月"
|
||||||
},
|
},
|
||||||
|
"quarterNumber": "第 {quarterNumber} 季度",
|
||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "搜索:"
|
"search_prefix": "搜索:"
|
||||||
},
|
},
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
"saturday": "Saturday",
|
"saturday": "Saturday",
|
||||||
"sunday": "Sunday"
|
"sunday": "Sunday"
|
||||||
},
|
},
|
||||||
|
"weekdayNumber": "Week {weekNumber}",
|
||||||
"months": {
|
"months": {
|
||||||
"january": "January",
|
"january": "January",
|
||||||
"february": "February",
|
"february": "February",
|
||||||
@ -188,6 +189,7 @@
|
|||||||
"november": "November",
|
"november": "November",
|
||||||
"december": "December"
|
"december": "December"
|
||||||
},
|
},
|
||||||
|
"quarterNumber": "Quarter {quarterNumber}",
|
||||||
"special_notes": {
|
"special_notes": {
|
||||||
"search_prefix": "Search:"
|
"search_prefix": "Search:"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user