mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
chore(release): bump plugin version to 1.0.1
中文: 将插件版本升至 1.0.1,恢复使用 Obsidian getLanguage() 作为语言来源,并把最低 Obsidian 版本同步提高到 1.8.7。 English: Bumps the plugin to 1.0.1, restores Obsidian getLanguage() as the locale source, and aligns the minimum Obsidian version with API usage.
This commit is contained in:
parent
6fb8583feb
commit
3e5ce2c1d2
11 changed files with 67 additions and 75 deletions
|
|
@ -26,7 +26,7 @@ Anki Heading Sync 是一个 Obsidian 桌面端插件,用来批量地制作 Ank
|
|||
|
||||
### 使用要求
|
||||
|
||||
- Obsidian 桌面端 1.5.0 或更高版本
|
||||
- Obsidian 桌面端 1.8.7 或更高版本
|
||||
- Anki 桌面端
|
||||
- Anki 中已安装并启用 AnkiConnect
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ Currently, the plugin mainly supports four card types:
|
|||
|
||||
### Requirements
|
||||
|
||||
- Obsidian desktop 1.5.0 or newer
|
||||
- Obsidian desktop 1.8.7 or newer
|
||||
- Anki desktop
|
||||
- AnkiConnect enabled in Anki
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Repository evidence was verified against these files and modules:
|
|||
- The GitHub repository currently shows no published releases, so the first submission release still needs to be created.
|
||||
- No exact `anki-heading-sync` ID match and no exact `Anki Heading Sync` name match were found in the current upstream Obsidian community-plugins.json.
|
||||
- manifest.json contains the expected release-facing fields: `id`, `name`, `version`, `minAppVersion`, `description`, `author`, `authorUrl`, and `isDesktopOnly`.
|
||||
- versions.json currently maps `1.0.0` to `1.5.0`, which matches manifest.json version and minAppVersion.
|
||||
- versions.json currently maps `1.0.1` to `1.8.7`, which matches manifest.json version and minAppVersion.
|
||||
- package.json defines the release-relevant commands: `lint`, `test`, `build`, and `build:obsidian`.
|
||||
- package-lock.json is present, and no yarn.lock was found.
|
||||
- LICENSE is present and uses MIT.
|
||||
|
|
@ -87,7 +87,7 @@ Based on the current Obsidian docs, the repository must satisfy these release co
|
|||
|
||||
## 4. What still needs fixing
|
||||
|
||||
- A GitHub release for version `1.0.0` still needs to be created manually, with assets attached.
|
||||
- A GitHub release for version `1.0.1` still needs to be created manually, with assets attached.
|
||||
- Manual QA in real Obsidian and real Anki is still required before submission.
|
||||
- The actual submission PR to obsidianmd/obsidian-releases still needs to be opened.
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ Run these checks in a real desktop Obsidian vault with real Anki running:
|
|||
- Run `npm test`.
|
||||
- Run `npm run build`.
|
||||
- Verify dist/plugin contains `main.js`, `manifest.json`, and `styles.css` when present.
|
||||
- Create a GitHub release tag exactly matching `manifest.json.version`, currently `1.0.0`.
|
||||
- Create a GitHub release tag exactly matching `manifest.json.version`, currently `1.0.1`.
|
||||
- Upload these assets to the release:
|
||||
- main.js
|
||||
- manifest.json
|
||||
|
|
@ -180,5 +180,5 @@ Repository-level release preparation is close to complete, but submission should
|
|||
|
||||
- local lint, test, and build validation remain green on the release branch
|
||||
- manual QA is completed in real Obsidian and real Anki
|
||||
- the GitHub release for version `1.0.0` exists with the correct assets
|
||||
- the GitHub release for version `1.0.1` exists with the correct assets
|
||||
- the submission PR to obsidianmd/obsidian-releases is opened with the correct metadata
|
||||
|
|
|
|||
36
main.js
36
main.js
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "anki-heading-sync",
|
||||
"name": "Anki Heading Sync",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.5.0",
|
||||
"version": "1.0.1",
|
||||
"minAppVersion": "1.8.7",
|
||||
"description": "Focused heading-based Markdown to Anki sync plugin.",
|
||||
"author": "Dusk",
|
||||
"authorUrl": "https://github.com/panAtGitHub",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-anki-heading-sync",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-anki-heading-sync",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"markdown-it": "^14.1.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-anki-heading-sync",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Focused heading-based Markdown to Anki sync plugin.",
|
||||
"author": "Dusk",
|
||||
"main": "dist/plugin/main.js",
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { getLanguageMock } = vi.hoisted(() => ({
|
||||
getLanguageMock: vi.fn(() => "en"),
|
||||
}));
|
||||
|
||||
vi.mock("obsidian", () => ({
|
||||
getLanguage: getLanguageMock,
|
||||
}));
|
||||
|
||||
import { PluginUserError, renderPluginFileFailure, renderPluginFileFailuresInline, renderUnknownUserFacingError, renderUserFacingMessage, renderUserMessage } from "./PluginUserError";
|
||||
|
||||
function setNavigatorLanguage(language: string): void {
|
||||
vi.stubGlobal("navigator", { language });
|
||||
function setObsidianLanguage(language: string): void {
|
||||
getLanguageMock.mockReturnValue(language);
|
||||
}
|
||||
|
||||
describe("PluginUserError", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders plugin-owned errors in English", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
|
||||
const error = new PluginUserError("errors.currentFileOutOfScope", { filePath: "notes/example.md" });
|
||||
|
||||
|
|
@ -21,7 +28,7 @@ describe("PluginUserError", () => {
|
|||
});
|
||||
|
||||
it("renders plugin-owned errors in Simplified Chinese", () => {
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
|
||||
const error = new PluginUserError("errors.currentFileOutOfScope", { filePath: "notes/example.md" });
|
||||
|
||||
|
|
@ -29,19 +36,19 @@ describe("PluginUserError", () => {
|
|||
});
|
||||
|
||||
it("renders scope-not-configured errors in English and Chinese", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
expect(renderUserMessage(new PluginUserError("errors.runScopeNotConfigured"))).toBe(
|
||||
"Run scope is not configured. In include mode, select at least one folder before syncing.",
|
||||
);
|
||||
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
expect(renderUserMessage(new PluginUserError("errors.runScopeNotConfigured"))).toBe(
|
||||
"运行范围尚未配置。当前是 include 模式,请至少选择一个文件夹后再同步。",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders write-back failure summaries with localized detail lines", () => {
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
|
||||
const error = new PluginUserError(
|
||||
"errors.writeBack.summary",
|
||||
|
|
@ -62,7 +69,7 @@ describe("PluginUserError", () => {
|
|||
});
|
||||
|
||||
it("renders raw and keyed user-facing messages plus inline failure lists", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
|
||||
expect(renderUserFacingMessage({ key: "notice.failedSavePluginSettings" })).toBe("Failed to save plugin settings.");
|
||||
expect(renderUserFacingMessage({ rawMessage: "raw failure" })).toBe("raw failure");
|
||||
|
|
@ -74,14 +81,14 @@ describe("PluginUserError", () => {
|
|||
});
|
||||
|
||||
it("falls back to raw error messages for unknown errors", () => {
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
|
||||
expect(renderUnknownUserFacingError(new Error("socket closed"), "notice.vaultSyncFailed")).toBe("socket closed");
|
||||
});
|
||||
|
||||
it("falls back to a translated default when the value is not an Error", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
|
||||
expect(renderUnknownUserFacingError(null, "notice.vaultSyncFailed")).toBe("Vault sync failed.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { getLanguage } from "obsidian";
|
||||
|
||||
export type PluginLocale = "en" | "zh";
|
||||
|
||||
export function resolvePluginLocale(languageCode = getPreferredLanguage()): PluginLocale {
|
||||
export function resolvePluginLocale(languageCode = getLanguage()): PluginLocale {
|
||||
const normalizedLanguage = languageCode.toLowerCase();
|
||||
return normalizedLanguage === "zh" || normalizedLanguage.startsWith("zh-") ? "zh" : "en";
|
||||
}
|
||||
|
|
@ -8,11 +10,3 @@ export function resolvePluginLocale(languageCode = getPreferredLanguage()): Plug
|
|||
export function formatList(locale: PluginLocale, items: string[]): string {
|
||||
return items.join(locale === "zh" ? "、" : ", ");
|
||||
}
|
||||
|
||||
function getPreferredLanguage(): string {
|
||||
if (typeof navigator !== "undefined" && typeof navigator.language === "string") {
|
||||
return navigator.language;
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
|
@ -173,12 +173,11 @@ type FakeButtonComponentInstance = InstanceType<typeof FakeButtonComponent>;
|
|||
type FakeContainerElInstance = InstanceType<typeof FakeModal>["contentEl"];
|
||||
type FakeToggleComponentInstance = InstanceType<typeof FakeToggleComponent>;
|
||||
|
||||
function setNavigatorLanguage(language: string): void {
|
||||
vi.stubGlobal("navigator", { language });
|
||||
function setObsidianLanguage(language: string): void {
|
||||
getLanguageMock.mockReturnValue(language);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
|
|
@ -204,7 +203,7 @@ function getFooterCountText(contentEl: FakeContainerElInstance): string | undefi
|
|||
|
||||
describe("EmptyDeckSelectionModal", () => {
|
||||
it("renders the modal text in English when Obsidian language is not zh", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B", "Deck C"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
@ -224,7 +223,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("starts with zero selected count and a disabled delete button", () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B", "Deck C"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
@ -236,7 +235,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("selects every candidate when the select-all button is clicked", async () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B", "Deck C"]);
|
||||
|
||||
const selectionPromise = modal.openAndGetSelection();
|
||||
|
|
@ -259,7 +258,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("clears every candidate when the clear-all button is clicked", async () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B", "Deck C"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
@ -277,7 +276,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("inverts selected candidates when the invert button is clicked", async () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B", "Deck C"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
@ -296,7 +295,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("enables and disables the delete button as manual selection changes", async () => {
|
||||
setNavigatorLanguage("en");
|
||||
setObsidianLanguage("en");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
@ -314,7 +313,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("returns null when cancel is clicked", async () => {
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B"]);
|
||||
|
||||
const selectionPromise = modal.openAndGetSelection();
|
||||
|
|
@ -326,7 +325,7 @@ describe("EmptyDeckSelectionModal", () => {
|
|||
});
|
||||
|
||||
it("renders selection text in Simplified Chinese when Obsidian language is zh", async () => {
|
||||
setNavigatorLanguage("zh");
|
||||
setObsidianLanguage("zh");
|
||||
const modal = new EmptyDeckSelectionModal({} as never, ["Deck A", "Deck B"]);
|
||||
|
||||
void modal.openAndGetSelection();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { getLanguageMock, noticeRecords } = vi.hoisted(() => {
|
||||
const hoistedGetLanguage = vi.fn(() => "en");
|
||||
|
|
@ -31,10 +31,6 @@ import type { ManualSyncResult } from "@/application/use-cases/manualSyncTypes";
|
|||
|
||||
import { NoticeService } from "./NoticeService";
|
||||
|
||||
function setNavigatorLanguage(language: string): void {
|
||||
vi.stubGlobal("navigator", { language });
|
||||
}
|
||||
|
||||
function createManualSyncResult(overrides: Partial<ManualSyncResult> = {}): ManualSyncResult {
|
||||
return {
|
||||
scannedFiles: 3,
|
||||
|
|
@ -85,11 +81,6 @@ describe("NoticeService", () => {
|
|||
noticeRecords.length = 0;
|
||||
getLanguageMock.mockReset();
|
||||
getLanguageMock.mockReturnValue("en");
|
||||
setNavigatorLanguage("en");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("renders sync summary and deck warnings in English", () => {
|
||||
|
|
@ -111,7 +102,7 @@ describe("NoticeService", () => {
|
|||
});
|
||||
|
||||
it("renders clear-current-file summaries with localized failures", () => {
|
||||
setNavigatorLanguage("zh");
|
||||
getLanguageMock.mockReturnValue("zh");
|
||||
const service = new NoticeService();
|
||||
|
||||
service.showClearCurrentFileSummary(createClearResult({
|
||||
|
|
@ -138,4 +129,4 @@ describe("NoticeService", () => {
|
|||
"Empty-deck cleanup completed: candidates 4, selected 3, deleted 2, skipped 2. Skipped decks: Deck C, Deck D.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"1.0.0": "1.5.0"
|
||||
}
|
||||
"1.0.0": "1.8.7",
|
||||
"1.0.1": "1.8.7"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue