mirror of
https://github.com/eondrcode/obsidian-manager.git
synced 2026-07-22 11:40:24 +00:00
chore: 1.0.4
This commit is contained in:
parent
fbbf5c1a5d
commit
60330e191c
9 changed files with 2108 additions and 212 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export interface ReleaseVersion {
|
|||
}
|
||||
|
||||
const API_BASE = "https://api.github.com";
|
||||
const RELEASES_PER_PAGE = 50;
|
||||
const RELEASES_PER_PAGE = 100;
|
||||
const RELEASE_VERSION_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
|
|
@ -292,9 +292,14 @@ export const fetchReleaseVersions = async (manager: Manager, repoInput: string):
|
|||
return cloneReleaseVersions(cached.versions);
|
||||
}
|
||||
|
||||
const url = `${API_BASE}/repos/${repo}/releases?per_page=${RELEASES_PER_PAGE}`;
|
||||
const releases = await fetchJson<ReleaseResponse[]>(url, token);
|
||||
if (!Array.isArray(releases)) return [];
|
||||
const releases: ReleaseResponse[] = [];
|
||||
for (let page = 1; ; page++) {
|
||||
const url = `${API_BASE}/repos/${repo}/releases?per_page=${RELEASES_PER_PAGE}&page=${page}`;
|
||||
const pageReleases = await fetchJson<ReleaseResponse[]>(url, token);
|
||||
if (!Array.isArray(pageReleases) || pageReleases.length === 0) break;
|
||||
releases.push(...pageReleases);
|
||||
if (pageReleases.length < RELEASES_PER_PAGE) break;
|
||||
}
|
||||
|
||||
const versions = releases
|
||||
.map((release) => ({
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ export default {
|
|||
通用_操作_文本: 'Operation',
|
||||
通用_搜索_文本: 'Search',
|
||||
通用_名称_文本: 'Name',
|
||||
通用_ID_文本: 'ID',
|
||||
通用_颜色_文本: 'Color',
|
||||
通用_无分组_文本: 'ALL',
|
||||
通用_无标签_文本: 'ALL',
|
||||
通用_无延迟_文本: 'No Delay',
|
||||
|
|
@ -90,7 +92,13 @@ export default {
|
|||
管理器_选择版本_获取失败提示: 'Failed to fetch version list. Please try again later.',
|
||||
管理器_选择版本_无版本提示: 'No version list available. Will try the detected or latest version.',
|
||||
管理器_选择版本_下载按钮: 'Download update',
|
||||
管理器_选择版本_切换按钮: 'Switch to selected version',
|
||||
管理器_选择版本_成功提示: 'Downloaded and updated plugin',
|
||||
管理器_选择版本_说明: 'Choose the GitHub Release version to install.',
|
||||
管理器_选择版本_列表标题: 'Available versions',
|
||||
管理器_选择版本_当前版本: 'Installed: {version}',
|
||||
管理器_选择版本_版本数量: '{count} versions',
|
||||
管理器_选择版本_选择版本: 'Select version {version}',
|
||||
|
||||
筛选_全部_描述: 'All',
|
||||
筛选_仅启用_描述: 'Enabled only',
|
||||
|
|
@ -469,6 +477,27 @@ export default {
|
|||
管理器_应用更改中_提示: 'Applying changes. Please avoid repeated actions.',
|
||||
管理器_暂无匹配插件: 'No matching plugins',
|
||||
管理器_安装_操作_安装中: 'Installing...',
|
||||
批量编辑_入口: 'Bulk edit',
|
||||
批量编辑_标题: 'Bulk edit',
|
||||
批量编辑_已选择数量: 'Selected {count}/{total}',
|
||||
批量编辑_全选当前列表: 'Select current list',
|
||||
批量编辑_清空选择: 'Clear selection',
|
||||
批量编辑_设置分组: 'Set group',
|
||||
批量编辑_清除分组: 'Clear group',
|
||||
批量编辑_添加标签: 'Add tag',
|
||||
批量编辑_移除标签: 'Remove tag',
|
||||
批量编辑_清除全部标签: 'Clear all tags',
|
||||
批量编辑_设置延迟: 'Set delay',
|
||||
批量编辑_选择插件: 'Select {name}',
|
||||
批量编辑_清除全部标签确认: 'Clear all tags from {count} plugins? Built-in BPM tags will be kept.',
|
||||
批量编辑_启用确认: 'Enable {count} plugins?',
|
||||
批量编辑_禁用确认: 'Disable {count} plugins?',
|
||||
批量编辑_无可操作插件: 'No actionable plugins',
|
||||
批量编辑_已更新分组: 'Updated group for {count} plugins',
|
||||
批量编辑_已添加标签: 'Added tag to {count} plugins',
|
||||
批量编辑_已移除标签: 'Updated tags for {count} plugins',
|
||||
批量编辑_已更新延迟: 'Updated delay for {count} plugins',
|
||||
批量编辑_已更新状态: 'Updated status for {count} plugins',
|
||||
更新_未获取到远端版本: 'Remote version not found',
|
||||
更新_无来源无法检测: 'No source available, cannot check',
|
||||
开发_刷新插件_提示: 'Refresh plugin',
|
||||
|
|
@ -570,6 +599,7 @@ export default {
|
|||
分组编辑_分组名称_标签: 'Group name',
|
||||
分组编辑_创建分组: 'Create group',
|
||||
分组编辑_创建全局分组: 'Create a new global group.',
|
||||
分组编辑_打开切换: 'Change group for {name}',
|
||||
标签编辑_标题: 'Edit Tags',
|
||||
标签编辑_说明: 'Assign tags to the current plugin, or maintain tag names and colors directly.',
|
||||
标签编辑_分配标题: 'Tag assignment',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ export default {
|
|||
通用_操作_文本: '操作',
|
||||
通用_搜索_文本: '搜索',
|
||||
通用_名称_文本: '名称',
|
||||
通用_ID_文本: 'ID',
|
||||
通用_颜色_文本: '颜色',
|
||||
通用_无分组_文本: '全部',
|
||||
通用_无标签_文本: '全部',
|
||||
通用_无延迟_文本: '无',
|
||||
|
|
@ -149,7 +151,13 @@ export default {
|
|||
管理器_选择版本_获取失败提示: '获取版本列表失败,请稍后再试',
|
||||
管理器_选择版本_无版本提示: '未获取到版本列表,将尝试使用检测到的版本或最新版本。',
|
||||
管理器_选择版本_下载按钮: '下载更新',
|
||||
管理器_选择版本_切换按钮: '切换到所选版本',
|
||||
管理器_选择版本_成功提示: '已下载并更新插件',
|
||||
管理器_选择版本_说明: '选择要安装的 GitHub Release 版本。',
|
||||
管理器_选择版本_列表标题: '可用版本',
|
||||
管理器_选择版本_当前版本: '当前安装:{version}',
|
||||
管理器_选择版本_版本数量: '{count} 个版本',
|
||||
管理器_选择版本_选择版本: '选择版本 {version}',
|
||||
|
||||
通知_ID已复制: 'ID已复制',
|
||||
|
||||
|
|
@ -471,6 +479,27 @@ export default {
|
|||
管理器_应用更改中_提示: '正在应用更改,请勿频繁操作。',
|
||||
管理器_暂无匹配插件: '暂无匹配插件',
|
||||
管理器_安装_操作_安装中: '安装中...',
|
||||
批量编辑_入口: '批量编辑',
|
||||
批量编辑_标题: '批量编辑',
|
||||
批量编辑_已选择数量: '已选择 {count}/{total}',
|
||||
批量编辑_全选当前列表: '选择当前列表',
|
||||
批量编辑_清空选择: '清空选择',
|
||||
批量编辑_设置分组: '设置分组',
|
||||
批量编辑_清除分组: '清除分组',
|
||||
批量编辑_添加标签: '添加标签',
|
||||
批量编辑_移除标签: '移除标签',
|
||||
批量编辑_清除全部标签: '清除全部标签',
|
||||
批量编辑_设置延迟: '设置延迟',
|
||||
批量编辑_选择插件: '选择 {name}',
|
||||
批量编辑_清除全部标签确认: '确定清除 {count} 个插件的全部标签吗?BPM 内置标签会保留。',
|
||||
批量编辑_启用确认: '确定启用 {count} 个插件吗?',
|
||||
批量编辑_禁用确认: '确定禁用 {count} 个插件吗?',
|
||||
批量编辑_无可操作插件: '没有可操作的插件',
|
||||
批量编辑_已更新分组: '已更新 {count} 个插件的分组',
|
||||
批量编辑_已添加标签: '已为 {count} 个插件添加标签',
|
||||
批量编辑_已移除标签: '已更新 {count} 个插件的标签',
|
||||
批量编辑_已更新延迟: '已更新 {count} 个插件的延迟',
|
||||
批量编辑_已更新状态: '已更新 {count} 个插件的状态',
|
||||
更新_未获取到远端版本: '未获取到远端版本',
|
||||
更新_无来源无法检测: '无来源,无法检测',
|
||||
开发_刷新插件_提示: '刷新插件',
|
||||
|
|
@ -572,6 +601,7 @@ export default {
|
|||
分组编辑_分组名称_标签: '分组名称',
|
||||
分组编辑_创建分组: '创建分组',
|
||||
分组编辑_创建全局分组: '创建新的全局分组。',
|
||||
分组编辑_打开切换: '切换 {name} 的分组',
|
||||
标签编辑_标题: '编辑标签',
|
||||
标签编辑_说明: '为当前插件分配标签,或直接维护标签名称与颜色。',
|
||||
标签编辑_分配标题: '标签分配',
|
||||
|
|
|
|||
|
|
@ -83,8 +83,15 @@ export class GroupModal extends Modal {
|
|||
summaryMain.createDiv({ cls: 'manager-tag-editor__summary-title', text: t('分组编辑_选择标题') });
|
||||
summaryMain.createDiv({ cls: 'manager-tag-editor__summary-desc', text: t('分组编辑_选择说明', { id: this.managerPlugin.id, selected: selectedCount, total: this.settings.GROUPS.length }) });
|
||||
const summaryStats = summary.createDiv('manager-tag-editor__summary-stats');
|
||||
summaryStats.createSpan({ cls: 'manager-tag-editor__summary-stat', text: t('设置_分类_统计', { label: t('通用_全部_文本'), count: this.settings.GROUPS.length }) });
|
||||
summaryStats.createSpan({ cls: 'manager-tag-editor__summary-stat', text: t('设置_分类_统计', { label: t('通用_已选择_文本'), count: selectedCount }) });
|
||||
const createSummaryStat = (label: string, value: number, icon: string) => {
|
||||
const stat = summaryStats.createSpan({ cls: 'manager-tag-editor__summary-stat' });
|
||||
const statIcon = stat.createSpan({ cls: 'manager-tag-editor__summary-stat-icon' });
|
||||
setIcon(statIcon, icon);
|
||||
stat.createSpan({ cls: 'manager-tag-editor__summary-stat-label', text: label });
|
||||
stat.createSpan({ cls: 'manager-tag-editor__summary-stat-value', text: `${value}` });
|
||||
};
|
||||
createSummaryStat(t('通用_全部_文本'), this.settings.GROUPS.length, 'folders');
|
||||
createSummaryStat(t('通用_已选择_文本'), selectedCount, 'check');
|
||||
|
||||
const list = page.createDiv('manager-tag-editor__list');
|
||||
for (const group of this.settings.GROUPS) {
|
||||
|
|
@ -103,7 +110,8 @@ export class GroupModal extends Modal {
|
|||
const previewLine = itemEl.nameEl.createDiv('manager-tag-editor__preview-line');
|
||||
const tag = this.manager.createTag(group.name || group.id, group.color, this.settings.GROUP_STYLE);
|
||||
tag.addClass('manager-tag-editor__chip');
|
||||
previewLine.appendChild(tag);
|
||||
const chipSlot = previewLine.createSpan({ cls: 'manager-tag-editor__chip-slot' });
|
||||
chipSlot.appendChild(tag);
|
||||
previewLine.createSpan({ cls: 'manager-tag-editor__id', text: group.id });
|
||||
if (selected) previewLine.createSpan({ cls: 'manager-tag-editor__badge is-assigned', text: t('通用_已选择_文本') });
|
||||
|
||||
|
|
@ -181,54 +189,103 @@ export class GroupModal extends Modal {
|
|||
}
|
||||
}
|
||||
if (this.add) {
|
||||
let id = '';
|
||||
let name = '';
|
||||
let color = this.pickDistinctColor(this.settings.GROUPS.map(g => g.color));
|
||||
const foodBar = new Setting(page).setClass('manager-bar__title');
|
||||
foodBar.settingEl.addClass('manager-tag-editor__add-panel');
|
||||
foodBar.nameEl.empty();
|
||||
foodBar.descEl.empty();
|
||||
const addTitle = foodBar.nameEl.createDiv('manager-tag-editor__add-title');
|
||||
const addIcon = addTitle.createSpan({ cls: 'manager-tag-editor__add-title-icon' });
|
||||
setIcon(addIcon, 'plus');
|
||||
addTitle.createSpan({ text: t('设置_分组设置_新增分组') });
|
||||
foodBar.descEl.setText(t('设置_分类_ID说明'));
|
||||
foodBar.addColorPicker(cb => cb
|
||||
.setValue(color)
|
||||
.onChange((value) => {
|
||||
color = value;
|
||||
})
|
||||
)
|
||||
foodBar.addText(cb => cb
|
||||
.setPlaceholder('ID')
|
||||
.onChange((value) => { id = value; })
|
||||
.inputEl.addClass('manager-editor__item-input')
|
||||
)
|
||||
foodBar.addText(cb => cb
|
||||
.setPlaceholder(this.manager.translator.t('通用_名称_文本'))
|
||||
.onChange((value) => { name = value; })
|
||||
.inputEl.addClass('manager-editor__item-input')
|
||||
)
|
||||
foodBar.addExtraButton((cb) => {
|
||||
cb.setIcon('plus');
|
||||
this.prepareIconButton(cb, t('分组编辑_创建分组'), 'manager-tag-editor__save-button');
|
||||
cb.onClick(() => {
|
||||
const nextId = id.trim();
|
||||
const nextName = name.trim() || nextId;
|
||||
const containsId = this.manager.settings.GROUPS.some(tag => tag.id === nextId);
|
||||
if (!containsId && nextId !== '') {
|
||||
if (color === '') color = this.pickDistinctColor(this.settings.GROUPS.map(g => g.color));
|
||||
this.manager.settings.GROUPS.push({ id: nextId, name: nextName, color });
|
||||
this.manager.saveSettings();
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
Commands(this.app, this.manager);
|
||||
new Notice(this.manager.translator.t('设置_分组设置_通知_一'));
|
||||
} else {
|
||||
new Notice(this.manager.translator.t('设置_分组设置_通知_二'));
|
||||
}
|
||||
});
|
||||
})
|
||||
const addPanel = page.createDiv('manager-tag-editor__create-panel');
|
||||
const preview = addPanel.createDiv('manager-tag-editor__create-preview');
|
||||
const previewMain = preview.createDiv('manager-tag-editor__create-preview-main');
|
||||
const previewChip = this.manager.createTag(t('通用_名称_文本'), color, this.settings.GROUP_STYLE);
|
||||
previewChip.addClass('manager-tag-editor__chip');
|
||||
previewChip.addClass('manager-tag-editor__create-chip');
|
||||
const previewChipSlot = previewMain.createSpan({ cls: 'manager-tag-editor__chip-slot manager-tag-editor__chip-slot--preview' });
|
||||
previewChipSlot.appendChild(previewChip);
|
||||
const previewText = previewMain.createDiv('manager-tag-editor__create-preview-text');
|
||||
previewText.createDiv({ cls: 'manager-tag-editor__create-title', text: t('设置_分组设置_新增分组') });
|
||||
const previewId = previewText.createDiv({ cls: 'manager-tag-editor__create-id', text: 'ID' });
|
||||
preview.createDiv({ cls: 'manager-tag-editor__create-desc', text: t('设置_分类_新增描述') });
|
||||
|
||||
const form = addPanel.createDiv('manager-tag-editor__create-form');
|
||||
const createField = (label: string, className = '') => {
|
||||
const field = form.createDiv(`manager-tag-editor__create-field ${className}`);
|
||||
field.createDiv({ cls: 'manager-tag-editor__create-label', text: label });
|
||||
return field;
|
||||
};
|
||||
const colorField = createField(t('通用_颜色_文本'), 'manager-tag-editor__create-field--color');
|
||||
const colorInput = colorField.createEl('input');
|
||||
colorInput.type = 'color';
|
||||
colorInput.value = color;
|
||||
colorInput.addClass('manager-tag-editor__create-color');
|
||||
colorInput.setAttribute('aria-label', t('通用_颜色_文本'));
|
||||
|
||||
const idField = createField(t('通用_ID_文本'));
|
||||
const idInput = idField.createEl('input');
|
||||
idInput.type = 'text';
|
||||
idInput.placeholder = 'group-id';
|
||||
idInput.spellcheck = false;
|
||||
idInput.addClass('manager-editor__item-input');
|
||||
idInput.addClass('manager-tag-editor__create-input');
|
||||
idInput.setAttribute('aria-label', t('通用_ID_文本'));
|
||||
|
||||
const nameField = createField(t('通用_名称_文本'));
|
||||
const nameInput = nameField.createEl('input');
|
||||
nameInput.type = 'text';
|
||||
nameInput.placeholder = t('通用_名称_文本');
|
||||
nameInput.addClass('manager-editor__item-input');
|
||||
nameInput.addClass('manager-tag-editor__create-input');
|
||||
nameInput.setAttribute('aria-label', t('通用_名称_文本'));
|
||||
|
||||
const updatePreview = () => {
|
||||
const nextId = idInput.value.trim();
|
||||
const nextName = nameInput.value.trim();
|
||||
previewChip.textContent = nextName || nextId || t('通用_名称_文本');
|
||||
previewChip.setAttribute('style', this.manager.generateTagStyle(color, this.settings.GROUP_STYLE));
|
||||
previewId.setText(nextId || 'ID');
|
||||
};
|
||||
const submit = () => {
|
||||
const nextId = idInput.value.trim();
|
||||
const nextName = nameInput.value.trim() || nextId;
|
||||
const containsId = this.manager.settings.GROUPS.some(tag => tag.id === nextId);
|
||||
if (!containsId && nextId !== '') {
|
||||
this.manager.settings.GROUPS.push({ id: nextId, name: nextName, color });
|
||||
this.manager.saveSettings();
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
Commands(this.app, this.manager);
|
||||
new Notice(this.manager.translator.t('设置_分组设置_通知_一'));
|
||||
} else {
|
||||
new Notice(this.manager.translator.t('设置_分组设置_通知_二'));
|
||||
}
|
||||
};
|
||||
const onEnter = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'Enter') return;
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
colorInput.addEventListener('input', () => {
|
||||
color = colorInput.value;
|
||||
updatePreview();
|
||||
});
|
||||
idInput.addEventListener('input', () => updatePreview());
|
||||
nameInput.addEventListener('input', () => updatePreview());
|
||||
idInput.addEventListener('keydown', onEnter);
|
||||
nameInput.addEventListener('keydown', onEnter);
|
||||
|
||||
const actions = addPanel.createDiv('manager-tag-editor__create-actions');
|
||||
const cancelButton = actions.createEl('button', { cls: 'manager-tag-editor__create-action' });
|
||||
cancelButton.type = 'button';
|
||||
cancelButton.setAttribute('aria-label', t('通用_取消_文本'));
|
||||
setIcon(cancelButton.createSpan({ cls: 'manager-tag-editor__create-action-icon' }), 'x');
|
||||
cancelButton.createSpan({ text: t('通用_取消_文本') });
|
||||
cancelButton.addEventListener('click', () => {
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
});
|
||||
const submitButton = actions.createEl('button', { cls: 'manager-tag-editor__create-action manager-tag-editor__create-action--primary' });
|
||||
submitButton.type = 'button';
|
||||
submitButton.setAttribute('aria-label', t('分组编辑_创建分组'));
|
||||
setIcon(submitButton.createSpan({ cls: 'manager-tag-editor__create-action-icon' }), 'plus');
|
||||
submitButton.createSpan({ text: t('分组编辑_创建分组') });
|
||||
submitButton.addEventListener('click', submit);
|
||||
window.setTimeout(() => idInput.focus(), 0);
|
||||
} else {
|
||||
// [底部行] 新增
|
||||
const foodBar = new Setting(page).setClass('manager-bar__title').setName(this.manager.translator.t('通用_新增_文本'));
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ export class ManagerModal extends Modal {
|
|||
groupDropdown?: DropdownComponent;
|
||||
tagDropdown?: DropdownComponent;
|
||||
delayDropdown?: DropdownComponent;
|
||||
private bulkEditMode = false;
|
||||
private bulkSelectedPluginIds = new Set<string>();
|
||||
actionCollapsed = false;
|
||||
filterCollapsed = false;
|
||||
private reloadingManifests = false;
|
||||
|
|
@ -496,12 +498,50 @@ export class ManagerModal extends Modal {
|
|||
return ((button as any).extraSettingsEl || (button as any).buttonEl) as HTMLElement | undefined;
|
||||
}
|
||||
|
||||
private async openPluginVersionList(pluginId: string, updateInfo?: PluginUpdateViewStatus | null) {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const progress = this.showInlineProgress(t("通知_获取版本中文案"), pluginId);
|
||||
progress.update(0, 1, pluginId);
|
||||
try {
|
||||
let status = updateInfo ?? (this.manager.updateStatus?.[pluginId] as PluginUpdateViewStatus | undefined);
|
||||
let repo = status?.repo || this.manager.settings.REPO_MAP?.[pluginId] || null;
|
||||
if (!repo) {
|
||||
try {
|
||||
repo = await this.manager.repoResolver.resolveRepo(pluginId);
|
||||
} catch {
|
||||
repo = null;
|
||||
}
|
||||
}
|
||||
if (!repo) {
|
||||
new Notice(t("下载更新_缺少仓库提示"));
|
||||
return;
|
||||
}
|
||||
|
||||
const versions = await fetchReleaseVersions(this.manager, repo);
|
||||
if (!status) status = {};
|
||||
status.repo = repo;
|
||||
status.versions = versions;
|
||||
const stableVersion = versions.find((release) => !release.prerelease)?.version;
|
||||
status.remoteVersion = stableVersion || versions[0]?.version || status.remoteVersion || null;
|
||||
this.manager.updateStatus[pluginId] = {
|
||||
...(this.manager.updateStatus?.[pluginId] ?? {}),
|
||||
...status,
|
||||
} as any;
|
||||
|
||||
progress.update(1, 1, pluginId);
|
||||
this.refreshSinglePluginUpdateUi(pluginId);
|
||||
this.updateStats();
|
||||
new UpdateModal(this.app, this.manager, pluginId, versions, status.remoteVersion ?? null, repo).open();
|
||||
} catch (e) {
|
||||
console.error("[BPM] fetch remote versions failed", e);
|
||||
new Notice(t("管理器_选择版本_获取失败提示"), 4000);
|
||||
} finally {
|
||||
progress.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private openPluginUpdateModal(pluginId: string, updateInfo: PluginUpdateViewStatus) {
|
||||
if (!updateInfo.remoteVersion) return;
|
||||
const versions = updateInfo.versions && updateInfo.versions.length > 0
|
||||
? updateInfo.versions
|
||||
: [{ version: updateInfo.remoteVersion, prerelease: false }];
|
||||
new UpdateModal(this.app, this.manager, pluginId, versions, updateInfo.remoteVersion, updateInfo.repo || undefined).open();
|
||||
void this.openPluginVersionList(pluginId, updateInfo);
|
||||
}
|
||||
|
||||
private addPluginDownloadButton(controlEl: HTMLElement, pluginId: string, updateInfo: PluginUpdateViewStatus, prepend = false) {
|
||||
|
|
@ -723,6 +763,265 @@ export class ManagerModal extends Modal {
|
|||
await this.reloadShowData();
|
||||
}
|
||||
|
||||
private getSelectedManagerPlugins(): ManagerPlugin[] {
|
||||
return this.settings.Plugins.filter((plugin) => this.bulkSelectedPluginIds.has(plugin.id));
|
||||
}
|
||||
|
||||
private getSelectableDisplayedPluginIds(): string[] {
|
||||
return this.displayPlugins
|
||||
.map((plugin) => plugin.id)
|
||||
.filter((id) => id !== this.manager.manifest.id);
|
||||
}
|
||||
|
||||
private getBulkSelectedCount(): number {
|
||||
return this.getSelectedManagerPlugins().length;
|
||||
}
|
||||
|
||||
private cleanupBulkSelection() {
|
||||
const validIds = new Set(this.settings.Plugins.map((plugin) => plugin.id));
|
||||
for (const id of Array.from(this.bulkSelectedPluginIds)) {
|
||||
if (!validIds.has(id) || id === this.manager.manifest.id) this.bulkSelectedPluginIds.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
private setBulkEditMode(value: boolean) {
|
||||
if (value && this.activePage !== "plugins") return;
|
||||
this.bulkEditMode = value;
|
||||
if (value) this.editorMode = false;
|
||||
if (!value) this.bulkSelectedPluginIds.clear();
|
||||
this.applyEditingStyle();
|
||||
this.renderContent();
|
||||
if (Platform.isMobileApp) this.showHeadMobile();
|
||||
}
|
||||
|
||||
private toggleBulkPluginSelection(pluginId: string, selected: boolean) {
|
||||
if (pluginId === this.manager.manifest.id) return;
|
||||
if (selected) this.bulkSelectedPluginIds.add(pluginId);
|
||||
else this.bulkSelectedPluginIds.delete(pluginId);
|
||||
this.reloadShowData();
|
||||
}
|
||||
|
||||
private selectDisplayedPlugins() {
|
||||
this.bulkSelectedPluginIds = new Set(this.getSelectableDisplayedPluginIds());
|
||||
this.reloadShowData();
|
||||
}
|
||||
|
||||
private clearBulkSelection() {
|
||||
this.bulkSelectedPluginIds.clear();
|
||||
this.reloadShowData();
|
||||
}
|
||||
|
||||
private createBulkActionButton(container: HTMLElement, icon: string, label: string, onClick: (event: MouseEvent) => void, disabled = false) {
|
||||
const button = container.createEl("button", { cls: "manager-bulk-bar__button" });
|
||||
button.type = "button";
|
||||
button.disabled = disabled;
|
||||
button.setAttribute("aria-label", label);
|
||||
button.setAttribute("title", label);
|
||||
const iconEl = button.createSpan({ cls: "manager-bulk-bar__button-icon" });
|
||||
iconEl.setAttribute("aria-hidden", "true");
|
||||
setIcon(iconEl, icon);
|
||||
button.createSpan({ cls: "manager-bulk-bar__button-label", text: label });
|
||||
button.addEventListener("click", onClick);
|
||||
this.bindLongPressTooltip(button, label);
|
||||
return button;
|
||||
}
|
||||
|
||||
private showBulkGroupMenu(event: MouseEvent) {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("批量编辑_清除分组"))
|
||||
.setIcon("folder-x")
|
||||
.onClick(() => { void this.applyBulkGroup(""); }));
|
||||
if (this.settings.GROUPS.length > 0) menu.addSeparator();
|
||||
for (const group of this.settings.GROUPS) {
|
||||
menu.addItem((item) => item
|
||||
.setTitle(group.name || group.id)
|
||||
.setIcon("folder-tree")
|
||||
.onClick(() => { void this.applyBulkGroup(group.id); }));
|
||||
}
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showBulkTagMenu(event: MouseEvent, mode: "add" | "remove") {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const menu = new Menu();
|
||||
const tags = this.settings.TAGS.filter((tag) => tag.id !== BPM_TAG_ID);
|
||||
if (mode === "remove") {
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("批量编辑_清除全部标签"))
|
||||
.setIcon("tags")
|
||||
.onClick(() => { void this.applyBulkClearTags(); }));
|
||||
if (tags.length > 0) menu.addSeparator();
|
||||
}
|
||||
for (const tag of tags) {
|
||||
menu.addItem((item) => item
|
||||
.setTitle(tag.name || tag.id)
|
||||
.setIcon(mode === "add" ? "tag" : "tag-x")
|
||||
.onClick(() => {
|
||||
if (mode === "add") void this.applyBulkAddTag(tag.id);
|
||||
else void this.applyBulkRemoveTag(tag.id);
|
||||
}));
|
||||
}
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showBulkDelayMenu(event: MouseEvent) {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("通用_无延迟_文本"))
|
||||
.setIcon("timer-off")
|
||||
.onClick(() => { void this.applyBulkDelay(""); }));
|
||||
if (this.settings.DELAYS.length > 0) menu.addSeparator();
|
||||
for (const delay of this.settings.DELAYS) {
|
||||
menu.addItem((item) => item
|
||||
.setTitle(`${delay.name || delay.id} (${delay.time}s)`)
|
||||
.setIcon("timer")
|
||||
.onClick(() => { void this.applyBulkDelay(delay.id); }));
|
||||
}
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showBulkMoreMenu(event: MouseEvent) {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const selectedCount = this.getBulkSelectedCount();
|
||||
const disabled = selectedCount === 0;
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("通用_启用_文本"))
|
||||
.setIcon("power")
|
||||
.setDisabled(disabled)
|
||||
.onClick(() => { void this.applyBulkEnabled(true); }));
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("通用_禁用_文本"))
|
||||
.setIcon("power-off")
|
||||
.setDisabled(disabled)
|
||||
.onClick(() => { void this.applyBulkEnabled(false); }));
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("批量编辑_全选当前列表"))
|
||||
.setIcon("list-checks")
|
||||
.onClick(() => this.selectDisplayedPlugins()));
|
||||
menu.addItem((item) => item
|
||||
.setTitle(t("批量编辑_清空选择"))
|
||||
.setIcon("x")
|
||||
.setDisabled(this.bulkSelectedPluginIds.size === 0)
|
||||
.onClick(() => this.clearBulkSelection()));
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private renderBulkBar(container: HTMLElement) {
|
||||
if (!this.bulkEditMode) return;
|
||||
this.cleanupBulkSelection();
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const selectedCount = this.getBulkSelectedCount();
|
||||
const displayedCount = this.getSelectableDisplayedPluginIds().length;
|
||||
const disabled = selectedCount === 0;
|
||||
const bar = container.createDiv("manager-bulk-bar");
|
||||
const summary = bar.createDiv("manager-bulk-bar__summary");
|
||||
const summaryIcon = summary.createSpan({ cls: "manager-bulk-bar__summary-icon" });
|
||||
setIcon(summaryIcon, "square-check-big");
|
||||
const text = summary.createDiv("manager-bulk-bar__summary-text");
|
||||
text.createSpan({ cls: "manager-bulk-bar__title", text: t("批量编辑_标题") });
|
||||
text.createSpan({ cls: "manager-bulk-bar__count", text: t("批量编辑_已选择数量", { count: selectedCount, total: displayedCount }) });
|
||||
|
||||
const actions = bar.createDiv("manager-bulk-bar__actions");
|
||||
this.createBulkActionButton(actions, "list-checks", t("批量编辑_全选当前列表"), () => this.selectDisplayedPlugins(), displayedCount === 0);
|
||||
this.createBulkActionButton(actions, "folder-tree", t("批量编辑_设置分组"), (event) => this.showBulkGroupMenu(event), disabled);
|
||||
this.createBulkActionButton(actions, "tag", t("批量编辑_添加标签"), (event) => this.showBulkTagMenu(event, "add"), disabled);
|
||||
this.createBulkActionButton(actions, "tag-x", t("批量编辑_移除标签"), (event) => this.showBulkTagMenu(event, "remove"), disabled);
|
||||
if (this.settings.DELAY) this.createBulkActionButton(actions, "timer", t("批量编辑_设置延迟"), (event) => this.showBulkDelayMenu(event), disabled);
|
||||
this.createBulkActionButton(actions, "more-horizontal", t("管理器_更多操作_描述"), (event) => this.showBulkMoreMenu(event));
|
||||
this.createBulkActionButton(actions, "x", t("通用_完成_文本"), () => this.setBulkEditMode(false));
|
||||
}
|
||||
|
||||
private async applyBulkGroup(groupId: string) {
|
||||
const plugins = this.getSelectedManagerPlugins();
|
||||
plugins.forEach((plugin) => { plugin.group = groupId; });
|
||||
await this.finishBulkMetadataEdit("批量编辑_已更新分组", plugins.length);
|
||||
}
|
||||
|
||||
private async applyBulkAddTag(tagId: string) {
|
||||
const plugins = this.getSelectedManagerPlugins();
|
||||
plugins.forEach((plugin) => {
|
||||
if (!plugin.tags.includes(tagId)) plugin.tags.push(tagId);
|
||||
});
|
||||
await this.finishBulkMetadataEdit("批量编辑_已添加标签", plugins.length);
|
||||
}
|
||||
|
||||
private async applyBulkRemoveTag(tagId: string) {
|
||||
const plugins = this.getSelectedManagerPlugins();
|
||||
plugins.forEach((plugin) => {
|
||||
plugin.tags = plugin.tags.filter((id) => id !== tagId);
|
||||
});
|
||||
await this.finishBulkMetadataEdit("批量编辑_已移除标签", plugins.length);
|
||||
}
|
||||
|
||||
private async applyBulkClearTags() {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const plugins = this.getSelectedManagerPlugins();
|
||||
if (plugins.length === 0) return;
|
||||
if (!window.confirm(t("批量编辑_清除全部标签确认", { count: plugins.length }))) return;
|
||||
plugins.forEach((plugin) => {
|
||||
plugin.tags = plugin.tags.filter((id) => id === BPM_TAG_ID);
|
||||
});
|
||||
await this.finishBulkMetadataEdit("批量编辑_已移除标签", plugins.length);
|
||||
}
|
||||
|
||||
private async applyBulkDelay(delayId: string) {
|
||||
const plugins = this.getSelectedManagerPlugins().filter((plugin) => !plugin.tags.includes(BPM_IGNORE_TAG));
|
||||
if (plugins.length === 0) {
|
||||
new Notice(this.manager.translator.t("批量编辑_无可操作插件"));
|
||||
return;
|
||||
}
|
||||
plugins.forEach((plugin) => {
|
||||
plugin.delay = delayId;
|
||||
});
|
||||
await this.finishBulkMetadataEdit("批量编辑_已更新延迟", plugins.length);
|
||||
}
|
||||
|
||||
private async finishBulkMetadataEdit(messageKey: string, count: number) {
|
||||
if (count === 0) return;
|
||||
await this.manager.saveSettings();
|
||||
Commands(this.app, this.manager);
|
||||
await this.refreshFilterOptions(true);
|
||||
new Notice(this.manager.translator.t(messageKey, { count }));
|
||||
}
|
||||
|
||||
private async applyBulkEnabled(targetEnabled: boolean) {
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const plugins = this.getSelectedManagerPlugins()
|
||||
.filter((plugin) => plugin.id !== this.manager.manifest.id && !plugin.tags.includes(BPM_IGNORE_TAG));
|
||||
if (plugins.length === 0) {
|
||||
new Notice(t("批量编辑_无可操作插件"));
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(t(targetEnabled ? "批量编辑_启用确认" : "批量编辑_禁用确认", { count: plugins.length }))) return;
|
||||
const progress = this.showInlineProgress(t("管理器_应用更改中_提示"));
|
||||
let processed = 0;
|
||||
for (const plugin of plugins) {
|
||||
const isEnabled = this.isManagedPluginEnabled(plugin.id);
|
||||
if (isEnabled !== targetEnabled) {
|
||||
plugin.enabled = targetEnabled;
|
||||
if (this.settings.DELAY) {
|
||||
if (targetEnabled) await this.appPlugins.enablePlugin(plugin.id);
|
||||
else await this.appPlugins.disablePlugin(plugin.id);
|
||||
} else {
|
||||
if (targetEnabled) await this.appPlugins.enablePluginAndSave(plugin.id);
|
||||
else await this.appPlugins.disablePluginAndSave(plugin.id);
|
||||
}
|
||||
}
|
||||
processed++;
|
||||
progress.update(processed, plugins.length, plugin.id);
|
||||
}
|
||||
progress.hide();
|
||||
await this.manager.saveSettings();
|
||||
Commands(this.app, this.manager);
|
||||
await this.reloadShowData();
|
||||
new Notice(t("批量编辑_已更新状态", { count: plugins.length }));
|
||||
}
|
||||
|
||||
private runDisplayedPluginsToggle() {
|
||||
const targetEnabled = this.getBulkToggleTarget();
|
||||
new DisableModal(this.app, this.manager, async () => {
|
||||
|
|
@ -752,6 +1051,7 @@ export class ManagerModal extends Modal {
|
|||
modalContainer?: HTMLElement;
|
||||
private desktopActionWrapper?: HTMLElement;
|
||||
private desktopFilterWrapper?: HTMLElement;
|
||||
private bulkEditButtonEl?: HTMLButtonElement;
|
||||
private pluginTabEl?: HTMLButtonElement;
|
||||
private installTabEl?: HTMLButtonElement;
|
||||
private sourcesTabEl?: HTMLButtonElement;
|
||||
|
|
@ -936,19 +1236,32 @@ export class ManagerModal extends Modal {
|
|||
|
||||
const tools = toolbar.createDiv("manager-toolbar__tools");
|
||||
const actionBar = new Setting(tools).setClass("manager-bar__action").setName("");
|
||||
const markTool = (btn: ButtonComponent, scope: "plugin" | "install" | "global" | "ribbon" | "layout" | "transfer" | "resource") => {
|
||||
const markTool = (btn: ButtonComponent, scope: "plugin" | "install" | "global" | "ribbon" | "layout" | "transfer" | "resource", order?: number) => {
|
||||
btn.buttonEl.addClass("manager-tool");
|
||||
btn.buttonEl.addClass(`manager-tool--${scope}`);
|
||||
if (order !== undefined) btn.buttonEl.style.setProperty("--manager-tool-order", `${order}`);
|
||||
};
|
||||
|
||||
const bulkEditButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(bulkEditButton, "plugin", 20);
|
||||
bulkEditButton.setIcon(this.bulkEditMode ? "square-check-big" : "list-plus");
|
||||
bulkEditButton.setTooltip(t("批量编辑_入口"));
|
||||
bulkEditButton.buttonEl.setAttribute("aria-label", t("批量编辑_入口"));
|
||||
bulkEditButton.buttonEl.classList.toggle("is-active", this.bulkEditMode);
|
||||
this.bulkEditButtonEl = bulkEditButton.buttonEl;
|
||||
this.bindLongPressTooltip(bulkEditButton.buttonEl, t("批量编辑_入口"));
|
||||
bulkEditButton.onClick(() => {
|
||||
this.setBulkEditMode(!this.bulkEditMode);
|
||||
});
|
||||
|
||||
// [操作行] 检查更新
|
||||
const updateButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(updateButton, "plugin");
|
||||
markTool(updateButton, "plugin", 10);
|
||||
this.preparePluginUpdateButton(updateButton);
|
||||
|
||||
// [操作行] 全选/全部取消当前列表
|
||||
const toggleAllButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(toggleAllButton, "plugin");
|
||||
markTool(toggleAllButton, "plugin", 30);
|
||||
toggleAllButton.setIcon("list-checks");
|
||||
toggleAllButton.setTooltip(this.manager.translator.t("管理器_全选取消_描述"));
|
||||
this.bindLongPressTooltip(toggleAllButton.buttonEl, this.manager.translator.t("管理器_全选取消_描述"));
|
||||
|
|
@ -958,7 +1271,7 @@ export class ManagerModal extends Modal {
|
|||
|
||||
// [操作行] 重载插件
|
||||
const reloadButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(reloadButton, "plugin");
|
||||
markTool(reloadButton, "plugin", 40);
|
||||
reloadButton.setIcon("refresh-ccw");
|
||||
reloadButton.setTooltip(this.manager.translator.t("管理器_重载插件_描述"));
|
||||
this.bindLongPressTooltip(reloadButton.buttonEl, this.manager.translator.t("管理器_重载插件_描述"));
|
||||
|
|
@ -990,12 +1303,16 @@ export class ManagerModal extends Modal {
|
|||
|
||||
// [操作行] 编辑模式
|
||||
const editorButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(editorButton, "plugin");
|
||||
markTool(editorButton, "plugin", 50);
|
||||
this.editorMode ? editorButton.setIcon("pen-off") : editorButton.setIcon("pen");
|
||||
editorButton.setTooltip(this.manager.translator.t("管理器_编辑模式_描述"));
|
||||
this.bindLongPressTooltip(editorButton.buttonEl, this.manager.translator.t("管理器_编辑模式_描述"));
|
||||
editorButton.onClick(async () => {
|
||||
this.editorMode = !this.editorMode;
|
||||
if (this.editorMode && this.bulkEditMode) {
|
||||
this.bulkEditMode = false;
|
||||
this.bulkSelectedPluginIds.clear();
|
||||
}
|
||||
this.editorMode ? editorButton.setIcon("pen-off") : editorButton.setIcon("pen");
|
||||
this.applyEditingStyle();
|
||||
if (!this.editorMode) {
|
||||
|
|
@ -1006,7 +1323,7 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
|
||||
const ribbonResetButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(ribbonResetButton, "ribbon");
|
||||
markTool(ribbonResetButton, "ribbon", 10);
|
||||
ribbonResetButton.setIcon("rotate-ccw");
|
||||
ribbonResetButton.setTooltip(t("Ribbon_重置_提示"));
|
||||
ribbonResetButton.buttonEl.setAttribute("aria-label", t("Ribbon_重置_提示"));
|
||||
|
|
@ -1020,7 +1337,7 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
|
||||
const addSeparatorButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(addSeparatorButton, "layout");
|
||||
markTool(addSeparatorButton, "layout", 60);
|
||||
addSeparatorButton.setIcon("separator-horizontal");
|
||||
addSeparatorButton.setTooltip(t("管理器_布局_添加分割线"));
|
||||
addSeparatorButton.buttonEl.setAttribute("aria-label", t("管理器_布局_添加分割线"));
|
||||
|
|
@ -1030,7 +1347,7 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
|
||||
const hiddenResetButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(hiddenResetButton, "layout");
|
||||
markTool(hiddenResetButton, "layout", 61);
|
||||
hiddenResetButton.setIcon("rotate-ccw");
|
||||
hiddenResetButton.setTooltip(t("管理器_布局_按名称重置"));
|
||||
hiddenResetButton.buttonEl.setAttribute("aria-label", t("管理器_布局_按名称重置"));
|
||||
|
|
@ -1041,21 +1358,21 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
|
||||
const githubButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(githubButton, "resource");
|
||||
markTool(githubButton, "resource", 120);
|
||||
githubButton.setIcon("github");
|
||||
githubButton.setTooltip(this.manager.translator.t("管理器_GITHUB_描述"));
|
||||
this.bindLongPressTooltip(githubButton.buttonEl, this.manager.translator.t("管理器_GITHUB_描述"));
|
||||
githubButton.onClick(() => { window.open("https://github.com/zenozero-dev/obsidian-manager"); });
|
||||
|
||||
const tutorialButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(tutorialButton, "resource");
|
||||
markTool(tutorialButton, "resource", 110);
|
||||
tutorialButton.setIcon("book-open");
|
||||
tutorialButton.setTooltip(this.manager.translator.t("管理器_视频教程_描述"));
|
||||
this.bindLongPressTooltip(tutorialButton.buttonEl, this.manager.translator.t("管理器_视频教程_描述"));
|
||||
tutorialButton.onClick(() => { window.open("https://www.bilibili.com/video/BV1WyrkYMEce/"); });
|
||||
|
||||
const supportGroupButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(supportGroupButton, "resource");
|
||||
markTool(supportGroupButton, "resource", 130);
|
||||
supportGroupButton.setIcon("message-circle");
|
||||
supportGroupButton.setTooltip(SUPPORT_QQ_GROUP_TOOLTIP);
|
||||
this.bindLongPressTooltip(supportGroupButton.buttonEl, SUPPORT_QQ_GROUP_TOOLTIP);
|
||||
|
|
@ -1063,7 +1380,7 @@ export class ManagerModal extends Modal {
|
|||
|
||||
// [操作行] 插件市场
|
||||
const marketButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(marketButton, "global");
|
||||
markTool(marketButton, "global", 70);
|
||||
marketButton.setIcon("store");
|
||||
marketButton.setTooltip(this.manager.translator.t("管理器_插件市场_描述"));
|
||||
this.bindLongPressTooltip(marketButton.buttonEl, this.manager.translator.t("管理器_插件市场_描述"));
|
||||
|
|
@ -1073,7 +1390,7 @@ export class ManagerModal extends Modal {
|
|||
|
||||
// [操作行] 插件设置
|
||||
const settingsButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(settingsButton, "global");
|
||||
markTool(settingsButton, "global", 80);
|
||||
settingsButton.setIcon("settings");
|
||||
settingsButton.setTooltip(this.manager.translator.t("管理器_插件设置_描述"));
|
||||
this.bindLongPressTooltip(settingsButton.buttonEl, this.manager.translator.t("管理器_插件设置_描述"));
|
||||
|
|
@ -1087,7 +1404,7 @@ export class ManagerModal extends Modal {
|
|||
// [测试行] 刷新插件
|
||||
if (this.developerMode) {
|
||||
const testButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(testButton, "plugin");
|
||||
markTool(testButton, "plugin", 90);
|
||||
testButton.setIcon("refresh-ccw");
|
||||
testButton.setTooltip(t("开发_刷新插件_提示"));
|
||||
testButton.onClick(async () => {
|
||||
|
|
@ -1100,7 +1417,7 @@ export class ManagerModal extends Modal {
|
|||
// [测试行] 测试插件
|
||||
if (this.developerMode) {
|
||||
const testButton = new ButtonComponent(actionBar.controlEl);
|
||||
markTool(testButton, "plugin");
|
||||
markTool(testButton, "plugin", 91);
|
||||
testButton.setIcon("test-tube");
|
||||
testButton.setTooltip(t("开发_测试插件_提示"));
|
||||
testButton.onClick(async () => {
|
||||
|
|
@ -1246,6 +1563,10 @@ export class ManagerModal extends Modal {
|
|||
|
||||
const topActions = topRow.createDiv("bpm-mobile-header__actions");
|
||||
|
||||
// 检查更新按钮
|
||||
const updateBtn = new ButtonComponent(topActions);
|
||||
this.preparePluginUpdateButton(updateBtn);
|
||||
|
||||
// 编辑模式
|
||||
const editorBtn = new ButtonComponent(topActions);
|
||||
editorBtn.setIcon(this.editorMode ? "pen-off" : "pen");
|
||||
|
|
@ -1253,6 +1574,10 @@ export class ManagerModal extends Modal {
|
|||
this.bindLongPressTooltip(editorBtn.buttonEl, t("管理器_编辑模式_描述"));
|
||||
editorBtn.onClick(async () => {
|
||||
this.editorMode = !this.editorMode;
|
||||
if (this.editorMode && this.bulkEditMode) {
|
||||
this.bulkEditMode = false;
|
||||
this.bulkSelectedPluginIds.clear();
|
||||
}
|
||||
this.applyEditingStyle();
|
||||
if (!this.editorMode) {
|
||||
await this.refreshFilterOptions(true);
|
||||
|
|
@ -1262,6 +1587,15 @@ export class ManagerModal extends Modal {
|
|||
this.showHeadMobile();
|
||||
});
|
||||
|
||||
const bulkBtn = new ButtonComponent(topActions);
|
||||
bulkBtn.setIcon(this.bulkEditMode ? "square-check-big" : "list-plus");
|
||||
bulkBtn.setTooltip(t("批量编辑_入口"));
|
||||
bulkBtn.buttonEl.toggleClass("is-active", this.bulkEditMode);
|
||||
this.bindLongPressTooltip(bulkBtn.buttonEl, t("批量编辑_入口"));
|
||||
bulkBtn.onClick(() => {
|
||||
this.setBulkEditMode(!this.bulkEditMode);
|
||||
});
|
||||
|
||||
// 安装/返回
|
||||
const installBtn = new ButtonComponent(topActions);
|
||||
installBtn.setIcon(this.installMode ? "arrow-left" : "download");
|
||||
|
|
@ -1275,10 +1609,6 @@ export class ManagerModal extends Modal {
|
|||
this.showHeadMobile();
|
||||
});
|
||||
|
||||
// 检查更新按钮
|
||||
const updateBtn = new ButtonComponent(topActions);
|
||||
this.preparePluginUpdateButton(updateBtn);
|
||||
|
||||
// 更多操作菜单
|
||||
const moreBtn = new ButtonComponent(topActions);
|
||||
moreBtn.setIcon("more-vertical");
|
||||
|
|
@ -1286,6 +1616,9 @@ export class ManagerModal extends Modal {
|
|||
this.bindLongPressTooltip(moreBtn.buttonEl, t("管理器_更多操作_描述"));
|
||||
moreBtn.buttonEl.addEventListener("click", (ev) => {
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => item.setTitle(t("批量编辑_入口")).setIcon("list-plus").onClick(() => {
|
||||
this.setBulkEditMode(!this.bulkEditMode);
|
||||
}));
|
||||
menu.addItem((item) => item.setTitle(t("管理器_全选取消_描述")).setIcon("list-checks").onClick(() => {
|
||||
this.runDisplayedPluginsToggle();
|
||||
}));
|
||||
|
|
@ -1576,6 +1909,12 @@ export class ManagerModal extends Modal {
|
|||
});
|
||||
footer.appendChild(toggleAllBtn);
|
||||
|
||||
const bulkBtn = createFooterBtn(this.bulkEditMode ? "square-check-big" : "list-plus", t("批量编辑_入口"), () => {
|
||||
this.setBulkEditMode(!this.bulkEditMode);
|
||||
});
|
||||
bulkBtn.toggleClass("is-active", this.bulkEditMode);
|
||||
footer.appendChild(bulkBtn);
|
||||
|
||||
// 检查更新按钮
|
||||
const updateBtn = createFooterBtn("rss", t("管理器_检查更新_描述"), () => {
|
||||
void this.runPluginUpdateCheck(updateBtn);
|
||||
|
|
@ -1630,6 +1969,7 @@ export class ManagerModal extends Modal {
|
|||
|
||||
public async showData(renderGeneration = this.renderGeneration) {
|
||||
this.syncPluginOverviewLayoutClass();
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
// 使用 manifests 按 id 去重,防止重复渲染
|
||||
const page: ManagerPage = "plugins";
|
||||
if (!this.isRenderCurrent(renderGeneration, page)) return;
|
||||
|
|
@ -1657,7 +1997,9 @@ export class ManagerModal extends Modal {
|
|||
|
||||
if (this.settings.DEBUG) console.log("[BPM] render showData before loop, children:", this.contentEl.children.length);
|
||||
this.displayPlugins = [];
|
||||
const bulkBarHost = this.bulkEditMode ? this.contentEl.createDiv("manager-bulk-bar-host") : null;
|
||||
const renderedIds = new Set<string>();
|
||||
let renderedCount = 0;
|
||||
for (const [layoutIndex, layoutItem] of layoutItems.entries()) {
|
||||
if (!this.isRenderCurrent(renderGeneration, page)) return;
|
||||
if (layoutItem.type === "separator") {
|
||||
|
|
@ -1717,6 +2059,7 @@ export class ManagerModal extends Modal {
|
|||
itemEl.settingEl.toggleClass("has-update", Boolean(this.manager.updateStatus?.[plugin.id]?.hasUpdate));
|
||||
itemEl.settingEl.toggleClass("is-bpm-ignored", ManagerPlugin.tags.includes(BPM_IGNORE_TAG));
|
||||
itemEl.settingEl.toggleClass("is-hidden-layout", hiddenPluginIds.has(plugin.id));
|
||||
itemEl.settingEl.toggleClass("is-bulk-selected", this.bulkSelectedPluginIds.has(plugin.id));
|
||||
itemEl.nameEl.addClass("manager-item__name-container");
|
||||
itemEl.nameEl.addClass("manager-plugin-card__header");
|
||||
itemEl.descEl.addClass("manager-item__description-container");
|
||||
|
|
@ -1729,9 +2072,26 @@ export class ManagerModal extends Modal {
|
|||
itemEl.settingEl.addClass("manager-plugin-card--layout-editing");
|
||||
this.bindPluginLayoutDragHandle(itemEl.settingEl, layoutIndex, ManagerPlugin.name || plugin.name || plugin.id);
|
||||
}
|
||||
if (this.bulkEditMode) {
|
||||
const selection = itemEl.settingEl.createDiv("manager-plugin-card__bulk-select");
|
||||
const checkbox = selection.createEl("input", { type: "checkbox" });
|
||||
checkbox.checked = this.bulkSelectedPluginIds.has(plugin.id);
|
||||
checkbox.disabled = isSelf;
|
||||
checkbox.setAttribute("aria-label", t("批量编辑_选择插件", { name: ManagerPlugin.name || plugin.name || plugin.id }));
|
||||
checkbox.addEventListener("click", (event) => event.stopPropagation());
|
||||
checkbox.addEventListener("change", () => {
|
||||
this.toggleBulkPluginSelection(plugin.id, checkbox.checked);
|
||||
});
|
||||
itemEl.settingEl.addEventListener("click", (event) => {
|
||||
const target = event.target;
|
||||
if (target instanceof HTMLElement && target.closest(".manager-plugin-card__actions, .manager-plugin-card__bulk-select, .manager-tag, .clickable-icon, button, input, select, textarea, a")) return;
|
||||
this.toggleBulkPluginSelection(plugin.id, !this.bulkSelectedPluginIds.has(plugin.id));
|
||||
});
|
||||
}
|
||||
|
||||
// [右键操作]
|
||||
itemEl.settingEl.addEventListener("contextmenu", (event) => {
|
||||
if (this.bulkEditMode) return;
|
||||
event.preventDefault(); // 阻止默认的右键菜单
|
||||
const menu = new Menu();
|
||||
let hasContextMenuItems = false;
|
||||
|
|
@ -1916,9 +2276,10 @@ export class ManagerModal extends Modal {
|
|||
|
||||
// [批量操作]
|
||||
this.displayPlugins.push(plugin);
|
||||
renderedCount++;
|
||||
|
||||
// [目录样式]
|
||||
if (!this.editorMode) {
|
||||
if (!this.editorMode && !this.bulkEditMode) {
|
||||
switch (this.settings.ITEM_STYLE) {
|
||||
case "alwaysExpand": itemEl.descEl.addClass("manager-display-block"); break;
|
||||
case "neverExpand": itemEl.descEl.addClass("manager-display-none"); break;
|
||||
|
|
@ -1981,7 +2342,20 @@ export class ManagerModal extends Modal {
|
|||
const item = groupSettingsById.get(ManagerPlugin.group);
|
||||
if (item) {
|
||||
const tag = this.manager.createTag(item.name, item.color, this.settings.GROUP_STYLE);
|
||||
if (this.editorMode) tag.onclick = () => { new GroupModal(this.app, this.manager, this, ManagerPlugin).open(); };
|
||||
tag.addClass("manager-item__group-chip");
|
||||
tag.setAttribute("role", "button");
|
||||
tag.setAttribute("tabindex", "0");
|
||||
tag.setAttribute("aria-label", this.manager.translator.t("分组编辑_打开切换", { name: ManagerPlugin.name || plugin.name || plugin.id }));
|
||||
const openGroupModal = (event?: Event) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
new GroupModal(this.app, this.manager, this, ManagerPlugin).open();
|
||||
};
|
||||
tag.onclick = openGroupModal;
|
||||
tag.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
openGroupModal(event);
|
||||
});
|
||||
group.appendChild(tag);
|
||||
}
|
||||
}
|
||||
|
|
@ -2024,26 +2398,8 @@ export class ManagerModal extends Modal {
|
|||
versionWrap.appendChild(version);
|
||||
if (!this.editorMode) {
|
||||
versionWrap.addClass("manager-item__versions--clickable");
|
||||
versionWrap.addEventListener("click", async () => {
|
||||
const progress = this.showInlineProgress(this.manager.translator.t("通知_获取版本中文案"), plugin.id);
|
||||
progress.update(0, 1, plugin.id);
|
||||
try {
|
||||
const st = await this.manager.checkUpdateForPlugin(plugin.id);
|
||||
progress.update(1, 1, plugin.id);
|
||||
this.refreshSinglePluginUpdateUi(plugin.id);
|
||||
this.updateStats();
|
||||
const versions = st?.versions && st.versions.length > 0
|
||||
? st.versions
|
||||
: st?.remoteVersion
|
||||
? [{ version: st.remoteVersion, prerelease: /-/.test(st.remoteVersion) }]
|
||||
: [];
|
||||
new UpdateModal(this.app, this.manager, plugin.id, versions, st?.remoteVersion ?? null, st?.repo || undefined).open();
|
||||
} catch (e) {
|
||||
console.error("[BPM] fetch remote versions failed", e);
|
||||
new Notice(this.manager.translator.t("管理器_选择版本_获取失败提示"), 4000);
|
||||
} finally {
|
||||
progress.hide();
|
||||
}
|
||||
versionWrap.addEventListener("click", () => {
|
||||
void this.openPluginVersionList(plugin.id, this.manager.updateStatus?.[plugin.id] as PluginUpdateViewStatus | undefined);
|
||||
});
|
||||
}
|
||||
const updateInfo = this.manager.updateStatus?.[plugin.id];
|
||||
|
|
@ -2561,6 +2917,13 @@ export class ManagerModal extends Modal {
|
|||
console.log("[BPM] render showData after loop, cards:", cards.length, "ids:", cards.map(el => el.getAttribute("data-plugin-id")).filter(Boolean).join(","));
|
||||
}
|
||||
}
|
||||
if (bulkBarHost) this.renderBulkBar(bulkBarHost);
|
||||
if (renderedCount === 0) {
|
||||
const empty = this.contentEl.createDiv("bpm-empty-state manager-plugin-page__empty");
|
||||
const icon = empty.createDiv();
|
||||
setIcon(icon, "search-x");
|
||||
empty.createDiv({ cls: "bpm-empty-state__text", text: t("管理器_暂无匹配插件") });
|
||||
}
|
||||
// 计算页尾
|
||||
this.updateStats();
|
||||
}
|
||||
|
|
@ -2678,6 +3041,8 @@ export class ManagerModal extends Modal {
|
|||
this.desktopActionWrapper?.classList.toggle("is-ribbon-page", isRibbon);
|
||||
this.desktopActionWrapper?.classList.toggle("is-troubleshoot-page", isTroubleshoot);
|
||||
this.desktopActionWrapper?.classList.toggle("is-layout-editing", showLayoutTools);
|
||||
this.desktopActionWrapper?.classList.toggle("is-bulk-editing", isPlugins && this.bulkEditMode);
|
||||
this.bulkEditButtonEl?.classList.toggle("is-active", this.bulkEditMode);
|
||||
if (this.desktopFilterWrapper) {
|
||||
this.desktopFilterWrapper.classList.toggle("manager-display-none", !isPlugins);
|
||||
this.desktopFilterWrapper.style.display = isPlugins ? "" : "none";
|
||||
|
|
@ -2694,6 +3059,11 @@ export class ManagerModal extends Modal {
|
|||
return;
|
||||
}
|
||||
this.activePage = page;
|
||||
if (page !== "plugins" && this.bulkEditMode) {
|
||||
this.bulkEditMode = false;
|
||||
this.bulkSelectedPluginIds.clear();
|
||||
this.applyEditingStyle();
|
||||
}
|
||||
this.syncPageChrome();
|
||||
this.renderContent();
|
||||
}
|
||||
|
|
@ -5351,15 +5721,21 @@ export class ManagerModal extends Modal {
|
|||
this.contentEl.empty();
|
||||
if (this.manager.ribbonModal === this.ribbonPage) this.manager.ribbonModal = null;
|
||||
if (this.modalContainer) this.modalContainer.removeClass("manager-container--editing");
|
||||
if (this.modalContainer) this.modalContainer.removeClass("manager-container--bulk-editing");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
if (this.bulkEditMode) {
|
||||
this.modalContainer.addClass("manager-container--bulk-editing");
|
||||
} else {
|
||||
this.modalContainer.removeClass("manager-container--bulk-editing");
|
||||
}
|
||||
if (this.desktopActionWrapper) this.syncPageChrome();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ export class TagsModal extends Modal {
|
|||
const previewLine = itemEl.nameEl.createDiv('manager-tag-editor__preview-line');
|
||||
const tagEl = this.manager.createTag(tag.name || tag.id, tag.color, this.settings.TAG_STYLE);
|
||||
tagEl.addClass('manager-tag-editor__chip');
|
||||
previewLine.appendChild(tagEl);
|
||||
const chipSlot = previewLine.createSpan({ cls: 'manager-tag-editor__chip-slot' });
|
||||
chipSlot.appendChild(tagEl);
|
||||
previewLine.createSpan({ cls: 'manager-tag-editor__id', text: tag.id });
|
||||
if (isPreset) previewLine.createSpan({ cls: 'manager-tag-editor__badge is-system', text: t('通用_系统_文本') });
|
||||
if (assigned) previewLine.createSpan({ cls: 'manager-tag-editor__badge is-assigned', text: t('通用_已分配_文本') });
|
||||
|
|
@ -211,52 +212,103 @@ export class TagsModal extends Modal {
|
|||
}
|
||||
}
|
||||
if (this.add) {
|
||||
let id = '';
|
||||
let name = '';
|
||||
let color = this.pickDistinctColor(this.settings.TAGS.map(t => t.color));
|
||||
const foodBar = new Setting(page).setClass('manager-bar__title');
|
||||
foodBar.settingEl.addClass('manager-tag-editor__add-panel');
|
||||
foodBar.nameEl.empty();
|
||||
foodBar.descEl.empty();
|
||||
const addTitle = foodBar.nameEl.createDiv('manager-tag-editor__add-title');
|
||||
const addIcon = addTitle.createSpan({ cls: 'manager-tag-editor__add-title-icon' });
|
||||
setIcon(addIcon, 'plus');
|
||||
addTitle.createSpan({ text: t('设置_标签设置_新增标签') });
|
||||
foodBar.descEl.setText(t('设置_分类_ID说明'));
|
||||
foodBar.addColorPicker(cb => cb
|
||||
.setValue(color)
|
||||
.onChange((value) => { color = value; })
|
||||
)
|
||||
foodBar.addText(cb => cb
|
||||
.setPlaceholder('ID')
|
||||
.onChange((value) => { id = value; })
|
||||
.inputEl.addClass('manager-editor__item-input')
|
||||
)
|
||||
foodBar.addText(cb => cb
|
||||
.setPlaceholder(this.manager.translator.t('通用_名称_文本'))
|
||||
.onChange((value) => { name = value; })
|
||||
.inputEl.addClass('manager-editor__item-input')
|
||||
)
|
||||
foodBar.addExtraButton((cb) => {
|
||||
cb.setIcon('plus');
|
||||
this.prepareIconButton(cb, t('标签编辑_创建标签'), 'manager-tag-editor__save-button');
|
||||
cb.onClick(() => {
|
||||
const nextId = id.trim();
|
||||
const nextName = name.trim() || nextId;
|
||||
const containsId = this.manager.settings.TAGS.some(tag => tag.id === nextId);
|
||||
if (!containsId && nextId !== '' && nextId !== BPM_TAG_ID && nextId !== BPM_IGNORE_TAG) {
|
||||
if (color === '') color = this.pickDistinctColor(this.settings.TAGS.map(t => t.color));
|
||||
this.manager.settings.TAGS.push({ id: nextId, name: nextName, color });
|
||||
this.manager.saveSettings();
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
Commands(this.app, this.manager);
|
||||
new Notice(this.manager.translator.t('设置_标签设置_通知_一'));
|
||||
} else {
|
||||
new Notice(this.manager.translator.t('设置_标签设置_通知_二'));
|
||||
}
|
||||
});
|
||||
})
|
||||
const addPanel = page.createDiv('manager-tag-editor__create-panel');
|
||||
const preview = addPanel.createDiv('manager-tag-editor__create-preview');
|
||||
const previewMain = preview.createDiv('manager-tag-editor__create-preview-main');
|
||||
const previewChip = this.manager.createTag(t('通用_名称_文本'), color, this.settings.TAG_STYLE);
|
||||
previewChip.addClass('manager-tag-editor__chip');
|
||||
previewChip.addClass('manager-tag-editor__create-chip');
|
||||
const previewChipSlot = previewMain.createSpan({ cls: 'manager-tag-editor__chip-slot manager-tag-editor__chip-slot--preview' });
|
||||
previewChipSlot.appendChild(previewChip);
|
||||
const previewText = previewMain.createDiv('manager-tag-editor__create-preview-text');
|
||||
previewText.createDiv({ cls: 'manager-tag-editor__create-title', text: t('设置_标签设置_新增标签') });
|
||||
const previewId = previewText.createDiv({ cls: 'manager-tag-editor__create-id', text: 'ID' });
|
||||
preview.createDiv({ cls: 'manager-tag-editor__create-desc', text: t('设置_分类_新增描述') });
|
||||
|
||||
const form = addPanel.createDiv('manager-tag-editor__create-form');
|
||||
const createField = (label: string, className = '') => {
|
||||
const field = form.createDiv(`manager-tag-editor__create-field ${className}`);
|
||||
field.createDiv({ cls: 'manager-tag-editor__create-label', text: label });
|
||||
return field;
|
||||
};
|
||||
const colorField = createField(t('通用_颜色_文本'), 'manager-tag-editor__create-field--color');
|
||||
const colorInput = colorField.createEl('input');
|
||||
colorInput.type = 'color';
|
||||
colorInput.value = color;
|
||||
colorInput.addClass('manager-tag-editor__create-color');
|
||||
colorInput.setAttribute('aria-label', t('通用_颜色_文本'));
|
||||
|
||||
const idField = createField(t('通用_ID_文本'));
|
||||
const idInput = idField.createEl('input');
|
||||
idInput.type = 'text';
|
||||
idInput.placeholder = 'tag-id';
|
||||
idInput.spellcheck = false;
|
||||
idInput.addClass('manager-editor__item-input');
|
||||
idInput.addClass('manager-tag-editor__create-input');
|
||||
idInput.setAttribute('aria-label', t('通用_ID_文本'));
|
||||
|
||||
const nameField = createField(t('通用_名称_文本'));
|
||||
const nameInput = nameField.createEl('input');
|
||||
nameInput.type = 'text';
|
||||
nameInput.placeholder = t('通用_名称_文本');
|
||||
nameInput.addClass('manager-editor__item-input');
|
||||
nameInput.addClass('manager-tag-editor__create-input');
|
||||
nameInput.setAttribute('aria-label', t('通用_名称_文本'));
|
||||
|
||||
const updatePreview = () => {
|
||||
const nextId = idInput.value.trim();
|
||||
const nextName = nameInput.value.trim();
|
||||
previewChip.textContent = nextName || nextId || t('通用_名称_文本');
|
||||
previewChip.setAttribute('style', this.manager.generateTagStyle(color, this.settings.TAG_STYLE));
|
||||
previewId.setText(nextId || 'ID');
|
||||
};
|
||||
const submit = () => {
|
||||
const nextId = idInput.value.trim();
|
||||
const nextName = nameInput.value.trim() || nextId;
|
||||
const containsId = this.manager.settings.TAGS.some(tag => tag.id === nextId);
|
||||
if (!containsId && nextId !== '' && nextId !== BPM_TAG_ID && nextId !== BPM_IGNORE_TAG) {
|
||||
this.manager.settings.TAGS.push({ id: nextId, name: nextName, color });
|
||||
this.manager.saveSettings();
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
Commands(this.app, this.manager);
|
||||
new Notice(this.manager.translator.t('设置_标签设置_通知_一'));
|
||||
} else {
|
||||
new Notice(this.manager.translator.t('设置_标签设置_通知_二'));
|
||||
}
|
||||
};
|
||||
const onEnter = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'Enter') return;
|
||||
event.preventDefault();
|
||||
submit();
|
||||
};
|
||||
colorInput.addEventListener('input', () => {
|
||||
color = colorInput.value;
|
||||
updatePreview();
|
||||
});
|
||||
idInput.addEventListener('input', () => updatePreview());
|
||||
nameInput.addEventListener('input', () => updatePreview());
|
||||
idInput.addEventListener('keydown', onEnter);
|
||||
nameInput.addEventListener('keydown', onEnter);
|
||||
|
||||
const actions = addPanel.createDiv('manager-tag-editor__create-actions');
|
||||
const cancelButton = actions.createEl('button', { cls: 'manager-tag-editor__create-action' });
|
||||
cancelButton.type = 'button';
|
||||
cancelButton.setAttribute('aria-label', t('通用_取消_文本'));
|
||||
setIcon(cancelButton.createSpan({ cls: 'manager-tag-editor__create-action-icon' }), 'x');
|
||||
cancelButton.createSpan({ text: t('通用_取消_文本') });
|
||||
cancelButton.addEventListener('click', () => {
|
||||
this.add = false;
|
||||
this.reloadShowData();
|
||||
});
|
||||
const submitButton = actions.createEl('button', { cls: 'manager-tag-editor__create-action manager-tag-editor__create-action--primary' });
|
||||
submitButton.type = 'button';
|
||||
submitButton.setAttribute('aria-label', t('标签编辑_创建标签'));
|
||||
setIcon(submitButton.createSpan({ cls: 'manager-tag-editor__create-action-icon' }), 'plus');
|
||||
submitButton.createSpan({ text: t('标签编辑_创建标签') });
|
||||
submitButton.addEventListener('click', submit);
|
||||
window.setTimeout(() => idInput.focus(), 0);
|
||||
} else {
|
||||
// [底部行] 新增
|
||||
const foodBar = new Setting(page).setClass('manager-bar__title').setName(this.manager.translator.t('通用_新增_文本'));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, DropdownComponent, Modal, Notice, Setting } from "obsidian";
|
||||
import { App, ExtraButtonComponent, Modal, Notice, setIcon, Setting } from "obsidian";
|
||||
import Manager from "main";
|
||||
import { ReleaseVersion, fetchReleaseVersions } from "src/github-install";
|
||||
|
||||
|
|
@ -8,6 +8,9 @@ export class UpdateModal extends Modal {
|
|||
private versions: ReleaseVersion[];
|
||||
private defaultVersion?: string | null;
|
||||
private repo?: string;
|
||||
private selectedVersion = "";
|
||||
private downloadButton?: HTMLButtonElement;
|
||||
private versionListEl?: HTMLElement;
|
||||
|
||||
constructor(app: App, manager: Manager, pluginId: string, versions: ReleaseVersion[], defaultVersion?: string | null, repo?: string) {
|
||||
super(app);
|
||||
|
|
@ -20,15 +23,55 @@ export class UpdateModal extends Modal {
|
|||
|
||||
async onOpen() {
|
||||
const { contentEl } = this;
|
||||
const t = (k: any) => this.manager.translator.t(k);
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
const manifest = this.manager.appPlugins.manifests?.[this.pluginId];
|
||||
contentEl.empty();
|
||||
this.selectedVersion = this.defaultVersion || manifest?.version || (this.versions[0]?.version ?? "");
|
||||
|
||||
const title = contentEl.createEl("h3", { text: t("管理器_选择版本_标题") });
|
||||
title.style.marginBottom = "8px";
|
||||
const modalEl = contentEl.parentElement as HTMLElement | null;
|
||||
modalEl?.addClass("manager-editor__container");
|
||||
modalEl?.addClass("manager-version-picker");
|
||||
modalEl?.getElementsByClassName("modal-close-button")[0]?.remove();
|
||||
this.titleEl.parentElement?.addClass("manager-container__header");
|
||||
contentEl.addClass("manager-item-container");
|
||||
contentEl.addClass("manager-version-picker__body");
|
||||
|
||||
const titleBar = new Setting(this.titleEl).setClass("manager-bar__title");
|
||||
titleBar.settingEl.addClass("manager-version-picker__titlebar");
|
||||
titleBar.nameEl.empty();
|
||||
titleBar.descEl.empty();
|
||||
const titleWrap = titleBar.nameEl.createDiv("manager-version-picker__title");
|
||||
const titleIcon = titleWrap.createSpan({ cls: "manager-version-picker__title-icon" });
|
||||
setIcon(titleIcon, "package-down");
|
||||
const titleText = titleWrap.createDiv("manager-version-picker__title-text");
|
||||
titleText.createDiv({ cls: "manager-version-picker__eyebrow", text: t("管理器_选择版本_标题") });
|
||||
titleText.createDiv({ cls: "manager-version-picker__plugin-name", text: manifest?.name || this.pluginId });
|
||||
titleBar.descEl.setText(t("管理器_选择版本_说明"));
|
||||
const closeButton = new ExtraButtonComponent(titleBar.controlEl);
|
||||
closeButton.setIcon("x");
|
||||
closeButton.setTooltip(t("通用_取消_文本"));
|
||||
const closeEl = ((closeButton as any).extraSettingsEl || (closeButton as any).buttonEl) as HTMLElement | undefined;
|
||||
closeEl?.setAttribute("aria-label", t("通用_取消_文本"));
|
||||
closeButton.onClick(() => this.close());
|
||||
|
||||
const page = contentEl.createDiv("manager-version-picker__page");
|
||||
const summary = page.createDiv("manager-version-picker__summary");
|
||||
const summaryMain = summary.createDiv("manager-version-picker__summary-main");
|
||||
summaryMain.createDiv({ cls: "manager-version-picker__summary-title", text: t("管理器_选择版本_列表标题") });
|
||||
summaryMain.createDiv({
|
||||
cls: "manager-version-picker__summary-desc",
|
||||
text: t("管理器_选择版本_当前版本", { version: manifest?.version || "-" }),
|
||||
});
|
||||
const summaryStats = summary.createDiv("manager-version-picker__summary-stats");
|
||||
const countStat = summaryStats.createSpan({ cls: "manager-version-picker__summary-stat" });
|
||||
setIcon(countStat.createSpan({ cls: "manager-version-picker__summary-stat-icon" }), "tags");
|
||||
countStat.createSpan({ text: `${this.versions.length}` });
|
||||
|
||||
this.versionListEl = page.createDiv("manager-version-picker__list");
|
||||
|
||||
let versionList = [...this.versions];
|
||||
if (versionList.length === 0 && this.repo) {
|
||||
const loading = contentEl.createDiv();
|
||||
const loading = this.versionListEl.createDiv("manager-version-picker__empty");
|
||||
loading.setText(t("管理器_选择版本_获取中"));
|
||||
try {
|
||||
versionList = await fetchReleaseVersions(this.manager, this.repo);
|
||||
|
|
@ -41,30 +84,42 @@ export class UpdateModal extends Modal {
|
|||
this.versions = versionList;
|
||||
}
|
||||
|
||||
let selected = this.defaultVersion || (versionList[0]?.version ?? "");
|
||||
if (versionList.length > 0) {
|
||||
new Setting(contentEl)
|
||||
.setName(t("管理器_选择版本_版本_标题"))
|
||||
.addDropdown((dd: DropdownComponent) => {
|
||||
versionList.forEach(v => {
|
||||
dd.addOption(v.version, `${v.version}${v.prerelease ? " (pre)" : ""}`);
|
||||
});
|
||||
dd.setValue(selected);
|
||||
dd.onChange((v) => { selected = v; });
|
||||
});
|
||||
const hasDefaultVersion = Boolean(this.defaultVersion && versionList.some((release) => release.version === this.defaultVersion));
|
||||
const hasLocalVersion = Boolean(manifest?.version && versionList.some((release) => release.version === manifest.version));
|
||||
this.selectedVersion = hasDefaultVersion
|
||||
? this.defaultVersion!
|
||||
: 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");
|
||||
releaseCount.createSpan({ text: t("管理器_选择版本_版本数量", { count: versionList.length }) });
|
||||
const currentStat = summaryStats.createSpan({ cls: "manager-version-picker__summary-stat" });
|
||||
setIcon(currentStat.createSpan({ cls: "manager-version-picker__summary-stat-icon" }), "badge-check");
|
||||
currentStat.createSpan({ text: this.selectedVersion || t("管理器_安装_版本_默认最新") });
|
||||
|
||||
if (versionList.length > 0 && this.versionListEl) {
|
||||
this.renderVersionList(versionList, manifest?.version || "");
|
||||
} else {
|
||||
const info = contentEl.createDiv();
|
||||
const info = this.versionListEl?.createDiv("manager-version-picker__empty") ?? page.createDiv("manager-version-picker__empty");
|
||||
info.setText(t("管理器_选择版本_无版本提示"));
|
||||
}
|
||||
|
||||
new Setting(contentEl)
|
||||
const footer = new Setting(page);
|
||||
footer.settingEl.addClass("manager-version-picker__footer");
|
||||
footer.nameEl.empty();
|
||||
footer.descEl.empty();
|
||||
footer
|
||||
.addButton((btn) => {
|
||||
btn.setButtonText(t("管理器_选择版本_下载按钮"));
|
||||
btn.setButtonText(t("管理器_选择版本_切换按钮"));
|
||||
btn.setCta();
|
||||
this.downloadButton = btn.buttonEl;
|
||||
btn.setDisabled(!this.selectedVersion);
|
||||
btn.onClick(async () => {
|
||||
btn.setDisabled(true);
|
||||
try {
|
||||
const ok = await this.manager.downloadUpdate(this.pluginId, selected);
|
||||
const ok = await this.manager.downloadUpdate(this.pluginId, this.selectedVersion);
|
||||
if (ok) {
|
||||
new Notice(t("管理器_选择版本_成功提示"), 3000);
|
||||
this.close();
|
||||
|
|
@ -79,5 +134,49 @@ export class UpdateModal extends Modal {
|
|||
btn.onClick(() => this.close());
|
||||
});
|
||||
}
|
||||
|
||||
private renderVersionList(versionList: ReleaseVersion[], localVersion: string) {
|
||||
if (!this.versionListEl) return;
|
||||
const t = (k: any, vars?: Record<string, string | number | boolean | null | undefined>) => this.manager.translator.t(k, vars);
|
||||
this.versionListEl.empty();
|
||||
versionList.forEach((release) => {
|
||||
const item = this.versionListEl!.createEl("button", { cls: "manager-version-picker__item" });
|
||||
item.type = "button";
|
||||
item.toggleClass("is-selected", release.version === this.selectedVersion);
|
||||
item.toggleClass("is-current", Boolean(localVersion && release.version === localVersion));
|
||||
item.setAttribute("aria-pressed", `${release.version === this.selectedVersion}`);
|
||||
item.setAttribute("aria-label", t("管理器_选择版本_选择版本", { version: release.version }));
|
||||
|
||||
const main = item.createDiv("manager-version-picker__item-main");
|
||||
const title = main.createDiv("manager-version-picker__item-title");
|
||||
title.createSpan({ cls: "manager-version-picker__item-version", text: release.version });
|
||||
if (release.version === localVersion) {
|
||||
title.createSpan({ cls: "manager-version-picker__badge", text: t("来源_当前") });
|
||||
}
|
||||
title.createSpan({
|
||||
cls: `manager-version-picker__badge ${release.prerelease ? "is-prerelease" : "is-stable"}`,
|
||||
text: t(release.prerelease ? "安装_发布类型_预发布" : "安装_发布类型_正式版"),
|
||||
});
|
||||
|
||||
const meta = main.createDiv("manager-version-picker__item-meta");
|
||||
if (release.name && release.name !== release.version) meta.createSpan({ text: release.name });
|
||||
if (release.publishedAt) meta.createSpan({ text: this.formatDate(release.publishedAt) });
|
||||
|
||||
const check = item.createSpan({ cls: "manager-version-picker__item-check" });
|
||||
setIcon(check, release.version === this.selectedVersion ? "check" : "circle");
|
||||
|
||||
item.addEventListener("click", () => {
|
||||
this.selectedVersion = release.version;
|
||||
this.renderVersionList(versionList, localVersion);
|
||||
this.downloadButton?.removeAttribute("disabled");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private formatDate(value: string): string {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1335
styles.css
1335
styles.css
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue