mirror of
https://github.com/Wanxp/obsidian-douban.git
synced 2026-05-30 06:28:23 +08:00
Merge d35dcfe51307c4a70f05dc35dd2386e55128914d into af09f43baa48423e243da7a5ef0e3678bf908892
This commit is contained in:
commit
a79f6c2917
@ -61,6 +61,7 @@ export const DEFAULT_SETTINGS: DoubanPluginSetting = {
|
||||
maxStar: 5,
|
||||
},
|
||||
searchDefaultType: SupportType.all,
|
||||
arrayLengthLimits: [],
|
||||
|
||||
}
|
||||
|
||||
|
||||
115
src/org/wanxp/douban/setting/ArrayLengthLimitSettingsHelper.ts
Normal file
115
src/org/wanxp/douban/setting/ArrayLengthLimitSettingsHelper.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import {ButtonComponent, DropdownComponent, Setting, TextComponent} from "obsidian";
|
||||
import SettingsManager from "./SettingsManager";
|
||||
import {i18nHelper} from "../../lang/helper";
|
||||
import {SupportType, SupportTypeMap} from "../../constant/Constsant";
|
||||
import {ArrayLengthLimit} from "./model/ArrayLengthLimit";
|
||||
|
||||
/**
|
||||
* "数组字段长度限制" 设置页签构造器。
|
||||
*
|
||||
* 允许用户为指定的内容类型(电影/电视剧/书籍/音乐/游戏/笔记/戏剧/全部)中的数组字段
|
||||
* (如 actor/director/author/translator/aliases/...) 自定义最大保留数量。
|
||||
*/
|
||||
export function constructArrayLengthLimitSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
|
||||
containerEl.createEl('p', {text: i18nHelper.getMessage('1271')});
|
||||
containerEl.createEl('p', {text: i18nHelper.getMessage('1272')});
|
||||
|
||||
if (!manager.plugin.settings.arrayLengthLimits) {
|
||||
manager.plugin.settings.arrayLengthLimits = [];
|
||||
}
|
||||
const limits = manager.plugin.settings.arrayLengthLimits;
|
||||
|
||||
new Setting(containerEl)
|
||||
.setDesc(i18nHelper.getMessage('1273'))
|
||||
.addButton((button) => {
|
||||
button.setButtonText(i18nHelper.getMessage('124101'));
|
||||
button.setTooltip(i18nHelper.getMessage('127401'));
|
||||
button.setIcon('plus');
|
||||
button.onClick(async () => {
|
||||
await manager.addArrayLengthLimit();
|
||||
renderItems(list, manager);
|
||||
});
|
||||
});
|
||||
|
||||
const list = containerEl.createDiv('array-length-limit-list');
|
||||
renderItems(list, manager);
|
||||
}
|
||||
|
||||
function renderItems(containerEl: HTMLElement, manager: SettingsManager) {
|
||||
containerEl.empty();
|
||||
const limits = manager.plugin.settings.arrayLengthLimits || [];
|
||||
for (let i = 0; i < limits.length; i++) {
|
||||
buildItem(containerEl, manager, limits, i);
|
||||
}
|
||||
}
|
||||
|
||||
function buildItem(containerEl: HTMLElement, manager: SettingsManager, limits: ArrayLengthLimit[], idx: number) {
|
||||
const data = limits[idx];
|
||||
|
||||
const item = containerEl.createEl('li');
|
||||
|
||||
// 适用类型
|
||||
item.createEl('span', {text: i18nHelper.getMessage('127402')});
|
||||
const typeDropdown = new DropdownComponent(item);
|
||||
for (const fieldSelect in SupportType) {
|
||||
typeDropdown.addOption(fieldSelect, i18nHelper.getMessage(fieldSelect));
|
||||
}
|
||||
let dataTypeValue = data.type;
|
||||
if (typeof dataTypeValue === 'string') {
|
||||
// @ts-ignore
|
||||
dataTypeValue = SupportTypeMap[dataTypeValue] || SupportType.all;
|
||||
}
|
||||
typeDropdown.setValue(dataTypeValue || SupportType.all)
|
||||
.onChange(async (value: SupportType) => {
|
||||
limits[idx].type = value;
|
||||
await manager.plugin.saveSettings();
|
||||
});
|
||||
const typeEl = typeDropdown.selectEl;
|
||||
typeEl.addClass('obsidian_douban_settings_input');
|
||||
item.appendChild(typeEl);
|
||||
|
||||
// 字段名
|
||||
item.createEl('span', {text: i18nHelper.getMessage('127403')});
|
||||
const fieldField = new TextComponent(item);
|
||||
fieldField.setPlaceholder(i18nHelper.getMessage('127404'))
|
||||
.setValue(data.field || '')
|
||||
.onChange(async (value) => {
|
||||
limits[idx].field = (value || '').trim();
|
||||
await manager.plugin.saveSettings();
|
||||
});
|
||||
const fieldEl = fieldField.inputEl;
|
||||
fieldEl.addClass('obsidian_douban_settings_input');
|
||||
item.appendChild(fieldEl);
|
||||
|
||||
// 最大数量
|
||||
item.createEl('span', {text: i18nHelper.getMessage('127405')});
|
||||
const limitField = new TextComponent(item);
|
||||
limitField.setPlaceholder(i18nHelper.getMessage('127406'))
|
||||
.setValue(data.limit != null ? String(data.limit) : '')
|
||||
.onChange(async (value) => {
|
||||
const parsed = parseInt(value, 10);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
// 无效输入忽略, 保持原值
|
||||
return;
|
||||
}
|
||||
limits[idx].limit = parsed;
|
||||
await manager.plugin.saveSettings();
|
||||
});
|
||||
const limitEl = limitField.inputEl;
|
||||
limitEl.type = 'number';
|
||||
limitEl.min = '0';
|
||||
limitEl.addClass('obsidian_douban_settings_input');
|
||||
item.appendChild(limitEl);
|
||||
|
||||
// 删除按钮
|
||||
const removeBtn = new ButtonComponent(item);
|
||||
removeBtn.setIcon('minus-with-circle');
|
||||
removeBtn.setTooltip(i18nHelper.getMessage('127407'));
|
||||
removeBtn.onClick(async () => {
|
||||
await manager.removeArrayLengthLimit(idx);
|
||||
renderItems(containerEl, manager);
|
||||
});
|
||||
const removeBtnEl = removeBtn.buttonEl;
|
||||
removeBtnEl.addClass('obsidian_douban_settings_button');
|
||||
item.appendChild(removeBtnEl);
|
||||
}
|
||||
@ -11,6 +11,7 @@ import { constructAdvancedUI } from "./AdvancedSettingsHelper";
|
||||
import {arraySettingDisplay, arraySettingDisplayUI} from "./ArrayDisplayTypeSettingsHelper";
|
||||
import {i18nHelper} from "../../lang/helper";
|
||||
import {constructLoginUI} from "./LoginSettingsHelper";
|
||||
import {constructArrayLengthLimitSettingsUI} from "./ArrayLengthLimitSettingsHelper";
|
||||
|
||||
/**
|
||||
* 部分逻辑参考以下项目
|
||||
@ -45,6 +46,7 @@ export class DoubanSettingTab extends PluginSettingTab {
|
||||
{name: i18nHelper.getMessage('1260'), construct: constructLoginUI},
|
||||
{name: i18nHelper.getMessage('1220'), construct: constructOutUI},
|
||||
{name: i18nHelper.getMessage('120601'), construct: arraySettingDisplayUI},
|
||||
{name: i18nHelper.getMessage('1270'), construct: constructArrayLengthLimitSettingsUI},
|
||||
{name: i18nHelper.getMessage('1240'), construct: constructCustomPropertySettingsUI},
|
||||
{name: i18nHelper.getMessage('1230'), construct: constructTemplateVariablesUI},
|
||||
{name: i18nHelper.getMessage('1250'), construct: constructAdvancedUI}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from "./model/ArraySetting";
|
||||
import {logger} from "bs-logger";
|
||||
import {i18nHelper} from "../../lang/helper";
|
||||
import {ArrayLengthLimit} from "./model/ArrayLengthLimit";
|
||||
|
||||
export default class SettingsManager {
|
||||
app: App;
|
||||
@ -247,4 +248,35 @@ export default class SettingsManager {
|
||||
getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一条数组长度限制配置, 默认 type=all, field 为空, limit=5。
|
||||
*/
|
||||
async addArrayLengthLimit(): Promise<ArrayLengthLimit> {
|
||||
if (!this.settings.arrayLengthLimits) {
|
||||
this.settings.arrayLengthLimits = [];
|
||||
}
|
||||
const limit: ArrayLengthLimit = {
|
||||
type: SupportType.all,
|
||||
field: '',
|
||||
limit: 5,
|
||||
};
|
||||
this.settings.arrayLengthLimits.push(limit);
|
||||
await this.plugin.saveSettings();
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定下标的数组长度限制配置。
|
||||
*/
|
||||
async removeArrayLengthLimit(index: number): Promise<void> {
|
||||
if (!this.settings.arrayLengthLimits) {
|
||||
return;
|
||||
}
|
||||
if (index < 0 || index >= this.settings.arrayLengthLimits.length) {
|
||||
return;
|
||||
}
|
||||
this.settings.arrayLengthLimits.splice(index, 1);
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
27
src/org/wanxp/douban/setting/model/ArrayLengthLimit.ts
Normal file
27
src/org/wanxp/douban/setting/model/ArrayLengthLimit.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import {SupportType} from "../../../constant/Constsant";
|
||||
|
||||
/**
|
||||
* 数组字段长度限制配置
|
||||
*
|
||||
* 用于限制从豆瓣拉取的内容中数组类型字段(如 actor、director、author 等)
|
||||
* 写入笔记时的最大元素数量。
|
||||
*
|
||||
* 例如: 设置 type=movie, field=actor, limit=5,
|
||||
* 则电影的演员列表只会保留前 5 个写入笔记。
|
||||
*
|
||||
* 当 type=all 时, 限制将作用于所有类型中匹配 field 名称的字段。
|
||||
*/
|
||||
export interface ArrayLengthLimit {
|
||||
/**
|
||||
* 适用的内容类型 (movie/book/music/teleplay/game/note/theater/all)
|
||||
*/
|
||||
type: SupportType;
|
||||
/**
|
||||
* 字段名 (如 actor, director, author, translator, aliases 等)
|
||||
*/
|
||||
field: string;
|
||||
/**
|
||||
* 最大保留元素数量, <=0 表示不生效
|
||||
*/
|
||||
limit: number;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import {ArraySetting} from "./ArraySetting";
|
||||
import {ScoreSetting} from "./ScoreSetting";
|
||||
import PictureBedSetting from "./PictureBedSetting";
|
||||
import {SupportType} from "../../../constant/Constsant";
|
||||
import {ArrayLengthLimit} from "./ArrayLengthLimit";
|
||||
|
||||
export interface DoubanPluginSetting {
|
||||
onlineSettingsFileName: string;
|
||||
@ -42,4 +43,5 @@ export interface DoubanPluginSetting {
|
||||
arraySettings: ArraySetting[],
|
||||
scoreSetting: ScoreSetting,
|
||||
searchDefaultType: SupportType,
|
||||
arrayLengthLimits: ArrayLengthLimit[],
|
||||
}
|
||||
|
||||
@ -138,6 +138,17 @@ PS: This file could be delete if you want to.
|
||||
'1220': `Output`,
|
||||
'1230': `Description`,
|
||||
'1260': `Login`,
|
||||
'1270': `Array Limit`,
|
||||
'1271': `Limit the maximum number of items for array fields (such as actor, director, author, translator, aliases, ...) in subjects (movie / teleplay / book / music / game / ...). Items beyond the limit will not be written to the note.`,
|
||||
'1272': `Tip: When the "Apply Type" is "all", the limit applies to matching fields across all subject types. Type-specific limits take precedence when both exist for the same field.`,
|
||||
'1273': `Add a limit, e.g. keep only the first 5 actors of a movie.`,
|
||||
'127401': `Add a new array length limit`,
|
||||
'127402': `Apply Type:`,
|
||||
'127403': `Field:`,
|
||||
'127404': `e.g. actor / director / author`,
|
||||
'127405': `Max Count:`,
|
||||
'127406': `e.g. 5`,
|
||||
'127407': `Delete this limit`,
|
||||
'1204': `Set template file path. The template file will be used when creating new notes . If keep empty, it will use the default template file to create file. All the usable variables at tab "Description" and "Custom Variables"`,
|
||||
'1205': `🧡Tip: You can click the 'Copy' button to copy default template content, then create and paste to your own template file. After that, back to select the file. `,
|
||||
'1250': `Advanced`,
|
||||
|
||||
@ -153,6 +153,17 @@ export default {
|
||||
'1220': `输出`,
|
||||
'1230': `字段`,
|
||||
'1260': `登录`,
|
||||
'1270': `数组限制`,
|
||||
'1271': `针对内容类型(电影/电视剧/书籍/音乐/游戏等)中的数组字段(如 actor、director、author、translator、aliases 等)设置最大保留数量, 超出部分将不会写入笔记。`,
|
||||
'1272': `提示: "适用类型"选择"全部类型"时, 限制将作用于所有类型中匹配字段名的字段; 选择具体类型时仅作用于该类型, 同字段同时存在两种限制时取较小值。`,
|
||||
'1273': `添加一条限制配置, 例如指定 movie 的 actor 只保留前 5 位。`,
|
||||
'127401': `添加数组长度限制`,
|
||||
'127402': `适用类型:`,
|
||||
'127403': `字段名:`,
|
||||
'127404': `如 actor / director / author 等`,
|
||||
'127405': `最大数量:`,
|
||||
'127406': `如 5`,
|
||||
'127407': `删除该限制`,
|
||||
|
||||
'1204': `配置对应类型的模板文件, 如果为空则使用默认模板. 模板可使用的参数在页签'字段'和'自定义字段'中查看`,
|
||||
'1205': `🧡提示: 建议点击右侧'复制'默认模板按钮, 然后在新建的文件中粘贴修改模板, 最后回到此处选择对应模板. `,
|
||||
|
||||
@ -9,6 +9,7 @@ import {DataField} from "./model/DataField";
|
||||
import {FieldVariable} from "./model/FieldVariable";
|
||||
import {CustomProperty} from "../douban/setting/model/CustomProperty";
|
||||
import {FileUtil} from "./FileUtil";
|
||||
import {ArrayLengthLimit} from "../douban/setting/model/ArrayLengthLimit";
|
||||
|
||||
type TargetType = 'text' | 'path' | 'yml_text';
|
||||
|
||||
@ -36,10 +37,12 @@ export class VariableUtil {
|
||||
return content;
|
||||
}
|
||||
if (obj instanceof Map) {
|
||||
this.applyArrayLengthLimits(subjectType, obj, settingManager);
|
||||
this.handleCustomVariable(subjectType, obj, settingManager, 'text')
|
||||
content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
|
||||
}else {
|
||||
const map = this.objToMap(obj);
|
||||
this.applyArrayLengthLimits(subjectType, map, settingManager);
|
||||
this.handleCustomVariable(subjectType, map, settingManager, 'text')
|
||||
content = this.replaceMap(map, allVariables, content, settingManager, targetType);
|
||||
}
|
||||
@ -277,6 +280,68 @@ export class VariableUtil {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户配置的 ArrayLengthLimit 截断 variableMap 中匹配的数组字段。
|
||||
*
|
||||
* 仅对 DataField.value 为数组的字段生效。当 limit > 0 且数组长度大于 limit 时,
|
||||
* 会取前 limit 个元素生成新的 DataField 替换原值; origin 保持不变以便后续逻辑。
|
||||
*
|
||||
* 当 type=all 时, 该限制作用于所有内容类型, 否则仅作用于 type 与当前 subjectType 相等的条目。
|
||||
*
|
||||
* @param subjectType 当前内容类型
|
||||
* @param variableMap 变量映射
|
||||
* @param settingManager
|
||||
*/
|
||||
static applyArrayLengthLimits(subjectType: SupportType,
|
||||
variableMap: Map<string, DataField>,
|
||||
settingManager: SettingsManager): void {
|
||||
if (!variableMap || variableMap.size == 0) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
const limits: ArrayLengthLimit[] = settingManager.getSetting('arrayLengthLimits');
|
||||
if (!limits || limits.length == 0) {
|
||||
return;
|
||||
}
|
||||
const effectiveLimits: Map<string, number> = new Map();
|
||||
limits
|
||||
.filter(l => l && l.field && l.limit && l.limit > 0)
|
||||
.filter(l => {
|
||||
if (!l.type) {
|
||||
return false;
|
||||
}
|
||||
const t = String(l.type).toLowerCase();
|
||||
return t === SupportType.all || t === subjectType;
|
||||
})
|
||||
.forEach(l => {
|
||||
const existing = effectiveLimits.get(l.field);
|
||||
// 当同一字段同时存在 type=all 和 type=具体 的限制时,
|
||||
// 优先使用更具体的(更小的)限制值, 行为更符合用户预期。
|
||||
if (existing == null || l.limit < existing) {
|
||||
effectiveLimits.set(l.field, l.limit);
|
||||
}
|
||||
});
|
||||
if (effectiveLimits.size == 0) {
|
||||
return;
|
||||
}
|
||||
effectiveLimits.forEach((limit, fieldName) => {
|
||||
const dataField = variableMap.get(fieldName);
|
||||
if (!dataField) {
|
||||
return;
|
||||
}
|
||||
const value = dataField.value;
|
||||
if (Array.isArray(value) && value.length > limit) {
|
||||
const truncated = value.slice(0, limit);
|
||||
variableMap.set(fieldName, new DataField(
|
||||
dataField.name,
|
||||
dataField.type,
|
||||
dataField.origin,
|
||||
truncated
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static handleText(v: string, targetType: TargetType, dataField: DataField = null): string {
|
||||
if (targetType === 'yml_text') {
|
||||
return YamlUtil.handleText(v, dataField);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user