Merge d35dcfe51307c4a70f05dc35dd2386e55128914d into af09f43baa48423e243da7a5ef0e3678bf908892

This commit is contained in:
callmer00t 2026-05-19 12:29:09 +00:00 committed by GitHub
commit a79f6c2917
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 266 additions and 0 deletions

@ -61,6 +61,7 @@ export const DEFAULT_SETTINGS: DoubanPluginSetting = {
maxStar: 5, maxStar: 5,
}, },
searchDefaultType: SupportType.all, searchDefaultType: SupportType.all,
arrayLengthLimits: [],
} }

@ -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 {arraySettingDisplay, arraySettingDisplayUI} from "./ArrayDisplayTypeSettingsHelper";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {constructLoginUI} from "./LoginSettingsHelper"; 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('1260'), construct: constructLoginUI},
{name: i18nHelper.getMessage('1220'), construct: constructOutUI}, {name: i18nHelper.getMessage('1220'), construct: constructOutUI},
{name: i18nHelper.getMessage('120601'), construct: arraySettingDisplayUI}, {name: i18nHelper.getMessage('120601'), construct: arraySettingDisplayUI},
{name: i18nHelper.getMessage('1270'), construct: constructArrayLengthLimitSettingsUI},
{name: i18nHelper.getMessage('1240'), construct: constructCustomPropertySettingsUI}, {name: i18nHelper.getMessage('1240'), construct: constructCustomPropertySettingsUI},
{name: i18nHelper.getMessage('1230'), construct: constructTemplateVariablesUI}, {name: i18nHelper.getMessage('1230'), construct: constructTemplateVariablesUI},
{name: i18nHelper.getMessage('1250'), construct: constructAdvancedUI} {name: i18nHelper.getMessage('1250'), construct: constructAdvancedUI}

@ -19,6 +19,7 @@ import {
} from "./model/ArraySetting"; } from "./model/ArraySetting";
import {logger} from "bs-logger"; import {logger} from "bs-logger";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {ArrayLengthLimit} from "./model/ArrayLengthLimit";
export default class SettingsManager { export default class SettingsManager {
app: App; app: App;
@ -247,4 +248,35 @@ export default class SettingsManager {
getSettings() { getSettings() {
return this.settings; 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();
}
} }

@ -0,0 +1,27 @@
import {SupportType} from "../../../constant/Constsant";
/**
*
*
* actordirectorauthor
*
*
* 例如: 设置 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 {ScoreSetting} from "./ScoreSetting";
import PictureBedSetting from "./PictureBedSetting"; import PictureBedSetting from "./PictureBedSetting";
import {SupportType} from "../../../constant/Constsant"; import {SupportType} from "../../../constant/Constsant";
import {ArrayLengthLimit} from "./ArrayLengthLimit";
export interface DoubanPluginSetting { export interface DoubanPluginSetting {
onlineSettingsFileName: string; onlineSettingsFileName: string;
@ -42,4 +43,5 @@ export interface DoubanPluginSetting {
arraySettings: ArraySetting[], arraySettings: ArraySetting[],
scoreSetting: ScoreSetting, scoreSetting: ScoreSetting,
searchDefaultType: SupportType, searchDefaultType: SupportType,
arrayLengthLimits: ArrayLengthLimit[],
} }

@ -138,6 +138,17 @@ PS: This file could be delete if you want to.
'1220': `Output`, '1220': `Output`,
'1230': `Description`, '1230': `Description`,
'1260': `Login`, '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"`, '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. `, '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`, '1250': `Advanced`,

@ -153,6 +153,17 @@ export default {
'1220': `输出`, '1220': `输出`,
'1230': `字段`, '1230': `字段`,
'1260': `登录`, '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': `配置对应类型的模板文件, 如果为空则使用默认模板. 模板可使用的参数在页签'字段'和'自定义字段'中查看`, '1204': `配置对应类型的模板文件, 如果为空则使用默认模板. 模板可使用的参数在页签'字段'和'自定义字段'中查看`,
'1205': `🧡提示: 建议点击右侧'复制'默认模板按钮, 然后在新建的文件中粘贴修改模板, 最后回到此处选择对应模板. `, '1205': `🧡提示: 建议点击右侧'复制'默认模板按钮, 然后在新建的文件中粘贴修改模板, 最后回到此处选择对应模板. `,

@ -9,6 +9,7 @@ import {DataField} from "./model/DataField";
import {FieldVariable} from "./model/FieldVariable"; import {FieldVariable} from "./model/FieldVariable";
import {CustomProperty} from "../douban/setting/model/CustomProperty"; import {CustomProperty} from "../douban/setting/model/CustomProperty";
import {FileUtil} from "./FileUtil"; import {FileUtil} from "./FileUtil";
import {ArrayLengthLimit} from "../douban/setting/model/ArrayLengthLimit";
type TargetType = 'text' | 'path' | 'yml_text'; type TargetType = 'text' | 'path' | 'yml_text';
@ -36,10 +37,12 @@ export class VariableUtil {
return content; return content;
} }
if (obj instanceof Map) { if (obj instanceof Map) {
this.applyArrayLengthLimits(subjectType, obj, settingManager);
this.handleCustomVariable(subjectType, obj, settingManager, 'text') this.handleCustomVariable(subjectType, obj, settingManager, 'text')
content = this.replaceMap(obj, allVariables, content, settingManager, targetType); content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
}else { }else {
const map = this.objToMap(obj); const map = this.objToMap(obj);
this.applyArrayLengthLimits(subjectType, map, settingManager);
this.handleCustomVariable(subjectType, map, settingManager, 'text') this.handleCustomVariable(subjectType, map, settingManager, 'text')
content = this.replaceMap(map, allVariables, content, settingManager, targetType); content = this.replaceMap(map, allVariables, content, settingManager, targetType);
} }
@ -277,6 +280,68 @@ export class VariableUtil {
return map; 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 { private static handleText(v: string, targetType: TargetType, dataField: DataField = null): string {
if (targetType === 'yml_text') { if (targetType === 'yml_text') {
return YamlUtil.handleText(v, dataField); return YamlUtil.handleText(v, dataField);