poi-plugin-inventory-cleaner 1.9.2 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README-CN.md CHANGED
@@ -15,6 +15,9 @@
15
15
  - **联动改修助手**:读该插件本地状态,任何在改修计划里的装备都标「改修计划中」,**不参与可拆**。
16
16
  - **未完成任务需求**:汇总任务文本里提到的装备,解析需要的数量(×2 / 2つ / ★max),按数量从可拆里预留,并在列表上方给出「未完成任务需要的装备」清单。任务来源有四个,自动合并去重:poi 进行中任务 → **任务规划插件**(`poi-plugin-quest-planner`)见过的任务 → **任务信息插件**(`poi-plugin-quest-info-2`)的任务列表 → 「我该怎么做」的未接取缓存。**不依赖某一个插件**,都没有时至少统计进行中任务。
17
17
  - **联动当前任务**:任务需要的装备标「任务需要 N 件」,不参与可拆的件数就是预留的那部分。
18
+ - **按装备类型筛选**:沿用游戏本体「装备筛选」的 14 个大类 / 60 多个细分类型(与「装备信息」插件一致,如舰上战斗机、夜间轰炸机、高角炮、大口径主炮(II)),下拉框按大类分组。
19
+ - **排序**:按攻略判定 / 数量 / 可拆件数 / 名称 / 类型,随时切换。
20
+ - **名称显示**:中文名为主,日文原名作为副标题保留。
18
21
  - **筛选与搜索**:全部 / 可拆 / 留一些 / 不拆 / 看计划 / 受保护,支持按名称或 id 搜索。
19
22
 
20
23
  - **独立窗口**:使用 poi 原生的「在新窗口打开插件」功能(`poiPlugin.windowMode`,默认开启)。点击插件图标时 poi 会自己创建窗口并承载插件,带标题栏、主题、缩放和窗口位置记忆;不需要的话在「设置 → 插件 → 库存清理大师」里关掉即可。
package/README.md CHANGED
@@ -13,6 +13,9 @@
13
13
  - **Minus/plus steppers** — nudge the keep count by one; starts from the quest requirement when unset.
14
14
  - **Remodel Assistant link** — equipment that appears in any improvement plan is marked "in improvement plan" and never counted as scrap.
15
15
  - **Unfinished quest needs** — collects equipment from quest text (in-progress quests, `poi-plugin-quest-planner` observed quests, the `poi-plugin-quest-info-2` quest list, or the I-Tell-You cache — whichever is available), parses the required count, and reserves that many pieces. It does not depend on a single plugin; with none installed it still counts in-progress quests.
16
+ - **Type filter** — the game's own 14 equipment categories and 60+ detail types, matching Equipment Info, grouped in the dropdown.
17
+ - **Sorting** — verdict / count / clearable / name / type.
18
+ - **Names** — Chinese name first, original Japanese shown underneath.
16
19
  - **Filters & search** — all / scrap / keep some / keep / check plan / protected; search by name or id.
17
20
 
18
21
  - **Native window mode** — ships `poiPlugin.windowMode`, so poi opens the plugin in its own managed window (titlebar, theme, zoom, remembered bounds). Toggle it off in poi settings if you prefer the tab.
package/i18n/en-US.json CHANGED
@@ -30,6 +30,14 @@
30
30
  "stock.keepMinus": "Keep one less",
31
31
  "stock.keepPlus": "Keep one more",
32
32
  "stock.batchToggle": "Batch keep",
33
+ "stock.categoryAll": "All categories",
34
+ "stock.categoryTitle": "Filter by in-game equipment type",
35
+ "stock.sortTitle": "Sort by",
36
+ "stock.sortVerdict": "Guide verdict",
37
+ "stock.sortCount": "Count",
38
+ "stock.sortClearable": "Clearable count",
39
+ "stock.sortName": "Name",
40
+ "stock.sortCategory": "Type",
33
41
  "stock.batchHint": "Set one keep count per category; leave empty and Apply to clear it",
34
42
  "stock.batchApply": "Apply",
35
43
  "stock.batchKinds": "{{count}} kinds",
package/i18n/zh-CN.json CHANGED
@@ -30,6 +30,14 @@
30
30
  "stock.keepMinus": "少留一件",
31
31
  "stock.keepPlus": "多留一件",
32
32
  "stock.batchToggle": "批量保留",
33
+ "stock.categoryAll": "全部类别",
34
+ "stock.categoryTitle": "按游戏装备类型筛选",
35
+ "stock.sortTitle": "排序方式",
36
+ "stock.sortVerdict": "按攻略判定",
37
+ "stock.sortCount": "按数量",
38
+ "stock.sortClearable": "按可拆件数",
39
+ "stock.sortName": "按名称",
40
+ "stock.sortCategory": "按类型",
33
41
  "stock.batchHint": "按类别统一设置;留空点「应用」= 清除该类自定义值",
34
42
  "stock.batchApply": "应用",
35
43
  "stock.batchKinds": "{{count}} 种",
package/index.js CHANGED
@@ -8,7 +8,9 @@ const {
8
8
  equipName,
9
9
  filterRows,
10
10
  stepKeep,
11
+ sortRows,
11
12
  summarize,
13
+ OTHER_CATEGORY,
12
14
  } = require('./lib/inventory')
13
15
  const { buildQuestNeeds } = require('./lib/quest-needs')
14
16
  const { CSS } = require('./lib/style')
@@ -68,14 +70,19 @@ function getState() {
68
70
 
69
71
  let snapshot = null
70
72
  let snapshotEquips = null
73
+ let snapshotConst = null
71
74
 
72
75
  function getSnapshot() {
73
76
  const state = getState()
74
77
  const equips =
75
78
  state && state.info && state.info.equips ? state.info.equips : null
76
- if (snapshot && snapshotEquips === equips) return snapshot
79
+ const constant = state ? state.const : null
80
+ if (snapshot && snapshotEquips === equips && snapshotConst === constant) {
81
+ return snapshot
82
+ }
77
83
  snapshotEquips = equips
78
- snapshot = state ? { info: { equips } } : null
84
+ snapshotConst = constant
85
+ snapshot = state ? { info: { equips }, const: constant } : null
79
86
  return snapshot
80
87
  }
81
88
 
@@ -134,9 +141,9 @@ function StockRow({ row, onKeepChange }) {
134
141
  h(
135
142
  'div',
136
143
  { className: 'stock-row-main' },
137
- h('span', { className: 'stock-name' }, row.jp || row.cn || `#${row.id}`),
138
- row.cn && row.cn !== row.jp
139
- ? h('span', { className: 'stock-cn' }, row.cn)
144
+ h('span', { className: 'stock-name' }, row.cn || row.jp || `#${row.id}`),
145
+ row.cn && row.jp && row.cn !== row.jp
146
+ ? h('span', { className: 'stock-cn' }, row.jp)
140
147
  : null,
141
148
  h('span', { className: `stock-badge ${verdict.className}` }, t(verdict.labelKey)),
142
149
  ),
@@ -260,6 +267,8 @@ function App() {
260
267
  const state = useExternalStore(subscribe, getSnapshot)
261
268
  const [view, setView] = React.useState('all')
262
269
  const [keyword, setKeyword] = React.useState('')
270
+ const [category, setCategory] = React.useState('')
271
+ const [sort, setSort] = React.useState('verdict')
263
272
  const [keeps, setKeeps] = React.useState(loadKeeps)
264
273
 
265
274
  const handleKeepChange = React.useCallback((id, rawValue) => {
@@ -295,19 +304,38 @@ function App() {
295
304
  const map = new Map()
296
305
  for (const row of rows) {
297
306
  const key = row.category || ''
298
- const entry = map.get(key) || { key, kinds: 0 }
307
+ const entry = map.get(key) || {
308
+ key,
309
+ kinds: 0,
310
+ group: row.group || '',
311
+ groupId: row.groupId || 99,
312
+ categoryId: row.categoryId || 0,
313
+ }
299
314
  entry.kinds += 1
300
315
  map.set(key, entry)
301
316
  }
302
- return [...map.values()].sort((a, b) => b.kinds - a.kinds)
317
+ return [...map.values()].sort(
318
+ (a, b) => a.groupId - b.groupId || a.categoryId - b.categoryId,
319
+ )
303
320
  }, [rows])
304
321
 
322
+ const categoryGroups = React.useMemo(() => {
323
+ const map = new Map()
324
+ for (const entry of categories) {
325
+ const name = entry.group || t('stock.batchOther')
326
+ const list = map.get(name) || []
327
+ list.push(entry)
328
+ map.set(name, list)
329
+ }
330
+ return [...map.entries()]
331
+ }, [categories])
332
+
305
333
  const applyBatch = (category) => {
306
334
  setKeeps((prev) => applyCategoryKeep(prev, rows, category, batchValues[category]))
307
335
  }
308
336
  const visible = React.useMemo(
309
- () => filterRows(rows, { view, keyword }),
310
- [rows, view, keyword],
337
+ () => sortRows(filterRows(rows, { view, keyword, category }), sort),
338
+ [rows, view, keyword, category, sort],
311
339
  )
312
340
 
313
341
  const data = React.useMemo(
@@ -363,6 +391,43 @@ function App() {
363
391
  placeholder: t('stock.search'),
364
392
  onChange: (event) => setKeyword(event.target.value),
365
393
  }),
394
+ h(
395
+ 'select',
396
+ {
397
+ className: 'stock-category',
398
+ value: category,
399
+ title: t('stock.categoryTitle'),
400
+ onChange: (event) => setCategory(event.target.value),
401
+ },
402
+ h('option', { value: '' }, t('stock.categoryAll')),
403
+ categoryGroups.map(([groupName, list]) =>
404
+ h(
405
+ 'optgroup',
406
+ { key: groupName, label: groupName },
407
+ list.map((entry) =>
408
+ h(
409
+ 'option',
410
+ { key: entry.key || 'other', value: entry.key || OTHER_CATEGORY },
411
+ (entry.key || t('stock.batchOther')) + ' (' + entry.kinds + ')',
412
+ ),
413
+ ),
414
+ ),
415
+ ),
416
+ ),
417
+ h(
418
+ 'select',
419
+ {
420
+ className: 'stock-category stock-sort',
421
+ value: sort,
422
+ title: t('stock.sortTitle'),
423
+ onChange: (event) => setSort(event.target.value),
424
+ },
425
+ h('option', { value: 'verdict' }, t('stock.sortVerdict')),
426
+ h('option', { value: 'count' }, t('stock.sortCount')),
427
+ h('option', { value: 'clearable' }, t('stock.sortClearable')),
428
+ h('option', { value: 'name' }, t('stock.sortName')),
429
+ h('option', { value: 'category' }, t('stock.sortCategory')),
430
+ ),
366
431
  h(
367
432
  'div',
368
433
  { className: 'stock-views' },
@@ -0,0 +1,181 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * 装备分类:照搬游戏本体 main.js 的「装备筛选」两级结构
5
+ * (14 个大类 + 细类),和「装备信息」插件(poi-plugin-item-info)显示一致。
6
+ *
7
+ * 装备的筛选类型由 api_type[2] 决定,但游戏会把「夜间机」「高角炮」等
8
+ * 用图标 api_type[3] 提成独立类型,少数装备还有 id 级覆盖,见下面的规则。
9
+ */
10
+
11
+ const GROUPS = [
12
+ { id: 1, cn: '舰载战斗机', jp: '艦上戦闘機' },
13
+ { id: 2, cn: '舰载轰炸·攻击机', jp: '艦上爆撃・攻撃機' },
14
+ { id: 3, cn: '侦察机', jp: '偵察機' },
15
+ { id: 4, cn: '主炮', jp: '主砲' },
16
+ { id: 5, cn: '弹药', jp: '弾薬' },
17
+ { id: 6, cn: '副炮·对空', jp: '副砲・対空' },
18
+ { id: 7, cn: '鱼雷', jp: '魚雷' },
19
+ { id: 8, cn: '对潜', jp: '対潜' },
20
+ { id: 9, cn: '电探', jp: '電探' },
21
+ { id: 10, cn: '登陆·运输', jp: '上陸・輸送' },
22
+ { id: 11, cn: '人员·物资', jp: '人員・物資' },
23
+ { id: 12, cn: '陆上机', jp: '陸上機' },
24
+ { id: 13, cn: '舰体强化', jp: '艦体強化' },
25
+ { id: 14, cn: '其他', jp: 'その他' },
26
+ ]
27
+
28
+ const GROUP_BY_ID = new Map(GROUPS.map((group) => [group.id, group]))
29
+
30
+ /** 细类:id 沿用游戏筛选里的编号,types 是归到这一类的筛选类型。 */
31
+ const DETAILS = [
32
+ { id: 11, group: 1, types: [6, 56] },
33
+ { id: 12, group: 1, types: [82] },
34
+ { id: 21, group: 2, types: [7, 57] },
35
+ { id: 22, group: 2, types: [8, 58] },
36
+ { id: 23, group: 2, types: [83] },
37
+ { id: 24, group: 2, types: [84] },
38
+ { id: 31, group: 3, types: [10, 85] },
39
+ { id: 32, group: 3, types: [9, 94] },
40
+ { id: 33, group: 3, types: [11] },
41
+ { id: 34, group: 3, types: [26, 45, 59] },
42
+ { id: 35, group: 3, types: [25] },
43
+ { id: 41, group: 4, types: [1] },
44
+ { id: 42, group: 4, types: [2] },
45
+ { id: 43, group: 4, types: [3, 38] },
46
+ { id: 51, group: 5, types: [19] },
47
+ { id: 52, group: 5, types: [18] },
48
+ { id: 53, group: 5, types: [20, 33] },
49
+ { id: 61, group: 6, types: [4, 95] },
50
+ { id: 62, group: 6, types: [81] },
51
+ { id: 63, group: 6, types: [21] },
52
+ { id: 64, group: 6, types: [36] },
53
+ { id: 71, group: 7, types: [5] },
54
+ { id: 72, group: 7, types: [22] },
55
+ { id: 73, group: 7, types: [32] },
56
+ { id: 81, group: 8, types: [14, 40] },
57
+ { id: 82, group: 8, types: [15] },
58
+ { id: 91, group: 9, types: [12] },
59
+ { id: 92, group: 9, types: [13, 93] },
60
+ { id: 93, group: 9, types: [51] },
61
+ { id: 101, group: 10, types: [24] },
62
+ { id: 102, group: 10, types: [46] },
63
+ { id: 103, group: 10, types: [30, 50, 52] },
64
+ { id: 111, group: 11, types: [43] },
65
+ { id: 112, group: 11, types: [23] },
66
+ { id: 113, group: 11, types: [39] },
67
+ { id: 114, group: 11, types: [35, 44] },
68
+ { id: 121, group: 12, types: [47, 91] },
69
+ { id: 122, group: 12, types: [48] },
70
+ { id: 123, group: 12, types: [49, 53, 41] },
71
+ { id: 131, group: 13, types: [17] },
72
+ { id: 132, group: 13, types: [16, 27, 28] },
73
+ { id: 141, group: 14, types: [29, 42] },
74
+ { id: 142, group: 14, types: [37] },
75
+ { id: 143, group: 14, types: [54] },
76
+ { id: 144, group: 14, types: [31, 34] },
77
+ ]
78
+
79
+ /** 客户端里才有的筛选类型(游戏类型表里没有),自己给名字。 */
80
+ const SYNTHETIC_NAMES = {
81
+ 81: { cn: '高角炮', jp: '高角砲' },
82
+ 82: { cn: '夜间战斗机', jp: '夜間戦闘機' },
83
+ 83: { cn: '夜间轰炸机', jp: '夜間爆撃機' },
84
+ 84: { cn: '夜间攻击机', jp: '夜間攻撃機' },
85
+ 85: { cn: '夜间侦察机', jp: '夜間偵察機' },
86
+ 91: { cn: '喷式轰炸机(II)', jp: '噴式爆撃機(II)' },
87
+ 93: { cn: '大型电探(II)', jp: '大型電探(II)' },
88
+ 94: { cn: '舰上侦察机(II)', jp: '艦上偵察機(II)' },
89
+ 95: { cn: '副炮(II)', jp: '副砲(II)' },
90
+ }
91
+
92
+ /** id 级覆盖:这些装备在游戏里被单独提出来筛选。 */
93
+ const TYPE_OVERRIDES = {
94
+ 128: 38,
95
+ 281: 38,
96
+ 465: 38,
97
+ 142: 93,
98
+ 460: 93,
99
+ 151: 94,
100
+ 561: 91,
101
+ }
102
+
103
+ /** 图标提级:白天/夜间、普通炮/高角炮共用类型号,靠图标区分。 */
104
+ const ICON_RULES = [
105
+ { types: [1, 2, 3, 4], icon: 16, result: 81 },
106
+ { types: [6, 56], icon: 45, result: 82 },
107
+ { types: [7, 57], icon: 58, result: 83 },
108
+ { types: [8, 58], icon: 46, result: 84 },
109
+ { types: [10], icon: 50, result: 85 },
110
+ ]
111
+
112
+ const DETAIL_BY_TYPE = new Map()
113
+ for (const detail of DETAILS) {
114
+ for (const type of detail.types) DETAIL_BY_TYPE.set(type, detail)
115
+ }
116
+
117
+ function filterTypeOf(id, apiType) {
118
+ const override = TYPE_OVERRIDES[Number(id)]
119
+ if (override) return override
120
+ if (!Array.isArray(apiType)) return 0
121
+ const type = Number(apiType[2]) || 0
122
+ const icon = Number(apiType[3]) || 0
123
+ for (const rule of ICON_RULES) {
124
+ if (rule.icon === icon && rule.types.indexOf(type) >= 0) return rule.result
125
+ }
126
+ return type
127
+ }
128
+
129
+ function labelsFor(filterType, equipTypes, detail) {
130
+ const synthetic = SYNTHETIC_NAMES[filterType]
131
+ if (synthetic) return synthetic
132
+
133
+ const own = equipTypes && equipTypes[filterType]
134
+ if (own && own.api_name) return { cn: String(own.api_name), jp: '' }
135
+
136
+ // 合成类型(如 大口径主炮(II))没有自己的名字,用所属细类的第一个类型的名字
137
+ if (detail) {
138
+ for (const type of detail.types) {
139
+ const base = equipTypes && equipTypes[type]
140
+ if (base && base.api_name) return { cn: String(base.api_name), jp: '' }
141
+ }
142
+ }
143
+ return null
144
+ }
145
+
146
+ /**
147
+ * 生成装备 id → 分类信息。数据来自 poi 的主数据:
148
+ * state.const.$equips(api_type)与 state.const.$equipTypes(类型名)。
149
+ */
150
+ function buildTypeIndex(state) {
151
+ const index = new Map()
152
+ const constant = state && state.const ? state.const : {}
153
+ const equips = constant.$equips
154
+ const equipTypes = constant.$equipTypes
155
+ if (!equips || typeof equips !== 'object') return index
156
+
157
+ for (const [id, master] of Object.entries(equips)) {
158
+ if (!master || typeof master !== 'object') continue
159
+ const filterType = filterTypeOf(id, master.api_type)
160
+ const detail = DETAIL_BY_TYPE.get(filterType)
161
+ const labels = labelsFor(filterType, equipTypes, detail)
162
+ const group = detail ? GROUP_BY_ID.get(detail.group) : null
163
+ index.set(Number(id), {
164
+ filterType,
165
+ detailId: detail ? detail.id : 0,
166
+ groupId: detail ? detail.group : 99,
167
+ category: labels ? labels.cn : '',
168
+ categoryJp: labels ? labels.jp : '',
169
+ group: group ? group.cn : '',
170
+ groupJp: group ? group.jp : '',
171
+ })
172
+ }
173
+ return index
174
+ }
175
+
176
+ module.exports = {
177
+ GROUPS,
178
+ DETAILS,
179
+ buildTypeIndex,
180
+ filterTypeOf,
181
+ }
package/lib/inventory.js CHANGED
@@ -15,6 +15,7 @@ const CONSUMED = require('../data/consumed-materials.json')
15
15
  const GUIDE = require('../data/equipment-guide.json')
16
16
  const { getProtection } = require('./protect')
17
17
  const { buildQuestNeeds } = require('./quest-needs')
18
+ const { buildTypeIndex } = require('./equip-types')
18
19
 
19
20
  function isRecord(value) {
20
21
  return typeof value === 'object' && value !== null && !Array.isArray(value)
@@ -47,6 +48,7 @@ function buildInventory(state, options = {}) {
47
48
  const protection = options.protection || getProtection(state)
48
49
  const planned = protection.planned || new Set()
49
50
  const keeps = options.keeps || {}
51
+ const typeIndex = options.typeIndex || buildTypeIndex(state)
50
52
  const questNeeds = options.questNeeds || buildQuestNeeds(state)
51
53
 
52
54
  const byId = new Map()
@@ -72,6 +74,7 @@ function buildInventory(state, options = {}) {
72
74
  const guide = GUIDE[String(row.id)] || null
73
75
  const consumed = CONSUMED[String(row.id)] || ''
74
76
 
77
+ const typeInfo = typeIndex.get(row.id) || null
75
78
  const verdict = rule ? rule.verdict : 'unknown'
76
79
  const inPlan = planned.has(row.id)
77
80
  const need = questNeeds.byEquip.get(row.id) || null
@@ -106,7 +109,11 @@ function buildInventory(state, options = {}) {
106
109
  unlocked: row.unlocked,
107
110
  verdict,
108
111
  rawVerdict: rule ? rule.raw : '',
109
- category: rule ? rule.category : '',
112
+ category: typeInfo && typeInfo.category ? typeInfo.category : (rule ? rule.category : ''),
113
+ categoryId: typeInfo ? typeInfo.detailId : 0,
114
+ group: typeInfo ? typeInfo.group : '',
115
+ groupId: typeInfo ? typeInfo.groupId : 99,
116
+ guideCategory: rule ? rule.category : '',
110
117
  usage: rule ? rule.usage : '',
111
118
  obtain: rule ? rule.obtain : '',
112
119
  consumed,
@@ -161,6 +168,36 @@ function stepKeep(row, delta) {
161
168
  return Math.max(0, Math.min(999, base + delta))
162
169
  }
163
170
 
171
+ const SORTS = ['verdict', 'count', 'clearable', 'name', 'category']
172
+
173
+ function sortRows(rows, sort = 'verdict') {
174
+ const key = SORTS.indexOf(sort) >= 0 ? sort : 'verdict'
175
+ const copy = rows.slice()
176
+ copy.sort((a, b) => {
177
+ if (key === 'count') return b.count - a.count || a.id - b.id
178
+ if (key === 'clearable') return b.clearable - a.clearable || b.count - a.count
179
+ if (key === 'name') {
180
+ const left = String(a.cn || a.jp || '')
181
+ const right = String(b.cn || b.jp || '')
182
+ return left.localeCompare(right, 'zh-CN') || a.id - b.id
183
+ }
184
+ if (key === 'category') {
185
+ return (
186
+ a.groupId - b.groupId ||
187
+ a.categoryId - b.categoryId ||
188
+ b.count - a.count ||
189
+ a.id - b.id
190
+ )
191
+ }
192
+ return (
193
+ VERDICT_RANK[a.verdict] - VERDICT_RANK[b.verdict] ||
194
+ b.count - a.count ||
195
+ a.id - b.id
196
+ )
197
+ })
198
+ return copy
199
+ }
200
+
164
201
  function summarize(rows) {
165
202
  const totalPieces = rows.reduce((sum, row) => sum + row.count, 0)
166
203
  const scrapPieces = rows.reduce((sum, row) => sum + row.clearable, 0)
@@ -179,12 +216,20 @@ function summarize(rows) {
179
216
  }
180
217
  }
181
218
 
219
+ const OTHER_CATEGORY = '__other__'
220
+
182
221
  function filterRows(rows, options = {}) {
183
222
  const keyword = String(options.keyword || '')
184
223
  .trim()
185
224
  .toLowerCase()
186
225
  const view = options.view || 'all'
226
+ const category = String(options.category || '').trim()
187
227
  return rows.filter((row) => {
228
+ if (category === OTHER_CATEGORY) {
229
+ if (row.category) return false
230
+ } else if (category && (row.category || '') !== category) {
231
+ return false
232
+ }
188
233
  if (view === 'scrap' && row.verdict !== 'scrap' && !row.manual) return false
189
234
  if (view === 'protected' && !row.protected) return false
190
235
  if (view === 'keepSome' && row.verdict !== 'keepSome') return false
@@ -202,6 +247,9 @@ function filterRows(rows, options = {}) {
202
247
  }
203
248
 
204
249
  module.exports = {
250
+ OTHER_CATEGORY,
251
+ SORTS,
252
+ sortRows,
205
253
  applyCategoryKeep,
206
254
  stepKeep,
207
255
  buildInventory,
package/lib/style.js CHANGED
@@ -100,6 +100,24 @@ const CSS = `
100
100
  border-color: var(--stock-indigo);
101
101
  }
102
102
 
103
+ .stock-category {
104
+ height: 32px;
105
+ padding: 0 8px;
106
+ border: 1px solid var(--stock-border);
107
+ border-radius: 8px;
108
+ color: inherit;
109
+ background: var(--stock-surface);
110
+ font-family: inherit;
111
+ font-size: 12px;
112
+ cursor: pointer;
113
+ outline: none;
114
+ max-width: 220px;
115
+ }
116
+
117
+ .stock-category:focus {
118
+ border-color: var(--stock-indigo);
119
+ }
120
+
103
121
  .stock-views {
104
122
  display: flex;
105
123
  gap: 6px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-inventory-cleaner",
3
- "version": "1.9.2",
3
+ "version": "1.11.1",
4
4
  "description": "库存清理大师:对照改修攻略与保留上限,找出囤积过量、可以清理的装备,并标出不能拆的改修材料。",
5
5
  "main": "index.js",
6
6
  "files": [