Merge pull request #1319 from TriliumNext/feature/rtl
Right-to-left support
@ -369,7 +369,8 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
|
||||
const { note, viewScope } = this;
|
||||
|
||||
let title = viewScope?.viewMode === "default" ? note.title : `${note.title}: ${viewScope?.viewMode}`;
|
||||
const isNormalView = (viewScope?.viewMode === "default" || viewScope?.viewMode === "contextual-help");
|
||||
let title = (isNormalView ? note.title : `${note.title}: ${viewScope?.viewMode}`);
|
||||
|
||||
if (viewScope?.attachmentId) {
|
||||
// assuming the attachment has been already loaded
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.92.0-beta",
|
||||
"appVersion": "0.92.2-beta",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@ -34,7 +34,7 @@
|
||||
"OkOZllzB3fqN",
|
||||
"yoAe4jV2yzbd"
|
||||
],
|
||||
"title": "Features",
|
||||
"title": "New Features",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
@ -47,53 +47,91 @@
|
||||
"value": "bx bx-star",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "sorted",
|
||||
"value": "dateCreated",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "sortDirection",
|
||||
"value": "desc",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Features",
|
||||
"dirFileName": "New Features",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "13D1lOc9sqmZ",
|
||||
"noteId": "3I277VKYxWDH",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"yoAe4jV2yzbd",
|
||||
"13D1lOc9sqmZ"
|
||||
"3I277VKYxWDH"
|
||||
],
|
||||
"title": "Export as PDF",
|
||||
"notePosition": 20,
|
||||
"title": "Right-to-left text notes",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-align-right",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Export as PDF.html",
|
||||
"dataFileName": "Right-to-left text notes.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "xsGM34t8ssKV",
|
||||
"attachmentId": "PSBNAvDyj5Vy",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Export as PDF_image.png"
|
||||
"dataFileName": "Right-to-left text notes_i.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "cvyes4f1Vhmm",
|
||||
"attachmentId": "YXYIJznak915",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Export as PDF_image.png"
|
||||
"dataFileName": "1_Right-to-left text notes_i.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "b3v1pLE6TF1Y",
|
||||
"attachmentId": "Do0S17lDl7uu",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Export as PDF_image.png"
|
||||
"dataFileName": "2_Right-to-left text notes_i.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "D3lyhPvPvocb",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Right-to-left text notes_i.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "Tu7llk3GgRkA",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Right-to-left text notes_i.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -106,12 +144,20 @@
|
||||
"B3YLYM4erjnW"
|
||||
],
|
||||
"title": "Zen mode",
|
||||
"notePosition": 30,
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxs-yin-yang",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Zen mode.html",
|
||||
"attachments": [
|
||||
@ -180,6 +226,50 @@
|
||||
"dataFileName": "7_Zen mode_image.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "13D1lOc9sqmZ",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"yoAe4jV2yzbd",
|
||||
"13D1lOc9sqmZ"
|
||||
],
|
||||
"title": "Export as PDF",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bxs-file-pdf",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Export as PDF.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "xsGM34t8ssKV",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Export as PDF_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "b3v1pLE6TF1Y",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Export as PDF_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -233,8 +323,47 @@
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Text.html",
|
||||
"attachments": []
|
||||
"attachments": [],
|
||||
"dirFileName": "Text",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "B0lcI9xz1r8K",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"wmegHv51MJMd",
|
||||
"crJtzsol4olb",
|
||||
"B0lcI9xz1r8K"
|
||||
],
|
||||
"title": "Content language",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "3I277VKYxWDH",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Content language.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "OpIv6CnYCLVa",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Content language_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
@ -382,7 +511,7 @@
|
||||
"title": "Book",
|
||||
"notePosition": 70,
|
||||
"prefix": null,
|
||||
"isExpanded": true,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
@ -576,6 +705,14 @@
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Calendar View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "JM6AU8N4MIgB",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "19_Calendar View_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -697,7 +834,7 @@
|
||||
"wmegHv51MJMd",
|
||||
"foPEtsL51pD2"
|
||||
],
|
||||
"title": "Geo Map",
|
||||
"title": "Geo map",
|
||||
"notePosition": 120,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
@ -713,23 +850,15 @@
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Geo Map.html",
|
||||
"dataFileName": "Geo map.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "J0baLTpafs7C",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "kcYjOvJDFkbS",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Geo Map_image.png"
|
||||
"dataFileName": "Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "FDP3JzIVSnuJ",
|
||||
@ -737,7 +866,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Geo Map_image.png"
|
||||
"dataFileName": "1_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "eUrcqc8RRuZG",
|
||||
@ -745,7 +874,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Geo Map_image.png"
|
||||
"dataFileName": "2_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "1quk4yxJpeHZ",
|
||||
@ -753,7 +882,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Geo Map_image.png"
|
||||
"dataFileName": "3_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "iSpyhQ5Ya6Nk",
|
||||
@ -761,7 +890,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Geo Map_image.png"
|
||||
"dataFileName": "4_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "ut6vm2aXVfXI",
|
||||
@ -769,7 +898,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "6_Geo Map_image.png"
|
||||
"dataFileName": "5_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "uYdb9wWf5Nuv",
|
||||
@ -777,15 +906,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "7_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "GhHYO2LteDmZ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "8_Geo Map_image.png"
|
||||
"dataFileName": "6_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "viN50n5G4kB0",
|
||||
@ -793,7 +914,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "9_Geo Map_image.png"
|
||||
"dataFileName": "7_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "mgwGrtQZjxxb",
|
||||
@ -801,7 +922,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "10_Geo Map_image.png"
|
||||
"dataFileName": "8_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "PMqmCbNLlZOG",
|
||||
@ -809,7 +930,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "11_Geo Map_image.png"
|
||||
"dataFileName": "9_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "0AwaQMqt3FVA",
|
||||
@ -817,7 +938,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "12_Geo Map_image.png"
|
||||
"dataFileName": "10_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gR2c2Thmfy3I",
|
||||
@ -825,7 +946,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "13_Geo Map_image.png"
|
||||
"dataFileName": "11_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "JULizn130rVI",
|
||||
@ -833,7 +954,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "14_Geo Map_image.png"
|
||||
"dataFileName": "12_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "MdC0DpifJwu4",
|
||||
@ -841,7 +962,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "15_Geo Map_image.png"
|
||||
"dataFileName": "13_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gFR2Izzp18LQ",
|
||||
@ -849,7 +970,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "16_Geo Map_image.png"
|
||||
"dataFileName": "14_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "42AncDs7SSAf",
|
||||
@ -857,15 +978,7 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "17_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "pKdtiq4r0eFY",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Geo Map_image.png"
|
||||
"dataFileName": "15_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "FXRVvYpOxWyR",
|
||||
@ -873,7 +986,23 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "19_Geo Map_image.png"
|
||||
"dataFileName": "16_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "qudP7UCtwIq3",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/jpg",
|
||||
"position": 10,
|
||||
"dataFileName": "17_Geo map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "utecGxWk08QY",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Geo map_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -943,173 +1072,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "DtJJ20yEozPA",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"DtJJ20yEozPA"
|
||||
],
|
||||
"title": "Theme development",
|
||||
"notePosition": 130,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-palette",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Theme development",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "5HH79ztN0fZA",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"DtJJ20yEozPA",
|
||||
"5HH79ztN0fZA"
|
||||
],
|
||||
"title": "Creating a custom theme",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "aH8Dk5aMiq7R",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Creating a custom theme.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "AJHVfQtIQgJ7",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gXLyv5KXjfxg",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "on1gD7BzCWdN",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "17p6z24yW5eP",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "K3cdwj8f90m0",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Creating a custom theme_im.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "bn93hwF7C8sR",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Creating a custom theme_im.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "aH8Dk5aMiq7R",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"DtJJ20yEozPA",
|
||||
"aH8Dk5aMiq7R"
|
||||
],
|
||||
"title": "Customize the Next theme",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "html",
|
||||
"dataFileName": "Customize the Next theme.html",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "5z4bC0x0eH0P",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Customize the Next theme_i.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "u0zkXkD7rGXA",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Customize the Next theme_i.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "pMq6N1oBV9oo",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"DtJJ20yEozPA",
|
||||
"pMq6N1oBV9oo"
|
||||
],
|
||||
"title": "Reference",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "po38jIc0LD2H",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"dataFileName": "Reference.html",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "LTnkDnYmmZ7s",
|
||||
@ -1283,7 +1245,7 @@
|
||||
"title": "ETAPI",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": true,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
@ -1333,7 +1295,7 @@
|
||||
"title": "Internal API",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": true,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
|
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 340 B |
After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@ -23,7 +23,7 @@
|
||||
as PDF. On the server or PWA (mobile), the option is not available due
|
||||
to technical constraints and it will be hidden.</p>
|
||||
<p>To print a note, select the
|
||||
<img src="2_Export as PDF_image.png" width="29"
|
||||
<img src="1_Export as PDF_image.png" width="29"
|
||||
height="31">button to the right of the note and select <i>Export as PDF</i>.</p>
|
||||
<p>Afterwards you will be prompted to select where to save the PDF file.
|
||||
Upon confirmation, the resulting PDF will be opened automatically using
|
||||
@ -33,7 +33,7 @@
|
||||
<a
|
||||
href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click
|
||||
on the
|
||||
<img src="2_Export as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML
|
||||
<img src="1_Export as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML
|
||||
in ZIP archive). Make sure not to accidentally leak any personal information.</p>
|
||||
<h2>Landscape mode</h2>
|
||||
<p>When exporting to PDF, there are no customizable settings such as page
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
@ -0,0 +1,56 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Right-to-left text notes</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Right-to-left text notes</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>Trilium now has basic support for right-to-left text, at note level.</p>
|
||||
<figure
|
||||
class="table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:906/557;" src="3_Right-to-left text notes_i.png"
|
||||
width="906" height="557">
|
||||
</figure>
|
||||
</td>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:906/557;" src="2_Right-to-left text notes_i.png"
|
||||
width="906" height="557">
|
||||
</figure>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>Note that only the Text note type supports this.</p>
|
||||
<p>The list of languages is configurable via the a new dedicated settings
|
||||
page:</p>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1248/635;" src="4_Right-to-left text notes_i.png"
|
||||
width="1248" height="635">
|
||||
</figure>
|
||||
<p>To select the corresponding language of the text, go to “Basic Properties”
|
||||
and select your desired language.</p>
|
||||
<p>
|
||||
<img src="1_Right-to-left text notes_i.png" width="635" height="492">
|
||||
</p>
|
||||
<p>Feel free to report any issues regarding right to left support.</p>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 515 KiB After Width: | Height: | Size: 515 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 397 KiB After Width: | Height: | Size: 397 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 4.6 KiB |
@ -118,6 +118,12 @@
|
||||
<td>When present (regardless of value), it will show the number of the week
|
||||
on the calendar.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~child:template</code>
|
||||
</td>
|
||||
<td>Defines the template for newly created notes in the calendar (via dragging
|
||||
or clicking).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
@ -175,6 +181,36 @@
|
||||
than the title, either a label (e.g. <code>#assignee</code>) or a relation
|
||||
(e.g. <code>~for</code>). See <i>Advanced use-cases</i> for more information.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:promotedAttributes</code>
|
||||
</td>
|
||||
<td>
|
||||
<p>Allows displaying the value of one or more promoted attributes in the
|
||||
calendar like this:
|
||||
<img src="19_Calendar View_image.png" width="131" height="113">
|
||||
</p><pre><code class="language-text-x-trilium-auto">#label:weight="promoted,number,single,precision=1"
|
||||
#label:mood="promoted,alias=Mood,single,text"
|
||||
#calendar:promotedAttributes="label:weight,label:mood" </code></pre>
|
||||
<p>It can also be used with relations, case in which it will display the
|
||||
title of the target note:</p><pre><code class="language-text-x-trilium-auto">#relation:assignee="promoted,alias=Assignee,single,text"
|
||||
#calendar:promotedAttributes="relation:assignee"
|
||||
~assignee=@My assignee </code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:startDate</code>
|
||||
</td>
|
||||
<td>Allows using a different label to represent the start date, other than <code>#startDate</code> (e.g. <code>#expiryDate</code>).
|
||||
The label name must be prefixed with <code>#</code>. If the label is not
|
||||
defined for a note, the default will be used instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:endDate</code>
|
||||
</td>
|
||||
<td>Allows using a different label to represent the start date, other than <code>#endDate</code>.
|
||||
The label name must be prefixed with <code>#</code>. If the label is not
|
||||
defined for a note, the default will be used instead.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
@ -5,12 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Geo Map</title>
|
||||
<title data-trilium-title>Geo map</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Geo Map</h1>
|
||||
<h1 data-trilium-h1>Geo map</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Creating a new geo map</h2>
|
||||
@ -26,7 +26,7 @@
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1256/1044;" src="9_Geo Map_image.png" width="1256"
|
||||
<img style="aspect-ratio:1256/1044;" src="7_Geo map_image.png" width="1256"
|
||||
height="1044">
|
||||
</figure>
|
||||
</td>
|
||||
@ -36,7 +36,7 @@
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1720/1396;" src="3_Geo Map_image.png" width="1720"
|
||||
<img style="aspect-ratio:1720/1396;" src="2_Geo map_image.png" width="1720"
|
||||
height="1396">
|
||||
</figure>
|
||||
</td>
|
||||
@ -69,18 +69,18 @@
|
||||
<p>To create a marker, first navigate to the desired point on the map. Then
|
||||
press the
|
||||
<img class="image_resized" style="aspect-ratio:72/66;width:7.37%;"
|
||||
src="4_Geo Map_image.png" width="72" height="66">button on the top-right of the map.</p>
|
||||
src="3_Geo map_image.png" width="72" height="66">button on the top-right of the map.</p>
|
||||
<p>If the button is not visible, make sure the button section is visible
|
||||
by pressing the chevron button (
|
||||
<img class="image_resized" style="aspect-ratio:72/66;width:7.51%;"
|
||||
src="10_Geo Map_image.png" width="72" height="66">) in the top-right of the map.</p>
|
||||
src="8_Geo map_image.png" width="72" height="66">) in the top-right of the map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1730/416;" src="14_Geo Map_image.png" width="1730"
|
||||
<img style="aspect-ratio:1730/416;" src="12_Geo map_image.png" width="1730"
|
||||
height="416">
|
||||
</figure>
|
||||
<p> </p>
|
||||
@ -96,7 +96,7 @@
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1586/404;" src="1_Geo Map_image.png" width="1586"
|
||||
<img style="aspect-ratio:1586/404;" src="Geo map_image.png" width="1586"
|
||||
height="404">
|
||||
</figure>
|
||||
<p> </p>
|
||||
@ -107,7 +107,7 @@
|
||||
<th>4</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1696/608;" src="6_Geo Map_image.png" width="1696"
|
||||
<img style="aspect-ratio:1696/608;" src="5_Geo map_image.png" width="1696"
|
||||
height="608">
|
||||
</figure>
|
||||
<p> </p>
|
||||
@ -122,7 +122,7 @@
|
||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||
of the child notes:</p>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1288/278;" src="12_Geo Map_image.png" width="1288"
|
||||
<img style="aspect-ratio:1288/278;" src="10_Geo map_image.png" width="1288"
|
||||
height="278">
|
||||
</figure>
|
||||
<p>This value can be added manually if needed. The value of the attribute
|
||||
@ -155,6 +155,13 @@
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Icon and color of the markers</h2>
|
||||
<p>
|
||||
<img src="18_Geo map_image.png" alt="image" width="523" height="295">
|
||||
</p>
|
||||
<p>The markers will have the same icon as the note.</p>
|
||||
<p>It's possible to add a custom color to a marker by assigning them a <code>#color</code> attribute
|
||||
such as <code>#color=green</code>.</p>
|
||||
<h2>Adding the coordinates manually</h2>
|
||||
<p>In a nutshell, create a child note and set the <code>#geolocation</code> attribute
|
||||
to the coordinates.</p>
|
||||
@ -168,7 +175,7 @@
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:732/918;" src="16_Geo Map_image.png" width="732"
|
||||
<img style="aspect-ratio:732/918;" src="14_Geo map_image.png" width="732"
|
||||
height="918">
|
||||
</figure>
|
||||
</td>
|
||||
@ -185,7 +192,7 @@
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:518/84;" src="19_Geo Map_image.png" width="518"
|
||||
<img style="aspect-ratio:518/84;" src="16_Geo map_image.png" width="518"
|
||||
height="84">
|
||||
</figure>
|
||||
</td>
|
||||
@ -199,7 +206,7 @@
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png" width="1074"
|
||||
<img style="aspect-ratio:1074/276;" src="9_Geo map_image.png" width="1074"
|
||||
height="276">
|
||||
</figure>
|
||||
</td>
|
||||
@ -225,7 +232,7 @@
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:562/454;" src="17_Geo Map_image.png" width="562"
|
||||
<img style="aspect-ratio:562/454;" src="15_Geo map_image.png" width="562"
|
||||
height="454">
|
||||
</figure>
|
||||
</td>
|
||||
@ -236,7 +243,7 @@
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:696/480;" src="13_Geo Map_image.png" width="696"
|
||||
<img style="aspect-ratio:696/480;" src="11_Geo map_image.png" width="696"
|
||||
height="480">
|
||||
</figure>
|
||||
</td>
|
||||
@ -250,7 +257,7 @@
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:640/276;" src="2_Geo Map_image.png" width="640"
|
||||
<img style="aspect-ratio:640/276;" src="1_Geo map_image.png" width="640"
|
||||
height="276">
|
||||
</figure>
|
||||
</td>
|
||||
@ -275,7 +282,7 @@
|
||||
<th>1</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:226/74;" src="7_Geo Map_image.png" width="226"
|
||||
<img style="aspect-ratio:226/74;" src="6_Geo map_image.png" width="226"
|
||||
height="74">
|
||||
</figure>
|
||||
</td>
|
||||
@ -286,7 +293,7 @@
|
||||
<th>2</th>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:322/222;" src="5_Geo Map_image.png" width="322"
|
||||
<img style="aspect-ratio:322/222;" src="4_Geo map_image.png" width="322"
|
||||
height="222">
|
||||
</figure>
|
||||
</td>
|
||||
@ -297,7 +304,7 @@
|
||||
<th>3</th>
|
||||
<td>
|
||||
<figure class="image image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:620/530;" src="15_Geo Map_image.png" width="620"
|
||||
<img style="aspect-ratio:620/530;" src="13_Geo map_image.png" width="620"
|
||||
height="530">
|
||||
</figure>
|
||||
</td>
|
||||
@ -310,9 +317,16 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<h2>Troubleshooting</h2>
|
||||
<h3>Grid-like artifacts on the map</h3>
|
||||
<p>
|
||||
<img class="image_resized" style="aspect-ratio:678/499;width:58%;" src="17_Geo map_image.png"
|
||||
width="678" height="499">
|
||||
</p>
|
||||
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
||||
of the map to not render correctly due to fractional scaling. The only
|
||||
possible solution i to set the UI zoom at 100% (default keyboard shortcut
|
||||
is Ctrl+0).</p>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@ -1,19 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Text</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Text</h1>
|
||||
|
||||
<div class="ck-content"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,34 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Content language</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Content language</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>A language hint can be provided for text notes. This option informs the
|
||||
browser or the desktop application about the language the note is written
|
||||
in (for example this might help with spellchecking), and it also determines
|
||||
whether the text is displayed from right-to-left for languages such as
|
||||
Arabic, Hebrew, etc.</p>
|
||||
<p>For more information about right-to-left support, see <a class="reference-link"
|
||||
href="../../New%20Features/Right-to-left%20text%20notes.html">Right-to-left text notes</a>.</p>
|
||||
<p>To set the language of the content, go to “Basic Properties” and look
|
||||
for the “Language” field. By default there will be no content languages
|
||||
set, they can be configured by going to settings or by selecting the “Configure
|
||||
languages” item in the list.</p>
|
||||
<p>
|
||||
<img src="Content language_image.png" width="635" height="492">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 4.7 KiB |
@ -1,94 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Creating a custom theme</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Creating a custom theme</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Step 1. Find a place to place the themes</h2>
|
||||
<p>Organization is an important aspect of managing a knowledge base. When
|
||||
developing a new theme or importing an existing one it's a good idea to
|
||||
keep them into one place.</p>
|
||||
<p>As such, the first step is to create a new note to gather all the themes.</p>
|
||||
<p>
|
||||
<img src="5_Creating a custom theme_im.png" width="181" height="84">
|
||||
</p>
|
||||
<h2>Step 2. Create the theme</h2>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:32.47%;">
|
||||
<col style="width:67.53%;">
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:651/220;" src="3_Creating a custom theme_im.png"
|
||||
width="651" height="220">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Themes are code notes with a special attribute. Start by creating a new
|
||||
code note.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:302/349;" src="1_Creating a custom theme_im.png"
|
||||
width="302" height="349">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">Then change the note type to a CSS code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:316/133;" src="Creating a custom theme_im.png"
|
||||
width="316" height="133">
|
||||
</figure>
|
||||
</td>
|
||||
<td style="vertical-align:top;">In the <i>Owned Attributes</i> section define the <code>#appTheme</code> attribute
|
||||
to point to any desired name. This is the name that will show up in the
|
||||
appearance section in settings.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Step 3. Define the theme's CSS</h2>
|
||||
<p>As a very simple example we will change the background color of the launcher
|
||||
pane to a shade of blue.</p>
|
||||
<p>To alter the different variables of the theme:</p><pre><code class="language-text-css">:root {
|
||||
--launcher-pane-background-color: #0d6efd;
|
||||
}</code></pre>
|
||||
<h2>Step 4. Activating the theme</h2>
|
||||
<p>Refresh the application (Ctrl+Shift+R is a good way to do so) and go to
|
||||
settings. You should see the newly created theme:</p>
|
||||
<p>
|
||||
<img src="2_Creating a custom theme_im.png" width="631" height="481">
|
||||
</p>
|
||||
<p>Afterwards the application will refresh itself with the new theme:</p>
|
||||
<p>
|
||||
<img src="4_Creating a custom theme_im.png" width="653" height="554">
|
||||
</p>
|
||||
<p>Do note that the theme will be based off of the legacy theme. To override
|
||||
that and base the theme on the new TriliumNext theme, see: <a class="reference-link"
|
||||
href="Customize%20the%20Next%20theme.html">Theme base (legacy vs. next)</a>
|
||||
</p>
|
||||
<h2>Step 5. Making changes</h2>
|
||||
<p>Simply go back to the note and change according to needs. To apply the
|
||||
changes to the current window, press Ctrl+Shift+R to refresh.</p>
|
||||
<p>It's a good idea to keep two windows, one for editing and the other one
|
||||
for previewing the changes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
Before Width: | Height: | Size: 11 KiB |
@ -1,36 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Customize the Next theme</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Customize the Next theme</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<p>By default, any custom theme will be based on the legacy light theme.
|
||||
To use the TriliumNext theme instead, add the <code>#appThemeBase=next</code> attribute
|
||||
onto the existing theme. The <code>appTheme</code> attribute must also be
|
||||
present.</p>
|
||||
<p>
|
||||
<img src="Customize the Next theme_i.png" width="424" height="140">
|
||||
</p>
|
||||
<p>When <code>appThemeBase</code> is set to <code>next</code> it will use the
|
||||
“TriliumNext (auto)” theme. Any other value is ignored and will use the
|
||||
legacy white theme instead.</p>
|
||||
<h2>Overrides</h2>
|
||||
<p>Do note that the TriliumNext theme has a few more overrides than the legacy
|
||||
theme, so you might need to suffix <code>!important</code> if the style changes
|
||||
are not applied.</p><pre><code class="language-text-css">:root {
|
||||
--launcher-pane-background-color: #0d6efd !important;
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
Before Width: | Height: | Size: 14 KiB |
@ -1,180 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../../style.css">
|
||||
<base target="_parent">
|
||||
<title data-trilium-title>Reference</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 data-trilium-h1>Reference</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<h2>Detecting mobile vs. desktop</h2>
|
||||
<p>The mobile layout is different than the one on the desktop. Use <code>body.mobile</code> and <code>body.desktop</code> to
|
||||
differentiate between them.</p><pre><code class="language-text-css">body.mobile #root-widget {
|
||||
/* Do something on mobile */
|
||||
}
|
||||
|
||||
body.desktop #root-widget {
|
||||
/* Do something on desktop */
|
||||
}</code></pre>
|
||||
<p>Do note that there is also a “tablet mode” in the mobile layout. For that
|
||||
particular case media queries are required:</p><pre><code class="language-text-css">@media (max-width: 991px) {
|
||||
|
||||
#launcher-pane {
|
||||
|
||||
/* Do something on mobile layout */
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width: 992px) {
|
||||
|
||||
#launcher-pane {
|
||||
|
||||
/* Do something on mobile tablet + desktop layout */
|
||||
|
||||
}
|
||||
|
||||
}</code></pre>
|
||||
<h2>Detecting horizontal vs. vertical layout</h2>
|
||||
<p>The user can select between vertical layout (the classical one, where
|
||||
the launcher bar is on the left) and a horizontal layout (where the launcher
|
||||
bar is on the top and tabs are full-width).</p>
|
||||
<p>Different styles can be applied by using classes at <code>body</code> level:</p><pre><code class="language-text-x-trilium-auto">body.layout-vertical #left-pane {
|
||||
/* Do something */
|
||||
}
|
||||
|
||||
body.layout-horizontal #center-pane {
|
||||
/* Do something else */
|
||||
}</code></pre>
|
||||
<p>The two different layouts use different containers (but they are present
|
||||
in the DOM regardless of the user's choice), for example <code>#horizontal-main-container</code> and <code>#vertical-main-container</code> can
|
||||
be used to customize the background of the content section.</p>
|
||||
<h2>Detecting platform (Windows, macOS) or Electron</h2>
|
||||
<p>It is possible to add particular styles that only apply to a given platform
|
||||
by using the classes in <code>body</code>:</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Windows</th>
|
||||
<th>macOS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><pre><code class="language-text-x-trilium-auto">body.platform-win32 {
|
||||
background: red;
|
||||
}</code></pre>
|
||||
</td>
|
||||
<td><pre><code class="language-text-x-trilium-auto">body.platform-darwin {
|
||||
background: red;
|
||||
}</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>It is also possible to only apply a style if running under Electron (desktop
|
||||
application):</p><pre><code class="language-text-x-trilium-auto">body.electron {
|
||||
background: blue;
|
||||
}</code></pre>
|
||||
<h3>Native title bar</h3>
|
||||
<p>It's possible to detect if the user has selected the native title bar
|
||||
or the custom title bar by querying against <code>body</code>:</p><pre><code class="language-text-x-trilium-auto">body.electron.native-titlebar {
|
||||
/* Do something */
|
||||
}
|
||||
|
||||
body.electron:not(.native-titlebar) {
|
||||
/* Do something else */
|
||||
}</code></pre>
|
||||
<h3>Native window buttons</h3>
|
||||
<p>When running under Electron with native title bar off, a feature was introduced
|
||||
to use the platform-specific window buttons such as the semaphore on macOS.</p>
|
||||
<p>See <a href="https://github.com/TriliumNext/Notes/pull/702">Native title bar buttons by eliandoran · Pull Request #702 · TriliumNext/Notes</a> for
|
||||
the original implementation of this feature, including screenshots.</p>
|
||||
<h4>On Windows</h4>
|
||||
<p>The colors of the native window button area can be adjusted using a RGB
|
||||
hex color:</p><pre><code class="language-text-x-trilium-auto">body {
|
||||
--native-titlebar-foreground: #ffffff;
|
||||
--native-titlebar-background: #ff0000;
|
||||
}</code></pre>
|
||||
<p>It is also possible to use transparency at the cost of reduced hover colors
|
||||
using a RGBA hex color:</p><pre><code class="language-text-x-trilium-auto">body {
|
||||
--native-titlebar-background: #ff0000aa;
|
||||
}</code></pre>
|
||||
<p>Note that the value is read when the window is initialized and then it
|
||||
is refreshed only when the user changes their light/dark mode preference.</p>
|
||||
<h4>On macOS</h4>
|
||||
<p>On macOS the semaphore window buttons are enabled by default when the
|
||||
native title bar is disabled. The offset of the buttons can be adjusted
|
||||
using:</p><pre><code class="language-text-css">body {
|
||||
--native-titlebar-darwin-x-offset: 12;
|
||||
--native-titlebar-darwin-y-offset: 14 !important;
|
||||
}</code></pre>
|
||||
<h3>Background/transparency effects on Windows (Mica)</h3>
|
||||
<p>Windows 11 offers a special background/transparency effect called Mica,
|
||||
which can be enabled by themes by setting the <code>--background-material</code> variable
|
||||
at <code>body</code> level:</p><pre><code class="language-text-css">body.electron.platform-win32 {
|
||||
--background-material: tabbed;
|
||||
}</code></pre>
|
||||
<p>The value can be either <code>tabbed</code> (especially useful for the horizontal
|
||||
layout) or <code>mica</code> (ideal for the vertical layout).</p>
|
||||
<p>Do note that the Mica effect is applied at <code>body</code> level and the
|
||||
theme needs to make the entire hierarchy (semi-)transparent in order for
|
||||
it to be visible. Use the TrilumNext theme as an inspiration.</p>
|
||||
<h2>Note icons, tab workspace accent color</h2>
|
||||
<p>Theme capabilities are small adjustments done through CSS variables that
|
||||
can affect the layout or the visual aspect of the application.</p>
|
||||
<p>In the tab bar, to display the icons of notes instead of the icon of the
|
||||
workspace:</p><pre><code class="language-text-css">:root {
|
||||
--tab-note-icons: true;
|
||||
}</code></pre>
|
||||
<p>When a workspace is hoisted for a given tab, it is possible to get the
|
||||
background color of that workspace, for example to apply a small strip
|
||||
on the tab instead of the whole background color:</p><pre><code class="language-text-css">.note-tab .note-tab-wrapper {
|
||||
--tab-background-color: initial !important;
|
||||
}
|
||||
|
||||
.note-tab .note-tab-wrapper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background-color: var(--workspace-tab-background-color);
|
||||
}</code></pre>
|
||||
<h2>Custom fonts</h2>
|
||||
<p>Currently the only way to include a custom font is to use <a class="reference-link"
|
||||
href="../Advanced%20topics/Custom%20resource%20providers.html">Custom resource providers</a>.
|
||||
Basically import a font into Trilium and assign it <code>#customResourceProvider=fonts/myfont.ttf</code> and
|
||||
then import the font in CSS via <code>/custom/fonts/myfont.ttf</code>.</p>
|
||||
<h2>Dark and light themes</h2>
|
||||
<p>A light theme needs to have the following CSS:</p><pre><code class="language-text-css">:root {
|
||||
--theme-style: light;
|
||||
}</code></pre>
|
||||
<p>if the theme is dark, then <code>--theme-style</code> needs to be <code>dark</code>.</p>
|
||||
<p>If the theme is auto (e.g. supports both light or dark based on <code>prefers-color-scheme</code>)
|
||||
it must also declare (in addition to setting <code>--theme-style</code> to
|
||||
either <code>light</code> or <code>dark</code>):</p><pre><code class="language-text-css">:root {
|
||||
|
||||
--theme-style-auto: true;
|
||||
|
||||
}</code></pre>
|
||||
<p>This will affect the behavior of the Electron application by informing
|
||||
the operating system of the color preference (e.g. background effects will
|
||||
appear correct on Windows).</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -6,6 +6,6 @@
|
||||
</head>
|
||||
<frameset cols="25%,75%">
|
||||
<frame name="navigation" src="navigation.html">
|
||||
<frame name="detail" src="User%20Guide/Features/Export%20as%20PDF.html">
|
||||
<frame name="detail" src="User%20Guide/New%20Features/Right-to-left%20text%20notes.html">
|
||||
</frameset>
|
||||
</html>
|
@ -9,17 +9,24 @@
|
||||
<ul>
|
||||
<li>User Guide
|
||||
<ul>
|
||||
<li>Features
|
||||
<li>New Features
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Features/Export%20as%20PDF.html" target="detail">Export as PDF</a>
|
||||
<li><a href="User%20Guide/New%20Features/Right-to-left%20text%20notes.html"
|
||||
target="detail">Right-to-left text notes</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Features/Zen%20mode.html" target="detail">Zen mode</a>
|
||||
<li><a href="User%20Guide/New%20Features/Zen%20mode.html" target="detail">Zen mode</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/New%20Features/Export%20as%20PDF.html" target="detail">Export as PDF</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Note Types
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Note%20Types/Text.html" target="detail">Text</a>
|
||||
<li>Text
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Note%20Types/Text/Content%20language.html" target="detail">Content language</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Note%20Types/Code.html" target="detail">Code</a>
|
||||
</li>
|
||||
@ -45,7 +52,7 @@
|
||||
</li>
|
||||
<li><a href="User%20Guide/Note%20Types/Mind%20Map.html" target="detail">Mind Map</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Note%20Types/Geo%20Map.html" target="detail">Geo Map</a>
|
||||
<li><a href="User%20Guide/Note%20Types/Geo%20map.html" target="detail">Geo map</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -56,18 +63,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Theme development
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Theme%20development/Creating%20a%20custom%20theme.html"
|
||||
target="detail">Creating a custom theme</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Theme%20development/Customize%20the%20Next%20theme.html"
|
||||
target="detail">Customize the Next theme</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Theme%20development/Reference.html" target="detail">Reference</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Scripting
|
||||
<ul>
|
||||
<li>Examples
|
||||
|
@ -23,6 +23,28 @@ async function removeAttributeById(noteId: string, attributeId: string) {
|
||||
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy.
|
||||
* For an attribute with an empty value, pass an empty string instead.
|
||||
*
|
||||
* @param note the note to set the attribute to.
|
||||
* @param type the type of attribute (label or relation).
|
||||
* @param name the name of the attribute to set.
|
||||
* @param value the value of the attribute to set.
|
||||
*/
|
||||
async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
} else {
|
||||
// Remove the attribute if it exists on the server but we don't define a value for it.
|
||||
const attributeId = note.getAttribute(type, name)?.attributeId;
|
||||
if (attributeId) {
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns - returns true if this attribute has the potential to influence the note in the argument.
|
||||
* That can happen in multiple ways:
|
||||
@ -66,6 +88,7 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin
|
||||
export default {
|
||||
addLabel,
|
||||
setLabel,
|
||||
setAttribute,
|
||||
removeAttributeById,
|
||||
isAffecting
|
||||
};
|
||||
|
@ -1,10 +1,16 @@
|
||||
import options from "./options.js";
|
||||
import i18next from "i18next";
|
||||
import i18nextHttpBackend from "i18next-http-backend";
|
||||
import server from "./server.js";
|
||||
import type { Locale } from "../../../services/i18n.js";
|
||||
|
||||
let locales: Locale[] | null;
|
||||
|
||||
export async function initLocale() {
|
||||
const locale = (options.get("locale") as string) || "en";
|
||||
|
||||
locales = await server.get<Locale[]>("options/locales");
|
||||
|
||||
await i18next.use(i18nextHttpBackend).init({
|
||||
lng: locale,
|
||||
fallbackLng: "en",
|
||||
@ -15,5 +21,24 @@ export async function initLocale() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getAvailableLocales() {
|
||||
if (!locales) {
|
||||
throw new Error("Tried to load list of locales, but localization is not yet initialized.")
|
||||
}
|
||||
|
||||
return locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the given locale by ID.
|
||||
*
|
||||
* @param localeId the locale ID to search for.
|
||||
* @returns the corresponding {@link Locale} or `null` if it was not found.
|
||||
*/
|
||||
export function getLocaleById(localeId: string | null | undefined) {
|
||||
if (!localeId) return null;
|
||||
return locales?.find((l) => l.id === localeId) ?? null;
|
||||
}
|
||||
|
||||
export const t = i18next.t;
|
||||
export const getCurrentLanguage = () => i18next.language;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import { Modal } from "bootstrap";
|
||||
import type { ViewScope } from "./link.js";
|
||||
|
||||
function reloadFrontendApp(reason?: string) {
|
||||
if (reason) {
|
||||
@ -388,6 +389,10 @@ function initHelpDropdown($el: JQuery<HTMLElement>) {
|
||||
const wikiBaseUrl = "https://triliumnext.github.io/Docs/Wiki/";
|
||||
|
||||
function openHelp($button: JQuery<HTMLElement>) {
|
||||
if ($button.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const helpPage = $button.attr("data-help-page");
|
||||
|
||||
if (helpPage) {
|
||||
@ -397,12 +402,44 @@ function openHelp($button: JQuery<HTMLElement>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openInAppHelp($button: JQuery<HTMLElement>) {
|
||||
if ($button.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inAppHelpPage = $button.attr("data-in-app-help");
|
||||
if (inAppHelpPage) {
|
||||
// Dynamic import to avoid import issues in tests.
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
|
||||
const targetNote = `_help_${inAppHelpPage}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help",
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
appContext.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
})
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) {
|
||||
// for some reason, the .on(event, listener, handler) does not work here (e.g. Options -> Sync -> Help button)
|
||||
// so we do it manually
|
||||
$el.on("click", (e) => {
|
||||
const $helpButton = $(e.target).closest("[data-help-page]");
|
||||
openHelp($helpButton);
|
||||
openHelp($(e.target).closest("[data-help-page]"));
|
||||
openInAppHelp($(e.target).closest("[data-in-app-help]"));
|
||||
});
|
||||
}
|
||||
|
||||
|
17
src/public/app/types.d.ts
vendored
@ -5,6 +5,7 @@ import utils from "./services/utils.ts";
|
||||
import appContext from "./components/app_context.ts";
|
||||
import server from "./services/server.ts";
|
||||
import library_loader, { Library } from "./services/library_loader.ts";
|
||||
import type { init } from "i18next";
|
||||
|
||||
interface ElectronProcess {
|
||||
type: string;
|
||||
@ -139,10 +140,26 @@ declare global {
|
||||
}
|
||||
interface MermaidLoader {
|
||||
|
||||
}
|
||||
interface MermaidChartConfig {
|
||||
useMaxWidth: boolean;
|
||||
}
|
||||
interface MermaidConfig {
|
||||
theme: string;
|
||||
securityLevel: "antiscript",
|
||||
flow: MermaidChartConfig;
|
||||
sequence: MermaidChartConfig;
|
||||
gantt: MermaidChartConfig;
|
||||
class: MermaidChartConfig;
|
||||
state: MermaidChartConfig;
|
||||
pie: MermaidChartConfig;
|
||||
journey: MermaidChartConfig;
|
||||
git: MermaidChartConfig;
|
||||
}
|
||||
var mermaid: {
|
||||
mermaidAPI: MermaidApi;
|
||||
registerLayoutLoaders(loader: MermaidLoader);
|
||||
init(config: MermaidConfig, el: HTMLElement | JQuery<HTMLElement>);
|
||||
parse(content: string, opts: {
|
||||
suppressErrors: true
|
||||
}): Promise<{
|
||||
|
@ -10,7 +10,8 @@ const TPL = `
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floating-buttons-children,.show-floating-buttons {
|
||||
.floating-buttons-children,
|
||||
.show-floating-buttons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@ -19,6 +20,21 @@ const TPL = `
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.note-split.rtl .floating-buttons-children,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
right: unset;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.type-canvas .floating-buttons-children {
|
||||
top: 70px;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { ViewScope } from "../../services/link.js";
|
||||
@ -39,47 +40,28 @@ const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
||||
private helpNoteIdToOpen?: string | null;
|
||||
|
||||
isEnabled() {
|
||||
this.helpNoteIdToOpen = null;
|
||||
|
||||
if (!super.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.note && this.note.type !== "book" && byNoteType[this.note.type]) {
|
||||
this.helpNoteIdToOpen = byNoteType[this.note.type];
|
||||
} else if (this.note && this.note.type === "book") {
|
||||
this.helpNoteIdToOpen = byBookType[(this.note.getAttributeValue("label", "viewType") as ViewTypeOptions) ?? ""];
|
||||
}
|
||||
|
||||
return !!this.helpNoteIdToOpen;
|
||||
return !!ContextualHelpButton.#getUrlToOpen(this.note);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => {
|
||||
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
||||
const targetNote = `_help_${this.helpNoteIdToOpen}`;
|
||||
const helpSubcontext = subContexts?.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help"
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
|
||||
this.triggerCommand("openNewNoteSplit", {
|
||||
ntxId,
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
});
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static #getUrlToOpen(note: FNote | null | undefined) {
|
||||
if (note && note.type !== "book" && byNoteType[note.type]) {
|
||||
return byNoteType[note.type];
|
||||
} else if (note && note.type === "book") {
|
||||
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote | null | undefined): Promise<void> {
|
||||
this.$widget.attr("data-in-app-help", ContextualHelpButton.#getUrlToOpen(this.note) ?? "");
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
|
@ -143,7 +143,7 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMermaidConfig() {
|
||||
export function getMermaidConfig(): MermaidConfig {
|
||||
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||
const mermaidTheme = documentStyle.getPropertyValue("--mermaid-theme");
|
||||
|
||||
|
@ -157,6 +157,9 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
typeWidget.spacedUpdate = this.spacedUpdate;
|
||||
typeWidget.setParent(this);
|
||||
|
||||
if (this.noteContext) {
|
||||
typeWidget.setNoteContextEvent({ noteContext: this.noteContext });
|
||||
}
|
||||
const $renderedWidget = typeWidget.render();
|
||||
keyboardActionsService.updateDisplayedShortcuts($renderedWidget);
|
||||
|
||||
|
169
src/public/app/widgets/note_language.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { Dropdown } from "bootstrap";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import { getAvailableLocales, getLocaleById } from "../services/i18n.js";
|
||||
import { t } from "i18next";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import attributes from "../services/attributes.js";
|
||||
import type { Locale } from "../../../services/i18n.js";
|
||||
import options from "../services/options.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
|
||||
const TPL = `\
|
||||
<div class="dropdown note-language-widget">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-language-button">
|
||||
<span class="note-language-desc"></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="note-language-dropdown dropdown-menu dropdown-menu-left tn-dropdown-list"></div>
|
||||
<button class="language-help-button icon-action bx bx-help-circle" type="button" data-in-app-help="B0lcI9xz1r8K" title="${t("open-help-page")}"></button>
|
||||
|
||||
<style>
|
||||
.note-language-widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.language-help-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.note-language-dropdown [dir=rtl] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown-item.rtl > .check {
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const DEFAULT_LOCALE: Locale = {
|
||||
id: "",
|
||||
name: t("note_language.not_set")
|
||||
};
|
||||
|
||||
export default class NoteLanguageWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: Dropdown;
|
||||
private $noteLanguageDropdown!: JQuery<HTMLElement>;
|
||||
private $noteLanguageDesc!: JQuery<HTMLElement>;
|
||||
private locales: (Locale | "---")[];
|
||||
private currentLanguageId?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.locales = NoteLanguageWidget.#buildLocales();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
this.$widget.on("show.bs.dropdown", () => this.renderDropdown());
|
||||
|
||||
this.$noteLanguageDropdown = this.$widget.find(".note-language-dropdown")
|
||||
this.$noteLanguageDesc = this.$widget.find(".note-language-desc");
|
||||
}
|
||||
|
||||
renderDropdown() {
|
||||
this.$noteLanguageDropdown.empty();
|
||||
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const locale of this.locales) {
|
||||
if (typeof locale === "object") {
|
||||
const $title = $("<span>").text(locale.name);
|
||||
|
||||
const $link = $('<a class="dropdown-item">')
|
||||
.attr("data-language", locale.id)
|
||||
.append('<span class="check">✓</span> ')
|
||||
.append($title)
|
||||
.on("click", () => {
|
||||
const languageId = $link.attr("data-language") ?? "";
|
||||
this.save(languageId);
|
||||
});
|
||||
|
||||
if (locale.rtl) {
|
||||
$link.attr("dir", "rtl");
|
||||
}
|
||||
|
||||
if (locale.id === this.currentLanguageId) {
|
||||
$link.addClass("selected");
|
||||
}
|
||||
|
||||
this.$noteLanguageDropdown.append($link);
|
||||
} else {
|
||||
this.$noteLanguageDropdown.append('<div class="dropdown-divider"></div>');
|
||||
}
|
||||
}
|
||||
|
||||
const $configureLink = $('<a class="dropdown-item">')
|
||||
.append(`<span>${t("note_language.configure-languages")}</span>`)
|
||||
.on("click", () => appContext.tabManager.openContextWithNote("_optionsLocalization", { activate: true }));
|
||||
this.$noteLanguageDropdown.append($configureLink);
|
||||
}
|
||||
|
||||
async save(languageId: string) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes.setAttribute(this.note, "label", "language", languageId);
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const currentLanguageId = note.getLabelValue("language") ?? "";
|
||||
const language = getLocaleById(currentLanguageId) ?? DEFAULT_LOCALE;
|
||||
this.currentLanguageId = currentLanguageId;
|
||||
this.$noteLanguageDesc.text(language.name);
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("languages")) {
|
||||
this.locales = NoteLanguageWidget.#buildLocales();
|
||||
}
|
||||
|
||||
if (loadResults.getAttributeRows().find((a) => a.noteId === this.noteId && a.name === "language")) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static #buildLocales() {
|
||||
const enabledLanguages = JSON.parse(options.get("languages") ?? "[]") as string[];
|
||||
const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id));
|
||||
const leftToRightLanguages = filteredLanguages.filter((l) => !l.rtl);
|
||||
const rightToLeftLanguages = filteredLanguages.filter((l) => l.rtl);
|
||||
|
||||
let locales: ("---" | Locale)[] = [
|
||||
DEFAULT_LOCALE
|
||||
];
|
||||
|
||||
if (leftToRightLanguages.length > 0) {
|
||||
locales = [
|
||||
...locales,
|
||||
"---",
|
||||
...leftToRightLanguages
|
||||
];
|
||||
}
|
||||
|
||||
if (rightToLeftLanguages.length > 0) {
|
||||
locales = [
|
||||
...locales,
|
||||
"---",
|
||||
...rightToLeftLanguages
|
||||
];
|
||||
}
|
||||
|
||||
// This will separate the list of languages from the "Configure languages" button.
|
||||
// If there is at least one language.
|
||||
if (locales.length > 2) {
|
||||
locales.push("---");
|
||||
}
|
||||
return locales;
|
||||
}
|
||||
|
||||
}
|
@ -89,7 +89,10 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isReadOnly = (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) || utils.isLaunchBarConfig(note.noteId) || this.noteContext?.viewScope?.viewMode !== "default";
|
||||
const isReadOnly =
|
||||
(note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|
||||
|| utils.isLaunchBarConfig(note.noteId)
|
||||
|| this.noteContext?.viewScope?.viewMode !== "default";
|
||||
|
||||
this.$noteTitle.val(isReadOnly ? (await this.noteContext?.getNavigationTitle()) || "" : note.title);
|
||||
this.$noteTitle.prop("readonly", isReadOnly);
|
||||
|
@ -5,6 +5,7 @@ import type BasicWidget from "./basic_widget.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { getLocaleById } from "../services/i18n.js";
|
||||
|
||||
export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
|
||||
@ -56,6 +57,10 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
this.$widget.addClass(utils.getMimeTypeClass(note.mime));
|
||||
|
||||
this.$widget.toggleClass("protected", note.isProtected);
|
||||
|
||||
const noteLanguage = note?.getLabelValue("language");
|
||||
const locale = getLocaleById(noteLanguage);
|
||||
this.$widget.toggleClass("rtl", !!locale?.rtl);
|
||||
}
|
||||
|
||||
#isFullWidthNote(note: FNote) {
|
||||
@ -76,7 +81,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
const noteId = this.noteContext?.noteId;
|
||||
if (
|
||||
loadResults.isNoteReloaded(noteId) ||
|
||||
loadResults.getAttributeRows().find((attr) => attr.type === "label" && attr.name === "cssClass" && attributeService.isAffecting(attr, this.noteContext?.note))
|
||||
loadResults.getAttributeRows().find((attr) => attr.type === "label" && ["cssClass", "language"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.noteContext?.note))
|
||||
) {
|
||||
this.refresh();
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import BookmarkSwitchWidget from "../bookmark_switch.js";
|
||||
import SharedSwitchWidget from "../shared_switch.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import TemplateSwitchWidget from "../template_switch.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import NoteLanguageWidget from "../note_language.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="basic-properties-widget">
|
||||
@ -16,40 +18,55 @@ const TPL = `
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.basic-properties-widget > * {
|
||||
|
||||
.basic-properties-widget > * {
|
||||
margin-top: 9px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
|
||||
.basic-properties-widget > * > :last-child {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.note-type-container, .editability-select-container {
|
||||
.note-type-container,
|
||||
.editability-select-container,
|
||||
.note-language-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="note-type-container">
|
||||
<span>${t("basic_properties.note_type")}:</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="protected-note-switch-container"></div>
|
||||
|
||||
|
||||
<div class="editability-select-container">
|
||||
<span>${t("basic_properties.editable")}:</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bookmark-switch-container"></div>
|
||||
|
||||
|
||||
<div class="shared-switch-container"></div>
|
||||
|
||||
<div class="template-switch-container"></div>
|
||||
|
||||
<div class="note-language-container">
|
||||
<span>${t("basic_properties.language")}:</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
|
||||
private noteTypeWidget: NoteTypeWidget;
|
||||
private protectedNoteSwitchWidget: ProtectedNoteSwitchWidget;
|
||||
private editabilitySelectWidget: EditabilitySelectWidget;
|
||||
private bookmarkSwitchWidget: BookmarkSwitchWidget;
|
||||
private sharedSwitchWidget: SharedSwitchWidget;
|
||||
private templateSwitchWidget: TemplateSwitchWidget;
|
||||
private noteLanguageWidget: NoteLanguageWidget;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -59,8 +76,16 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
this.bookmarkSwitchWidget = new BookmarkSwitchWidget().contentSized();
|
||||
this.sharedSwitchWidget = new SharedSwitchWidget().contentSized();
|
||||
this.templateSwitchWidget = new TemplateSwitchWidget().contentSized();
|
||||
this.noteLanguageWidget = new NoteLanguageWidget().contentSized();
|
||||
|
||||
this.child(this.noteTypeWidget, this.protectedNoteSwitchWidget, this.editabilitySelectWidget, this.bookmarkSwitchWidget, this.sharedSwitchWidget, this.templateSwitchWidget);
|
||||
this.child(
|
||||
this.noteTypeWidget,
|
||||
this.protectedNoteSwitchWidget,
|
||||
this.editabilitySelectWidget,
|
||||
this.bookmarkSwitchWidget,
|
||||
this.sharedSwitchWidget,
|
||||
this.templateSwitchWidget,
|
||||
this.noteLanguageWidget);
|
||||
}
|
||||
|
||||
get name() {
|
||||
@ -73,7 +98,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
|
||||
getTitle() {
|
||||
return {
|
||||
show: !this.note.isLaunchBarConfig(),
|
||||
show: !this.note?.isLaunchBarConfig(),
|
||||
title: t("basic_properties.basic_properties"),
|
||||
icon: "bx bx-slider"
|
||||
};
|
||||
@ -89,11 +114,16 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
|
||||
this.$widget.find(".bookmark-switch-container").append(this.bookmarkSwitchWidget.render());
|
||||
this.$widget.find(".shared-switch-container").append(this.sharedSwitchWidget.render());
|
||||
this.$widget.find(".template-switch-container").append(this.templateSwitchWidget.render());
|
||||
this.$widget.find(".note-language-container").append(this.noteLanguageWidget.render());
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
async refreshWithNote(note: FNote) {
|
||||
await super.refreshWithNote(note);
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$widget.find(".editability-select-container").toggle(this.note && ["text", "code"].includes(this.note.type));
|
||||
this.$widget.find(".note-language-container").toggle(this.note && ["text"].includes(this.note.type));
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import linkService from "../../services/link.js";
|
||||
import contentRenderer from "../../services/content_renderer.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import options from "../../services/options.js";
|
||||
import attributes from "../../services/attributes.js";
|
||||
|
||||
export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
doRender() {
|
||||
@ -117,5 +118,16 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
if (loadResults.isOptionReloaded("codeBlockWordWrap")) {
|
||||
this.refreshCodeBlockOptions();
|
||||
}
|
||||
|
||||
if (loadResults.getAttributeRows().find((attr) =>
|
||||
attr.type === "label" &&
|
||||
attr.name === "language" &&
|
||||
attributes.isAffecting(attr, this.note)))
|
||||
{
|
||||
await this.onLanguageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
async onLanguageChanged() { }
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ import EditorOptions from "./options/text_notes/editor.js";
|
||||
import ShareSettingsOptions from "./options/other/share_settings.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import { t } from "i18next";
|
||||
import LanguageOptions from "./options/i18n/language.js";
|
||||
|
||||
const TPL = `<div class="note-detail-content-widget note-detail-printable">
|
||||
<style>
|
||||
@ -81,6 +83,7 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
|
||||
HtmlImportTagsOptions,
|
||||
ShareSettingsOptions
|
||||
],
|
||||
_optionsLocalization: [ LanguageOptions ],
|
||||
_optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions],
|
||||
_backendLog: [BackendLogWidget]
|
||||
};
|
||||
@ -119,7 +122,7 @@ export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||
await widget.refresh();
|
||||
}
|
||||
} else {
|
||||
this.$content.append(`Unknown widget for "${note.noteId}"`);
|
||||
this.$content.append(t("content_widget.unknown_widget", { id: note.noteId }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
|
||||
}
|
||||
|
||||
.note-detail-doc.contextual-help {
|
||||
padding-bottom: 15vh;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.note-detail-doc.contextual-help h2,
|
||||
@ -36,7 +36,7 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 90vw;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
@ -140,8 +140,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
const isClassicEditor = utils.isMobile() || options.get("textNoteEditorType") === "ckeditor-classic";
|
||||
const editorClass = isClassicEditor ? CKEditor.DecoupledEditor : CKEditor.BalloonEditor;
|
||||
|
||||
const codeBlockLanguages = buildListOfLanguages();
|
||||
|
||||
// CKEditor since version 12 needs the element to be visible before initialization. At the same time,
|
||||
// we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate
|
||||
// display of $widget in both branches.
|
||||
@ -185,7 +183,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.watchdog.setCreator(async (elementOrData, editorConfig) => {
|
||||
logInfo("Creating new CKEditor");
|
||||
|
||||
const editor = await editorClass.create(elementOrData, {
|
||||
const finalConfig = {
|
||||
...editorConfig,
|
||||
...buildConfig(),
|
||||
...buildToolbarConfig(isClassicEditor),
|
||||
@ -195,7 +193,20 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
classes: true,
|
||||
attributes: true
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const contentLanguage = this.note.getLabelValue("language");
|
||||
if (contentLanguage) {
|
||||
finalConfig.language = {
|
||||
ui: (typeof finalConfig.language === "string" ? finalConfig.language : "en"),
|
||||
content: contentLanguage
|
||||
}
|
||||
this.contentLanguage = contentLanguage;
|
||||
} else {
|
||||
this.contentLanguage = null;
|
||||
}
|
||||
|
||||
const editor = await editorClass.create(elementOrData, finalConfig);
|
||||
|
||||
const notificationsPlugin = editor.plugins.get("Notification");
|
||||
notificationsPlugin.on("show:warning", (evt, data) => {
|
||||
@ -242,11 +253,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
return editor;
|
||||
});
|
||||
|
||||
await this.createEditor();
|
||||
}
|
||||
|
||||
async createEditor() {
|
||||
await this.watchdog.create(this.$editor[0], {
|
||||
placeholder: t("editable_text.placeholder"),
|
||||
mention: mentionSetup,
|
||||
codeBlock: {
|
||||
languages: codeBlockLanguages
|
||||
languages: buildListOfLanguages()
|
||||
},
|
||||
math: {
|
||||
engine: "katex",
|
||||
@ -265,7 +280,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async doRefresh(note) {
|
||||
const blob = await note.getBlob();
|
||||
|
||||
await this.spacedUpdate.allowUpdateWithoutChange(() => this.watchdog.editor.setData(blob.content || ""));
|
||||
await this.spacedUpdate.allowUpdateWithoutChange(async () => {
|
||||
const data = blob.content || "";
|
||||
const newContentLanguage = this.note.getLabelValue("language");
|
||||
if (this.contentLanguage !== newContentLanguage) {
|
||||
await this.reinitialize(data);
|
||||
} else {
|
||||
this.watchdog.editor.setData(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getData() {
|
||||
@ -468,4 +491,20 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async refreshIncludedNoteEvent({ noteId }) {
|
||||
this.refreshIncludedNote(this.$editor, noteId);
|
||||
}
|
||||
|
||||
async reinitialize(data) {
|
||||
if (!this.watchdog) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.watchdog.destroy();
|
||||
await this.createEditor();
|
||||
this.watchdog.editor.setData(data);
|
||||
}
|
||||
|
||||
async onLanguageChanged() {
|
||||
const data = this.watchdog.editor.getData();
|
||||
await this.reinitialize(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import OptionsWidget from "../options_widget.js";
|
||||
import server from "../../../../services/server.js";
|
||||
import utils from "../../../../services/utils.js";
|
||||
import { t } from "../../../../services/i18n.js";
|
||||
import { getAvailableLocales, t } from "../../../../services/i18n.js";
|
||||
import type { OptionMap } from "../../../../../../services/options_interface.js";
|
||||
|
||||
const TPL = `
|
||||
@ -25,12 +25,6 @@ const TPL = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
interface Locale {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default class LocalizationOptions extends OptionsWidget {
|
||||
|
||||
private $localeSelect!: JQuery<HTMLElement>;
|
||||
@ -53,7 +47,7 @@ export default class LocalizationOptions extends OptionsWidget {
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
const availableLocales = await server.get<Locale[]>("options/locales");
|
||||
const availableLocales = getAvailableLocales().filter(l => !l.contentOnly);
|
||||
this.$localeSelect.empty();
|
||||
|
||||
for (const locale of availableLocales) {
|
||||
|
63
src/public/app/widgets/type_widgets/options/i18n/language.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import OptionsWidget from "../options_widget.js";
|
||||
import type { OptionMap } from "../../../../../../services/options_interface.js";
|
||||
import { getAvailableLocales } from "../../../../services/i18n.js";
|
||||
import { t } from "i18next";
|
||||
|
||||
const TPL = `
|
||||
<div class="options-section">
|
||||
<h4>${t("content_language.title")}</h4>
|
||||
<p>${t("content_language.description")}</p>
|
||||
|
||||
<ul class="options-languages">
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
ul.options-languages {
|
||||
list-style-type: none;
|
||||
margin-bottom: 0;
|
||||
column-width: 400px;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class LanguageOptions extends OptionsWidget {
|
||||
|
||||
private $languagesContainer!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$languagesContainer = this.$widget.find(".options-languages");
|
||||
}
|
||||
|
||||
async save() {
|
||||
const enabledLanguages: string[] = [];
|
||||
|
||||
this.$languagesContainer.find("input:checked").each((i, el) => {
|
||||
const languageId = $(el).attr("data-language-id");
|
||||
if (languageId) {
|
||||
enabledLanguages.push(languageId);
|
||||
}
|
||||
});
|
||||
|
||||
await this.updateOption("languages", JSON.stringify(enabledLanguages));
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
const availableLocales = getAvailableLocales();
|
||||
const enabledLanguages = (JSON.parse(options.languages) as string[]);
|
||||
|
||||
this.$languagesContainer.empty();
|
||||
for (const locale of availableLocales) {
|
||||
const checkbox = $('<input type="checkbox" class="form-check-input">')
|
||||
.attr("data-language-id", locale.id)
|
||||
.prop("checked", enabledLanguages.includes(locale.id));
|
||||
const wrapper = $(`<label class="tn-checkbox">`)
|
||||
.append(checkbox)
|
||||
.on("change", () => this.save())
|
||||
.append(locale.name);
|
||||
this.$languagesContainer.append($("<li>").append(wrapper));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,9 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
||||
import { getMermaidConfig } from "../mermaid.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { getLocaleById } from "../../services/i18n.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-readonly-text note-detail-printable">
|
||||
@ -29,7 +32,7 @@ const TPL = `
|
||||
body.heading-style-underline .note-detail-readonly-text h6 { border-bottom: 1px solid var(--main-border-color); }
|
||||
|
||||
.note-detail-readonly-text {
|
||||
padding-left: 24px;
|
||||
padding-inline-start: 24px;
|
||||
padding-top: 10px;
|
||||
font-family: var(--detail-font-family);
|
||||
min-height: 50px;
|
||||
@ -71,6 +74,9 @@ const TPL = `
|
||||
`;
|
||||
|
||||
export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
private $content!: JQuery<HTMLElement>;
|
||||
|
||||
static getType() {
|
||||
return "readOnlyText";
|
||||
}
|
||||
@ -89,21 +95,23 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.$content.html("");
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
async doRefresh(note: FNote) {
|
||||
// we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes
|
||||
// we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time
|
||||
// (see https://github.com/zadam/trilium/issues/1590 for example of such conflict)
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
|
||||
this.onLanguageChanged();
|
||||
|
||||
const blob = await note.getBlob();
|
||||
|
||||
this.$content.html(blob.content);
|
||||
this.$content.html(blob?.content ?? "");
|
||||
|
||||
this.$content.find("a.reference-link").each(async (_, el) => {
|
||||
this.$content.find("a.reference-link").each((_, el) => {
|
||||
this.loadReferenceLinkTitle($(el));
|
||||
});
|
||||
|
||||
this.$content.find("section").each(async (_, el) => {
|
||||
this.$content.find("section").each((_, el) => {
|
||||
const noteId = $(el).attr("data-note-id");
|
||||
|
||||
this.loadIncludedNote(noteId, $(el));
|
||||
@ -135,11 +143,11 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
mermaid.init(getMermaidConfig(), this.$content.find(".mermaid-diagram"));
|
||||
}
|
||||
|
||||
async refreshIncludedNoteEvent({ noteId }) {
|
||||
async refreshIncludedNoteEvent({ noteId }: EventData<"refreshIncludedNote">) {
|
||||
this.refreshIncludedNote(this.$content, noteId);
|
||||
}
|
||||
|
||||
async executeWithContentElementEvent({ resolve, ntxId }) {
|
||||
async executeWithContentElementEvent({ resolve, ntxId }: EventData<"executeWithContentElement">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
@ -148,4 +156,12 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
resolve(this.$content);
|
||||
}
|
||||
|
||||
async onLanguageChanged(): Promise<void> {
|
||||
const languageCode = this.note?.getLabelValue("language");
|
||||
const correspondingLocale = getLocaleById(languageCode);
|
||||
const isRtl = correspondingLocale?.rtl;
|
||||
this.$widget.attr("dir", isRtl ? "rtl" : "ltr");
|
||||
}
|
||||
|
||||
}
|
@ -229,8 +229,8 @@ export default class CalendarView extends ViewMode {
|
||||
return;
|
||||
}
|
||||
|
||||
CalendarView.#setAttribute(note, "label", "startDate", startDate);
|
||||
CalendarView.#setAttribute(note, "label", "endDate", endDate);
|
||||
attributes.setAttribute(note, "label", "startDate", startDate);
|
||||
attributes.setAttribute(note, "label", "endDate", endDate);
|
||||
}
|
||||
|
||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
@ -435,20 +435,6 @@ export default class CalendarView extends ViewMode {
|
||||
return [note.title];
|
||||
}
|
||||
|
||||
static async #setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
} else {
|
||||
// Remove the attribute if it exists on the server but we don't define a value for it.
|
||||
const attributeId = note.getAttribute(type, name)?.attributeId;
|
||||
if (attributeId) {
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
}
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
}
|
||||
|
||||
static #formatDateToLocalISO(date: Date | null | undefined) {
|
||||
if (!date) {
|
||||
return undefined;
|
||||
|
@ -552,7 +552,8 @@ optgroup {
|
||||
|
||||
/* Switches */
|
||||
|
||||
.switch-widget.component {
|
||||
.switch-widget.component,
|
||||
.note-language-container {
|
||||
--icon-button-size: 28px;
|
||||
}
|
||||
|
||||
|
@ -1025,7 +1025,8 @@ body.desktop .dropdown-submenu .dropdown-menu {
|
||||
|
||||
.dropdown-item,
|
||||
body.mobile .dropdown-submenu .dropdown-toggle {
|
||||
padding: 2px 16px 2px 8px !important;
|
||||
padding: 2px 2px 2px 8px !important;
|
||||
padding-inline-end: 16px;
|
||||
/* Note: the right padding should also accommodate the submenu arrow. */
|
||||
border-radius: 6px;
|
||||
cursor: default !important;
|
||||
|
@ -744,7 +744,8 @@
|
||||
"basic_properties": {
|
||||
"note_type": "Note type",
|
||||
"editable": "Editable",
|
||||
"basic_properties": "Basic Properties"
|
||||
"basic_properties": "Basic Properties",
|
||||
"language": "Language"
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "View type",
|
||||
@ -1682,5 +1683,16 @@
|
||||
"tomorrow": "Tomorrow",
|
||||
"yesterday": "Yesterday"
|
||||
}
|
||||
},
|
||||
"content_widget": {
|
||||
"unknown_widget": "Unknown widget for \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Not set",
|
||||
"configure-languages": "Configure languages..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Content languages",
|
||||
"description": "Select one or more languages that should appear in the language selection in the Basic Properties section of a read-only or editable text note. This will allow features such as spell-checking or right-to-left support."
|
||||
}
|
||||
}
|
||||
|
@ -1685,5 +1685,8 @@
|
||||
"tomorrow": "Mâine",
|
||||
"yesterday": "Ieri"
|
||||
}
|
||||
},
|
||||
"content_widget": {
|
||||
"unknown_widget": "Nu s-a putut găsi widget-ul corespunzător pentru „{{id}}”."
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ const ALLOWED_OPTIONS = new Set([
|
||||
"editedNotesOpenInRibbon",
|
||||
"locale",
|
||||
"firstDayOfWeek",
|
||||
"languages",
|
||||
"textNoteEditorType",
|
||||
"textNoteEditorMultilineToolbar",
|
||||
"layoutOrientation",
|
||||
|
@ -275,6 +275,7 @@ function buildHiddenSubtreeDefinition(helpSubtree: HiddenSubtreeItem[]): HiddenS
|
||||
{ id: "_optionsBackup", title: t("hidden-subtree.backup-title"), type: "contentWidget", icon: "bx-data" },
|
||||
{ id: "_optionsSync", title: t("hidden-subtree.sync-title"), type: "contentWidget", icon: "bx-wifi" },
|
||||
{ id: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" },
|
||||
{ id: "_optionsLocalization", title: t("hidden-subtree.localization"), type: "contentWidget", icon: "bx-world" },
|
||||
{ id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" }
|
||||
]
|
||||
},
|
||||
|
@ -7,6 +7,10 @@ function checkTranslations(translationDir: string, translationFileName: string)
|
||||
const locales = i18n.getLocales();
|
||||
|
||||
for (const locale of locales) {
|
||||
if (locale.contentOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const translationPath = path.join(translationDir, locale.id, translationFileName);
|
||||
const translationFile = fs.readFileSync(translationPath, { encoding: "utf-8" });
|
||||
expect(() => {
|
||||
|
@ -6,6 +6,76 @@ import { join } from "path";
|
||||
import { getResourceDir } from "./utils.js";
|
||||
import hidden_subtree from "./hidden_subtree.js";
|
||||
|
||||
export interface Locale {
|
||||
id: string;
|
||||
name: string;
|
||||
/** `true` if the language is a right-to-left one, or `false` if it's left-to-right. */
|
||||
rtl?: boolean;
|
||||
/** `true` if the language is not supported by the application as a display language, but it is selectable by the user for the content. */
|
||||
contentOnly?: boolean;
|
||||
}
|
||||
|
||||
const LOCALES: Locale[] = [
|
||||
{
|
||||
id: "en",
|
||||
name: "English"
|
||||
},
|
||||
{
|
||||
id: "de",
|
||||
name: "Deutsch"
|
||||
},
|
||||
{
|
||||
id: "es",
|
||||
name: "Español"
|
||||
},
|
||||
{
|
||||
id: "fr",
|
||||
name: "Français"
|
||||
},
|
||||
{
|
||||
id: "cn",
|
||||
name: "简体中文"
|
||||
},
|
||||
{
|
||||
id: "tw",
|
||||
name: "繁體中文"
|
||||
},
|
||||
{
|
||||
id: "ro",
|
||||
name: "Română"
|
||||
},
|
||||
|
||||
/*
|
||||
* Right to left languages
|
||||
*
|
||||
* Currently they are only for setting the language of text notes.
|
||||
*/
|
||||
{ // Arabic
|
||||
id: "ar",
|
||||
name: "اَلْعَرَبِيَّةُ",
|
||||
rtl: true,
|
||||
contentOnly: true
|
||||
},
|
||||
{ // Hebrew
|
||||
id: "he",
|
||||
name: "עברית",
|
||||
rtl: true,
|
||||
contentOnly: true
|
||||
},
|
||||
{ // Kurdish
|
||||
id: "ku",
|
||||
name: "کوردی",
|
||||
rtl: true,
|
||||
contentOnly: true
|
||||
},
|
||||
{ // Persian
|
||||
id: "fa",
|
||||
name: "فارسی",
|
||||
rtl: true,
|
||||
contentOnly: true
|
||||
}
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
export async function initializeTranslations() {
|
||||
const resourceDir = getResourceDir();
|
||||
|
||||
@ -20,38 +90,8 @@ export async function initializeTranslations() {
|
||||
});
|
||||
}
|
||||
|
||||
export function getLocales() {
|
||||
// TODO: Currently hardcoded, needs to read the list of available languages.
|
||||
return [
|
||||
{
|
||||
id: "en",
|
||||
name: "English"
|
||||
},
|
||||
{
|
||||
id: "de",
|
||||
name: "Deutsch"
|
||||
},
|
||||
{
|
||||
id: "es",
|
||||
name: "Español"
|
||||
},
|
||||
{
|
||||
id: "fr",
|
||||
name: "Français"
|
||||
},
|
||||
{
|
||||
id: "cn",
|
||||
name: "简体中文"
|
||||
},
|
||||
{
|
||||
id: "tw",
|
||||
name: "繁體中文"
|
||||
},
|
||||
{
|
||||
id: "ro",
|
||||
name: "Română"
|
||||
}
|
||||
];
|
||||
export function getLocales(): Locale[] {
|
||||
return LOCALES;
|
||||
}
|
||||
|
||||
function getCurrentLanguage() {
|
||||
|
@ -134,6 +134,7 @@ const defaultOptions: DefaultOption[] = [
|
||||
// Internationalization
|
||||
{ name: "locale", value: "en", isSynced: true },
|
||||
{ name: "firstDayOfWeek", value: "1", isSynced: true },
|
||||
{ name: "languages", value: "[]", isSynced: true },
|
||||
|
||||
// Code block configuration
|
||||
{
|
||||
|
@ -71,6 +71,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
eraseUnusedAttachmentsAfterSeconds: number;
|
||||
eraseUnusedAttachmentsAfterTimeScale: number;
|
||||
firstDayOfWeek: number;
|
||||
languages: string;
|
||||
|
||||
initialized: boolean;
|
||||
isPasswordSet: boolean;
|
||||
|
@ -244,7 +244,8 @@
|
||||
"other": "Other",
|
||||
"advanced-title": "Advanced",
|
||||
"visible-launchers-title": "Visible Launchers",
|
||||
"user-guide": "User Guide"
|
||||
"user-guide": "User Guide",
|
||||
"localization": "Language & Region"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "New note",
|
||||
|