feat: redesign settings UI, add Integrations tab, and support dynamic Zotero cleanup command

- Renamed TikZJax tab to Integrations and consolidated PDF Export, TikZJax, and Zotero cleanup under it
- Grouped PDF Export, TikZJax, and Zotero cleanup configurations inside modular gear-button modals using SettingsGroupModal
- Added an "Enable Zotero cleanup" toggle to dynamically register/unregister the `remove-duplicate-zotero-annotations` command
- Restructured headers, line separators, and vertical spacing in CSS to match the full-calendar design
- Updated translation keys in en.json and corrected casing across settings descriptions
- Verified that all lint checks, type check compilations, and Jest tests pass with zero errors
This commit is contained in:
JK 2026-06-05 14:41:49 +02:00
parent 7c08b356d4
commit 2cf270523a
9 changed files with 267 additions and 45 deletions

View file

@ -1,7 +1,7 @@
{
"name": "obsitexcore",
"version": "0.0.1",
"description": "An Obsidian.md plugin that provides a powerful indexing & referencing system for theorems & equations in your vault. Bring LaTeX-like workflow into Obsidian with theorem environments, automatic equation numbering, and more.",
"description": "Automatic equation numbering, Tex Diagrams rendering, and more for Obsidian.md",
"main": "main.js",
"packageManager": "pnpm@11.1.2+sha512.415a1cc25974731e75455c1468371be74c5aa5fb7621b50d4056d222451609f11412f23fd602e6169f1e060466641f798597e1be961a10688836a67b16569499",
"scripts": {

View file

@ -1,13 +1,12 @@
{
"settings": {
"title": "ObsiTeXcore settings",
"description": "Switch between focused setting groups to keep configuration lighter and easier to scan.",
"tab": {
"general": "General",
"generalDesc": "Configure equation numbering, referencing, autocomplete, and Zotero clean up settings.",
"pdf": "PDF Export",
"pdfDesc": "Configure layout, templates, and rendering limits for PDF exports.",
"tikz": "TikZJax",
"tikzDesc": "Configure TikZ rendering options and dark mode behavior.",
"generalDesc": "Configure equation numbering, referencing, and autocomplete settings.",
"integrations": "Integrations",
"integrationsDesc": "Configure integrations such as pdf exports, Tikzjax rendering, and Zotero clean up.",
"hotkeys": "Custom Notes",
"hotkeysDesc": "Configure keyboard shortcuts to quickly open specific notes.",
"changelog": "What's new",

View file

@ -90,15 +90,7 @@ export default class LatexReferencer extends Plugin {
this.addSettingTab(new MathSettingTab(this.app, this));
// Commands
this.addCommand({
id: 'remove-duplicate-zotero-annotations',
name: 'Remove duplicate Zotero annotations',
editorCallback: (editor: Editor, ctx: MarkdownView | MarkdownFileInfo) => {
if (ctx instanceof MarkdownView) {
void processZoteroCleanup(this, ctx);
}
}
});
this.registerZoteroCommand();
this.addCommand({
id: 'fix-callout-equations',
@ -348,6 +340,34 @@ export default class LatexReferencer extends Plugin {
this.register(uninstaller);
}
registerZoteroCommand() {
const commandId = 'remove-duplicate-zotero-annotations';
const fullCommandId = `${this.manifest.id}:${commandId}`;
const appCommands = this.app.commands;
// 1. Unregister if it exists
if (appCommands && typeof appCommands.removeCommand === 'function') {
try {
appCommands.removeCommand(fullCommandId);
} catch {
// Safe to ignore
}
}
// 2. Register if enabled
if (this.settings.enableZoteroCleanup) {
this.addCommand({
id: commandId,
name: 'Remove duplicate Zotero annotations',
editorCallback: (editor: Editor, ctx: MarkdownView | MarkdownFileInfo) => {
if (ctx instanceof MarkdownView) {
void processZoteroCleanup(this, ctx);
}
}
});
}
}
async generateToc(root: TFolder | TFile) {
const tocPath = root.path === '/' || root.path === '.' ? '_TOC_.md' : `${root.path}/_TOC_.md`;
let content = `---\ntoc: true\ntitle: ${root.name || 'Root'}\n---\n`;

View file

@ -68,6 +68,7 @@ export interface PluginSettings {
concurrency: string;
// Zotero Cleanup
enableZoteroCleanup: boolean;
zoteroCleanDirectories: string;
// Custom Note Hotkeys
@ -146,6 +147,7 @@ export const DEFAULT_SETTINGS: Required<PluginSettings> = {
concurrency: '5',
// Zotero Cleanup
enableZoteroCleanup: false,
zoteroCleanDirectories: '',
// Custom Note Hotkeys

View file

@ -6,8 +6,9 @@ import { setCssProps } from 'utils/obsidian';
import { createDescWithDocs } from './docsLinks';
import { changelogData } from './changelogData';
import { t } from '../i18n/t';
import { SettingsGroupModal } from '../ui/modals/SettingsGroupModal';
type SettingsTabId = 'general' | 'pdf' | 'tikz' | 'hotkeys' | 'changelog';
type SettingsTabId = 'general' | 'integrations' | 'hotkeys' | 'changelog';
interface TabCategory {
id: SettingsTabId;
@ -22,14 +23,9 @@ const TABS: TabCategory[] = [
descKey: 'settings.tab.generalDesc'
},
{
id: 'pdf',
labelKey: 'settings.tab.pdf',
descKey: 'settings.tab.pdfDesc'
},
{
id: 'tikz',
labelKey: 'settings.tab.tikz',
descKey: 'settings.tab.tikzDesc'
id: 'integrations',
labelKey: 'settings.tab.integrations',
descKey: 'settings.tab.integrationsDesc'
},
{
id: 'hotkeys',
@ -68,7 +64,10 @@ export class MathSettingTab extends PluginSettingTab {
// Header
const headerEl = shellEl.createDiv('obsitexcore-settings-header');
new Setting(headerEl).setName(t('settings.title')).setHeading();
headerEl.createEl('p', {
text: t('settings.description'),
cls: 'obsitexcore-settings-header-desc'
});
// Tabs & Search row
const tabsRowEl = shellEl.createDiv('obsitexcore-settings-tabs-row');
@ -224,11 +223,8 @@ export class MathSettingTab extends PluginSettingTab {
case 'general':
this.renderGeneral(panelEl);
break;
case 'pdf':
this.renderPdf(panelEl);
break;
case 'tikz':
this.renderTikz(panelEl);
case 'integrations':
this.renderIntegrations(panelEl);
break;
case 'hotkeys':
this.renderHotkeys(panelEl);
@ -240,6 +236,46 @@ export class MathSettingTab extends PluginSettingTab {
}
private renderGeneral(containerEl: HTMLElement): void {
const isSearching = !!this.searchQuery.trim();
if (isSearching) {
this.renderDetailedGeneralSettings(containerEl);
this.renderDetailedAutocompleteSettings(containerEl);
return;
}
new Setting(containerEl)
.setName('Equation numbering & referencing')
.setDesc(
'Configure automatic numbering prefixes, suffixes, initial counts, styles, and link formatting.'
)
.addExtraButton(button => {
button
.setIcon('gear')
.setTooltip('Configure options')
.onClick(() => {
new SettingsGroupModal(this.app, 'Equation Numbering & Referencing', bodyEl =>
this.renderDetailedGeneralSettings(bodyEl)
).open();
});
});
new Setting(containerEl)
.setName('Autocomplete & search')
.setDesc('Configure triggers, autocompletion options, and rendering behaviors.')
.addExtraButton(button => {
button
.setIcon('gear')
.setTooltip('Configure options')
.onClick(() => {
new SettingsGroupModal(this.app, 'Autocomplete & Search', bodyEl =>
this.renderDetailedAutocompleteSettings(bodyEl)
).open();
});
});
}
private renderDetailedGeneralSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Equation numbering & referencing').setHeading();
new Setting(containerEl)
@ -316,7 +352,9 @@ export class MathSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
}
private renderDetailedAutocompleteSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Autocomplete & search').setHeading();
new Setting(containerEl)
@ -346,9 +384,22 @@ export class MathSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
}
private renderDetailedZoteroSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Zotero cleanup').setHeading();
new Setting(containerEl)
.setName('Enable Zotero cleanup')
.setDesc('Enable the command to remove duplicate Zotero annotations in your active notes.')
.addToggle(toggle =>
toggle.setValue(this.plugin.settings.enableZoteroCleanup).onChange(async value => {
this.plugin.settings.enableZoteroCleanup = value;
await this.plugin.saveSettings();
this.plugin.registerZoteroCommand();
})
);
new Setting(containerEl)
.setName('Directories to search')
.setDesc(
@ -363,7 +414,7 @@ export class MathSettingTab extends PluginSettingTab {
});
}
private renderPdf(containerEl: HTMLElement): void {
private renderDetailedPdfSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Pdf export').setHeading();
new Setting(containerEl)
@ -518,7 +569,7 @@ export class MathSettingTab extends PluginSettingTab {
});
}
private renderTikz(containerEl: HTMLElement): void {
private renderDetailedTikzSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('TikZJax rendering').setHeading();
new Setting(containerEl)
@ -546,6 +597,59 @@ export class MathSettingTab extends PluginSettingTab {
);
}
private renderIntegrations(containerEl: HTMLElement): void {
const isSearching = !!this.searchQuery.trim();
if (isSearching) {
this.renderDetailedPdfSettings(containerEl);
this.renderDetailedTikzSettings(containerEl);
this.renderDetailedZoteroSettings(containerEl);
return;
}
new Setting(containerEl)
.setName('Pdf export options')
.setDesc('Configure layout, templates, and rendering limits for pdf exports.')
.addExtraButton(button => {
button
.setIcon('gear')
.setTooltip('Configure options')
.onClick(() => {
new SettingsGroupModal(this.app, 'PDF Export Options', bodyEl =>
this.renderDetailedPdfSettings(bodyEl)
).open();
});
});
new Setting(containerEl)
.setName('Tikzjax rendering')
.setDesc('Configure TikZ rendering options and dark mode behavior.')
.addExtraButton(button => {
button
.setIcon('gear')
.setTooltip('Configure options')
.onClick(() => {
new SettingsGroupModal(this.app, 'TikZJax Rendering Options', bodyEl =>
this.renderDetailedTikzSettings(bodyEl)
).open();
});
});
new Setting(containerEl)
.setName('Zotero cleanup')
.setDesc('Configure Zotero search directories and automatic annotations cleanup.')
.addExtraButton(button => {
button
.setIcon('gear')
.setTooltip('Configure options')
.onClick(() => {
new SettingsGroupModal(this.app, 'Zotero Cleanup Options', bodyEl =>
this.renderDetailedZoteroSettings(bodyEl)
).open();
});
});
}
private renderHotkeys(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Custom note hotkeys').setHeading();
containerEl.createEl('p', {
@ -779,7 +883,9 @@ export class MathSettingTab extends PluginSettingTab {
emoji = '🛠️';
}
const itemEl = verContainer.createDiv(`full-calendar-change-item full-calendar-change-type-${change.type}`);
const itemEl = verContainer.createDiv(
`full-calendar-change-item full-calendar-change-type-${change.type}`
);
itemEl.createDiv({
cls: 'full-calendar-change-icon',
text: emoji

View file

@ -360,13 +360,19 @@
.obsitexcore-settings-shell {
display: flex;
flex-direction: column;
gap: var(--size-4-3);
margin-top: 15px;
gap: var(--size-4-5);
}
.obsitexcore-settings-header {
border-bottom: 1px solid var(--background-modifier-border);
padding-bottom: 10px;
display: flex;
flex-direction: column;
gap: var(--size-4-2);
}
.obsitexcore-settings-header-desc {
margin: 0;
color: var(--text-muted);
font-size: var(--font-ui-medium);
}
.obsitexcore-settings-tabs-row {
@ -441,11 +447,19 @@
}
.obsitexcore-settings-category-intro {
font-size: 0.95em;
color: var(--text-muted);
display: flex;
flex-direction: column;
gap: var(--size-4-1);
padding-bottom: var(--size-4-2);
border-bottom: 1px solid var(--background-modifier-border);
margin-bottom: 15px;
}
.obsitexcore-settings-category-intro p {
margin: 0;
color: var(--text-muted);
}
.obsitexcore-settings-panel {
display: flex;
flex-direction: column;
@ -537,4 +551,43 @@
.full-calendar-change-description {
font-size: var(--font-ui-small);
color: var(--text-muted);
}
/* ====== Settings Modals (Exact styles from full-calendar) ====== */
.ofc-settings-modal-wide {
width: min(900px, 88vw);
max-width: 900px;
max-height: min(860px, 88vh);
}
.ofc-settings-modal-wide .modal-content {
padding: 0;
overflow: hidden;
}
.ofc-settings-modal-shell {
display: flex;
flex-direction: column;
max-height: min(860px, 88vh);
min-height: min(620px, 82vh);
}
.ofc-settings-modal-body {
flex: 1 1 auto;
overflow-y: auto;
padding: 18px 22px;
}
.ofc-settings-modal-footer {
align-items: center;
background-color: var(--background-primary);
border-top: 1px solid var(--background-modifier-border);
display: flex;
flex: 0 0 auto;
gap: 10px;
justify-content: flex-end;
padding: 12px 22px;
position: sticky;
bottom: 0;
z-index: 1;
}

View file

@ -0,0 +1,44 @@
import { App, Modal, ButtonComponent } from 'obsidian';
export class SettingsGroupModal extends Modal {
constructor(
app: App,
private modalTitle: string,
private onRender: (bodyEl: HTMLElement) => void,
private onCloseCallback?: () => void
) {
super(app);
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
this.modalEl.addClass('ofc-settings-modal-wide');
const shellEl = contentEl.createDiv('ofc-settings-modal-shell');
// Header
const headerEl = shellEl.createDiv('modal-header');
headerEl.createEl('h2', { text: this.modalTitle });
// Body
const bodyEl = shellEl.createDiv('ofc-settings-modal-body');
this.onRender(bodyEl);
// Footer
const footerEl = shellEl.createDiv('ofc-settings-modal-footer');
new ButtonComponent(footerEl)
.setButtonText('Close')
.setCta()
.onClick(() => {
this.close();
});
}
onClose() {
this.contentEl.empty();
if (this.onCloseCallback) {
this.onCloseCallback();
}
}
}

View file

@ -66,7 +66,9 @@ export class WhatsNewModal extends Modal {
emoji = '🛠️';
}
const itemEl = changeList.createDiv(`full-calendar-change-item full-calendar-change-type-${change.type}`);
const itemEl = changeList.createDiv(
`full-calendar-change-item full-calendar-change-type-${change.type}`
);
itemEl.createDiv({
cls: 'full-calendar-change-icon',
text: emoji

View file

@ -1,7 +1,3 @@
{
"0.3.0": "1.3.5",
"0.3.1": "1.3.5",
"0.3.2": "1.3.5",
"2.2.3": "1.3.5",
"2.2.4": "1.3.5"
"0.0.1": "1.3.5"
}