Merge pull request #1204 from TriliumNext/feat_timeScaleSelectWidget

feat: add time selector option widget
This commit is contained in:
Elian Doran 2025-02-17 22:23:29 +02:00 committed by GitHub
commit 6706332be3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 176 additions and 124 deletions

View File

@ -2,33 +2,37 @@ import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
import TimeSelector from "../time_selector.js";
const TPL = `
<div class="options-section">
<h4>${t("attachment_erasure_timeout.attachment_erasure_timeout")}</h4>
<p>${t("attachment_erasure_timeout.attachment_auto_deletion_description")}</p>
<div class="form-group">
<label>${t("attachment_erasure_timeout.erase_attachments_after_x_seconds")}</label>
<input class="erase-unused-attachments-after-time-in-seconds form-control options-number-input" type="number" min="0">
</div>
<div id="time-selector-placeholder"></div>
<p>${t("attachment_erasure_timeout.manual_erasing_description")}</p>
<button class="erase-unused-attachments-now-button btn btn-secondary">${t("attachment_erasure_timeout.erase_unused_attachments_now")}</button>
</div>`;
export default class AttachmentErasureTimeoutOptions extends OptionsWidget {
private $eraseUnusedAttachmentsAfterTimeInSeconds!: JQuery<HTMLElement>;
export default class AttachmentErasureTimeoutOptions extends TimeSelector {
private $eraseUnusedAttachmentsNowButton!: JQuery<HTMLElement>;
constructor() {
super({
widgetId: "erase-unused-attachments-after",
widgetLabelId: "attachment_erasure_timeout.erase_attachments_after",
optionValueId: "eraseUnusedAttachmentsAfterSeconds",
optionTimeScaleId: "eraseUnusedAttachmentsAfterTimeScale"
});
super.doRender();
}
doRender() {
const $timeSelector = this.$widget;
this.$widget = $(TPL);
this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds");
this.$eraseUnusedAttachmentsAfterTimeInSeconds.on("change", () => this.updateOption("eraseUnusedAttachmentsAfterSeconds", this.$eraseUnusedAttachmentsAfterTimeInSeconds.val()));
// inject TimeSelector widget template
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector);
this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button");
this.$eraseUnusedAttachmentsNowButton.on("click", () => {
@ -37,8 +41,4 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget {
});
});
}
async optionsLoaded(options: OptionMap) {
this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedAttachmentsAfterSeconds);
}
}

View File

@ -3,118 +3,42 @@ import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
import TimeSelector from "../time_selector.js";
const TPL = `
<div class="options-section">
<h4>${t("note_erasure_timeout.note_erasure_timeout_title")}</h4>
<p>${t("note_erasure_timeout.note_erasure_description")}</p>
<div class="form-group">
<label for="erase-entities-after-time">${t("note_erasure_timeout.erase_notes_after")}</label>
<div class="d-flex gap-2">
<input id="erase-entities-after-time" class="form-control options-number-input" type="number" min="0" steps="1" required>
<!-- TriliumNextTODO: i18n the strings when refactoring this to a standalone widget -->
<select id="erase-entities-after-time-scale" class="form-select duration-selector" required>
<option value="1">${t("duration.seconds")}</option>
<option value="60">${t("duration.minutes")}</option>
<option value="3600">${t("duration.hours")}</option>
<option value="86400">${t("duration.days")}</option>
</select>
</div>
</div>
<div id="time-selector-placeholder"></div>
<p>${t("note_erasure_timeout.manual_erasing_description")}</p>
<button id="erase-deleted-notes-now-button" class="btn btn-secondary">${t("note_erasure_timeout.erase_deleted_notes_now")}</button>
<style>
.duration-selector {
width: auto;
}
</style>
</div>`;
export default class NoteErasureTimeoutOptions extends OptionsWidget {
private $eraseEntitiesAfterTime!: JQuery<HTMLInputElement>;
private $eraseEntitiesAfterTimeScale!: JQuery<HTMLSelectElement>;
export default class NoteErasureTimeoutOptions extends TimeSelector {
private $eraseDeletedNotesButton!: JQuery<HTMLButtonElement>;
private eraseEntitiesAfterTimeInSeconds!: string | number;
constructor() {
super({
widgetId: "erase-entities-after",
widgetLabelId: "note_erasure_timeout.erase_notes_after",
optionValueId: "eraseEntitiesAfterTimeInSeconds",
optionTimeScaleId: "eraseEntitiesAfterTimeScale"
});
super.doRender();
}
doRender() {
const $timeSelector = this.$widget;
// inject TimeSelector widget template
this.$widget = $(TPL);
this.$eraseEntitiesAfterTime = this.$widget.find("#erase-entities-after-time");
this.$eraseEntitiesAfterTimeScale = this.$widget.find("#erase-entities-after-time-scale");
this.$eraseEntitiesAfterTime.on("change", () => {
const time = this.$eraseEntitiesAfterTime.val();
const timeScale = this.$eraseEntitiesAfterTimeScale.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return;
this.eraseEntitiesAfterTimeInSeconds = this.convertTime(time, timeScale).toOption();
this.updateOption("eraseEntitiesAfterTimeInSeconds", this.eraseEntitiesAfterTimeInSeconds);
});
this.$eraseEntitiesAfterTimeScale.on("change", () => {
const timeScale = this.$eraseEntitiesAfterTimeScale.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string") return;
//calculate the new displayed value
const displayedTime = this.convertTime(this.eraseEntitiesAfterTimeInSeconds, timeScale).toDisplay();
this.updateOption("eraseEntitiesAfterTimeScale", timeScale);
this.$eraseEntitiesAfterTime.val(displayedTime).trigger("change");
});
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button");
this.$eraseDeletedNotesButton.on("click", () => {
server.post("notes/erase-deleted-notes-now").then(() => {
toastService.showMessage(t("note_erasure_timeout.deleted_notes_erased"));
});
});
}
async optionsLoaded(options: OptionMap) {
this.eraseEntitiesAfterTimeInSeconds = options.eraseEntitiesAfterTimeInSeconds;
const displayedTime = this.convertTime(options.eraseEntitiesAfterTimeInSeconds, options.eraseEntitiesAfterTimeScale).toDisplay();
this.$eraseEntitiesAfterTime.val(displayedTime);
this.$eraseEntitiesAfterTimeScale.val(options.eraseEntitiesAfterTimeScale);
}
convertTime(time: string | number, timeScale: string | number) {
const value = typeof time === "number" ? time : parseInt(time);
if (Number.isNaN(value)) {
throw new Error(`Time needs to be a valid integer, but received: ${time}`);
}
const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale);
if (Number.isNaN(operand) || operand < 1) {
throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`);
}
return {
toOption: () => Math.ceil(value * operand),
toDisplay: () => Math.ceil(value / operand),
}
}
handleTimeValidation() {
if (this.$eraseEntitiesAfterTime.is(":invalid")) {
// TriliumNextTODO: i18n
toastService.showMessage("The entered time value is not a valid number.");
return false
}
return true;
}
}

View File

@ -0,0 +1,122 @@
import OptionsWidget from "./options_widget.js";
import toastService from "../../../services/toast.js";
import { t } from "../../../services/i18n.js";
import type { OptionDefinitions, OptionMap } from "../../../../../services/options_interface.js";
type TimeSelectorConstructor = {
widgetId: string;
widgetLabelId: string;
optionValueId: keyof OptionDefinitions;
optionTimeScaleId: keyof OptionDefinitions;
includedTimeScales?: Set<TimeSelectorScale>;
};
type TimeSelectorScale = "seconds" | "minutes" | "hours" | "days";
const TPL = (options: Omit<TimeSelectorConstructor, "optionValueId" | "optionTimeScaleId">) => `
<div class="form-group">
<label for="${options.widgetId}">${t(options.widgetLabelId)}</label>
<div class="d-flex gap-2">
<input id="${options.widgetId}" class="form-control options-number-input" type="number" min="0" steps="1" required>
<select id="${options.widgetId}-time-scale" class="form-select duration-selector" required>
${options.includedTimeScales?.has("seconds") ? `<option value="1">${t("duration.seconds")}</option>` : ""}
${options.includedTimeScales?.has("minutes") ? `<option value="60">${t("duration.minutes")}</option>` : ""}
${options.includedTimeScales?.has("hours") ? `<option value="3600">${t("duration.hours")}</option>` : ""}
${options.includedTimeScales?.has("days") ? `<option value="86400">${t("duration.days")}</option>` : ""}
</select>
</div>
</div>
</div>
<style>
.duration-selector {
width: auto;
}
</style>`;
export default class TimeSelector extends OptionsWidget {
private $timeValueInput!: JQuery<HTMLInputElement>;
private $timeScaleSelect!: JQuery<HTMLSelectElement>;
private internalTimeInSeconds!: string | number;
private widgetId: string;
private widgetLabelId: string;
private optionValueId: keyof OptionDefinitions;
private optionTimeScaleId: keyof OptionDefinitions;
private includedTimeScales: Set<TimeSelectorScale>;
constructor(options: TimeSelectorConstructor) {
super();
this.widgetId = options.widgetId;
this.widgetLabelId = options.widgetLabelId;
this.optionValueId = options.optionValueId;
this.optionTimeScaleId = options.optionTimeScaleId;
this.includedTimeScales = !options.includedTimeScales ? new Set(["seconds", "minutes", "hours", "days"]) : options.includedTimeScales;
}
doRender() {
this.$widget = $(
TPL({
widgetId: this.widgetId,
widgetLabelId: this.widgetLabelId,
includedTimeScales: this.includedTimeScales
})
);
this.$timeValueInput = this.$widget.find(`#${this.widgetId}`);
this.$timeScaleSelect = this.$widget.find(`#${this.widgetId}-time-scale`);
this.$timeValueInput.on("change", () => {
const time = this.$timeValueInput.val();
const timeScale = this.$timeScaleSelect.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return;
this.internalTimeInSeconds = this.convertTime(time, timeScale).toOption();
this.updateOption(this.optionValueId, this.internalTimeInSeconds);
});
this.$timeScaleSelect.on("change", () => {
const timeScale = this.$timeScaleSelect.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string") return;
//calculate the new displayed value
const displayedTime = this.convertTime(this.internalTimeInSeconds, timeScale).toDisplay();
this.updateOption(this.optionTimeScaleId, timeScale);
this.$timeValueInput.val(displayedTime).trigger("change");
});
}
async optionsLoaded(options: OptionMap) {
this.internalTimeInSeconds = options[this.optionValueId];
const displayedTime = this.convertTime(options[this.optionValueId], options[this.optionTimeScaleId]).toDisplay();
this.$timeValueInput.val(displayedTime);
this.$timeScaleSelect.val(options[this.optionTimeScaleId]);
}
convertTime(time: string | number, timeScale: string | number) {
const value = typeof time === "number" ? time : parseInt(time);
if (Number.isNaN(value)) {
throw new Error(`Time needs to be a valid integer, but received: ${time}`);
}
const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale);
if (Number.isNaN(operand) || operand < 1) {
throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`);
}
return {
toOption: () => Math.ceil(value * operand),
toDisplay: () => Math.ceil(value / operand)
};
}
handleTimeValidation() {
if (this.$timeValueInput.is(":invalid")) {
toastService.showError(t("time_selector.invalid_input"));
return false;
}
return true;
}
}

View File

@ -1120,7 +1120,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "附件清理超时",
"attachment_auto_deletion_description": "如果附件在一段时间后不再被笔记引用,它们将自动被删除(并被清理)。",
"erase_attachments_after_x_seconds": "在附件在笔记中未被使用 X 秒后清理",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "您还可以手动触发清理(而不考虑上述定义的超时时间):",
"erase_unused_attachments_now": "立即清理未使用的附件笔记",
"unused_attachments_erased": "未使用的附件已被删除。"
@ -1132,7 +1132,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "笔记清理超时",
"note_erasure_description": "被删除的笔记(以及属性、历史版本等)最初仅被标记为“删除”,可以从“最近修改”对话框中恢复它们。经过一段时间后,已删除的笔记会被“清理”,这意味着它们的内容将无法恢复。此设置允许您配置从删除到清除笔记之间的时间长度。",
"erase_notes_after": "Erase notes after",
"erase_notes_after": "Erase notes after:",
"manual_erasing_description": "您还可以手动触发清理(不考虑上述定义的超时):",
"erase_deleted_notes_now": "立即清理已删除的笔记",
"deleted_notes_erased": "已删除的笔记已被清理。"

View File

@ -1088,7 +1088,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen",
"attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.",
"erase_attachments_after_x_seconds": "Anhänge nach X Sekunden löschen, nachdem sie nicht in der Notiz verwendet wurden",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen",
"unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht."
@ -1100,7 +1100,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung",
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
"erase_notes_after": "Notizen löschen nach",
"erase_notes_after": "Notizen löschen nach:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen",
"deleted_notes_erased": "Gelöschte Notizen wurden gelöscht."

View File

@ -1146,7 +1146,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Attachment Erasure Timeout",
"attachment_auto_deletion_description": "Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.",
"erase_attachments_after_x_seconds": "Erase attachments after X seconds of not being used in its note",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):",
"erase_unused_attachments_now": "Erase unused attachment notes now",
"unused_attachments_erased": "Unused attachments have been erased."
@ -1158,7 +1158,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "Note Erasure Timeout",
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
"erase_notes_after": "Erase notes after",
"erase_notes_after": "Erase notes after:",
"manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):",
"erase_deleted_notes_now": "Erase deleted notes now",
"deleted_notes_erased": "Deleted notes have been erased."
@ -1654,5 +1654,8 @@
"minutes": "Minutes",
"hours": "Hours",
"days": "Days"
},
"time_selector": {
"invalid_input": "The entered time value is not a valid number."
}
}

View File

@ -1144,7 +1144,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos",
"attachment_auto_deletion_description": "Los archivos adjuntos se eliminan (y borran) automáticamente si ya no se hace referencia a ellos en su nota después de un tiempo de espera definido.",
"erase_attachments_after_x_seconds": "Borrar archivos adjuntos después de X segundos de no usarse en su nota",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):",
"erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota",
"unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado."
@ -1156,7 +1156,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "Tiempo de espera de borrado de notas",
"note_erasure_description": "Las notas eliminadas (y los atributos, las revisiones ...) en principio solo están marcadas como eliminadas y es posible recuperarlas del diálogo de Notas recientes. Después de un período de tiempo, las notas eliminadas son \" borradas\", lo que significa que su contenido ya no es recuperable. Esta configuración le permite configurar la longitud del período entre eliminar y borrar la nota.",
"erase_notes_after": "Borrar notas después de",
"erase_notes_after": "Borrar notas después de:",
"manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):",
"erase_deleted_notes_now": "Borrar notas eliminadas ahora",
"deleted_notes_erased": "Las notas eliminadas han sido borradas."

View File

@ -1089,7 +1089,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Délai d'effacement des pièces jointes",
"attachment_auto_deletion_description": "Les pièces jointes sont automatiquement supprimées (et effacées) si elles ne sont plus référencées par leur note après un certain délai.",
"erase_attachments_after_x_seconds": "Effacer les pièces jointes après X secondes sans utilisation dans sa note",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte du délai défini ci-dessus) :",
"erase_unused_attachments_now": "Effacez maintenant les pièces jointes inutilisées",
"unused_attachments_erased": "Les pièces jointes inutilisées ont été effacées."
@ -1101,7 +1101,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "Délai d'effacement des notes",
"note_erasure_description": "Les notes supprimées (et les attributs, versions...) sont seulement marquées comme supprimées et il est possible de les récupérer à partir de la boîte de dialogue Notes récentes. Après un certain temps, les notes supprimées sont « effacées », ce qui signifie que leur contenu n'est plus récupérable. Ce paramètre vous permet de configurer la durée entre la suppression et l'effacement de la note.",
"erase_notes_after": "Effacer les notes après",
"erase_notes_after": "Effacer les notes après:",
"manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte de la durée définie ci-dessus) :",
"erase_deleted_notes_now": "Effacer les notes supprimées maintenant",
"deleted_notes_erased": "Les notes supprimées ont été effacées."

View File

@ -78,7 +78,7 @@
"attachment_erasure_timeout": {
"attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.",
"attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor",
"erase_attachments_after_x_seconds": "Șterge atașamentele după X secunde după ce acestea n-au mai fost folosite într-o notiță",
"erase_attachments_after": "Erase unused attachments after:",
"erase_unused_attachments_now": "Elimină atașamentele șterse acum",
"manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe",
"unused_attachments_erased": "Atașamentele nefolosite au fost șterse."
@ -836,7 +836,7 @@
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
"erase_deleted_notes_now": "Elimină notițele șterse acum",
"erase_notes_after": "Elimină notițele șterse după",
"erase_notes_after": "Elimină notițele șterse după:",
"manual_erasing_description": "Se poate rula o eliminare manuală (fără a lua în considerare timpul definit mai sus):",
"note_erasure_description": "Notițele șterse (precum și atributele, reviziile) sunt prima oară doar marcate drept șterse și este posibil să fie recuperate din ecranul Notițe recente. După o perioadă de timp, notițele șterse vor fi „eliminate”, caz în care conținutul lor nu se poate recupera. Această setare permite configurarea duratei de timp dintre ștergerea și eliminarea notițelor.",
"note_erasure_timeout_title": "Timpul de eliminare automată a notițelor șterse"

View File

@ -1098,7 +1098,7 @@
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "附件清理超時",
"attachment_auto_deletion_description": "如果附件在一段時間後不再被筆記引用,它們將自動被刪除(並被清理)。",
"erase_attachments_after_x_seconds": "在附件在筆記中未被使用 X 秒後清理",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "您還可以手動觸發清理(而不考慮上述定義的超時時間):",
"erase_unused_attachments_now": "立即清理未使用的附件筆記",
"unused_attachments_erased": "未使用的附件已被刪除。"
@ -1110,7 +1110,7 @@
"note_erasure_timeout": {
"note_erasure_timeout_title": "筆記清理超時",
"note_erasure_description": "被刪除的筆記(以及屬性、歷史版本等)最初僅被標記為「刪除」,可以從「最近修改」對話框中恢復它們。經過一段時間後,已刪除的筆記會被「清理」,這意味著它們的內容將無法恢復。此設定允許您設定從刪除到清除筆記之間的時間長度。",
"erase_notes_after": "Erase notes after",
"erase_notes_after": "Erase notes after:",
"manual_erasing_description": "您還可以手動觸發清理(不考慮上述定義的超時):",
"erase_deleted_notes_now": "立即清理已刪除的筆記",
"deleted_notes_erased": "已刪除的筆記已被清理。"

View File

@ -61,6 +61,7 @@ const ALLOWED_OPTIONS = new Set([
"checkForUpdates",
"disableTray",
"eraseUnusedAttachmentsAfterSeconds",
"eraseUnusedAttachmentsAfterTimeScale",
"disableTray",
"customSearchEngineName",
"customSearchEngineUrl",

View File

@ -122,7 +122,8 @@ const defaultOptions: DefaultOption[] = [
{ name: "highlightsList", value: '["bold","italic","underline","color","bgColor"]', isSynced: true },
{ name: "checkForUpdates", value: "true", isSynced: true },
{ name: "disableTray", value: "false", isSynced: false },
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true },
{ name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days
{ name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day
{ name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true },
{ name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true },
{ name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true },

View File

@ -67,6 +67,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
maxContentWidth: number;
minTocHeadings: number;
eraseUnusedAttachmentsAfterSeconds: number;
eraseUnusedAttachmentsAfterTimeScale: number;
firstDayOfWeek: number;
initialized: boolean;