refactor(settings): group options by user workflow

Why:
- The settings tab mixes unrelated options under broad or service-specific headings.

What:
- Reorder the catalog into six contiguous user-facing groups.
- Lock group membership and display order with a focused test.
This commit is contained in:
Kodai Nakamura 2026-07-17 22:30:28 +09:00
parent c936b4384c
commit 059f15622f
2 changed files with 198 additions and 118 deletions

View file

@ -16,17 +16,97 @@ describe("SETTINGS_CATALOG", () => {
expect(new Set(catalogKeys).size).toBe(catalogKeys.length)
})
it("groups settings by user workflow in display order", () => {
const expectedGroups = [
{
group: "Formatting Workflow",
keys: [
"formatOnSave",
"formatDelayMs",
"runPrettierAfterFormatting",
"runLinterAfterFormatting",
],
},
{
group: "Link Behavior",
keys: [
"respectNewFileFolderPath",
"proximityBasedLinking",
"includeAliases",
"removeAliasInDirs",
"ignoreCase",
"matchSentenceCase",
],
},
{
group: "Exclusions",
keys: [
"preventSelfLinking",
"ignoreDateFormats",
"ignoreHeadings",
"ignoreMarkdownTables",
"excludeDirsFromAutoLinking",
],
},
{
group: "URL Formatting",
keys: [
"formatGitHubURLs",
"githubEnterpriseURLs",
"formatJiraURLs",
"jiraURLs",
"formatLinearURLs",
"replaceUrlWithTitle",
"replaceUrlWithTitleIgnoreDomains",
],
},
{
group: "AI Link Enhancement (Beta)",
keys: [
"aiEnabled",
"aiEndpoint",
"aiModel",
"aiMaxContext",
],
},
{
group: "Diagnostics",
keys: ["showNotice", "debug"],
},
]
const actualGroups = SETTINGS_CATALOG.reduce<Array<{
group: string
keys: Array<keyof typeof DEFAULT_SETTINGS>
}>>((groups, entry) => {
const currentGroup = groups[groups.length - 1]
if (currentGroup?.group === entry.group) {
currentGroup.keys.push(entry.key)
return groups
}
groups.push({
group: entry.group,
keys: [entry.key],
})
return groups
}, [])
expect(actualGroups).toEqual(expectedGroups)
})
it("marks only the URL textarea settings for explicit sizing", () => {
const sizedTextareaKeys = SETTINGS_CATALOG
.filter(entry => entry.control === "textarea")
.filter(entry => (entry as { rows?: number }).rows === 4)
.filter(entry => (entry as { cols?: number }).cols === 50)
.map(entry => entry.key)
.sort()
expect(sizedTextareaKeys).toEqual([
"replaceUrlWithTitleIgnoreDomains",
"githubEnterpriseURLs",
"jiraURLs",
"replaceUrlWithTitleIgnoreDomains",
])
})

View file

@ -81,34 +81,53 @@ export const DEFAULT_SETTINGS: AutomaticLinkerSettings = {
export const SETTINGS_CATALOG = [
{
key: "formatOnSave",
group: "Formatting",
group: "Formatting Workflow",
name: "Format on save",
description:
"When enabled, the file will be automatically formatted (links replaced) when saving.",
control: "toggle",
refreshesIndex: false,
},
{
key: "formatDelayMs",
group: "Formatting Workflow",
name: "Format delay (ms)",
description:
"Delay in milliseconds before formatting. Increase this value if the linter/prettier runs before the file is fully saved.",
control: "text",
placeholder: "e.g. 100",
refreshesIndex: false,
},
{
key: "runPrettierAfterFormatting",
group: "Formatting Workflow",
name: "Run Prettier after formatting",
description:
"When enabled, Prettier will be executed after Automatic Linker formatting. This requires prettier-format plugin to be installed. https://github.com/dylanarmstrong/obsidian-prettier-plugin",
control: "toggle",
refreshesIndex: false,
},
{
key: "runLinterAfterFormatting",
group: "Formatting Workflow",
name: "Run Obsidian Linter after formatting",
description:
"When enabled, Obsidian Linter will be executed after Automatic Linker formatting. This requires the Obsidian Linter plugin to be installed.",
control: "toggle",
refreshesIndex: false,
},
{
key: "respectNewFileFolderPath",
group: "Formatting",
group: "Link Behavior",
name: "Respect 'Folder to create new notes in' setting",
description:
"When enabled, the plugin will use Obsidian's 'Folder to create new notes in' setting as the base directory for omitting folder prefixes in links.",
control: "toggle",
refreshesIndex: true,
},
{
key: "includeAliases",
group: "Formatting",
name: "Include aliases",
description:
"When enabled, aliases will be included when processing links. Note: A restart is required for changes to take effect.",
control: "toggle",
refreshesIndex: true,
},
{
key: "proximityBasedLinking",
group: "Formatting",
group: "Link Behavior",
name: "Proximity-based linking",
description:
"When enabled, the plugin will automatically resolve namespaces for shorthand links. If multiple candidates share the same shorthand, the candidate with the most common path segments relative to the current file will be selected.",
@ -116,73 +135,17 @@ export const SETTINGS_CATALOG = [
refreshesIndex: true,
},
{
key: "ignoreDateFormats",
group: "Formatting",
name: "Ignore date formats",
key: "includeAliases",
group: "Link Behavior",
name: "Include aliases",
description:
"When enabled, links that match date formats (e.g. 2025-02-10) will be ignored. This helps maintain compatibility with Obsidian Tasks.",
"When enabled, aliases will be included when processing links. Note: A restart is required for changes to take effect.",
control: "toggle",
refreshesIndex: true,
},
{
key: "ignoreHeadings",
group: "Formatting",
name: "Ignore headings",
description:
"When enabled, headings (lines starting with #) will not have links added to them.",
control: "toggle",
refreshesIndex: false,
},
{
key: "ignoreMarkdownTables",
group: "Formatting",
name: "Ignore Markdown tables",
description:
"When enabled, Markdown table rows will not have links added to them.",
control: "toggle",
refreshesIndex: false,
},
{
key: "ignoreCase",
group: "Formatting",
name: "Ignore case",
description:
"When enabled, link matching will be case-insensitive. The original case of the text will be preserved in the link.",
control: "toggle",
refreshesIndex: true,
},
{
key: "matchSentenceCase",
group: "Formatting",
name: "Match sentence case",
description:
"When 'Ignore case' is OFF, match text that is capitalized at the start of a sentence. For example, 'My name' at a sentence start will match the file 'my name'. This setting is ignored when 'Ignore case' is ON.",
control: "toggle",
refreshesIndex: false,
},
{
key: "preventSelfLinking",
group: "Formatting",
name: "Prevent self-linking",
description:
"When enabled, text will not be linked to its own file. For example, the word 'VPN' inside VPN.md will not be converted to a link.",
control: "toggle",
refreshesIndex: true,
},
{
key: "excludeDirsFromAutoLinking",
group: "Formatting",
name: "Exclude directories from automatic linking",
description:
"Directories to be excluded from automatic linking, one per line (e.g. 'Templates')",
control: "textarea",
placeholder: "Templates\nArchive",
multiline: true,
refreshesIndex: true,
},
{
key: "removeAliasInDirs",
group: "Formatting",
group: "Link Behavior",
name: "Remove aliases in directories",
description:
"Directories where link aliases should be removed, one per line (e.g. 'dir' will convert [[dir/xxx|yyy]] to [[dir/xxx]]). This affects both auto-generated aliases and frontmatter aliases.",
@ -192,58 +155,73 @@ export const SETTINGS_CATALOG = [
refreshesIndex: true,
},
{
key: "runLinterAfterFormatting",
group: "Integrations",
name: "Run Obsidian Linter after formatting",
key: "ignoreCase",
group: "Link Behavior",
name: "Ignore case",
description:
"When enabled, Obsidian Linter will be executed after Automatic Linker formatting. This requires the Obsidian Linter plugin to be installed.",
"When enabled, link matching will be case-insensitive. The original case of the text will be preserved in the link.",
control: "toggle",
refreshesIndex: true,
},
{
key: "matchSentenceCase",
group: "Link Behavior",
name: "Match sentence case",
description:
"When 'Ignore case' is OFF, match text that is capitalized at the start of a sentence. For example, 'My name' at a sentence start will match the file 'my name'. This setting is ignored when 'Ignore case' is ON.",
control: "toggle",
refreshesIndex: false,
},
{
key: "runPrettierAfterFormatting",
group: "Integrations",
name: "Run Prettier after formatting",
key: "preventSelfLinking",
group: "Exclusions",
name: "Prevent self-linking",
description:
"When enabled, Prettier will be executed after Automatic Linker formatting. This requires prettier-format plugin to be installed. https://github.com/dylanarmstrong/obsidian-prettier-plugin",
"When enabled, text will not be linked to its own file. For example, the word 'VPN' inside VPN.md will not be converted to a link.",
control: "toggle",
refreshesIndex: true,
},
{
key: "ignoreDateFormats",
group: "Exclusions",
name: "Ignore date formats",
description:
"When enabled, links that match date formats (e.g. 2025-02-10) will be ignored. This helps maintain compatibility with Obsidian Tasks.",
control: "toggle",
refreshesIndex: true,
},
{
key: "ignoreHeadings",
group: "Exclusions",
name: "Ignore headings",
description:
"When enabled, headings (lines starting with #) will not have links added to them.",
control: "toggle",
refreshesIndex: false,
},
{
key: "formatDelayMs",
group: "Integrations",
name: "Format delay (ms)",
key: "ignoreMarkdownTables",
group: "Exclusions",
name: "Ignore Markdown tables",
description:
"Delay in milliseconds before formatting. Increase this value if the linter/prettier runs before the file is fully saved.",
control: "text",
placeholder: "e.g. 100",
refreshesIndex: false,
},
{
key: "replaceUrlWithTitle",
group: "URL Replacement with Title",
name: "Replace URL with title",
description:
"When enabled, raw URLs will be replaced with [Page Title](URL). Requires fetching the URL content.",
"When enabled, Markdown table rows will not have links added to them.",
control: "toggle",
refreshesIndex: false,
},
{
key: "replaceUrlWithTitleIgnoreDomains",
group: "URL Replacement with Title",
name: "Ignore domains",
key: "excludeDirsFromAutoLinking",
group: "Exclusions",
name: "Exclude directories from automatic linking",
description:
"Ignore domains for replacing URLs with titles, one per line (e.g. x.com)",
"Directories to be excluded from automatic linking, one per line (e.g. 'Templates')",
control: "textarea",
placeholder: "",
placeholder: "Templates\nArchive",
multiline: true,
rows: 4,
cols: 50,
refreshesIndex: false,
refreshesIndex: true,
},
{
key: "formatGitHubURLs",
group: "URL Formatting for GitHub",
group: "URL Formatting",
name: "Format GitHub URLs on save",
description:
"When enabled, GitHub URLs will be formatted when saving the file.",
@ -252,7 +230,7 @@ export const SETTINGS_CATALOG = [
},
{
key: "githubEnterpriseURLs",
group: "URL Formatting for GitHub",
group: "URL Formatting",
name: "GitHub Enterprise URLs",
description:
"Add your GitHub Enterprise URLs, one per line (e.g. github.enterprise.com)",
@ -265,7 +243,7 @@ export const SETTINGS_CATALOG = [
},
{
key: "formatJiraURLs",
group: "URL Formatting for Jira",
group: "URL Formatting",
name: "Format JIRA URLs on save",
description:
"When enabled, JIRA URLs will be formatted when saving the file.",
@ -274,7 +252,7 @@ export const SETTINGS_CATALOG = [
},
{
key: "jiraURLs",
group: "URL Formatting for Jira",
group: "URL Formatting",
name: "JIRA URLs",
description:
"Add your JIRA URLs, one per line (e.g. jira.enterprise.com)",
@ -287,7 +265,7 @@ export const SETTINGS_CATALOG = [
},
{
key: "formatLinearURLs",
group: "URL Formatting for Linear",
group: "URL Formatting",
name: "Format Linear URLs on save",
description:
"When enabled, Linear URLs will be formatted when saving the file.",
@ -295,20 +273,25 @@ export const SETTINGS_CATALOG = [
refreshesIndex: false,
},
{
key: "showNotice",
group: "Debug",
name: "Show load notice",
description: "Display a notice when markdown files are loaded.",
key: "replaceUrlWithTitle",
group: "URL Formatting",
name: "Replace URL with title",
description:
"When enabled, raw URLs will be replaced with [Page Title](URL). Requires fetching the URL content.",
control: "toggle",
refreshesIndex: false,
},
{
key: "debug",
group: "Debug",
name: "Debug mode",
key: "replaceUrlWithTitleIgnoreDomains",
group: "URL Formatting",
name: "Ignore domains",
description:
"When enabled, debug information will be logged to the console.",
control: "toggle",
"Ignore domains for replacing URLs with titles, one per line (e.g. x.com)",
control: "textarea",
placeholder: "",
multiline: true,
rows: 4,
cols: 50,
refreshesIndex: false,
},
{
@ -349,6 +332,23 @@ export const SETTINGS_CATALOG = [
placeholder: "500",
refreshesIndex: false,
},
{
key: "showNotice",
group: "Diagnostics",
name: "Show load notice",
description: "Display a notice when markdown files are loaded.",
control: "toggle",
refreshesIndex: false,
},
{
key: "debug",
group: "Diagnostics",
name: "Debug mode",
description:
"When enabled, debug information will be logged to the console.",
control: "toggle",
refreshesIndex: false,
},
] as const satisfies readonly SettingCatalogEntry[]
export const settingRefreshesIndex = (