mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
feat: adopt Obsidian 1.11.0 API features with backwards compatibility
- Add icon property to settings tab (displays in sidebar on 1.11.0+) - Refactor all settings tabs to use native SettingGroup class - Create LegacySettingGroup fallback for Obsidian versions < 1.11.0 - Add configure* helper functions for use with SettingGroup.addSetting - Add type declarations for new Obsidian 1.11.0 APIs - Update test mocks with requireApiVersion and SettingGroup
This commit is contained in:
parent
b9e7eb7c56
commit
a07e3ffc47
10 changed files with 2238 additions and 1758 deletions
|
|
@ -26,6 +26,11 @@ Example:
|
|||
|
||||
## Added
|
||||
|
||||
- Adopted Obsidian 1.11.0 API features with backwards compatibility
|
||||
- Settings tab now displays TaskNotes icon in the sidebar (Obsidian 1.11.0+)
|
||||
- Settings sections now use native `SettingGroup` for improved visual grouping (Obsidian 1.11.0+)
|
||||
- Falls back gracefully to traditional section headers on older Obsidian versions
|
||||
|
||||
- (#59) Added `shortYear` template variable for custom filename and folder templates
|
||||
- Use `{shortYear}` in filename templates (e.g., "25" for 2025)
|
||||
- Use `{{shortYear}}` in folder templates
|
||||
|
|
@ -39,6 +44,11 @@ Example:
|
|||
|
||||
## Fixed
|
||||
|
||||
- (#1386) Fixed `timeEstimateCategory` formula showing "Long (>2h)" instead of "No estimate" for new tasks
|
||||
- The condition didn't properly handle null values when `timeEstimate` property is unset
|
||||
- Also fixed the same issue in `trackingStatus` formula
|
||||
- Thanks to @nicou for reporting and suggesting the fix
|
||||
|
||||
- (#1397) Fixed Bases views (Kanban, Calendar, Task List) resetting to Calendar view after a few minutes
|
||||
- Views would show "?" in the Views menu due to view type mismatch
|
||||
- Caused by typo in view type properties not matching registration IDs
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, PluginSettingTab, Platform } from "obsidian";
|
||||
import { App, PluginSettingTab, Platform, requireApiVersion } from "obsidian";
|
||||
import TaskNotesPlugin from "../main";
|
||||
import { debounce } from "./components/settingHelpers";
|
||||
import { renderGeneralTab } from "./tabs/generalTab";
|
||||
|
|
@ -25,6 +25,11 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
|
||||
// Set icon for settings sidebar (Obsidian 1.11.0+)
|
||||
if (requireApiVersion("1.11.0")) {
|
||||
this.icon = "tasknotes-simple";
|
||||
}
|
||||
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.i18n.on("locale-changed", () => {
|
||||
if (this.containerEl.isConnected) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Setting } from "obsidian";
|
||||
import { Setting, SettingGroup, requireApiVersion } from "obsidian";
|
||||
|
||||
export interface ToggleSettingOptions {
|
||||
name: string;
|
||||
|
|
@ -46,14 +46,100 @@ export interface ButtonSettingOptions {
|
|||
buttonClass?: string;
|
||||
}
|
||||
|
||||
export interface SettingGroupOptions {
|
||||
heading: string;
|
||||
description?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard toggle settings
|
||||
* Check if the current Obsidian version supports SettingGroup (1.11.0+)
|
||||
*/
|
||||
export function createToggleSetting(
|
||||
function supportsSettingGroup(): boolean {
|
||||
return requireApiVersion("1.11.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy fallback that mimics SettingGroup API for Obsidian < 1.11.0
|
||||
* Uses the old pattern of section headers and individual settings
|
||||
*/
|
||||
class LegacySettingGroup {
|
||||
private containerEl: HTMLElement;
|
||||
|
||||
constructor(containerEl: HTMLElement) {
|
||||
this.containerEl = containerEl;
|
||||
}
|
||||
|
||||
setHeading(text: string | DocumentFragment): this {
|
||||
new Setting(this.containerEl).setName(text).setHeading();
|
||||
return this;
|
||||
}
|
||||
|
||||
addClass(_cls: string): this {
|
||||
// No-op for legacy - classes were not applied to groups
|
||||
return this;
|
||||
}
|
||||
|
||||
addSetting(cb: (setting: Setting) => void): this {
|
||||
const setting = new Setting(this.containerEl);
|
||||
cb(setting);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating a setting group with heading
|
||||
* Uses native SettingGroup on Obsidian 1.11.0+, falls back to legacy pattern on older versions
|
||||
*/
|
||||
export function createSettingGroup(
|
||||
container: HTMLElement,
|
||||
options: ToggleSettingOptions
|
||||
): Setting {
|
||||
return new Setting(container)
|
||||
options: SettingGroupOptions,
|
||||
addSettings: (group: SettingGroup | LegacySettingGroup) => void
|
||||
): SettingGroup | LegacySettingGroup {
|
||||
if (supportsSettingGroup()) {
|
||||
// Use native SettingGroup on Obsidian 1.11.0+
|
||||
const group = new SettingGroup(container).setHeading(options.heading);
|
||||
|
||||
if (options.className) {
|
||||
group.addClass(options.className);
|
||||
}
|
||||
|
||||
// Add description as help text if provided
|
||||
if (options.description) {
|
||||
group.addSetting((setting) => {
|
||||
setting.setDesc(options.description!);
|
||||
setting.settingEl.addClass("settings-view__group-description");
|
||||
});
|
||||
}
|
||||
|
||||
addSettings(group);
|
||||
return group;
|
||||
} else {
|
||||
// Fall back to legacy pattern on older Obsidian versions
|
||||
const group = new LegacySettingGroup(container).setHeading(options.heading);
|
||||
|
||||
if (options.className) {
|
||||
group.addClass(options.className);
|
||||
}
|
||||
|
||||
// Add description as help text if provided
|
||||
if (options.description) {
|
||||
group.addSetting((setting) => {
|
||||
setting.setDesc(options.description!);
|
||||
setting.settingEl.addClass("settings-view__group-description");
|
||||
});
|
||||
}
|
||||
|
||||
addSettings(group);
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for configuring a toggle setting (works with SettingGroup.addSetting)
|
||||
*/
|
||||
export function configureToggleSetting(setting: Setting, options: ToggleSettingOptions): Setting {
|
||||
return setting
|
||||
.setName(options.name)
|
||||
.setDesc(options.desc)
|
||||
.addToggle((toggle) => {
|
||||
|
|
@ -62,10 +148,20 @@ export function createToggleSetting(
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard text input settings
|
||||
* Helper for creating standard toggle settings
|
||||
*/
|
||||
export function createTextSetting(container: HTMLElement, options: TextSettingOptions): Setting {
|
||||
return new Setting(container)
|
||||
export function createToggleSetting(
|
||||
container: HTMLElement,
|
||||
options: ToggleSettingOptions
|
||||
): Setting {
|
||||
return configureToggleSetting(new Setting(container), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for configuring a text input setting (works with SettingGroup.addSetting)
|
||||
*/
|
||||
export function configureTextSetting(setting: Setting, options: TextSettingOptions): Setting {
|
||||
return setting
|
||||
.setName(options.name)
|
||||
.setDesc(options.desc)
|
||||
.addText((text) => {
|
||||
|
|
@ -95,13 +191,17 @@ export function createTextSetting(container: HTMLElement, options: TextSettingOp
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard dropdown settings
|
||||
* Helper for creating standard text input settings
|
||||
*/
|
||||
export function createDropdownSetting(
|
||||
container: HTMLElement,
|
||||
options: DropdownSettingOptions
|
||||
): Setting {
|
||||
return new Setting(container)
|
||||
export function createTextSetting(container: HTMLElement, options: TextSettingOptions): Setting {
|
||||
return configureTextSetting(new Setting(container), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for configuring a dropdown setting (works with SettingGroup.addSetting)
|
||||
*/
|
||||
export function configureDropdownSetting(setting: Setting, options: DropdownSettingOptions): Setting {
|
||||
return setting
|
||||
.setName(options.name)
|
||||
.setDesc(options.desc)
|
||||
.addDropdown((dropdown) => {
|
||||
|
|
@ -120,17 +220,24 @@ export function createDropdownSetting(
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard number input settings
|
||||
* Helper for creating standard dropdown settings
|
||||
*/
|
||||
export function createNumberSetting(
|
||||
export function createDropdownSetting(
|
||||
container: HTMLElement,
|
||||
options: NumberSettingOptions
|
||||
options: DropdownSettingOptions
|
||||
): Setting {
|
||||
return configureDropdownSetting(new Setting(container), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for configuring a number input setting (works with SettingGroup.addSetting)
|
||||
*/
|
||||
export function configureNumberSetting(setting: Setting, options: NumberSettingOptions): Setting {
|
||||
const setValue = options.debounceMs
|
||||
? debounce(options.setValue, options.debounceMs)
|
||||
: options.setValue;
|
||||
|
||||
return new Setting(container)
|
||||
return setting
|
||||
.setName(options.name)
|
||||
.setDesc(options.desc)
|
||||
.addText((text) => {
|
||||
|
|
@ -169,13 +276,20 @@ export function createNumberSetting(
|
|||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard button settings
|
||||
* Helper for creating standard number input settings
|
||||
*/
|
||||
export function createButtonSetting(
|
||||
export function createNumberSetting(
|
||||
container: HTMLElement,
|
||||
options: ButtonSettingOptions
|
||||
options: NumberSettingOptions
|
||||
): Setting {
|
||||
return new Setting(container)
|
||||
return configureNumberSetting(new Setting(container), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for configuring a button setting (works with SettingGroup.addSetting)
|
||||
*/
|
||||
export function configureButtonSetting(setting: Setting, options: ButtonSettingOptions): Setting {
|
||||
return setting
|
||||
.setName(options.name)
|
||||
.setDesc(options.desc)
|
||||
.addButton((button) => {
|
||||
|
|
@ -191,6 +305,16 @@ export function createButtonSetting(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating standard button settings
|
||||
*/
|
||||
export function createButtonSetting(
|
||||
container: HTMLElement,
|
||||
options: ButtonSettingOptions
|
||||
): Setting {
|
||||
return configureButtonSetting(new Setting(container), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for creating section headers
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,10 @@
|
|||
import { Setting, Notice } from "obsidian";
|
||||
import { Notice } from "obsidian";
|
||||
import TaskNotesPlugin from "../../main";
|
||||
import {
|
||||
createSectionHeader,
|
||||
createTextSetting,
|
||||
createToggleSetting,
|
||||
createDropdownSetting,
|
||||
createHelpText,
|
||||
createSettingGroup,
|
||||
configureTextSetting,
|
||||
configureToggleSetting,
|
||||
configureDropdownSetting,
|
||||
} from "../components/settingHelpers";
|
||||
import { TranslationKey } from "../../i18n";
|
||||
import { showConfirmationModal } from "../../modals/ConfirmationModal";
|
||||
|
|
@ -24,156 +23,167 @@ export function renderGeneralTab(
|
|||
plugin.i18n.translate(key, params);
|
||||
|
||||
// Tasks Storage Section
|
||||
createSectionHeader(container, translate("settings.general.taskStorage.header"));
|
||||
createHelpText(container, translate("settings.general.taskStorage.description"));
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.taskStorage.defaultFolder.name"),
|
||||
desc: translate("settings.general.taskStorage.defaultFolder.description"),
|
||||
placeholder: "TaskNotes",
|
||||
getValue: () => plugin.settings.tasksFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.tasksFolder = value;
|
||||
save();
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.general.taskStorage.header"),
|
||||
description: translate("settings.general.taskStorage.description"),
|
||||
},
|
||||
ariaLabel: "Default folder path for new tasks",
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.taskStorage.defaultFolder.name"),
|
||||
desc: translate("settings.general.taskStorage.defaultFolder.description"),
|
||||
placeholder: "TaskNotes",
|
||||
getValue: () => plugin.settings.tasksFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.tasksFolder = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Default folder path for new tasks",
|
||||
})
|
||||
);
|
||||
|
||||
// Folder for converted inline tasks (only shown when instant convert is enabled)
|
||||
if (plugin.settings.enableInstantTaskConvert) {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.features.instantConvert.folder.name"),
|
||||
desc: translate("settings.features.instantConvert.folder.description"),
|
||||
placeholder: "{{currentNotePath}}",
|
||||
getValue: () => plugin.settings.inlineTaskConvertFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.inlineTaskConvertFolder = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Folder for converted inline tasks",
|
||||
});
|
||||
}
|
||||
// Folder for converted inline tasks (only shown when instant convert is enabled)
|
||||
if (plugin.settings.enableInstantTaskConvert) {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.features.instantConvert.folder.name"),
|
||||
desc: translate("settings.features.instantConvert.folder.description"),
|
||||
placeholder: "{{currentNotePath}}",
|
||||
getValue: () => plugin.settings.inlineTaskConvertFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.inlineTaskConvertFolder = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Folder for converted inline tasks",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.general.taskStorage.moveArchived.name"),
|
||||
desc: translate("settings.general.taskStorage.moveArchived.description"),
|
||||
getValue: () => plugin.settings.moveArchivedTasks,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.moveArchivedTasks = value;
|
||||
save();
|
||||
// Re-render to show/hide archive folder setting
|
||||
renderGeneralTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.general.taskStorage.moveArchived.name"),
|
||||
desc: translate("settings.general.taskStorage.moveArchived.description"),
|
||||
getValue: () => plugin.settings.moveArchivedTasks,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.moveArchivedTasks = value;
|
||||
save();
|
||||
// Re-render to show/hide archive folder setting
|
||||
renderGeneralTab(container, plugin, save);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (plugin.settings.moveArchivedTasks) {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.taskStorage.archiveFolder.name"),
|
||||
desc: translate("settings.general.taskStorage.archiveFolder.description"),
|
||||
placeholder: "TaskNotes/Archive",
|
||||
getValue: () => plugin.settings.archiveFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.archiveFolder = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Archive folder path",
|
||||
});
|
||||
}
|
||||
if (plugin.settings.moveArchivedTasks) {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.taskStorage.archiveFolder.name"),
|
||||
desc: translate("settings.general.taskStorage.archiveFolder.description"),
|
||||
placeholder: "TaskNotes/Archive",
|
||||
getValue: () => plugin.settings.archiveFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.archiveFolder = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Archive folder path",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Task Identification Section
|
||||
createSectionHeader(container, translate("settings.general.taskIdentification.header"));
|
||||
createHelpText(container, translate("settings.general.taskIdentification.description"));
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.general.taskIdentification.identifyBy.name"),
|
||||
desc: translate("settings.general.taskIdentification.identifyBy.description"),
|
||||
options: [
|
||||
{
|
||||
value: "tag",
|
||||
label: translate("settings.general.taskIdentification.identifyBy.options.tag"),
|
||||
},
|
||||
{
|
||||
value: "property",
|
||||
label: translate("settings.general.taskIdentification.identifyBy.options.property"),
|
||||
},
|
||||
],
|
||||
getValue: () => plugin.settings.taskIdentificationMethod,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskIdentificationMethod = value as "tag" | "property";
|
||||
save();
|
||||
// Re-render to show/hide conditional fields
|
||||
renderGeneralTab(container, plugin, save);
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.general.taskIdentification.header"),
|
||||
description: translate("settings.general.taskIdentification.description"),
|
||||
},
|
||||
ariaLabel: "Task identification method",
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureDropdownSetting(setting, {
|
||||
name: translate("settings.general.taskIdentification.identifyBy.name"),
|
||||
desc: translate("settings.general.taskIdentification.identifyBy.description"),
|
||||
options: [
|
||||
{
|
||||
value: "tag",
|
||||
label: translate("settings.general.taskIdentification.identifyBy.options.tag"),
|
||||
},
|
||||
{
|
||||
value: "property",
|
||||
label: translate("settings.general.taskIdentification.identifyBy.options.property"),
|
||||
},
|
||||
],
|
||||
getValue: () => plugin.settings.taskIdentificationMethod,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskIdentificationMethod = value as "tag" | "property";
|
||||
save();
|
||||
// Re-render to show/hide conditional fields
|
||||
renderGeneralTab(container, plugin, save);
|
||||
},
|
||||
ariaLabel: "Task identification method",
|
||||
})
|
||||
);
|
||||
|
||||
if (plugin.settings.taskIdentificationMethod === "tag") {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.taskIdentification.taskTag.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskTag.description"),
|
||||
placeholder: "task",
|
||||
getValue: () => plugin.settings.taskTag,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskTag = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Task identification tag",
|
||||
});
|
||||
if (plugin.settings.taskIdentificationMethod === "tag") {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.taskIdentification.taskTag.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskTag.description"),
|
||||
placeholder: "task",
|
||||
getValue: () => plugin.settings.taskTag,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskTag = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Task identification tag",
|
||||
})
|
||||
);
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.general.taskIdentification.hideIdentifyingTags.name"),
|
||||
desc: translate("settings.general.taskIdentification.hideIdentifyingTags.description"),
|
||||
getValue: () => plugin.settings.hideIdentifyingTagsInCards,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.hideIdentifyingTagsInCards = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.taskIdentification.taskProperty.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskProperty.description"),
|
||||
placeholder: "category",
|
||||
getValue: () => plugin.settings.taskPropertyName,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskPropertyName = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.general.taskIdentification.hideIdentifyingTags.name"),
|
||||
desc: translate("settings.general.taskIdentification.hideIdentifyingTags.description"),
|
||||
getValue: () => plugin.settings.hideIdentifyingTagsInCards,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.hideIdentifyingTagsInCards = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.taskIdentification.taskProperty.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskProperty.description"),
|
||||
placeholder: "category",
|
||||
getValue: () => plugin.settings.taskPropertyName,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskPropertyName = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.taskIdentification.taskPropertyValue.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskPropertyValue.description"),
|
||||
placeholder: "task",
|
||||
getValue: () => plugin.settings.taskPropertyValue,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskPropertyValue = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
}
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.taskIdentification.taskPropertyValue.name"),
|
||||
desc: translate("settings.general.taskIdentification.taskPropertyValue.description"),
|
||||
placeholder: "task",
|
||||
getValue: () => plugin.settings.taskPropertyValue,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskPropertyValue = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Views & Base Files Section (moved above Folder Management)
|
||||
createSectionHeader(container, translate("settings.integrations.basesIntegration.viewCommands.header"));
|
||||
createHelpText(
|
||||
container,
|
||||
translate("settings.integrations.basesIntegration.viewCommands.description")
|
||||
);
|
||||
createHelpText(
|
||||
container,
|
||||
translate("settings.integrations.basesIntegration.viewCommands.descriptionRegen")
|
||||
);
|
||||
|
||||
// Documentation link
|
||||
const docsLinkContainer = container.createDiv({ cls: "setting-item-description" });
|
||||
const docsLink = docsLinkContainer.createEl("a", {
|
||||
text: translate("settings.integrations.basesIntegration.viewCommands.docsLink"),
|
||||
href: translate("settings.integrations.basesIntegration.viewCommands.docsLinkUrl"),
|
||||
});
|
||||
docsLink.setAttr("target", "_blank");
|
||||
docsLinkContainer.style.marginBottom = "1em";
|
||||
|
||||
// Command file mappings
|
||||
// Command file mappings data
|
||||
const commandMappings = [
|
||||
{
|
||||
id: 'open-calendar-view',
|
||||
|
|
@ -207,145 +217,182 @@ export function renderGeneralTab(
|
|||
},
|
||||
];
|
||||
|
||||
commandMappings.forEach(({ id, nameKey, defaultPath }) => {
|
||||
const setting = new Setting(container);
|
||||
const commandName = translate(`settings.integrations.basesIntegration.viewCommands.commands.${nameKey}` as any);
|
||||
setting.setName(commandName);
|
||||
setting.setDesc(translate("settings.integrations.basesIntegration.viewCommands.fileLabel", {
|
||||
path: plugin.settings.commandFileMapping[id]
|
||||
}));
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.basesIntegration.viewCommands.header"),
|
||||
description: translate("settings.integrations.basesIntegration.viewCommands.description"),
|
||||
},
|
||||
(group) => {
|
||||
// Additional description
|
||||
group.addSetting((setting) => {
|
||||
setting.setDesc(translate("settings.integrations.basesIntegration.viewCommands.descriptionRegen"));
|
||||
setting.settingEl.addClass("settings-view__group-description");
|
||||
});
|
||||
|
||||
// Text input for file path
|
||||
setting.addText(text => {
|
||||
text.setPlaceholder(defaultPath)
|
||||
.setValue(plugin.settings.commandFileMapping[id])
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.commandFileMapping[id] = value;
|
||||
await save();
|
||||
// Update description
|
||||
// Documentation link
|
||||
group.addSetting((setting) => {
|
||||
const descEl = setting.descEl;
|
||||
const docsLink = descEl.createEl("a", {
|
||||
text: translate("settings.integrations.basesIntegration.viewCommands.docsLink"),
|
||||
href: translate("settings.integrations.basesIntegration.viewCommands.docsLinkUrl"),
|
||||
});
|
||||
docsLink.setAttr("target", "_blank");
|
||||
setting.settingEl.addClass("settings-view__group-description");
|
||||
});
|
||||
|
||||
// Command file mappings
|
||||
commandMappings.forEach(({ id, nameKey, defaultPath }) => {
|
||||
group.addSetting((setting) => {
|
||||
const commandName = translate(`settings.integrations.basesIntegration.viewCommands.commands.${nameKey}` as any);
|
||||
setting.setName(commandName);
|
||||
setting.setDesc(translate("settings.integrations.basesIntegration.viewCommands.fileLabel", {
|
||||
path: value
|
||||
path: plugin.settings.commandFileMapping[id]
|
||||
}));
|
||||
});
|
||||
text.inputEl.style.width = '100%';
|
||||
return text;
|
||||
});
|
||||
|
||||
// Reset button
|
||||
setting.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.viewCommands.resetButton"))
|
||||
.setTooltip(translate("settings.integrations.basesIntegration.viewCommands.resetTooltip"))
|
||||
.onClick(async () => {
|
||||
plugin.settings.commandFileMapping[id] = defaultPath;
|
||||
await save();
|
||||
// Refresh the entire settings display
|
||||
if (app.setting.activeTab) {
|
||||
app.setting.openTabById(app.setting.activeTab.id);
|
||||
}
|
||||
});
|
||||
return button;
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-create default files toggle
|
||||
new Setting(container)
|
||||
.setName(translate("settings.integrations.basesIntegration.autoCreateDefaultFiles.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.autoCreateDefaultFiles.description"))
|
||||
.addToggle(toggle => {
|
||||
toggle.setValue(plugin.settings.autoCreateDefaultBasesFiles)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.autoCreateDefaultBasesFiles = value;
|
||||
await save();
|
||||
});
|
||||
return toggle;
|
||||
});
|
||||
|
||||
// Create Default Files button
|
||||
new Setting(container)
|
||||
.setName(translate("settings.integrations.basesIntegration.createDefaultFiles.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.createDefaultFiles.description"))
|
||||
.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.createDefaultFiles.buttonText"))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await plugin.createDefaultBasesFiles();
|
||||
});
|
||||
return button;
|
||||
});
|
||||
|
||||
// Export All Saved Views button
|
||||
new Setting(container)
|
||||
.setName(translate("settings.integrations.basesIntegration.exportV3Views.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.exportV3Views.description"))
|
||||
.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.exportV3Views.buttonText"))
|
||||
.onClick(async () => {
|
||||
try {
|
||||
const savedViews = plugin.viewStateManager.getSavedViews();
|
||||
|
||||
if (savedViews.length === 0) {
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.noViews"));
|
||||
return;
|
||||
}
|
||||
|
||||
const basesContent = plugin.basesFilterConverter.convertAllSavedViewsToBasesFile(savedViews);
|
||||
const fileName = 'all-saved-views.base';
|
||||
const filePath = `TaskNotes/Views/${fileName}`;
|
||||
|
||||
// Create folder if needed
|
||||
const folder = plugin.app.vault.getAbstractFileByPath('TaskNotes/Views');
|
||||
if (!folder) {
|
||||
await plugin.app.vault.createFolder('TaskNotes/Views');
|
||||
}
|
||||
|
||||
// Handle file overwrite confirmation
|
||||
const existingFile = plugin.app.vault.getAbstractFileByPath(filePath);
|
||||
if (existingFile) {
|
||||
const confirmed = await showConfirmationModal(plugin.app, {
|
||||
title: translate("settings.integrations.basesIntegration.exportV3Views.fileExists"),
|
||||
message: translate("settings.integrations.basesIntegration.exportV3Views.confirmOverwrite", { fileName }),
|
||||
isDestructive: false,
|
||||
// Text input for file path
|
||||
setting.addText(text => {
|
||||
text.setPlaceholder(defaultPath)
|
||||
.setValue(plugin.settings.commandFileMapping[id])
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.commandFileMapping[id] = value;
|
||||
await save();
|
||||
// Update description
|
||||
setting.setDesc(translate("settings.integrations.basesIntegration.viewCommands.fileLabel", {
|
||||
path: value
|
||||
}));
|
||||
});
|
||||
if (!confirmed) return;
|
||||
await plugin.app.vault.modify(existingFile as any, basesContent);
|
||||
} else {
|
||||
await plugin.app.vault.create(filePath, basesContent);
|
||||
}
|
||||
text.inputEl.style.width = '100%';
|
||||
return text;
|
||||
});
|
||||
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.success", {
|
||||
count: savedViews.length.toString(),
|
||||
filePath
|
||||
}));
|
||||
await plugin.app.workspace.openLinkText(filePath, '', true);
|
||||
} catch (error) {
|
||||
console.error('Error exporting all views to Bases:', error);
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.error", {
|
||||
message: error.message
|
||||
}));
|
||||
}
|
||||
// Reset button
|
||||
setting.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.viewCommands.resetButton"))
|
||||
.setTooltip(translate("settings.integrations.basesIntegration.viewCommands.resetTooltip"))
|
||||
.onClick(async () => {
|
||||
plugin.settings.commandFileMapping[id] = defaultPath;
|
||||
await save();
|
||||
// Refresh the entire settings display
|
||||
if (app.setting.activeTab) {
|
||||
app.setting.openTabById(app.setting.activeTab.id);
|
||||
}
|
||||
});
|
||||
return button;
|
||||
});
|
||||
});
|
||||
return button;
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-create default files toggle
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName(translate("settings.integrations.basesIntegration.autoCreateDefaultFiles.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.autoCreateDefaultFiles.description"))
|
||||
.addToggle(toggle => {
|
||||
toggle.setValue(plugin.settings.autoCreateDefaultBasesFiles)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.autoCreateDefaultBasesFiles = value;
|
||||
await save();
|
||||
});
|
||||
return toggle;
|
||||
});
|
||||
});
|
||||
|
||||
// Create Default Files button
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName(translate("settings.integrations.basesIntegration.createDefaultFiles.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.createDefaultFiles.description"))
|
||||
.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.createDefaultFiles.buttonText"))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await plugin.createDefaultBasesFiles();
|
||||
});
|
||||
return button;
|
||||
});
|
||||
});
|
||||
|
||||
// Export All Saved Views button
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName(translate("settings.integrations.basesIntegration.exportV3Views.name"))
|
||||
.setDesc(translate("settings.integrations.basesIntegration.exportV3Views.description"))
|
||||
.addButton(button => {
|
||||
button.setButtonText(translate("settings.integrations.basesIntegration.exportV3Views.buttonText"))
|
||||
.onClick(async () => {
|
||||
try {
|
||||
const savedViews = plugin.viewStateManager.getSavedViews();
|
||||
|
||||
if (savedViews.length === 0) {
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.noViews"));
|
||||
return;
|
||||
}
|
||||
|
||||
const basesContent = plugin.basesFilterConverter.convertAllSavedViewsToBasesFile(savedViews);
|
||||
const fileName = 'all-saved-views.base';
|
||||
const filePath = `TaskNotes/Views/${fileName}`;
|
||||
|
||||
// Create folder if needed
|
||||
const folder = plugin.app.vault.getAbstractFileByPath('TaskNotes/Views');
|
||||
if (!folder) {
|
||||
await plugin.app.vault.createFolder('TaskNotes/Views');
|
||||
}
|
||||
|
||||
// Handle file overwrite confirmation
|
||||
const existingFile = plugin.app.vault.getAbstractFileByPath(filePath);
|
||||
if (existingFile) {
|
||||
const confirmed = await showConfirmationModal(plugin.app, {
|
||||
title: translate("settings.integrations.basesIntegration.exportV3Views.fileExists"),
|
||||
message: translate("settings.integrations.basesIntegration.exportV3Views.confirmOverwrite", { fileName }),
|
||||
isDestructive: false,
|
||||
});
|
||||
if (!confirmed) return;
|
||||
await plugin.app.vault.modify(existingFile as any, basesContent);
|
||||
} else {
|
||||
await plugin.app.vault.create(filePath, basesContent);
|
||||
}
|
||||
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.success", {
|
||||
count: savedViews.length.toString(),
|
||||
filePath
|
||||
}));
|
||||
await plugin.app.workspace.openLinkText(filePath, '', true);
|
||||
} catch (error) {
|
||||
console.error('Error exporting all views to Bases:', error);
|
||||
new Notice(translate("settings.integrations.basesIntegration.exportV3Views.error", {
|
||||
message: error.message
|
||||
}));
|
||||
}
|
||||
});
|
||||
return button;
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Folder Management Section
|
||||
createSectionHeader(container, translate("settings.general.folderManagement.header"));
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.general.folderManagement.excludedFolders.name"),
|
||||
desc: translate("settings.general.folderManagement.excludedFolders.description"),
|
||||
placeholder: "Templates, Archive",
|
||||
getValue: () => plugin.settings.excludedFolders,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.excludedFolders = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Excluded folder paths",
|
||||
});
|
||||
createSettingGroup(
|
||||
container,
|
||||
{ heading: translate("settings.general.folderManagement.header") },
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.general.folderManagement.excludedFolders.name"),
|
||||
desc: translate("settings.general.folderManagement.excludedFolders.description"),
|
||||
placeholder: "Templates, Archive",
|
||||
getValue: () => plugin.settings.excludedFolders,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.excludedFolders = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Excluded folder paths",
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// UI Language Section
|
||||
createSectionHeader(container, translate("settings.features.uiLanguage.header"));
|
||||
createHelpText(container, translate("settings.features.uiLanguage.description"));
|
||||
|
||||
const uiLanguageOptions = (() => {
|
||||
const options: Array<{ value: string; label: string }> = [
|
||||
{ value: "system", label: translate("common.systemDefault") },
|
||||
|
|
@ -358,59 +405,88 @@ export function renderGeneralTab(
|
|||
return options;
|
||||
})();
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.features.uiLanguage.dropdown.name"),
|
||||
desc: translate("settings.features.uiLanguage.dropdown.description"),
|
||||
options: uiLanguageOptions,
|
||||
getValue: () => plugin.settings.uiLanguage ?? "system",
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.uiLanguage = value;
|
||||
plugin.i18n.setLocale(value);
|
||||
save();
|
||||
renderGeneralTab(container, plugin, save);
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.features.uiLanguage.header"),
|
||||
description: translate("settings.features.uiLanguage.description"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureDropdownSetting(setting, {
|
||||
name: translate("settings.features.uiLanguage.dropdown.name"),
|
||||
desc: translate("settings.features.uiLanguage.dropdown.description"),
|
||||
options: uiLanguageOptions,
|
||||
getValue: () => plugin.settings.uiLanguage ?? "system",
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.uiLanguage = value;
|
||||
plugin.i18n.setLocale(value);
|
||||
save();
|
||||
renderGeneralTab(container, plugin, save);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Frontmatter Section - only show if user has markdown links enabled globally
|
||||
const useMarkdownLinks = plugin.app.vault.getConfig('useMarkdownLinks');
|
||||
if (useMarkdownLinks) {
|
||||
createSectionHeader(container, translate("settings.general.frontmatter.header"));
|
||||
createHelpText(container, translate("settings.general.frontmatter.description"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.general.frontmatter.useMarkdownLinks.name"),
|
||||
desc: translate("settings.general.frontmatter.useMarkdownLinks.description"),
|
||||
getValue: () => plugin.settings.useFrontmatterMarkdownLinks,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.useFrontmatterMarkdownLinks = value;
|
||||
save();
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.general.frontmatter.header"),
|
||||
description: translate("settings.general.frontmatter.description"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.general.frontmatter.useMarkdownLinks.name"),
|
||||
desc: translate("settings.general.frontmatter.useMarkdownLinks.description"),
|
||||
getValue: () => plugin.settings.useFrontmatterMarkdownLinks,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.useFrontmatterMarkdownLinks = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Release Notes Section
|
||||
createSectionHeader(container, translate("settings.general.releaseNotes.header"));
|
||||
createHelpText(container, translate("settings.general.releaseNotes.description", { version: plugin.manifest.version }));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.general.releaseNotes.showOnUpdate.name"),
|
||||
desc: translate("settings.general.releaseNotes.showOnUpdate.description"),
|
||||
getValue: () => plugin.settings.showReleaseNotesOnUpdate ?? true,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.showReleaseNotesOnUpdate = value;
|
||||
save();
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.general.releaseNotes.header"),
|
||||
description: translate("settings.general.releaseNotes.description", { version: plugin.manifest.version }),
|
||||
},
|
||||
});
|
||||
|
||||
new Setting(container)
|
||||
.setName(translate("settings.general.releaseNotes.viewButton.name"))
|
||||
.setDesc(translate("settings.general.releaseNotes.viewButton.description"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(translate("settings.general.releaseNotes.viewButton.buttonText"))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await plugin.activateReleaseNotesView();
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.general.releaseNotes.showOnUpdate.name"),
|
||||
desc: translate("settings.general.releaseNotes.showOnUpdate.description"),
|
||||
getValue: () => plugin.settings.showReleaseNotesOnUpdate ?? true,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.showReleaseNotesOnUpdate = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
);
|
||||
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName(translate("settings.general.releaseNotes.viewButton.name"))
|
||||
.setDesc(translate("settings.general.releaseNotes.viewButton.description"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(translate("settings.general.releaseNotes.viewButton.buttonText"))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
await plugin.activateReleaseNotesView();
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { WebhookConfig } from "../../types";
|
|||
import { TranslationKey } from "../../i18n";
|
||||
import { loadAPIEndpoints } from "../../api/loadAPIEndpoints";
|
||||
import {
|
||||
createSectionHeader,
|
||||
createTextSetting,
|
||||
createToggleSetting,
|
||||
createDropdownSetting,
|
||||
createNumberSetting,
|
||||
createSettingGroup,
|
||||
configureTextSetting,
|
||||
configureToggleSetting,
|
||||
configureDropdownSetting,
|
||||
configureNumberSetting,
|
||||
configureButtonSetting,
|
||||
createHelpText,
|
||||
createButtonSetting,
|
||||
} from "../components/settingHelpers";
|
||||
import { showConfirmationModal } from "../../modals/ConfirmationModal";
|
||||
import {
|
||||
|
|
@ -84,10 +84,15 @@ export function renderIntegrationsTab(
|
|||
plugin.i18n.translate(key, params);
|
||||
|
||||
// OAuth Calendar Integration Section
|
||||
createSectionHeader(container, "OAuth Calendar Integration");
|
||||
createHelpText(
|
||||
createSettingGroup(
|
||||
container,
|
||||
"Connect your Google Calendar or Microsoft Outlook to sync events directly into TaskNotes."
|
||||
{
|
||||
heading: "OAuth Calendar Integration",
|
||||
description: "Connect your Google Calendar or Microsoft Outlook to sync events directly into TaskNotes.",
|
||||
},
|
||||
() => {
|
||||
// Settings added via card components below
|
||||
}
|
||||
);
|
||||
|
||||
// TaskNotes License Card (appears before calendar cards)
|
||||
|
|
@ -777,206 +782,275 @@ export function renderIntegrationsTab(
|
|||
renderMicrosoftCalendarCard();
|
||||
|
||||
// Calendar Subscriptions Section (ICS)
|
||||
createSectionHeader(container, translate("settings.integrations.calendarSubscriptions.header"));
|
||||
createHelpText(container, translate("settings.integrations.calendarSubscriptions.description"));
|
||||
|
||||
// Default settings for ICS integration
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.defaultNoteTemplate.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteTemplate.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteTemplate.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.defaultNoteTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.defaultNoteTemplate = value;
|
||||
save();
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.calendarSubscriptions.header"),
|
||||
description: translate("settings.integrations.calendarSubscriptions.description"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
// Default settings for ICS integration
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.defaultNoteTemplate.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteTemplate.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteTemplate.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.defaultNoteTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.defaultNoteTemplate = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.defaultNoteFolder.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteFolder.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteFolder.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.defaultNoteFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.defaultNoteFolder = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.defaultNoteFolder.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteFolder.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.defaultNoteFolder.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.defaultNoteFolder,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.defaultNoteFolder = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.filenameFormat.name"),
|
||||
desc: translate("settings.integrations.calendarSubscriptions.filenameFormat.description"),
|
||||
options: [
|
||||
{
|
||||
value: "title",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.title"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "zettel",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.zettel"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "timestamp",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.timestamp"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "custom",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.custom"
|
||||
),
|
||||
},
|
||||
],
|
||||
getValue: () => plugin.settings.icsIntegration.icsNoteFilenameFormat,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.icsNoteFilenameFormat = value as any;
|
||||
save();
|
||||
// Re-render to show custom template field if needed
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
group.addSetting((setting) =>
|
||||
configureDropdownSetting(setting, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.filenameFormat.name"),
|
||||
desc: translate("settings.integrations.calendarSubscriptions.filenameFormat.description"),
|
||||
options: [
|
||||
{
|
||||
value: "title",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.title"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "zettel",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.zettel"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "timestamp",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.timestamp"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "custom",
|
||||
label: translate(
|
||||
"settings.integrations.calendarSubscriptions.filenameFormat.options.custom"
|
||||
),
|
||||
},
|
||||
],
|
||||
getValue: () => plugin.settings.icsIntegration.icsNoteFilenameFormat,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.icsNoteFilenameFormat = value as any;
|
||||
save();
|
||||
// Re-render to show custom template field if needed
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (plugin.settings.icsIntegration.icsNoteFilenameFormat === "custom") {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.customTemplate.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.customTemplate.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.customTemplate.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.customICSNoteFilenameTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.customICSNoteFilenameTemplate = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
}
|
||||
if (plugin.settings.icsIntegration.icsNoteFilenameFormat === "custom") {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.integrations.calendarSubscriptions.customTemplate.name"),
|
||||
desc: translate(
|
||||
"settings.integrations.calendarSubscriptions.customTemplate.description"
|
||||
),
|
||||
placeholder: translate(
|
||||
"settings.integrations.calendarSubscriptions.customTemplate.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.icsIntegration.customICSNoteFilenameTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.customICSNoteFilenameTemplate = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ICS Subscriptions List - Add proper section header
|
||||
createSectionHeader(container, translate("settings.integrations.subscriptionsList.header"));
|
||||
// ICS Subscriptions List
|
||||
const icsContainer = container.createDiv("ics-subscriptions-container");
|
||||
renderICSSubscriptionsList(icsContainer, plugin, save);
|
||||
|
||||
// Add subscription button
|
||||
createButtonSetting(container, {
|
||||
name: translate("settings.integrations.subscriptionsList.addSubscription.name"),
|
||||
desc: translate("settings.integrations.subscriptionsList.addSubscription.description"),
|
||||
buttonText: translate("settings.integrations.subscriptionsList.addSubscription.buttonText"),
|
||||
onClick: async () => {
|
||||
// Create a new subscription with temporary values
|
||||
const newSubscription = {
|
||||
name: translate("settings.integrations.subscriptionsList.newCalendarName"),
|
||||
url: "",
|
||||
color: "#6366f1",
|
||||
enabled: false, // Start disabled until user fills in details
|
||||
type: "remote" as const,
|
||||
refreshInterval: 60,
|
||||
};
|
||||
|
||||
if (!plugin.icsSubscriptionService) {
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.serviceUnavailable")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await plugin.icsSubscriptionService.addSubscription(newSubscription);
|
||||
new Notice(translate("settings.integrations.subscriptionsList.notices.addSuccess"));
|
||||
// Re-render to show the new subscription card
|
||||
renderICSSubscriptionsList(icsContainer, plugin, save);
|
||||
} catch (error) {
|
||||
console.error("Error adding subscription:", error);
|
||||
new Notice(translate("settings.integrations.subscriptionsList.notices.addFailure"));
|
||||
}
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.subscriptionsList.header"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
// Add subscription button
|
||||
group.addSetting((setting) =>
|
||||
configureButtonSetting(setting, {
|
||||
name: translate("settings.integrations.subscriptionsList.addSubscription.name"),
|
||||
desc: translate("settings.integrations.subscriptionsList.addSubscription.description"),
|
||||
buttonText: translate("settings.integrations.subscriptionsList.addSubscription.buttonText"),
|
||||
onClick: async () => {
|
||||
// Create a new subscription with temporary values
|
||||
const newSubscription = {
|
||||
name: translate("settings.integrations.subscriptionsList.newCalendarName"),
|
||||
url: "",
|
||||
color: "#6366f1",
|
||||
enabled: false, // Start disabled until user fills in details
|
||||
type: "remote" as const,
|
||||
refreshInterval: 60,
|
||||
};
|
||||
|
||||
// Refresh all subscriptions button
|
||||
createButtonSetting(container, {
|
||||
name: translate("settings.integrations.subscriptionsList.refreshAll.name"),
|
||||
desc: translate("settings.integrations.subscriptionsList.refreshAll.description"),
|
||||
buttonText: translate("settings.integrations.subscriptionsList.refreshAll.buttonText"),
|
||||
onClick: async () => {
|
||||
if (plugin.icsSubscriptionService) {
|
||||
try {
|
||||
await plugin.icsSubscriptionService.refreshAllSubscriptions();
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.refreshSuccess")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error refreshing subscriptions:", error);
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.refreshFailure")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
if (!plugin.icsSubscriptionService) {
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.serviceUnavailable")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await plugin.icsSubscriptionService.addSubscription(newSubscription);
|
||||
new Notice(translate("settings.integrations.subscriptionsList.notices.addSuccess"));
|
||||
// Re-render to show the new subscription card
|
||||
renderICSSubscriptionsList(icsContainer, plugin, save);
|
||||
} catch (error) {
|
||||
console.error("Error adding subscription:", error);
|
||||
new Notice(translate("settings.integrations.subscriptionsList.notices.addFailure"));
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Refresh all subscriptions button
|
||||
group.addSetting((setting) =>
|
||||
configureButtonSetting(setting, {
|
||||
name: translate("settings.integrations.subscriptionsList.refreshAll.name"),
|
||||
desc: translate("settings.integrations.subscriptionsList.refreshAll.description"),
|
||||
buttonText: translate("settings.integrations.subscriptionsList.refreshAll.buttonText"),
|
||||
onClick: async () => {
|
||||
if (plugin.icsSubscriptionService) {
|
||||
try {
|
||||
await plugin.icsSubscriptionService.refreshAllSubscriptions();
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.refreshSuccess")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error refreshing subscriptions:", error);
|
||||
new Notice(
|
||||
translate("settings.integrations.subscriptionsList.notices.refreshFailure")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Automatic ICS Export Section
|
||||
createSectionHeader(container, translate("settings.integrations.autoExport.header"));
|
||||
createHelpText(container, translate("settings.integrations.autoExport.description"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.integrations.autoExport.enable.name"),
|
||||
desc: translate("settings.integrations.autoExport.enable.description"),
|
||||
getValue: () => plugin.settings.icsIntegration.enableAutoExport,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.icsIntegration.enableAutoExport = value;
|
||||
save();
|
||||
new Notice(translate("settings.integrations.autoExport.notices.reloadRequired"));
|
||||
// Re-render to show/hide export settings
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.autoExport.header"),
|
||||
description: translate("settings.integrations.autoExport.description"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.integrations.autoExport.enable.name"),
|
||||
desc: translate("settings.integrations.autoExport.enable.description"),
|
||||
getValue: () => plugin.settings.icsIntegration.enableAutoExport,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.icsIntegration.enableAutoExport = value;
|
||||
save();
|
||||
new Notice(translate("settings.integrations.autoExport.notices.reloadRequired"));
|
||||
// Re-render to show/hide export settings
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (plugin.settings.icsIntegration.enableAutoExport) {
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.integrations.autoExport.filePath.name"),
|
||||
desc: translate("settings.integrations.autoExport.filePath.description"),
|
||||
placeholder: translate("settings.integrations.autoExport.filePath.placeholder"),
|
||||
getValue: () => plugin.settings.icsIntegration.autoExportPath,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.autoExportPath = value || "tasknotes-calendar.ics";
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
group.addSetting((setting) =>
|
||||
configureNumberSetting(setting, {
|
||||
name: translate("settings.integrations.autoExport.interval.name"),
|
||||
desc: translate("settings.integrations.autoExport.interval.description"),
|
||||
placeholder: translate("settings.integrations.autoExport.interval.placeholder"),
|
||||
min: 5,
|
||||
max: 1440, // 24 hours max
|
||||
getValue: () => plugin.settings.icsIntegration.autoExportInterval,
|
||||
setValue: async (value: number) => {
|
||||
plugin.settings.icsIntegration.autoExportInterval = Math.max(5, value || 60);
|
||||
save();
|
||||
// Restart the auto export service with new interval
|
||||
if (plugin.autoExportService) {
|
||||
plugin.autoExportService.updateInterval(
|
||||
plugin.settings.icsIntegration.autoExportInterval
|
||||
);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Manual export trigger button
|
||||
group.addSetting((setting) =>
|
||||
configureButtonSetting(setting, {
|
||||
name: translate("settings.integrations.autoExport.exportNow.name"),
|
||||
desc: translate("settings.integrations.autoExport.exportNow.description"),
|
||||
buttonText: translate("settings.integrations.autoExport.exportNow.buttonText"),
|
||||
onClick: async () => {
|
||||
if (plugin.autoExportService) {
|
||||
try {
|
||||
await plugin.autoExportService.exportNow();
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.exportSuccess")
|
||||
);
|
||||
// Re-render to update status
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
} catch (error) {
|
||||
console.error("Manual export failed:", error);
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.exportFailure")
|
||||
);
|
||||
}
|
||||
} else {
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.serviceUnavailable")
|
||||
);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Show current export status (outside group, as a dynamic element)
|
||||
if (plugin.settings.icsIntegration.enableAutoExport) {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.integrations.autoExport.filePath.name"),
|
||||
desc: translate("settings.integrations.autoExport.filePath.description"),
|
||||
placeholder: translate("settings.integrations.autoExport.filePath.placeholder"),
|
||||
getValue: () => plugin.settings.icsIntegration.autoExportPath,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.icsIntegration.autoExportPath = value || "tasknotes-calendar.ics";
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createNumberSetting(container, {
|
||||
name: translate("settings.integrations.autoExport.interval.name"),
|
||||
desc: translate("settings.integrations.autoExport.interval.description"),
|
||||
placeholder: translate("settings.integrations.autoExport.interval.placeholder"),
|
||||
min: 5,
|
||||
max: 1440, // 24 hours max
|
||||
getValue: () => plugin.settings.icsIntegration.autoExportInterval,
|
||||
setValue: async (value: number) => {
|
||||
plugin.settings.icsIntegration.autoExportInterval = Math.max(5, value || 60);
|
||||
save();
|
||||
// Restart the auto export service with new interval
|
||||
if (plugin.autoExportService) {
|
||||
plugin.autoExportService.updateInterval(
|
||||
plugin.settings.icsIntegration.autoExportInterval
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Show current export status
|
||||
const statusContainer = container.createDiv("auto-export-status");
|
||||
statusContainer.style.marginTop = "10px";
|
||||
statusContainer.style.padding = "10px";
|
||||
|
|
@ -1013,79 +1087,65 @@ export function renderIntegrationsTab(
|
|||
errorDiv.style.color = "var(--text-warning)";
|
||||
errorDiv.textContent = translate("settings.integrations.autoExport.status.serviceNotInitialized");
|
||||
}
|
||||
|
||||
// Manual export trigger button
|
||||
createButtonSetting(container, {
|
||||
name: translate("settings.integrations.autoExport.exportNow.name"),
|
||||
desc: translate("settings.integrations.autoExport.exportNow.description"),
|
||||
buttonText: translate("settings.integrations.autoExport.exportNow.buttonText"),
|
||||
onClick: async () => {
|
||||
if (plugin.autoExportService) {
|
||||
try {
|
||||
await plugin.autoExportService.exportNow();
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.exportSuccess")
|
||||
);
|
||||
// Re-render to update status
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
} catch (error) {
|
||||
console.error("Manual export failed:", error);
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.exportFailure")
|
||||
);
|
||||
}
|
||||
} else {
|
||||
new Notice(
|
||||
translate("settings.integrations.autoExport.notices.serviceUnavailable")
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// HTTP API Section (Skip on mobile)
|
||||
if (!Platform.isMobile) {
|
||||
createSectionHeader(container, translate("settings.integrations.httpApi.header"));
|
||||
createHelpText(container, translate("settings.integrations.httpApi.description"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.integrations.httpApi.enable.name"),
|
||||
desc: translate("settings.integrations.httpApi.enable.description"),
|
||||
getValue: () => plugin.settings.enableAPI,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.enableAPI = value;
|
||||
save();
|
||||
// Re-render to show API settings
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.httpApi.header"),
|
||||
description: translate("settings.integrations.httpApi.description"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: translate("settings.integrations.httpApi.enable.name"),
|
||||
desc: translate("settings.integrations.httpApi.enable.description"),
|
||||
getValue: () => plugin.settings.enableAPI,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.enableAPI = value;
|
||||
save();
|
||||
// Re-render to show API settings
|
||||
renderIntegrationsTab(container, plugin, save);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (plugin.settings.enableAPI) {
|
||||
group.addSetting((setting) =>
|
||||
configureNumberSetting(setting, {
|
||||
name: translate("settings.integrations.httpApi.port.name"),
|
||||
desc: translate("settings.integrations.httpApi.port.description"),
|
||||
placeholder: translate("settings.integrations.httpApi.port.placeholder"),
|
||||
min: 1024,
|
||||
max: 65535,
|
||||
getValue: () => plugin.settings.apiPort,
|
||||
setValue: async (value: number) => {
|
||||
plugin.settings.apiPort = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
group.addSetting((setting) =>
|
||||
configureTextSetting(setting, {
|
||||
name: translate("settings.integrations.httpApi.authToken.name"),
|
||||
desc: translate("settings.integrations.httpApi.authToken.description"),
|
||||
placeholder: translate("settings.integrations.httpApi.authToken.placeholder"),
|
||||
getValue: () => plugin.settings.apiAuthToken,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.apiAuthToken = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// API endpoint info (outside group, as a collapsible dynamic element)
|
||||
if (plugin.settings.enableAPI) {
|
||||
createNumberSetting(container, {
|
||||
name: translate("settings.integrations.httpApi.port.name"),
|
||||
desc: translate("settings.integrations.httpApi.port.description"),
|
||||
placeholder: translate("settings.integrations.httpApi.port.placeholder"),
|
||||
min: 1024,
|
||||
max: 65535,
|
||||
getValue: () => plugin.settings.apiPort,
|
||||
setValue: async (value: number) => {
|
||||
plugin.settings.apiPort = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.integrations.httpApi.authToken.name"),
|
||||
desc: translate("settings.integrations.httpApi.authToken.description"),
|
||||
placeholder: translate("settings.integrations.httpApi.authToken.placeholder"),
|
||||
getValue: () => plugin.settings.apiAuthToken,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.apiAuthToken = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// API endpoint info
|
||||
const apiInfoContainer = container.createDiv("tasknotes-settings__help-section");
|
||||
const apiHeader = apiInfoContainer.createDiv("tasknotes-settings__collapsible-header");
|
||||
const apiHeaderContent = apiHeader.createDiv(
|
||||
|
|
@ -1121,71 +1181,79 @@ export function renderIntegrationsTab(
|
|||
}
|
||||
|
||||
// Webhooks Section
|
||||
createSectionHeader(container, translate("settings.integrations.webhooks.header"));
|
||||
|
||||
// Webhook description
|
||||
const webhookDescEl = container.createDiv("setting-item-description");
|
||||
webhookDescEl.createEl("p", {
|
||||
text: translate("settings.integrations.webhooks.description.overview"),
|
||||
});
|
||||
webhookDescEl.createEl("p", {
|
||||
text: translate("settings.integrations.webhooks.description.usage"),
|
||||
});
|
||||
|
||||
// Webhook management
|
||||
renderWebhookList(container, plugin, save);
|
||||
|
||||
// Add webhook button
|
||||
createButtonSetting(container, {
|
||||
name: translate("settings.integrations.webhooks.addWebhook.name"),
|
||||
desc: translate("settings.integrations.webhooks.addWebhook.description"),
|
||||
buttonText: translate("settings.integrations.webhooks.addWebhook.buttonText"),
|
||||
onClick: async () => {
|
||||
const modal = new WebhookModal(
|
||||
plugin.app,
|
||||
async (webhookConfig: Partial<WebhookConfig>) => {
|
||||
// Generate ID and secret
|
||||
const webhook: WebhookConfig = {
|
||||
id: `wh_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
url: webhookConfig.url || "",
|
||||
events: webhookConfig.events || [],
|
||||
secret: generateWebhookSecret(),
|
||||
active: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
failureCount: 0,
|
||||
successCount: 0,
|
||||
transformFile: webhookConfig.transformFile,
|
||||
corsHeaders: webhookConfig.corsHeaders,
|
||||
};
|
||||
|
||||
if (!plugin.settings.webhooks) {
|
||||
plugin.settings.webhooks = [];
|
||||
}
|
||||
|
||||
plugin.settings.webhooks.push(webhook);
|
||||
save();
|
||||
|
||||
// Re-render webhook list to show the new webhook
|
||||
renderWebhookList(
|
||||
container.querySelector(".tasknotes-webhooks-container")
|
||||
?.parentElement || container,
|
||||
plugin,
|
||||
save
|
||||
);
|
||||
|
||||
// Show success message with secret
|
||||
new SecretNoticeModal(plugin.app, webhook.secret).open();
|
||||
new Notice(translate("settings.integrations.webhooks.notices.created"));
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.webhooks.header"),
|
||||
description: translate("settings.integrations.webhooks.description.overview") + " " + translate("settings.integrations.webhooks.description.usage"),
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
// Add webhook button
|
||||
group.addSetting((setting) =>
|
||||
configureButtonSetting(setting, {
|
||||
name: translate("settings.integrations.webhooks.addWebhook.name"),
|
||||
desc: translate("settings.integrations.webhooks.addWebhook.description"),
|
||||
buttonText: translate("settings.integrations.webhooks.addWebhook.buttonText"),
|
||||
onClick: async () => {
|
||||
const modal = new WebhookModal(
|
||||
plugin.app,
|
||||
async (webhookConfig: Partial<WebhookConfig>) => {
|
||||
// Generate ID and secret
|
||||
const webhook: WebhookConfig = {
|
||||
id: `wh_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
url: webhookConfig.url || "",
|
||||
events: webhookConfig.events || [],
|
||||
secret: generateWebhookSecret(),
|
||||
active: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
failureCount: 0,
|
||||
successCount: 0,
|
||||
transformFile: webhookConfig.transformFile,
|
||||
corsHeaders: webhookConfig.corsHeaders,
|
||||
};
|
||||
|
||||
if (!plugin.settings.webhooks) {
|
||||
plugin.settings.webhooks = [];
|
||||
}
|
||||
|
||||
plugin.settings.webhooks.push(webhook);
|
||||
save();
|
||||
|
||||
// Re-render webhook list to show the new webhook
|
||||
renderWebhookList(
|
||||
container.querySelector(".tasknotes-webhooks-container")
|
||||
?.parentElement || container,
|
||||
plugin,
|
||||
save
|
||||
);
|
||||
|
||||
// Show success message with secret
|
||||
new SecretNoticeModal(plugin.app, webhook.secret).open();
|
||||
new Notice(translate("settings.integrations.webhooks.notices.created"));
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Other Integrations Section
|
||||
createSectionHeader(container, translate("settings.integrations.otherIntegrations.header"));
|
||||
createHelpText(container, translate("settings.integrations.otherIntegrations.description"));
|
||||
createSettingGroup(
|
||||
container,
|
||||
{
|
||||
heading: translate("settings.integrations.otherIntegrations.header"),
|
||||
description: translate("settings.integrations.otherIntegrations.description"),
|
||||
},
|
||||
() => {
|
||||
// No settings yet - placeholder for future integrations
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function renderICSSubscriptionsList(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Notice } from "obsidian";
|
||||
import TaskNotesPlugin from "../../main";
|
||||
import { TranslationKey } from "../../i18n";
|
||||
import { createSectionHeader, createHelpText, createToggleSetting } from "../components/settingHelpers";
|
||||
import { createSettingGroup, configureToggleSetting } from "../components/settingHelpers";
|
||||
import { createFieldManager, addFieldManagerStyles } from "../components/FieldManagerComponent";
|
||||
import { initializeFieldConfig } from "../../utils/fieldConfigDefaults";
|
||||
import type { TaskModalFieldsConfig, UserMappedField } from "../../types/settings";
|
||||
|
|
@ -32,48 +32,81 @@ export function renderModalFieldsTab(
|
|||
save(); // Save the initialized config
|
||||
}
|
||||
|
||||
// Header
|
||||
createSectionHeader(
|
||||
// Configuration Section
|
||||
createSettingGroup(
|
||||
container,
|
||||
"Task Modal Fields Configuration"
|
||||
);
|
||||
|
||||
createHelpText(
|
||||
container,
|
||||
"Configure which fields appear in task creation and edit modals. Drag fields to reorder them within each group."
|
||||
);
|
||||
|
||||
// Split layout toggle
|
||||
createToggleSetting(container, {
|
||||
name: "Split layout on wide screens",
|
||||
desc: "When enabled, the details editor appears in a right column on screens 900px or wider. When disabled, the modal uses a stacked layout.",
|
||||
getValue: () => plugin.settings.enableModalSplitLayout,
|
||||
setValue: (value) => {
|
||||
plugin.settings.enableModalSplitLayout = value;
|
||||
save();
|
||||
{
|
||||
heading: "Task Modal Fields Configuration",
|
||||
description: "Configure which fields appear in task creation and edit modals. Drag fields to reorder them within each group.",
|
||||
},
|
||||
});
|
||||
(group) => {
|
||||
// Split layout toggle
|
||||
group.addSetting((setting) =>
|
||||
configureToggleSetting(setting, {
|
||||
name: "Split layout on wide screens",
|
||||
desc: "When enabled, the details editor appears in a right column on screens 900px or wider. When disabled, the modal uses a stacked layout.",
|
||||
getValue: () => plugin.settings.enableModalSplitLayout,
|
||||
setValue: (value) => {
|
||||
plugin.settings.enableModalSplitLayout = value;
|
||||
save();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Sync button to update from user fields
|
||||
const syncContainer = container.createDiv({ cls: "modal-fields-sync" });
|
||||
const syncButton = syncContainer.createEl("button", {
|
||||
cls: "mod-cta",
|
||||
text: "Sync User Fields",
|
||||
});
|
||||
syncButton.onclick = () => {
|
||||
syncUserFieldsToConfig(plugin);
|
||||
save();
|
||||
new Notice("User fields synced to modal configuration");
|
||||
// Re-render the tab
|
||||
renderModalFieldsTab(container, plugin, save);
|
||||
};
|
||||
// Sync button
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName("Sync User Fields")
|
||||
.setDesc("Click to sync custom user fields from Task Properties settings into this configuration.")
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Sync User Fields")
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
syncUserFieldsToConfig(plugin);
|
||||
save();
|
||||
new Notice("User fields synced to modal configuration");
|
||||
// Re-render the tab
|
||||
renderModalFieldsTab(container, plugin, save);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
createHelpText(
|
||||
syncContainer,
|
||||
"Click to sync custom user fields from Task Properties settings into this configuration."
|
||||
// Reset button
|
||||
group.addSetting((setting) => {
|
||||
setting
|
||||
.setName("Reset to Defaults")
|
||||
.setDesc("Reset all field configurations to their default values. This will remove any custom configurations.")
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText("Reset to Defaults")
|
||||
.setWarning()
|
||||
.onClick(async () => {
|
||||
const confirmed = await showConfirmationModal(plugin.app, {
|
||||
title: "Reset Field Configuration",
|
||||
message: "Are you sure you want to reset field configuration to defaults? This will remove any custom field configurations.",
|
||||
confirmText: "Reset",
|
||||
cancelText: "Cancel",
|
||||
isDestructive: true,
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
plugin.settings.modalFieldsConfig = initializeFieldConfig(
|
||||
undefined,
|
||||
plugin.settings.userFields
|
||||
);
|
||||
save();
|
||||
new Notice("Field configuration reset to defaults");
|
||||
// Re-render the tab
|
||||
renderModalFieldsTab(container, plugin, save);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Field manager
|
||||
// Field manager (keep existing component for now - has its own internal tabs)
|
||||
const managerContainer = container.createDiv({ cls: "modal-fields-manager-container" });
|
||||
|
||||
// Double-check config exists before creating field manager
|
||||
|
|
@ -92,33 +125,6 @@ export function renderModalFieldsTab(
|
|||
},
|
||||
plugin.app
|
||||
);
|
||||
|
||||
// Reset button
|
||||
const resetContainer = container.createDiv({ cls: "modal-fields-reset" });
|
||||
const resetButton = resetContainer.createEl("button", {
|
||||
cls: "mod-warning",
|
||||
text: "Reset to Defaults",
|
||||
});
|
||||
resetButton.onclick = async () => {
|
||||
const confirmed = await showConfirmationModal(plugin.app, {
|
||||
title: "Reset Field Configuration",
|
||||
message: "Are you sure you want to reset field configuration to defaults? This will remove any custom field configurations.",
|
||||
confirmText: "Reset",
|
||||
cancelText: "Cancel",
|
||||
isDestructive: true,
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
plugin.settings.modalFieldsConfig = initializeFieldConfig(
|
||||
undefined,
|
||||
plugin.settings.userFields
|
||||
);
|
||||
save();
|
||||
new Notice("Field configuration reset to defaults");
|
||||
// Re-render the tab
|
||||
renderModalFieldsTab(container, plugin, save);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
55
src/types/obsidian-1.11.d.ts
vendored
Normal file
55
src/types/obsidian-1.11.d.ts
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Type declarations for Obsidian 1.11.0 APIs
|
||||
* These augment the existing obsidian module types
|
||||
*/
|
||||
|
||||
import "obsidian";
|
||||
|
||||
declare module "obsidian" {
|
||||
/**
|
||||
* A group of related settings with a heading
|
||||
* @since Obsidian 1.11.0
|
||||
*/
|
||||
export class SettingGroup {
|
||||
/**
|
||||
* Creates a new setting group
|
||||
* @param containerEl - The container element to add the group to
|
||||
*/
|
||||
constructor(containerEl: HTMLElement);
|
||||
|
||||
/**
|
||||
* Sets the heading text for the group
|
||||
* @param text - The heading text or a DocumentFragment
|
||||
*/
|
||||
setHeading(text: string | DocumentFragment): this;
|
||||
|
||||
/**
|
||||
* Adds a CSS class to the group element
|
||||
* @param cls - The class name to add
|
||||
*/
|
||||
addClass(cls: string): this;
|
||||
|
||||
/**
|
||||
* Adds a setting to the group
|
||||
* @param cb - Callback that receives the Setting to configure
|
||||
*/
|
||||
addSetting(cb: (setting: Setting) => void): this;
|
||||
}
|
||||
|
||||
interface Setting {
|
||||
/**
|
||||
* Adds a custom component to the setting
|
||||
* @since Obsidian 1.11.0
|
||||
* @param cb - Callback that receives the setting element and returns a component
|
||||
*/
|
||||
addComponent<T extends BaseComponent>(cb: (el: HTMLElement) => T): this;
|
||||
}
|
||||
|
||||
interface SettingTab {
|
||||
/**
|
||||
* The icon to display in the settings sidebar
|
||||
* @since Obsidian 1.11.0
|
||||
*/
|
||||
icon?: IconName;
|
||||
}
|
||||
}
|
||||
|
|
@ -1011,6 +1011,45 @@ export function setTooltip(element: HTMLElement, tooltip: string, options?: { pl
|
|||
element.classList.add('has-tooltip');
|
||||
}
|
||||
|
||||
// API version check utilities (added in Obsidian 1.11.0)
|
||||
export function requireApiVersion(version: string): boolean {
|
||||
// Mock implementation - returns true for testing purposes
|
||||
// This allows testing code that uses SettingGroup
|
||||
return true;
|
||||
}
|
||||
|
||||
// SettingGroup mock class (added in Obsidian 1.11.0)
|
||||
export class SettingGroup {
|
||||
private containerEl: HTMLElement;
|
||||
|
||||
constructor(containerEl: HTMLElement) {
|
||||
this.containerEl = containerEl;
|
||||
}
|
||||
|
||||
setHeading(text: string | DocumentFragment): this {
|
||||
const heading = document.createElement('div');
|
||||
heading.classList.add('setting-item-heading');
|
||||
if (typeof text === 'string') {
|
||||
heading.textContent = text;
|
||||
} else {
|
||||
heading.appendChild(text);
|
||||
}
|
||||
this.containerEl.appendChild(heading);
|
||||
return this;
|
||||
}
|
||||
|
||||
addClass(cls: string): this {
|
||||
this.containerEl.classList.add(cls);
|
||||
return this;
|
||||
}
|
||||
|
||||
addSetting(cb: (setting: Setting) => void): this {
|
||||
const setting = new Setting(this.containerEl);
|
||||
cb(setting);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Keymap mock class
|
||||
export class Keymap {
|
||||
pushScope = jest.fn();
|
||||
|
|
|
|||
Loading…
Reference in a new issue