mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
fix(settings): use native CTA buttons
中文: 将设置页操作按钮改为 Obsidian 原生 ButtonComponent CTA,移除自定义按钮样式覆盖,并更新测试与构建产物。 English: Use native Obsidian ButtonComponent CTA buttons for settings actions, remove custom button style overrides, and update tests and build output.
This commit is contained in:
parent
c9e80d4034
commit
dfccdcbfc5
4 changed files with 60 additions and 80 deletions
32
main.js
32
main.js
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,7 @@ import { DEFAULT_SETTINGS, normalizePluginSettings } from "@/application/config/
|
|||
const QA_GROUP_USER_MODEL_NAME = "问答题(多级列表)";
|
||||
|
||||
const {
|
||||
FakeButtonComponent,
|
||||
FakeElement,
|
||||
FakePluginSettingTab,
|
||||
FakeSetting,
|
||||
|
|
@ -175,9 +176,13 @@ const {
|
|||
|
||||
class HoistedFakeButtonComponent {
|
||||
public text = "";
|
||||
public readonly buttonEl = new HoistedFakeElement(new HoistedFakeContainerEl(), "button", null);
|
||||
public readonly buttonEl: HoistedFakeElement;
|
||||
private onClickHandler?: () => void | Promise<void>;
|
||||
|
||||
constructor(containerEl?: HoistedFakeElement) {
|
||||
this.buttonEl = containerEl?.createEl("button") ?? new HoistedFakeElement(new HoistedFakeContainerEl(), "button", null);
|
||||
}
|
||||
|
||||
setButtonText(text: string): this {
|
||||
this.text = text;
|
||||
this.buttonEl.text = text;
|
||||
|
|
@ -185,8 +190,19 @@ const {
|
|||
return this;
|
||||
}
|
||||
|
||||
setCta(): this {
|
||||
this.buttonEl.classList.add("mod-cta");
|
||||
return this;
|
||||
}
|
||||
|
||||
setDisabled(disabled: boolean): this {
|
||||
this.buttonEl.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
onClick(callback: () => void | Promise<void>): this {
|
||||
this.onClickHandler = callback;
|
||||
this.buttonEl.addEventListener("click", callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -373,6 +389,7 @@ const {
|
|||
});
|
||||
|
||||
vi.mock("obsidian", () => ({
|
||||
ButtonComponent: FakeButtonComponent,
|
||||
getLanguage: getLanguageMock,
|
||||
PluginSettingTab: FakePluginSettingTab,
|
||||
Setting: FakeSetting,
|
||||
|
|
@ -837,7 +854,7 @@ describe("PluginSettingTab", () => {
|
|||
expect(plugin.updateCalls).toContainEqual({ convertHighlightsToCloze: false });
|
||||
});
|
||||
|
||||
it("applies the themed class only to settings action buttons", async () => {
|
||||
it("uses native Obsidian CTA buttons only for settings actions", async () => {
|
||||
const plugin = new FakePlugin();
|
||||
plugin.settings = normalizePluginSettings({
|
||||
...plugin.settings,
|
||||
|
|
@ -850,14 +867,14 @@ describe("PluginSettingTab", () => {
|
|||
await expandCard(tab, "scope");
|
||||
await flushPromises();
|
||||
|
||||
expect(queryByDataset(tab.containerEl, "cardTypesRefresh", "true").classList.contains("ahs-theme-action-button")).toBe(true);
|
||||
expect(queryByDataset(tab.containerEl, "scopeRefreshFolders", "true").classList.contains("ahs-theme-action-button")).toBe(true);
|
||||
expect(queryByDataset(tab.containerEl, "cardTypesRefresh", "true").classList.contains("mod-cta")).toBe(true);
|
||||
expect(queryByDataset(tab.containerEl, "scopeRefreshFolders", "true").classList.contains("mod-cta")).toBe(true);
|
||||
|
||||
const cardHeaderButton = queryByDataset(tab.containerEl, "settingsCardToggle", "card-types");
|
||||
expect(cardHeaderButton.classList.contains("ahs-theme-action-button")).toBe(false);
|
||||
expect(cardHeaderButton.classList.contains("mod-cta")).toBe(false);
|
||||
|
||||
const folderToggleButton = queryByDataset(tab.containerEl, "folderToggle", "notes");
|
||||
expect(folderToggleButton.classList.contains("ahs-theme-action-button")).toBe(false);
|
||||
expect(folderToggleButton.classList.contains("mod-cta")).toBe(false);
|
||||
|
||||
await expandCard(tab, "deck");
|
||||
const fileDeckSetting = findSetting(tab.containerEl, "开启文件级自定义牌组");
|
||||
|
|
@ -865,7 +882,7 @@ describe("PluginSettingTab", () => {
|
|||
|
||||
const insertTemplateSetting = findSetting(tab.containerEl, "向当前文件插入牌组模板");
|
||||
const insertTemplateButton = getButton(insertTemplateSetting);
|
||||
expect(insertTemplateButton.buttonEl.classList.contains("ahs-theme-action-button")).toBe(true);
|
||||
expect(insertTemplateButton.buttonEl.classList.contains("mod-cta")).toBe(true);
|
||||
|
||||
await insertTemplateButton.click();
|
||||
expect(plugin.insertDeckTemplateCalls).toBe(1);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { PluginSettingTab, Setting } from "obsidian";
|
||||
import type { ButtonComponent } from "obsidian";
|
||||
import { ButtonComponent, PluginSettingTab, Setting } from "obsidian";
|
||||
|
||||
import {
|
||||
DEFAULT_OBSIDIAN_BACKLINK_LABEL,
|
||||
|
|
@ -51,7 +50,6 @@ const SETTINGS_PAGE_HEADER_MASK_TOP = "-128px";
|
|||
const SETTINGS_PAGE_HEADER_MASK_SIDE = "-24px";
|
||||
const DECK_HELPER_TEXT_INDENT = "32px";
|
||||
const SETTINGS_ROOT_CLASS = "anki-heading-sync-settings";
|
||||
const THEME_ACTION_BUTTON_CLASS = "ahs-theme-action-button";
|
||||
|
||||
type SettingsCardId = (typeof SETTINGS_CARD_ORDER)[number];
|
||||
type NoteTypeCacheCheckStatus = "idle" | "checking" | "same" | "changed" | "failed";
|
||||
|
|
@ -167,12 +165,8 @@ export class AnkiHeadingSyncSettingTab extends PluginSettingTab {
|
|||
containerEl.scrollTop = previousScrollTop;
|
||||
}
|
||||
|
||||
private markThemeButton(buttonEl: HTMLButtonElement): void {
|
||||
buttonEl.classList.add(THEME_ACTION_BUTTON_CLASS);
|
||||
}
|
||||
|
||||
private markThemeButtonComponent(button: ButtonComponent): void {
|
||||
this.markThemeButton(button.buttonEl);
|
||||
private markActionButton(button: ButtonComponent): void {
|
||||
button.setCta();
|
||||
}
|
||||
|
||||
private initializeCards(containerEl: HTMLElement): void {
|
||||
|
|
@ -305,16 +299,14 @@ export class AnkiHeadingSyncSettingTab extends PluginSettingTab {
|
|||
actionRow.style.flexDirection = "column";
|
||||
actionRow.style.alignItems = "flex-start";
|
||||
actionRow.style.gap = "8px";
|
||||
const loadButton = actionRow.createEl("button", {
|
||||
text: this.ankiConfigLoading
|
||||
const loadButton = new ButtonComponent(actionRow)
|
||||
.setButtonText(this.ankiConfigLoading
|
||||
? t("settings.cards.cardTypes.loadAnki.loading")
|
||||
: t("settings.cards.cardTypes.loadAnki.button"),
|
||||
}) as HTMLButtonElement;
|
||||
this.markThemeButton(loadButton);
|
||||
loadButton.type = "button";
|
||||
loadButton.dataset.cardTypesRefresh = "true";
|
||||
loadButton.disabled = this.ankiConfigLoading;
|
||||
loadButton.addEventListener("click", () => this.loadAnkiCardTypeConfig());
|
||||
: t("settings.cards.cardTypes.loadAnki.button"))
|
||||
.setCta()
|
||||
.setDisabled(this.ankiConfigLoading)
|
||||
.onClick(() => this.loadAnkiCardTypeConfig());
|
||||
loadButton.buttonEl.dataset.cardTypesRefresh = "true";
|
||||
actionRow.createEl("span", {
|
||||
text: `${t("settings.cards.cardTypes.statusLabel")}${renderUserFacingMessage(this.getCardTypeStatusMessage())}`,
|
||||
});
|
||||
|
|
@ -691,14 +683,14 @@ export class AnkiHeadingSyncSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
const refreshRow = containerEl.createDiv();
|
||||
const refreshButton = refreshRow.createEl("button", { text: t("settings.cards.scope.refreshFolders") }) as HTMLButtonElement;
|
||||
this.markThemeButton(refreshButton);
|
||||
refreshButton.type = "button";
|
||||
refreshButton.dataset.scopeRefreshFolders = "true";
|
||||
refreshButton.disabled = Boolean(this.folderTreeLoadPromise);
|
||||
refreshButton.addEventListener("click", () => {
|
||||
void this.refreshFolderTree();
|
||||
});
|
||||
const refreshButton = new ButtonComponent(refreshRow)
|
||||
.setButtonText(t("settings.cards.scope.refreshFolders"))
|
||||
.setCta()
|
||||
.setDisabled(Boolean(this.folderTreeLoadPromise))
|
||||
.onClick(() => {
|
||||
void this.refreshFolderTree();
|
||||
});
|
||||
refreshButton.buttonEl.dataset.scopeRefreshFolders = "true";
|
||||
|
||||
this.ensureFolderTreeLoaded();
|
||||
containerEl.createEl("p", { text: getScopeModeTreeDescription(this.plugin.settings.scopeMode) });
|
||||
|
|
@ -809,7 +801,7 @@ export class AnkiHeadingSyncSettingTab extends PluginSettingTab {
|
|||
.setName(t("settings.deck.insertTemplate.name"))
|
||||
.setDesc(t("settings.deck.insertTemplate.desc"))
|
||||
.addButton((button) => {
|
||||
this.markThemeButtonComponent(button);
|
||||
this.markActionButton(button);
|
||||
button.setButtonText(t("settings.deck.insertTemplate.button")).onClick(() => {
|
||||
void this.plugin.insertDeckTemplateToCurrentFile();
|
||||
});
|
||||
|
|
|
|||
31
styles.css
31
styles.css
|
|
@ -1,30 +1 @@
|
|||
.anki-heading-sync-settings .ahs-theme-action-button {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: 1px solid var(--interactive-accent-hover);
|
||||
border-radius: 8px;
|
||||
box-shadow: none;
|
||||
transition:
|
||||
background-color 140ms ease,
|
||||
border-color 140ms ease,
|
||||
opacity 140ms ease,
|
||||
transform 140ms ease;
|
||||
}
|
||||
|
||||
.anki-heading-sync-settings .ahs-theme-action-button:hover:not(:disabled),
|
||||
.anki-heading-sync-settings .ahs-theme-action-button:focus-visible:not(:disabled) {
|
||||
background: var(--interactive-accent-hover);
|
||||
border-color: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.anki-heading-sync-settings .ahs-theme-action-button:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.anki-heading-sync-settings .ahs-theme-action-button:disabled {
|
||||
background: var(--background-secondary);
|
||||
color: var(--text-muted);
|
||||
border-color: var(--background-modifier-border);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.72;
|
||||
}
|
||||
/* Intentionally empty: settings controls use native Obsidian component styles. */
|
||||
|
|
|
|||
Loading…
Reference in a new issue