mirror of
https://github.com/olcubo/obsidian-cubox.git
synced 2026-07-22 05:43:25 +00:00
feat: enhance type selection modal with new info icon and improve type filter handling
This commit is contained in:
parent
618137dfca
commit
202cb56275
5 changed files with 146 additions and 41 deletions
|
|
@ -2,6 +2,7 @@ import { Notice, requestUrl } from 'obsidian';
|
|||
import { ALL_FOLDERS_ID } from './modal/folderSelectModal';
|
||||
import { ALL_ITEMS } from './modal/tagSelectModal';
|
||||
import { ALL_STATUS_ID } from './modal/statusSelectModal';
|
||||
import { expandTypeFilterForApi } from './modal/typeSelectModal';
|
||||
|
||||
export interface CuboxArticle {
|
||||
id: string;
|
||||
|
|
@ -152,9 +153,9 @@ export class CuboxApi {
|
|||
}
|
||||
}
|
||||
|
||||
// 添加类型过滤
|
||||
// 添加类型过滤(Others 展开为 Memo/Image/Audio/Video/File)
|
||||
if (params.typeFilter && params.typeFilter.length > 0) {
|
||||
requestBody.type_filters = params.typeFilter;
|
||||
requestBody.type_filters = expandTypeFilterForApi(params.typeFilter);
|
||||
}
|
||||
|
||||
// 添加状态过滤
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type CuboxSyncPlugin from './main';
|
|||
import { FRONT_MATTER_VARIABLES } from './templateProcessor';
|
||||
import { ALL_FOLDERS_ID, FolderSelectModal } from './modal/folderSelectModal';
|
||||
import { filenameTemplateInstructions, metadataVariablesInstructions, contentTemplateInstructions, cuboxDateFormat, getFilenameReferenceLink, getMetadataReferenceLink, getContentReferenceLink, getDateReferenceLink, getInstructionUrl } from './templateInstructions';
|
||||
import { ALL_CONTENT_TYPES, TypeSelectModal } from './modal/typeSelectModal';
|
||||
import { isFullTypeSelection, storedTypesToModalSelection, TypeSelectModal } from './modal/typeSelectModal';
|
||||
import { ALL_STATUS_ID, StatusSelectModal } from './modal/statusSelectModal';
|
||||
import { ALL_ITEMS, TagSelectModal } from './modal/tagSelectModal';
|
||||
import { normalizePath } from 'obsidian';
|
||||
|
|
@ -266,13 +266,14 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
|
|||
// 打开类型选择模态框
|
||||
const modal = new TypeSelectModal(
|
||||
this.app,
|
||||
this.plugin.settings.typeFilter,
|
||||
storedTypesToModalSelection(this.plugin.settings.typeFilter),
|
||||
async (selectedTypes) => {
|
||||
this.plugin.settings.typeFilter = selectedTypes;
|
||||
await this.plugin.saveSettings();
|
||||
// 更新按钮文本
|
||||
button.setButtonText(this.getTypeFilterButtonText());
|
||||
}
|
||||
},
|
||||
this.plugin.settings.domain
|
||||
);
|
||||
modal.open();
|
||||
}));
|
||||
|
|
@ -578,7 +579,7 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
|
|||
const typeFilters = this.plugin.settings.typeFilter;
|
||||
if (!typeFilters || typeFilters.length === 0) {
|
||||
return 'Select...';
|
||||
} else if (typeFilters.length === ALL_CONTENT_TYPES.length) {
|
||||
} else if (isFullTypeSelection(typeFilters)) {
|
||||
return 'All types';
|
||||
} else {
|
||||
return `${typeFilters.length} selected`;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { CuboxApi } from './cuboxApi';
|
|||
import { TemplateProcessor } from './templateProcessor';
|
||||
import { formatDateTime } from './utils';
|
||||
import { CuboxSyncSettingTab, CuboxSyncSettings, DEFAULT_SETTINGS } from './cuboxSetting';
|
||||
import { normalizeTypeFilterStorage } from './modal/typeSelectModal';
|
||||
|
||||
|
||||
export default class CuboxSyncPlugin extends Plugin {
|
||||
|
|
@ -54,6 +55,7 @@ export default class CuboxSyncPlugin extends Plugin {
|
|||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this.settings.typeFilter = normalizeTypeFilterStorage(this.settings.typeFilter);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,92 @@
|
|||
import { App, Modal, Setting, Notice } from 'obsidian';
|
||||
import { App, Modal, Setting, Notice, setIcon } from 'obsidian';
|
||||
|
||||
export const ALL_CONTENT_TYPES = [
|
||||
'Article',
|
||||
'Snippet',
|
||||
'Memo',
|
||||
'Image',
|
||||
'Audio',
|
||||
'Video',
|
||||
'File'
|
||||
];
|
||||
/** API 层使用的五个类型,在 UI 中合并为 Others */
|
||||
export const OTHERS_MEMBER_TYPES = ['Memo', 'Image', 'Audio', 'Video', 'File'] as const;
|
||||
|
||||
const OTHERS_MEMBER_SET = new Set<string>(OTHERS_MEMBER_TYPES);
|
||||
|
||||
/** 配置中用于表示上述五类合并项的标识 */
|
||||
export const OTHERS_TYPE_ID = 'Others';
|
||||
|
||||
export const ALL_CONTENT_TYPES = ['Article', 'Snippet', OTHERS_TYPE_ID];
|
||||
|
||||
/**
|
||||
* 读盘或读入的 typeFilter 规范化:将历史配置中的 Memo/Image/... 合并为 Others
|
||||
*/
|
||||
export function normalizeTypeFilterStorage(typeFilter: string[] | undefined): string[] {
|
||||
if (!typeFilter || typeFilter.length === 0) return [];
|
||||
const hasLegacyOthers = OTHERS_MEMBER_TYPES.some((t) => typeFilter.includes(t));
|
||||
const next = typeFilter.filter((t) => !OTHERS_MEMBER_SET.has(t));
|
||||
if (hasLegacyOthers && !next.includes(OTHERS_TYPE_ID)) {
|
||||
next.push(OTHERS_TYPE_ID);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意历史成员被选中则视为 Others 选中,供类型选择弹窗初始状态使用
|
||||
*/
|
||||
export function storedTypesToModalSelection(stored: string[]): string[] {
|
||||
const result = new Set<string>();
|
||||
let othersSelected = false;
|
||||
for (const t of stored) {
|
||||
if (OTHERS_MEMBER_SET.has(t)) {
|
||||
othersSelected = true;
|
||||
} else if (t === OTHERS_TYPE_ID) {
|
||||
othersSelected = true;
|
||||
} else {
|
||||
result.add(t);
|
||||
}
|
||||
}
|
||||
if (othersSelected) {
|
||||
result.add(OTHERS_TYPE_ID);
|
||||
}
|
||||
return Array.from(result);
|
||||
}
|
||||
|
||||
/** 发起 API 请求时把 Others 展开为五个具体 type_filters */
|
||||
export function expandTypeFilterForApi(stored: string[]): string[] {
|
||||
const out: string[] = [];
|
||||
for (const t of stored) {
|
||||
if (t === OTHERS_TYPE_ID) {
|
||||
out.push(...OTHERS_MEMBER_TYPES);
|
||||
} else {
|
||||
out.push(t);
|
||||
}
|
||||
}
|
||||
return [...new Set(out)];
|
||||
}
|
||||
|
||||
/** Article + Snippet + Others(或历史上五个子类全选)视为「全部类型」 */
|
||||
export function isFullTypeSelection(stored: string[]): boolean {
|
||||
if (!stored || stored.length === 0) return false;
|
||||
const hasArticle = stored.includes('Article');
|
||||
const hasSnippet = stored.includes('Snippet');
|
||||
const hasOthers =
|
||||
stored.includes(OTHERS_TYPE_ID) ||
|
||||
OTHERS_MEMBER_TYPES.every((t) => stored.includes(t));
|
||||
return hasArticle && hasSnippet && hasOthers;
|
||||
}
|
||||
|
||||
export class TypeSelectModal extends Modal {
|
||||
private onSave: (selectedTypes: string[]) => void;
|
||||
private selectedTypes: Set<string> = new Set();
|
||||
private listEl: HTMLElement;
|
||||
private footerEl: HTMLElement;
|
||||
private domain: string;
|
||||
|
||||
constructor(app: App, initialSelected: string[] = [], onSave: (selectedTypes: string[]) => void) {
|
||||
constructor(
|
||||
app: App,
|
||||
initialSelected: string[] = [],
|
||||
onSave: (selectedTypes: string[]) => void,
|
||||
domain: string = ''
|
||||
) {
|
||||
super(app);
|
||||
this.onSave = onSave;
|
||||
|
||||
// 初始化已选择的类型
|
||||
this.domain = domain;
|
||||
|
||||
if (initialSelected && initialSelected.length > 0) {
|
||||
// 添加初始选择的类型
|
||||
initialSelected.forEach(id => {
|
||||
initialSelected.forEach((id) => {
|
||||
if (id) this.selectedTypes.add(id);
|
||||
});
|
||||
}
|
||||
|
|
@ -32,29 +95,27 @@ export class TypeSelectModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
|
||||
contentEl.createEl('h2', { text: 'Manage Cubox content types to be synced' });
|
||||
contentEl.addClass('cubox-modal');
|
||||
|
||||
|
||||
this.listEl = contentEl.createDiv({ cls: 'type-list-container cubox-list-container' });
|
||||
this.footerEl = contentEl.createDiv({ cls: 'modal-footer' });
|
||||
|
||||
// 创建类型列表
|
||||
|
||||
this.createTypeList();
|
||||
|
||||
|
||||
const cancelButton = this.footerEl.createEl('button', { text: 'Cancel' });
|
||||
cancelButton.addEventListener('click', () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
|
||||
const saveButton = this.footerEl.createEl('button', { text: 'Done', cls: 'mod-cta' });
|
||||
saveButton.addEventListener('click', () => {
|
||||
// 检查是否至少选择了一个选项
|
||||
if (this.selectedTypes.size === 0) {
|
||||
new Notice('Please select at least one option.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const selectedTypes = Array.from(this.selectedTypes);
|
||||
this.onSave(selectedTypes);
|
||||
this.close();
|
||||
|
|
@ -63,18 +124,39 @@ export class TypeSelectModal extends Modal {
|
|||
|
||||
private createTypeList() {
|
||||
this.listEl.empty();
|
||||
|
||||
// 添加每个类型的选项
|
||||
ALL_CONTENT_TYPES.forEach(typeId => {
|
||||
new Setting(this.listEl)
|
||||
.setName(typeId)
|
||||
.addToggle(toggle => {
|
||||
toggle.setValue(this.selectedTypes.has(typeId));
|
||||
toggle.onChange(value => {
|
||||
this.handleTypeToggle(typeId, value);
|
||||
this.redraw();
|
||||
});
|
||||
|
||||
ALL_CONTENT_TYPES.forEach((typeId) => {
|
||||
const row = new Setting(this.listEl);
|
||||
if (typeId === OTHERS_TYPE_ID) {
|
||||
const nameFrag = document.createDocumentFragment();
|
||||
const label = document.createElement('span');
|
||||
label.textContent = OTHERS_TYPE_ID;
|
||||
nameFrag.appendChild(label);
|
||||
const infoEl = document.createElement('span');
|
||||
infoEl.className = 'cubox-others-info-icon';
|
||||
infoEl.setAttribute('aria-label', 'About Memo and file types');
|
||||
setIcon(infoEl, 'info');
|
||||
infoEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const url =
|
||||
this.domain === 'cubox.pro'
|
||||
? 'https://story.cubox.pro/memo-file-update'
|
||||
: 'https://cubox.cc/blog/memo-file-update/';
|
||||
window.open(url);
|
||||
});
|
||||
nameFrag.appendChild(infoEl);
|
||||
row.setName(nameFrag);
|
||||
} else {
|
||||
row.setName(typeId);
|
||||
}
|
||||
row.addToggle((toggle) => {
|
||||
toggle.setValue(this.selectedTypes.has(typeId));
|
||||
toggle.onChange((value) => {
|
||||
this.handleTypeToggle(typeId, value);
|
||||
this.redraw();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -94,4 +176,4 @@ export class TypeSelectModal extends Modal {
|
|||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
styles.css
19
styles.css
|
|
@ -108,4 +108,23 @@ If your plugin does not need CSS, delete this file.
|
|||
|
||||
.type-list-container .setting-item:first-child .setting-item-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.cubox-others-info-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
cursor: var(--cursor);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cubox-others-info-icon:hover {
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.cubox-others-info-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
Loading…
Reference in a new issue