mirror of
https://github.com/eondrcode/obsidian-manager.git
synced 2026-07-22 11:40:24 +00:00
fix: 优化UI及兼容性策略
This commit is contained in:
parent
83ffa27eb4
commit
43c13c47df
11 changed files with 600 additions and 54 deletions
|
|
@ -47,6 +47,8 @@ export interface PluginLayoutItem {
|
|||
title?: string;
|
||||
}
|
||||
|
||||
export type ReleaseCompatibilityMode = "compatible" | "all";
|
||||
|
||||
export interface BetaSource {
|
||||
id: string;
|
||||
repo: string;
|
||||
|
|
@ -55,6 +57,7 @@ export interface BetaSource {
|
|||
frozenVersion?: string;
|
||||
includePrerelease?: boolean;
|
||||
updateCheckMode?: "release" | "version";
|
||||
compatibilityMode?: ReleaseCompatibilityMode;
|
||||
updateDelayDays?: number;
|
||||
autoUpdate: boolean;
|
||||
enabled: boolean;
|
||||
|
|
@ -63,8 +66,10 @@ export interface BetaSource {
|
|||
latestPublishedAt?: string;
|
||||
installedReleaseTag?: string;
|
||||
installedReleasePublishedAt?: string;
|
||||
installedReleaseIsGithubLatest?: boolean;
|
||||
latestReleaseTag?: string;
|
||||
latestReleasePublishedAt?: string;
|
||||
latestReleaseIsGithubLatest?: boolean;
|
||||
installedAt?: number;
|
||||
lastChecked?: number;
|
||||
error?: string;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ export interface ReleaseVersion {
|
|||
body?: string;
|
||||
publishedAt?: string;
|
||||
url?: string;
|
||||
minAppVersion?: string;
|
||||
manifestVersion?: string;
|
||||
isGithubLatest?: boolean;
|
||||
compatibilityChecked?: boolean;
|
||||
}
|
||||
|
||||
export interface FetchReleaseVersionOptions {
|
||||
includeManifest?: boolean;
|
||||
}
|
||||
|
||||
const API_BASE = "https://api.github.com";
|
||||
|
|
@ -164,6 +172,13 @@ const cloneReleaseVersions = (versions: ReleaseVersion[]): ReleaseVersion[] =>
|
|||
const buildReleaseCacheKey = (repo: string, token?: string, proxy?: string): string =>
|
||||
`${repo}\n${token ? "authenticated" : "anonymous"}\n${proxy || "direct"}`;
|
||||
|
||||
const buildReleaseCacheKeyWithOptions = (
|
||||
repo: string,
|
||||
token: string | undefined,
|
||||
proxy: string | undefined,
|
||||
options?: FetchReleaseVersionOptions
|
||||
): string => `${buildReleaseCacheKey(repo, token, proxy)}\nmanifest:${options?.includeManifest ? "1" : "0"}`;
|
||||
|
||||
/**
|
||||
* 生成 raw 文件候选地址。
|
||||
*
|
||||
|
|
@ -283,16 +298,77 @@ const upsertInstalledPluginRecord = (
|
|||
manager.settings.Plugins.push(record);
|
||||
};
|
||||
|
||||
const releaseToVersion = (release: ReleaseResponse, latestTag?: string): ReleaseVersion => ({
|
||||
version: release.tag_name || "",
|
||||
prerelease: Boolean(release.prerelease),
|
||||
name: release.name || undefined,
|
||||
body: release.body || undefined,
|
||||
publishedAt: release.published_at || undefined,
|
||||
url: release.html_url || undefined,
|
||||
isGithubLatest: Boolean(latestTag && release.tag_name === latestTag),
|
||||
});
|
||||
|
||||
const fetchReleaseManifestText = async (
|
||||
manager: Manager,
|
||||
repo: string,
|
||||
release: ReleaseResponse,
|
||||
token?: string
|
||||
): Promise<string | null> => {
|
||||
const manifestUrl = pickAsset(release, "manifest.json");
|
||||
if (manifestUrl) {
|
||||
try {
|
||||
return await fetchText(manager, manifestUrl, token);
|
||||
} catch {
|
||||
// Fall back to the source tag below.
|
||||
}
|
||||
}
|
||||
|
||||
const tag = release.tag_name || "";
|
||||
if (!tag) return null;
|
||||
try {
|
||||
return await fetchRawFromTag(manager, repo, tag, "manifest.json", token);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const enrichReleaseVersionWithManifest = async (
|
||||
manager: Manager,
|
||||
repo: string,
|
||||
release: ReleaseResponse,
|
||||
version: ReleaseVersion,
|
||||
token?: string
|
||||
): Promise<ReleaseVersion> => {
|
||||
const manifestText = await fetchReleaseManifestText(manager, repo, release, token);
|
||||
if (!manifestText) return { ...version, compatibilityChecked: true };
|
||||
|
||||
try {
|
||||
const manifest = JSON.parse(manifestText) as { version?: string; minAppVersion?: string };
|
||||
return {
|
||||
...version,
|
||||
manifestVersion: typeof manifest.version === "string" ? manifest.version : undefined,
|
||||
minAppVersion: typeof manifest.minAppVersion === "string" ? manifest.minAppVersion : undefined,
|
||||
compatibilityChecked: true,
|
||||
};
|
||||
} catch {
|
||||
return { ...version, compatibilityChecked: true };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 拉取指定仓库的 release 版本列表。
|
||||
*
|
||||
* 返回值用于下拉选择和更新检查。短 TTL 缓存可以避免用户打开弹窗、点击刷新、
|
||||
* 批量检查更新时反复打同一个 GitHub API。
|
||||
*/
|
||||
export const fetchReleaseVersions = async (manager: Manager, repoInput: string): Promise<ReleaseVersion[]> => {
|
||||
export const fetchReleaseVersions = async (
|
||||
manager: Manager,
|
||||
repoInput: string,
|
||||
options: FetchReleaseVersionOptions = {}
|
||||
): Promise<ReleaseVersion[]> => {
|
||||
const repo = sanitizeRepo(repoInput);
|
||||
const token = await getToken(manager);
|
||||
const cacheKey = buildReleaseCacheKey(repo, token, manager.settings.GITHUB_PROXY);
|
||||
const cacheKey = buildReleaseCacheKeyWithOptions(repo, token, manager.settings.GITHUB_PROXY, options);
|
||||
const cached = releaseVersionCache.get(cacheKey);
|
||||
const now = Date.now();
|
||||
|
||||
|
|
@ -300,6 +376,8 @@ export const fetchReleaseVersions = async (manager: Manager, repoInput: string):
|
|||
return cloneReleaseVersions(cached.versions);
|
||||
}
|
||||
|
||||
const latestRelease = await getRelease(manager, repo, undefined, token).catch(() => null);
|
||||
const latestTag = latestRelease?.tag_name || undefined;
|
||||
const releases: ReleaseResponse[] = [];
|
||||
for (let page = 1; ; page++) {
|
||||
const url = `${API_BASE}/repos/${repo}/releases?per_page=${RELEASES_PER_PAGE}&page=${page}`;
|
||||
|
|
@ -309,17 +387,19 @@ export const fetchReleaseVersions = async (manager: Manager, repoInput: string):
|
|||
if (pageReleases.length < RELEASES_PER_PAGE) break;
|
||||
}
|
||||
|
||||
const versions = releases
|
||||
let releaseEntries = releases
|
||||
.filter((release) => !release.draft)
|
||||
.map((release) => ({
|
||||
version: release.tag_name || "",
|
||||
prerelease: Boolean(release.prerelease),
|
||||
name: release.name || undefined,
|
||||
body: release.body || undefined,
|
||||
publishedAt: release.published_at || undefined,
|
||||
url: release.html_url || undefined,
|
||||
}))
|
||||
.filter((release) => release.version);
|
||||
.map((release) => ({ release, version: releaseToVersion(release, latestTag) }))
|
||||
.filter((entry) => entry.version.version);
|
||||
|
||||
if (options.includeManifest) {
|
||||
releaseEntries = await Promise.all(releaseEntries.map(async (entry) => ({
|
||||
release: entry.release,
|
||||
version: await enrichReleaseVersionWithManifest(manager, repo, entry.release, entry.version, token),
|
||||
})));
|
||||
}
|
||||
|
||||
const versions = releaseEntries.map((entry) => entry.version);
|
||||
|
||||
releaseVersionCache.set(cacheKey, {
|
||||
expiresAt: now + RELEASE_VERSION_CACHE_TTL_MS,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,13 @@ export default {
|
|||
管理器_选择版本_当前版本: 'Installed: {version}',
|
||||
管理器_选择版本_版本数量: '{count} versions',
|
||||
管理器_选择版本_选择版本: 'Select version {version}',
|
||||
兼容性_策略: 'Compatibility',
|
||||
兼容性_仅兼容: 'Compatible only',
|
||||
兼容性_显示全部: 'Show all',
|
||||
兼容性_GitHubLatest: 'GitHub Latest',
|
||||
兼容性_需要版本: 'Requires Obsidian {version}',
|
||||
兼容性_兼容: 'Compatible',
|
||||
兼容性_不兼容: 'Incompatible',
|
||||
|
||||
筛选_全部_描述: 'All',
|
||||
筛选_仅启用_描述: 'Enabled only',
|
||||
|
|
@ -132,6 +139,10 @@ export default {
|
|||
更新检测配置_说明: 'Choose how this overview check determines updates and delay days.',
|
||||
更新检测配置_方式_标题: 'Detection method',
|
||||
更新检测配置_方式_描述: 'Release order works for repositories with non-monotonic tags; version works for semantic versions.',
|
||||
更新检测配置_兼容性_标题: 'Compatibility strategy',
|
||||
更新检测配置_兼容性_描述: 'Uses manifest.json minAppVersion. Compatible first recommends versions that can run on this Obsidian version; show all lets latest and compatibility branches both participate.',
|
||||
更新检测配置_兼容性_兼容优先: 'Recommend compatible only',
|
||||
更新检测配置_兼容性_显示全部: 'Show all versions',
|
||||
更新检测配置_延迟_标题: 'Delay days',
|
||||
更新检测配置_延迟_描述: 'Only treat releases older than this many days as updates. 0 disables the delay.',
|
||||
更新检测配置_开始按钮: 'Start check',
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ export default {
|
|||
更新检测配置_说明: '选择本次总览检测的判定方式和延迟天数。',
|
||||
更新检测配置_方式_标题: '检测方式',
|
||||
更新检测配置_方式_描述: '发布顺序适合 tag 不严格递增的仓库;版本号适合规范语义化版本。',
|
||||
更新检测配置_兼容性_标题: '兼容性策略',
|
||||
更新检测配置_兼容性_描述: '按 manifest.json 的 minAppVersion 判断。兼容优先会推荐当前 Obsidian 可运行的版本;显示全部则允许最新版或兼容分支都参与判断。',
|
||||
更新检测配置_兼容性_兼容优先: '只推荐兼容版本',
|
||||
更新检测配置_兼容性_显示全部: '显示全部版本',
|
||||
更新检测配置_延迟_标题: '延迟天数',
|
||||
更新检测配置_延迟_描述: '只把发布满指定天数的版本视为可更新;0 表示不延迟。',
|
||||
更新检测配置_开始按钮: '开始检查',
|
||||
|
|
@ -183,6 +187,13 @@ export default {
|
|||
管理器_选择版本_当前版本: '当前安装:{version}',
|
||||
管理器_选择版本_版本数量: '{count} 个版本',
|
||||
管理器_选择版本_选择版本: '选择版本 {version}',
|
||||
兼容性_策略: '兼容性策略',
|
||||
兼容性_仅兼容: '只推荐兼容',
|
||||
兼容性_显示全部: '显示全部',
|
||||
兼容性_GitHubLatest: 'GitHub 最新',
|
||||
兼容性_需要版本: '需要 Obsidian {version}',
|
||||
兼容性_兼容: '兼容',
|
||||
兼容性_不兼容: '不兼容',
|
||||
|
||||
通知_ID已复制: 'ID已复制',
|
||||
|
||||
|
|
|
|||
34
src/main.ts
34
src/main.ts
|
|
@ -1,5 +1,5 @@
|
|||
import { normalizePath, ObsidianProtocolData, Plugin, PluginManifest, Workspace } from 'obsidian';
|
||||
import { DEFAULT_SETTINGS, ManagerSettings, PluginUpdateCheckMode } from './settings/data';
|
||||
import { DEFAULT_SETTINGS, ManagerSettings, PluginUpdateCheckMode, ReleaseCompatibilityMode } from './settings/data';
|
||||
import { ManagerSettingTab } from './settings';
|
||||
import { Translator } from './lang/inxdex';
|
||||
import { ManagerModal } from './modal/manager-modal';
|
||||
|
|
@ -36,6 +36,7 @@ interface UpdateStatus {
|
|||
|
||||
interface PluginUpdateCheckOptions {
|
||||
updateCheckMode?: PluginUpdateCheckMode;
|
||||
compatibilityMode?: ReleaseCompatibilityMode;
|
||||
updateDelayDays?: number;
|
||||
}
|
||||
|
||||
|
|
@ -509,12 +510,14 @@ export default class Manager extends Plugin {
|
|||
public getPluginUpdateCheckOptions(): Required<PluginUpdateCheckOptions> {
|
||||
return {
|
||||
updateCheckMode: this.normalizePluginUpdateCheckMode(this.settings.PLUGIN_UPDATE_CHECK_MODE),
|
||||
compatibilityMode: this.normalizeReleaseCompatibilityMode(this.settings.PLUGIN_UPDATE_COMPATIBILITY_MODE),
|
||||
updateDelayDays: this.normalizePluginUpdateDelayDays(this.settings.PLUGIN_UPDATE_DELAY_DAYS),
|
||||
};
|
||||
}
|
||||
|
||||
public async setPluginUpdateCheckOptions(options: PluginUpdateCheckOptions): Promise<void> {
|
||||
this.settings.PLUGIN_UPDATE_CHECK_MODE = this.normalizePluginUpdateCheckMode(options.updateCheckMode);
|
||||
this.settings.PLUGIN_UPDATE_COMPATIBILITY_MODE = this.normalizeReleaseCompatibilityMode(options.compatibilityMode);
|
||||
this.settings.PLUGIN_UPDATE_DELAY_DAYS = this.normalizePluginUpdateDelayDays(options.updateDelayDays);
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
|
@ -589,7 +592,7 @@ export default class Manager extends Plugin {
|
|||
|
||||
private async checkBetaSource(source: BetaSource): Promise<void> {
|
||||
try {
|
||||
const versions = await fetchReleaseVersions(this, source.repo);
|
||||
const versions = await fetchReleaseVersions(this, source.repo, { includeManifest: source.type === "plugin" });
|
||||
let localVersion = source.localVersion || "";
|
||||
if (source.type === "plugin") {
|
||||
const pluginId = this.getBetaSourcePluginId(source);
|
||||
|
|
@ -644,7 +647,7 @@ export default class Manager extends Plugin {
|
|||
? source.latestReleasePublishedAt || source.latestPublishedAt
|
||||
: undefined;
|
||||
if (!targetVersion) {
|
||||
const versions = await fetchReleaseVersions(this, source.repo);
|
||||
const versions = await fetchReleaseVersions(this, source.repo, { includeManifest: source.type === "plugin" });
|
||||
let localVersion = source.localVersion || "";
|
||||
if (source.type === "plugin") {
|
||||
const pluginId = this.getBetaSourcePluginId(source);
|
||||
|
|
@ -1024,6 +1027,10 @@ export default class Manager extends Plugin {
|
|||
return value === "version" ? "version" : "release";
|
||||
}
|
||||
|
||||
private normalizeReleaseCompatibilityMode(value?: string | null): ReleaseCompatibilityMode {
|
||||
return value === "all" ? "all" : "compatible";
|
||||
}
|
||||
|
||||
private normalizePluginUpdateDelayDays(value: unknown): number {
|
||||
const days = Math.floor(Number(value));
|
||||
return Number.isFinite(days) && days > 0 ? days : 0;
|
||||
|
|
@ -1032,6 +1039,7 @@ export default class Manager extends Plugin {
|
|||
private resolvePluginUpdateCheckOptions(options?: PluginUpdateCheckOptions): Required<PluginUpdateCheckOptions> {
|
||||
return {
|
||||
updateCheckMode: this.normalizePluginUpdateCheckMode(options?.updateCheckMode ?? this.settings.PLUGIN_UPDATE_CHECK_MODE),
|
||||
compatibilityMode: this.normalizeReleaseCompatibilityMode(options?.compatibilityMode ?? this.settings.PLUGIN_UPDATE_COMPATIBILITY_MODE),
|
||||
updateDelayDays: this.normalizePluginUpdateDelayDays(options?.updateDelayDays ?? this.settings.PLUGIN_UPDATE_DELAY_DAYS),
|
||||
};
|
||||
}
|
||||
|
|
@ -1060,6 +1068,7 @@ export default class Manager extends Plugin {
|
|||
mode: "latest",
|
||||
includePrerelease: false,
|
||||
updateCheckMode: options.updateCheckMode,
|
||||
compatibilityMode: options.compatibilityMode,
|
||||
updateDelayDays: options.updateDelayDays || undefined,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
|
|
@ -1103,7 +1112,7 @@ export default class Manager extends Plugin {
|
|||
st.repo = null;
|
||||
}
|
||||
if (st.repo) {
|
||||
st.versions = await this.fetchGithubVersions(st.repo, updateOptions.updateCheckMode === "release");
|
||||
st.versions = await this.fetchGithubVersions(st.repo, updateOptions.updateCheckMode === "release", true);
|
||||
}
|
||||
this.applyPluginUpdateStatus(st, pm, st.repo || null, st.versions || [], official, updateOptions);
|
||||
if (this.settings.DEBUG) console.log("[BPM] update official match", pm.id, localVersion, "->", st.remoteVersion);
|
||||
|
|
@ -1120,10 +1129,8 @@ export default class Manager extends Plugin {
|
|||
if (repo) {
|
||||
st.source = 'github';
|
||||
st.repo = repo;
|
||||
st.versions = await this.fetchGithubVersions(repo, updateOptions.updateCheckMode === "release");
|
||||
// 选择一个默认的远端版本:优先最新稳定,否则第一个
|
||||
const pick = st.versions?.find(v => !v.prerelease) ?? st.versions?.[0] ?? null;
|
||||
const remoteVersion = pick?.version ?? await this.fetchGithubManifestVersion(repo);
|
||||
st.versions = await this.fetchGithubVersions(repo, updateOptions.updateCheckMode === "release", true);
|
||||
const remoteVersion = st.versions.length > 0 ? null : await this.fetchGithubManifestVersion(repo);
|
||||
this.applyPluginUpdateStatus(st, pm, repo, st.versions || [], remoteVersion, updateOptions);
|
||||
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
|
||||
if (this.settings.DEBUG) console.log("[BPM] update github match", pm.id, repo, localVersion, "->", st.remoteVersion);
|
||||
|
|
@ -1157,7 +1164,7 @@ export default class Manager extends Plugin {
|
|||
st.source = "official";
|
||||
try {
|
||||
st.repo = await this.repoResolver.resolveRepo(pm.id);
|
||||
if (st.repo) st.versions = await this.fetchGithubVersions(st.repo, updateOptions.updateCheckMode === "release");
|
||||
if (st.repo) st.versions = await this.fetchGithubVersions(st.repo, updateOptions.updateCheckMode === "release", true);
|
||||
} catch {
|
||||
if (updateOptions.updateCheckMode === "release") throw new Error(this.translator.t("更新_未获取到远端版本"));
|
||||
}
|
||||
|
|
@ -1174,9 +1181,8 @@ export default class Manager extends Plugin {
|
|||
if (repo) {
|
||||
st.source = "github";
|
||||
st.repo = repo;
|
||||
st.versions = await this.fetchGithubVersions(repo, updateOptions.updateCheckMode === "release");
|
||||
const pick = st.versions?.find(v => !v.prerelease) ?? st.versions?.[0] ?? null;
|
||||
const remoteVersion = pick?.version ?? await this.fetchGithubManifestVersion(repo);
|
||||
st.versions = await this.fetchGithubVersions(repo, updateOptions.updateCheckMode === "release", true);
|
||||
const remoteVersion = st.versions.length > 0 ? null : await this.fetchGithubManifestVersion(repo);
|
||||
this.applyPluginUpdateStatus(st, pm, repo, st.versions || [], remoteVersion, updateOptions);
|
||||
if (!st.remoteVersion) st.message = this.translator.t("更新_未获取到远端版本");
|
||||
if (this.settings.DEBUG) console.log("[BPM] single update github", pm.id, repo, localVersion, "->", st.remoteVersion);
|
||||
|
|
@ -1262,9 +1268,9 @@ export default class Manager extends Plugin {
|
|||
return null;
|
||||
}
|
||||
|
||||
private async fetchGithubVersions(repoInput: string, throwOnError = false): Promise<ReleaseVersion[]> {
|
||||
private async fetchGithubVersions(repoInput: string, throwOnError = false, includeManifest = false): Promise<ReleaseVersion[]> {
|
||||
try {
|
||||
return await fetchReleaseVersions(this, repoInput);
|
||||
return await fetchReleaseVersions(this, repoInput, { includeManifest });
|
||||
} catch (e) {
|
||||
console.error("[BPM] fetchGithubVersions error", repoInput, e);
|
||||
if (throwOnError) throw e;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import {
|
|||
ManagerTransferTheme,
|
||||
parseManagerTransferPackage,
|
||||
} from "../import-export";
|
||||
import { markSourceInstalledRelease, sourceHasUpdate as sourceHasConfiguredUpdate, syncSourceReleaseCheck } from "../source-release";
|
||||
import { markSourceInstalledRelease, pickSourceTargetRelease, releaseIsCompatible, sourceHasUpdate as sourceHasConfiguredUpdate, syncSourceReleaseCheck } from "../source-release";
|
||||
import {
|
||||
createSharedVaultLinks,
|
||||
forgetSharedVault,
|
||||
|
|
@ -945,14 +945,26 @@ export class ManagerModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
const versions = await fetchReleaseVersions(this.manager, repo);
|
||||
const versions = await fetchReleaseVersions(this.manager, repo, { includeManifest: true });
|
||||
if (!status) status = {};
|
||||
status.repo = repo;
|
||||
status.versions = versions;
|
||||
status.error = "";
|
||||
status.message = "";
|
||||
const stableVersion = versions.find((release) => !release.prerelease)?.version;
|
||||
status.remoteVersion = stableVersion || versions[0]?.version || status.remoteVersion || null;
|
||||
const updateOptions = this.manager.getPluginUpdateCheckOptions();
|
||||
const target = pickSourceTargetRelease({
|
||||
id: pluginId,
|
||||
repo,
|
||||
type: "plugin",
|
||||
mode: "latest",
|
||||
includePrerelease: false,
|
||||
updateCheckMode: updateOptions.updateCheckMode,
|
||||
compatibilityMode: updateOptions.compatibilityMode,
|
||||
updateDelayDays: updateOptions.updateDelayDays || undefined,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
}, versions);
|
||||
status.remoteVersion = target?.tag || status.remoteVersion || null;
|
||||
this.manager.updateStatus[pluginId] = {
|
||||
...(this.manager.updateStatus?.[pluginId] ?? {}),
|
||||
...status,
|
||||
|
|
@ -4450,6 +4462,7 @@ export class ManagerModal extends Modal {
|
|||
autoUpdate: source.autoUpdate ?? false,
|
||||
mode: source.mode || "latest",
|
||||
updateCheckMode: source.updateCheckMode || "release",
|
||||
compatibilityMode: source.compatibilityMode || "compatible",
|
||||
updateDelayDays: this.normalizeSourceUpdateDelayDays(source.updateDelayDays),
|
||||
};
|
||||
if (existing) {
|
||||
|
|
@ -4461,7 +4474,7 @@ export class ManagerModal extends Modal {
|
|||
|
||||
private async checkBetaSource(source: BetaSource): Promise<BetaSource> {
|
||||
try {
|
||||
const versions = await fetchReleaseVersions(this.manager, source.repo);
|
||||
const versions = await fetchReleaseVersions(this.manager, source.repo, { includeManifest: source.type === "plugin" });
|
||||
const localVersion = this.getSourceLocalVersion(source);
|
||||
const releaseCheck = syncSourceReleaseCheck(source, versions, localVersion);
|
||||
await this.refreshSourcePackageCreatedAt(source);
|
||||
|
|
@ -4725,6 +4738,8 @@ export class ManagerModal extends Modal {
|
|||
|
||||
const card = list.createDiv("manager-source-card");
|
||||
card.setAttribute("data-source-repo", source.repo);
|
||||
card.toggleClass("has-update", hasUpdate);
|
||||
card.toggleClass("has-error", Boolean(source.error));
|
||||
|
||||
const cardMain = card.createDiv("manager-source-card__main");
|
||||
const cardHeader = cardMain.createDiv("manager-source-card__header");
|
||||
|
|
@ -4751,6 +4766,10 @@ export class ManagerModal extends Modal {
|
|||
text: source.updateCheckMode === "version" ? t("来源_检测方式_版本号") : t("来源_检测方式_发布顺序"),
|
||||
cls: `manager-source-item__chip ${source.updateCheckMode === "version" ? "is-latest" : "is-frozen"}`,
|
||||
}));
|
||||
chips.appendChild(createSpan({
|
||||
text: source.compatibilityMode === "all" ? t("兼容性_显示全部") : t("兼容性_仅兼容"),
|
||||
cls: `manager-source-item__chip ${source.compatibilityMode === "all" ? "is-frozen" : "is-latest"}`,
|
||||
}));
|
||||
if (source.autoUpdate) {
|
||||
chips.appendChild(createSpan({ text: t("来源_自动更新"), cls: "manager-source-item__chip is-auto" }));
|
||||
}
|
||||
|
|
@ -4781,7 +4800,9 @@ export class ManagerModal extends Modal {
|
|||
}
|
||||
|
||||
const controls = card.createDiv("manager-source-card__controls");
|
||||
const strategyGroup = controls.createDiv("manager-source-card__control-group manager-source-card__control-group--strategy");
|
||||
const strategyPanel = controls.createDiv("manager-source-card__control-panel manager-source-card__control-panel--strategy");
|
||||
const updatePanel = controls.createDiv("manager-source-card__control-panel manager-source-card__control-panel--update");
|
||||
const strategyGroup = strategyPanel.createDiv("manager-source-card__control-group manager-source-card__control-group--strategy");
|
||||
strategyGroup.createSpan({ cls: "manager-source-card__control-label", text: t("来源_版本策略") });
|
||||
const strategyWrap = strategyGroup.createDiv("manager-source-item__strategy");
|
||||
const modeDropdown = new DropdownComponent(strategyWrap);
|
||||
|
|
@ -4813,6 +4834,24 @@ export class ManagerModal extends Modal {
|
|||
updateCheckDropdown.selectEl.addClass("manager-source-item__check-mode");
|
||||
updateCheckDropdown.selectEl.setAttribute("aria-label", t("来源_检测方式"));
|
||||
|
||||
if (source.type === "plugin") {
|
||||
const compatibilityGroup = strategyPanel.createDiv("manager-source-card__control-group manager-source-card__control-group--compatibility");
|
||||
compatibilityGroup.createSpan({ cls: "manager-source-card__control-label", text: t("兼容性_策略") });
|
||||
const compatibilityDropdown = new DropdownComponent(compatibilityGroup);
|
||||
compatibilityDropdown.addOptions({
|
||||
compatible: t("兼容性_仅兼容"),
|
||||
all: t("兼容性_显示全部"),
|
||||
});
|
||||
compatibilityDropdown.setValue(source.compatibilityMode || "compatible");
|
||||
compatibilityDropdown.onChange(async (value) => {
|
||||
source.compatibilityMode = value === "all" ? "all" : "compatible";
|
||||
await this.checkBetaSource(source);
|
||||
this.renderContent();
|
||||
});
|
||||
compatibilityDropdown.selectEl.addClass("manager-source-item__mode");
|
||||
compatibilityDropdown.selectEl.setAttribute("aria-label", t("兼容性_策略"));
|
||||
}
|
||||
|
||||
const delayWrap = strategyWrap.createDiv("manager-source-item__delay");
|
||||
delayWrap.createSpan({ cls: "manager-source-item__delay-label", text: t("来源_更新延迟_标签") });
|
||||
const delayInput = new TextComponent(delayWrap);
|
||||
|
|
@ -4846,7 +4885,8 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
const autoGroup = controls.createDiv("manager-source-card__control-group manager-source-card__control-group--auto");
|
||||
const updateToggleGrid = updatePanel.createDiv("manager-source-card__toggle-grid");
|
||||
const autoGroup = updateToggleGrid.createDiv("manager-source-card__control-group manager-source-card__control-group--auto");
|
||||
autoGroup.createSpan({ cls: "manager-source-card__control-label", text: t("来源_更新") });
|
||||
const autoWrap = autoGroup.createDiv("manager-source-item__toggle");
|
||||
autoWrap.createSpan({ cls: "manager-source-item__toggle-label", text: t("来源_自动") });
|
||||
|
|
@ -4859,7 +4899,7 @@ export class ManagerModal extends Modal {
|
|||
this.renderContent();
|
||||
});
|
||||
|
||||
const prereleaseGroup = controls.createDiv("manager-source-card__control-group manager-source-card__control-group--pre");
|
||||
const prereleaseGroup = updateToggleGrid.createDiv("manager-source-card__control-group manager-source-card__control-group--pre");
|
||||
prereleaseGroup.createSpan({ cls: "manager-source-card__control-label", text: t("安装_发布类型_预发布") });
|
||||
const prereleaseWrap = prereleaseGroup.createDiv("manager-source-item__toggle");
|
||||
prereleaseWrap.createSpan({ cls: "manager-source-item__toggle-label", text: t("来源_最新") });
|
||||
|
|
@ -4873,7 +4913,7 @@ export class ManagerModal extends Modal {
|
|||
this.renderContent();
|
||||
});
|
||||
|
||||
const actionGroup = controls.createDiv("manager-source-card__actions");
|
||||
const actionGroup = updatePanel.createDiv("manager-source-card__actions");
|
||||
const prepareActionButton = (btn: ButtonComponent, label: string) => {
|
||||
btn.setTooltip(label);
|
||||
btn.buttonEl.setAttribute("aria-label", label);
|
||||
|
|
@ -5056,6 +5096,9 @@ export class ManagerModal extends Modal {
|
|||
};
|
||||
const getReleaseOptionLabel = (release: ReleaseVersion) => [
|
||||
release.version,
|
||||
release.isGithubLatest ? t("兼容性_GitHubLatest") : "",
|
||||
release.minAppVersion ? t("兼容性_需要版本", { version: release.minAppVersion }) : "",
|
||||
release.minAppVersion ? t(releaseIsCompatible(release) ? "兼容性_兼容" : "兼容性_不兼容") : "",
|
||||
formatReleaseDate(release.publishedAt),
|
||||
release.prerelease ? t("安装_发布类型_预发布") : "",
|
||||
].filter(Boolean).join(" · ");
|
||||
|
|
@ -5063,6 +5106,21 @@ export class ManagerModal extends Modal {
|
|||
if (this.installVersions.length === 0) return null;
|
||||
const selected = this.installVersion.trim();
|
||||
if (selected) return this.installVersions.find((item) => item.version === selected) ?? null;
|
||||
if (this.installType === "plugin") {
|
||||
const updateOptions = this.manager.getPluginUpdateCheckOptions();
|
||||
const target = pickSourceTargetRelease({
|
||||
id: this.getNormalizedInstallRepo(),
|
||||
repo: this.getNormalizedInstallRepo(),
|
||||
type: "plugin",
|
||||
mode: "latest",
|
||||
includePrerelease: false,
|
||||
updateCheckMode: "release",
|
||||
compatibilityMode: updateOptions.compatibilityMode,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
}, this.installVersions);
|
||||
if (target) return this.installVersions.find((item) => item.version === target.tag) ?? null;
|
||||
}
|
||||
return this.installVersions.find((item) => !item.prerelease) || this.installVersions[0];
|
||||
};
|
||||
const updateReleaseInfo = () => {
|
||||
|
|
@ -5081,6 +5139,9 @@ export class ManagerModal extends Modal {
|
|||
releaseEls.title.setText(release.name || release.version);
|
||||
const metaParts = [
|
||||
release.version,
|
||||
release.isGithubLatest ? t("兼容性_GitHubLatest") : "",
|
||||
release.minAppVersion ? t("兼容性_需要版本", { version: release.minAppVersion }) : "",
|
||||
release.minAppVersion ? t(releaseIsCompatible(release) ? "兼容性_兼容" : "兼容性_不兼容") : "",
|
||||
release.prerelease ? t("安装_发布类型_预发布") : t("安装_发布类型_正式版"),
|
||||
formatReleaseDate(release.publishedAt),
|
||||
].filter(Boolean);
|
||||
|
|
@ -5096,7 +5157,7 @@ export class ManagerModal extends Modal {
|
|||
fetchButton?.setDisabled(true);
|
||||
fetchButton?.setButtonText(t("管理器_安装_版本_获取中"));
|
||||
try {
|
||||
this.installVersions = await fetchReleaseVersions(this.manager, validRepo);
|
||||
this.installVersions = await fetchReleaseVersions(this.manager, validRepo, { includeManifest: this.installType === "plugin" });
|
||||
this.installVersion = "";
|
||||
if (this.installVersions.length === 0) new Notice(t("管理器_安装_版本_空提示"));
|
||||
} catch (e) {
|
||||
|
|
@ -5277,10 +5338,23 @@ export class ManagerModal extends Modal {
|
|||
let selectedRelease = getSelectedRelease();
|
||||
if (!selectedRelease) {
|
||||
try {
|
||||
const versions = await fetchReleaseVersions(this.manager, validRepo);
|
||||
const updateOptions = this.manager.getPluginUpdateCheckOptions();
|
||||
const versions = await fetchReleaseVersions(this.manager, validRepo, { includeManifest: this.installType === "plugin" });
|
||||
selectedRelease = this.installVersion
|
||||
? versions.find((item) => item.version === this.installVersion) ?? null
|
||||
: versions.find((item) => !item.prerelease) || versions[0] || null;
|
||||
: this.installType === "plugin"
|
||||
? versions.find((item) => item.version === pickSourceTargetRelease({
|
||||
id: pluginId || validRepo,
|
||||
repo: validRepo,
|
||||
type: "plugin",
|
||||
mode: "latest",
|
||||
includePrerelease: false,
|
||||
updateCheckMode: "release",
|
||||
compatibilityMode: updateOptions.compatibilityMode,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
}, versions)?.tag) || versions.find((item) => !item.prerelease) || versions[0] || null
|
||||
: versions.find((item) => !item.prerelease) || versions[0] || null;
|
||||
} catch {
|
||||
selectedRelease = null;
|
||||
}
|
||||
|
|
@ -5298,6 +5372,7 @@ export class ManagerModal extends Modal {
|
|||
frozenVersion: this.installVersion || undefined,
|
||||
includePrerelease: Boolean(selectedRelease?.prerelease),
|
||||
updateCheckMode: "release",
|
||||
compatibilityMode: this.manager.getPluginUpdateCheckOptions().compatibilityMode,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
localVersion,
|
||||
|
|
@ -6825,14 +6900,14 @@ export class ManagerModal extends Modal {
|
|||
private applyEditingStyle() {
|
||||
if (!this.modalContainer) return;
|
||||
if (this.editorMode) {
|
||||
this.modalContainer.addClass("manager-container--editing");
|
||||
this.modalContainer.addClass("manager-container--editing");
|
||||
} else {
|
||||
this.modalContainer.removeClass("manager-container--editing");
|
||||
this.modalContainer.removeClass("manager-container--editing");
|
||||
}
|
||||
if (this.bulkEditMode) {
|
||||
this.modalContainer.addClass("manager-container--bulk-editing");
|
||||
this.modalContainer.addClass("manager-container--bulk-editing");
|
||||
} else {
|
||||
this.modalContainer.removeClass("manager-container--bulk-editing");
|
||||
this.modalContainer.removeClass("manager-container--bulk-editing");
|
||||
}
|
||||
if (this.desktopActionWrapper) this.syncPageChrome();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ import {
|
|||
} from "obsidian";
|
||||
|
||||
import Manager from "main";
|
||||
import type { PluginUpdateCheckMode } from "../settings/data";
|
||||
import type { PluginUpdateCheckMode, ReleaseCompatibilityMode } from "../settings/data";
|
||||
|
||||
export interface PluginUpdateCheckConfig {
|
||||
updateCheckMode: PluginUpdateCheckMode;
|
||||
compatibilityMode: ReleaseCompatibilityMode;
|
||||
updateDelayDays: number;
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +25,9 @@ export const normalizePluginUpdateDelayDays = (value: unknown): number => {
|
|||
export const normalizePluginUpdateCheckMode = (value: unknown): PluginUpdateCheckMode =>
|
||||
value === "version" ? "version" : "release";
|
||||
|
||||
export const normalizeReleaseCompatibilityMode = (value: unknown): ReleaseCompatibilityMode =>
|
||||
value === "all" ? "all" : "compatible";
|
||||
|
||||
export const openPluginUpdateCheckModal = (
|
||||
app: App,
|
||||
manager: Manager,
|
||||
|
|
@ -46,6 +50,7 @@ class PluginUpdateCheckModal extends Modal {
|
|||
super(app);
|
||||
this.config = {
|
||||
updateCheckMode: normalizePluginUpdateCheckMode(initial.updateCheckMode),
|
||||
compatibilityMode: normalizeReleaseCompatibilityMode(initial.compatibilityMode),
|
||||
updateDelayDays: normalizePluginUpdateDelayDays(initial.updateDelayDays),
|
||||
};
|
||||
}
|
||||
|
|
@ -83,6 +88,21 @@ class PluginUpdateCheckModal extends Modal {
|
|||
});
|
||||
});
|
||||
|
||||
new Setting(body)
|
||||
.setName(t("更新检测配置_兼容性_标题"))
|
||||
.setDesc(t("更新检测配置_兼容性_描述"))
|
||||
.addDropdown((dropdown: DropdownComponent) => {
|
||||
dropdown.addOptions({
|
||||
compatible: t("更新检测配置_兼容性_兼容优先"),
|
||||
all: t("更新检测配置_兼容性_显示全部"),
|
||||
});
|
||||
dropdown.setValue(this.config.compatibilityMode);
|
||||
dropdown.selectEl.setAttribute("aria-label", t("更新检测配置_兼容性_标题"));
|
||||
dropdown.onChange((value) => {
|
||||
this.config.compatibilityMode = normalizeReleaseCompatibilityMode(value);
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(body)
|
||||
.setName(t("更新检测配置_延迟_标题"))
|
||||
.setDesc(t("更新检测配置_延迟_描述"))
|
||||
|
|
@ -127,6 +147,7 @@ class PluginUpdateCheckModal extends Modal {
|
|||
if (this.resolved) return;
|
||||
this.resolved = true;
|
||||
this.config.updateDelayDays = normalizePluginUpdateDelayDays(this.config.updateDelayDays);
|
||||
this.config.compatibilityMode = normalizeReleaseCompatibilityMode(this.config.compatibilityMode);
|
||||
this.resolve({ ...this.config });
|
||||
this.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { App, ExtraButtonComponent, Modal, Notice, setIcon, Setting } from "obsi
|
|||
import Manager from "main";
|
||||
import { ReleaseVersion, fetchReleaseVersions } from "src/github-install";
|
||||
import { getExtraButtonElement } from "src/obsidian-internals";
|
||||
import { pickSourceTargetRelease, releaseIsCompatible } from "src/source-release";
|
||||
|
||||
export class UpdateModal extends Modal {
|
||||
private manager: Manager;
|
||||
|
|
@ -75,7 +76,7 @@ export class UpdateModal extends Modal {
|
|||
const loading = this.versionListEl.createDiv("manager-version-picker__empty");
|
||||
loading.setText(t("管理器_选择版本_获取中"));
|
||||
try {
|
||||
versionList = await fetchReleaseVersions(this.manager, this.repo);
|
||||
versionList = await fetchReleaseVersions(this.manager, this.repo, { includeManifest: true });
|
||||
} catch (e) {
|
||||
console.error("fetch versions in modal failed", e);
|
||||
new Notice(t("管理器_选择版本_获取失败提示"), 4000);
|
||||
|
|
@ -87,11 +88,22 @@ export class UpdateModal extends Modal {
|
|||
|
||||
const hasDefaultVersion = Boolean(this.defaultVersion && versionList.some((release) => release.version === this.defaultVersion));
|
||||
const hasLocalVersion = Boolean(manifest?.version && versionList.some((release) => release.version === manifest.version));
|
||||
const updateOptions = this.manager.getPluginUpdateCheckOptions();
|
||||
const target = this.repo ? pickSourceTargetRelease({
|
||||
id: this.pluginId,
|
||||
repo: this.repo,
|
||||
type: "plugin",
|
||||
mode: "latest",
|
||||
includePrerelease: false,
|
||||
updateCheckMode: updateOptions.updateCheckMode,
|
||||
compatibilityMode: updateOptions.compatibilityMode,
|
||||
updateDelayDays: updateOptions.updateDelayDays || undefined,
|
||||
autoUpdate: false,
|
||||
enabled: true,
|
||||
}, versionList) : null;
|
||||
this.selectedVersion = hasDefaultVersion
|
||||
? this.defaultVersion!
|
||||
: hasLocalVersion
|
||||
? manifest.version
|
||||
: (versionList[0]?.version ?? "");
|
||||
: target?.tag || (hasLocalVersion ? manifest.version : (versionList[0]?.version ?? ""));
|
||||
summaryStats.empty();
|
||||
const releaseCount = summaryStats.createSpan({ cls: "manager-version-picker__summary-stat" });
|
||||
setIcon(releaseCount.createSpan({ cls: "manager-version-picker__summary-stat-icon" }), "tags");
|
||||
|
|
@ -154,13 +166,23 @@ export class UpdateModal extends Modal {
|
|||
if (release.version === localVersion) {
|
||||
title.createSpan({ cls: "manager-version-picker__badge", text: t("来源_当前") });
|
||||
}
|
||||
if (release.isGithubLatest) {
|
||||
title.createSpan({ cls: "manager-version-picker__badge is-latest", text: t("兼容性_GitHubLatest") });
|
||||
}
|
||||
title.createSpan({
|
||||
cls: `manager-version-picker__badge ${release.prerelease ? "is-prerelease" : "is-stable"}`,
|
||||
text: t(release.prerelease ? "安装_发布类型_预发布" : "安装_发布类型_正式版"),
|
||||
});
|
||||
if (release.minAppVersion) {
|
||||
title.createSpan({
|
||||
cls: `manager-version-picker__badge ${releaseIsCompatible(release) ? "is-compatible" : "is-incompatible"}`,
|
||||
text: t(releaseIsCompatible(release) ? "兼容性_兼容" : "兼容性_不兼容"),
|
||||
});
|
||||
}
|
||||
|
||||
const meta = main.createDiv("manager-version-picker__item-meta");
|
||||
if (release.name && release.name !== release.version) meta.createSpan({ text: release.name });
|
||||
if (release.minAppVersion) meta.createSpan({ text: t("兼容性_需要版本", { version: release.minAppVersion }) });
|
||||
if (release.publishedAt) meta.createSpan({ text: this.formatDate(release.publishedAt) });
|
||||
|
||||
const check = item.createSpan({ cls: "manager-version-picker__item-check" });
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export type FilterOperator = "contains" | "not-contains";
|
|||
export type TagFilterOperator = FilterOperator;
|
||||
export type PluginOverviewLayout = "list" | "two-column";
|
||||
export type PluginUpdateCheckMode = NonNullable<BetaSource["updateCheckMode"]>;
|
||||
export type ReleaseCompatibilityMode = NonNullable<BetaSource["compatibilityMode"]>;
|
||||
|
||||
export const DEFAULT_MAIN_PAGE_ACTION_PLACEMENT: Record<MainPageActionId, MainPageActionPlacement> = {
|
||||
checkUpdate: "menu",
|
||||
|
|
@ -83,6 +84,8 @@ export interface ManagerSettings {
|
|||
SOURCE_AUTO_UPDATE: boolean;
|
||||
/** 插件总览更新检测的判定方式。release 按 GitHub 发布顺序,version 按版本号比较。 */
|
||||
PLUGIN_UPDATE_CHECK_MODE: PluginUpdateCheckMode;
|
||||
/** 插件总览更新检测是否按 manifest.minAppVersion 过滤不兼容版本。 */
|
||||
PLUGIN_UPDATE_COMPATIBILITY_MODE: ReleaseCompatibilityMode;
|
||||
/** 插件总览更新检测延迟天数;只把发布满指定天数的版本视为可更新。 */
|
||||
PLUGIN_UPDATE_DELAY_DAYS: number;
|
||||
/** GitHub 请求中转站;留空时直连官方 GitHub。支持前缀或 {url}/{encodedUrl} 模板。 */
|
||||
|
|
@ -188,6 +191,7 @@ export const DEFAULT_SETTINGS: ManagerSettings = {
|
|||
SOURCE_STARTUP_CHECK_UPDATES: false,
|
||||
SOURCE_AUTO_UPDATE: true,
|
||||
PLUGIN_UPDATE_CHECK_MODE: "release",
|
||||
PLUGIN_UPDATE_COMPATIBILITY_MODE: "compatible",
|
||||
PLUGIN_UPDATE_DELAY_DAYS: 0,
|
||||
GITHUB_PROXY: "",
|
||||
GITHUB_TOKEN: "",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { requireApiVersion } from "obsidian";
|
||||
import type { BetaSource } from "./data/types";
|
||||
import type { ReleaseVersion } from "./github-install";
|
||||
|
||||
|
|
@ -6,6 +7,7 @@ export interface SourceReleaseRef {
|
|||
publishedAt?: string;
|
||||
prerelease?: boolean;
|
||||
index?: number;
|
||||
isGithubLatest?: boolean;
|
||||
}
|
||||
|
||||
export interface SourceReleaseCheck {
|
||||
|
|
@ -35,6 +37,7 @@ const releaseToRef = (release: ReleaseVersion, index: number): SourceReleaseRef
|
|||
publishedAt: release.publishedAt,
|
||||
prerelease: release.prerelease,
|
||||
index,
|
||||
isGithubLatest: release.isGithubLatest,
|
||||
});
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
|
@ -61,6 +64,16 @@ const releaseIsMatureEnough = (source: BetaSource, release: ReleaseVersion, now
|
|||
return publishedAt <= now - delayDays * DAY_MS;
|
||||
};
|
||||
|
||||
export const releaseIsCompatible = (release: ReleaseVersion): boolean => {
|
||||
const minAppVersion = release.minAppVersion?.trim();
|
||||
if (!minAppVersion) return true;
|
||||
try {
|
||||
return requireApiVersion(minAppVersion);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const sourceTargetIsMatureEnough = (source: BetaSource): boolean => {
|
||||
const delayDays = normalizeUpdateDelayDays(source);
|
||||
if (delayDays <= 0) return true;
|
||||
|
|
@ -91,8 +104,16 @@ export const pickSourceTargetRelease = (source: BetaSource, versions: ReleaseVer
|
|||
};
|
||||
}
|
||||
|
||||
const release = (source.includePrerelease ? versions : versions.filter((item) => !item.prerelease))
|
||||
.find((item) => releaseIsMatureEnough(source, item));
|
||||
const compatibleVersions = source.compatibilityMode === "all"
|
||||
? versions
|
||||
: versions.filter((item) => releaseIsCompatible(item));
|
||||
const candidates = source.includePrerelease
|
||||
? compatibleVersions
|
||||
: compatibleVersions.filter((item) => !item.prerelease);
|
||||
const githubLatest = candidates.find((item) => item.isGithubLatest);
|
||||
const release = githubLatest && releaseIsMatureEnough(source, githubLatest)
|
||||
? githubLatest
|
||||
: candidates.find((item) => releaseIsMatureEnough(source, item));
|
||||
if (!release) return null;
|
||||
return releaseToRef(release, versions.indexOf(release));
|
||||
};
|
||||
|
|
@ -136,11 +157,13 @@ export const syncSourceReleaseCheck = (
|
|||
source.localVersion = localVersion || source.localVersion || "";
|
||||
source.latestReleaseTag = target?.tag || "";
|
||||
source.latestReleasePublishedAt = target?.publishedAt;
|
||||
source.latestReleaseIsGithubLatest = target?.isGithubLatest;
|
||||
source.latestVersion = target?.tag || "";
|
||||
source.latestPublishedAt = target?.publishedAt;
|
||||
|
||||
if (installed?.tag) source.installedReleaseTag = installed.tag;
|
||||
if (installed?.publishedAt) source.installedReleasePublishedAt = installed.publishedAt;
|
||||
source.installedReleaseIsGithubLatest = installed?.isGithubLatest;
|
||||
|
||||
return { target, installed };
|
||||
};
|
||||
|
|
@ -154,6 +177,9 @@ export const markSourceInstalledRelease = (
|
|||
const tag = cleanReleaseTag(releaseTag);
|
||||
if (tag) source.installedReleaseTag = tag;
|
||||
source.installedReleasePublishedAt = publishedAt || source.installedReleasePublishedAt;
|
||||
if (releaseTagsMatch(tag, source.latestReleaseTag)) {
|
||||
source.installedReleaseIsGithubLatest = source.latestReleaseIsGithubLatest;
|
||||
}
|
||||
source.localVersion = localVersion || source.localVersion || tag;
|
||||
};
|
||||
|
||||
|
|
@ -197,6 +223,7 @@ export const sourceHasReleaseUpdate = (source: BetaSource): boolean => {
|
|||
if (!targetTag || !installedTag) return false;
|
||||
if (releaseTagsMatch(targetTag, installedTag)) return false;
|
||||
if (!sourceTargetIsMatureEnough(source)) return false;
|
||||
if (source.compatibilityMode !== "all" && source.installedReleaseIsGithubLatest && !source.latestReleaseIsGithubLatest) return false;
|
||||
|
||||
if (source.mode === "frozen") return true;
|
||||
|
||||
|
|
|
|||
294
styles.css
294
styles.css
|
|
@ -9480,6 +9480,19 @@ body.theme-dark {
|
|||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.manager-version-picker__badge.is-compatible,
|
||||
.manager-version-picker__badge.is-latest {
|
||||
border-color: color-mix(in srgb, var(--interactive-accent) 42%, var(--bpm-border));
|
||||
background: color-mix(in srgb, var(--interactive-accent) 10%, var(--bpm-card));
|
||||
color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.manager-version-picker__badge.is-incompatible {
|
||||
border-color: color-mix(in srgb, var(--text-error) 48%, var(--bpm-border));
|
||||
background: color-mix(in srgb, var(--text-error) 10%, var(--bpm-card));
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
.manager-version-picker__item-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -9905,11 +9918,11 @@ body.theme-dark {
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.manager-header {
|
||||
height: var(--bpm-ui-header-height);
|
||||
min-height: var(--bpm-ui-header-height);
|
||||
padding-right: 12px;
|
||||
@media (max-width: 520px) {
|
||||
.manager-header {
|
||||
height: var(--bpm-ui-header-height);
|
||||
min-height: var(--bpm-ui-header-height);
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
|
|
@ -9921,6 +9934,277 @@ body.theme-dark {
|
|||
}
|
||||
}
|
||||
|
||||
/* Source subscription cards: separated identity, status, configuration, and actions. */
|
||||
.manager-source-card {
|
||||
position: relative;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--bpm-border) 70%, transparent);
|
||||
background: color-mix(in srgb, var(--bpm-card) 94%, var(--bpm-surface));
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.manager-source-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.manager-source-card:hover {
|
||||
border-color: color-mix(in srgb, var(--text-muted) 28%, var(--bpm-border));
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.manager-source-card.has-update {
|
||||
border-color: color-mix(in srgb, var(--color-green) 24%, var(--bpm-border));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
color-mix(in srgb, var(--color-green) 5%, var(--bpm-card)) 0%,
|
||||
var(--bpm-card) 22%
|
||||
);
|
||||
}
|
||||
|
||||
.manager-source-card.has-update::before {
|
||||
background: color-mix(in srgb, var(--color-green) 72%, var(--bpm-border));
|
||||
}
|
||||
|
||||
.manager-source-card.has-error {
|
||||
border-color: color-mix(in srgb, var(--text-error) 28%, var(--bpm-border));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
color-mix(in srgb, var(--text-error) 5%, var(--bpm-card)) 0%,
|
||||
var(--bpm-card) 22%
|
||||
);
|
||||
}
|
||||
|
||||
.manager-source-card.has-error::before {
|
||||
background: color-mix(in srgb, var(--text-error) 74%, var(--bpm-border));
|
||||
}
|
||||
|
||||
.manager-source-card__header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(180px, auto);
|
||||
align-items: start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.manager-source-card__title-block,
|
||||
.manager-source-card__repo-line {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-source-card__repo {
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.manager-source-card__chips {
|
||||
justify-content: flex-end;
|
||||
align-content: flex-start;
|
||||
max-width: 430px;
|
||||
}
|
||||
|
||||
.manager-source-card__meta-grid {
|
||||
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
||||
}
|
||||
|
||||
.manager-source-card__meta {
|
||||
grid-template-columns: 16px minmax(0, 1fr);
|
||||
grid-template-areas:
|
||||
"icon label"
|
||||
"icon value";
|
||||
align-items: center;
|
||||
gap: 3px 7px;
|
||||
min-height: 42px;
|
||||
padding: 7px 8px;
|
||||
border-color: color-mix(in srgb, var(--bpm-border) 46%, transparent);
|
||||
background: color-mix(in srgb, var(--bpm-muted) 34%, transparent);
|
||||
}
|
||||
|
||||
.manager-source-card__meta-icon {
|
||||
grid-area: icon;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.manager-source-card__meta-label {
|
||||
grid-area: label;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.manager-source-card__meta-value {
|
||||
grid-area: value;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.manager-source-card__controls {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(230px, 0.65fr);
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.manager-source-card__control-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
padding: 9px;
|
||||
border: 0;
|
||||
border-radius: var(--bpm-radius-sm);
|
||||
background: color-mix(in srgb, var(--bpm-muted) 34%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bpm-border) 42%, transparent);
|
||||
}
|
||||
|
||||
.manager-source-card__control-panel--update {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.manager-source-card__control-group,
|
||||
.manager-source-card__control-group--strategy,
|
||||
.manager-source-card__control-group--compatibility,
|
||||
.manager-source-card__control-group--auto {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-source-card__control-group--strategy,
|
||||
.manager-source-card__control-group--compatibility {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.manager-source-card__control-group--compatibility .manager-source-item__mode {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manager-source-card__control-label {
|
||||
flex: 0 0 auto;
|
||||
white-space: normal;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.manager-source-card__toggle-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-source-card__control-group--auto,
|
||||
.manager-source-card__control-group--pre {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.manager-source-item__strategy {
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
gap: 7px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manager-source-item__mode,
|
||||
.manager-source-item__version {
|
||||
flex: 1 1 124px;
|
||||
width: auto;
|
||||
min-width: 118px;
|
||||
}
|
||||
|
||||
.manager-source-item__check-mode {
|
||||
min-width: 132px;
|
||||
}
|
||||
|
||||
.manager-source-item__delay {
|
||||
flex: 1 1 132px;
|
||||
justify-content: space-between;
|
||||
min-width: 128px;
|
||||
}
|
||||
|
||||
.manager-source-item__delay-input {
|
||||
flex: 0 0 58px;
|
||||
width: 58px;
|
||||
}
|
||||
|
||||
.manager-source-item__toggle {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manager-source-card__actions {
|
||||
align-self: stretch;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(34px, 1fr));
|
||||
gap: 6px;
|
||||
justify-content: stretch;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--bpm-border) 44%, transparent);
|
||||
}
|
||||
|
||||
.manager-source-card__actions button {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-source-card__actions button:disabled {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.manager-source-card__header,
|
||||
.manager-source-card__controls {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.manager-source-card__chips {
|
||||
justify-content: flex-start;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.manager-source-card__meta-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.manager-source-card__toggle-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.manager-source-card__actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.manager-source-card__actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manager-source-item__mode,
|
||||
.manager-source-item__version,
|
||||
.manager-source-item__delay {
|
||||
flex: 1 1 148px;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manager-source-item__delay-input {
|
||||
flex: 0 0 58px;
|
||||
width: 58px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Final uninstall modal overrides after legacy delete rules. */
|
||||
.manager-delete__title {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in a new issue