Support hiding items in Custom Item sections, and allow adding items to default Kefin custom item sections

This commit is contained in:
HighImKevin committed 2026-10-11 08:25:56 -04:00
1 parent 9c2838f470
commit 07aff27058
9 files changed
+338 -43

No files matched your search

+1 -14
View File
@@ -1008,20 +1008,7 @@
}
const browseSection = studiosGroup.sections.find((s) => s?.id === BROWSE_BY_STUDIO_SECTION_ID);
const catalogNames = new Set(BROWSE_BY_STUDIO_CATALOG.map((entry) => entry.name));
// Prune items no longer in Kefin catalog (even when catalog is otherwise fully resolved)
if (browseSection && Array.isArray(browseSection.items)) {
const beforeLen = browseSection.items.length;
browseSection.items = browseSection.items.filter(
(item) => item?.Name && catalogNames.has(item.Name)
);
if (browseSection.items.length !== beforeLen) {
hasChanges = true;
LOG(`Pruned ${beforeLen - browseSection.items.length} browse-by-studio studios not in catalog`);
}
}
// Append-only: do not prune user-kept studios that leave the Kefin catalog
const existingItems = Array.isArray(browseSection?.items) ? browseSection.items : [];
if (browseSection && isBrowseByStudioCatalogFullyResolved(existingItems)) {
+67 -3
View File
@@ -563,8 +563,9 @@
display: flex;
flex-wrap: wrap;
gap: 1em;
justify-content: center;
justify-content: start;
padding: 0.5em 0 1em;
align-items: center;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-tile,
@@ -576,6 +577,68 @@
gap: 0.35em;
}
.hsse-custom-items-grid > * {
max-width: 120px;
}
.listItemBodyText.hsse-custom-item-tile-label {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.hsse-custom-item-edit-badge {
position: absolute;
bottom: 0.2em;
right: 0.2em;
}
.hsse-custom-item-actions > * {
height: 25px;
width: 25px;
display: flex;
justify-content: center;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-tile.is-hidden
.hsse-custom-item-tile-image,
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-tile.is-hidden
.hsse-custom-item-tile-label {
opacity: 0.4;
filter: grayscale(0.35);
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 0.2em;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-action,
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-trash {
font-size: 0;
padding: 0.3em;
min-height: 0;
min-width: 0;
line-height: 1;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-action:disabled {
opacity: 0.35;
cursor: default;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-action-icon,
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"])
.hsse-custom-item-trash-icon {
font-size: 1.1rem;
}
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-tile-image,
:is([data-modal-id="kefin-homescreen-section-editor"], [data-modal-id="kefin-homescreen-section-create-flow"]) .hsse-custom-item-add-tile {
width: auto;
@@ -1598,7 +1661,7 @@
display: grid !important;
}
/* Item source — show only the panel matching the active source (profile rules still gate custom items on non-full profiles) */
/* Item source — show only the panel matching the active source */
.hsae-root[data-item-source="jellyfin"] .hsae-custom-items-group,
.hsae-root[data-item-source="jellyfin"] .hsae-external-list-group {
display: none !important;
@@ -1660,6 +1723,7 @@
}
.hsse-image-preview img {
height: 200px;
height: 120px;
max-width: 120px;
width: auto !important;
}
+27 -6
View File
@@ -656,6 +656,23 @@
return window.KefinHomeScreenCustomItemsEditor;
}
/** User-created / custom-group sections may delete items; default Kefin sections may only hide. */
function shouldAllowCustomItemRemove(state) {
const id = state?.section?.id || '';
if (state?.context?.isCustomSection === true) return true;
if (state?.section?.isCustom === true) return true;
if (id.startsWith('custom-')) return true;
return false;
}
function getAdvancedCustomItemsOptions(state) {
const customEditor = getCustomItemsEditor();
if (!customEditor?.getCustomItemsOptions) return {};
return customEditor.getCustomItemsOptions({
allowRemove: shouldAllowCustomItemRemove(state)
});
}
function getCachePresets() {
const cache = window.KefinHomeConfig2?.CACHE || window.KefinHomeConfig2?.CACHE_CONFIG || {};
return [
@@ -1110,7 +1127,7 @@
const customEditor = getCustomItemsEditor();
if (customEditor && state.itemSource === 'custom') {
customEditor.collectCustomItemsFromForm(root, state, customEditor.getCustomItemsOptions());
customEditor.collectCustomItemsFromForm(root, state, getAdvancedCustomItemsOptions(state));
}
if (state.itemSource === 'external') {
@@ -1863,15 +1880,19 @@
function buildCustomItemsHTML(state) {
const customEditor = getCustomItemsEditor();
const allowRemove = shouldAllowCustomItemRemove(state);
const html = customEditor
? customEditor.renderCustomItemsHTML(state, customEditor.getCustomItemsOptions())
? customEditor.renderCustomItemsHTML(state, getAdvancedCustomItemsOptions(state))
: '<div class="listItemBodyText secondary">Custom items editor not available.</div>';
const groupDesc = allowRemove
? 'Create cards with custom images and links'
: 'Reorder or hide cards. Default section items cannot be deleted.';
return `
<section class="hsae-group hsae-custom-items-group" data-hsae-profiles="full">
<section class="hsae-group hsae-custom-items-group" data-hsae-profiles="full minimal postProcessing normal dateCutoffs">
<div class="hsae-group-header">
<div class="listItemBodyText hsae-group-title">Custom Items</div>
<div class="listItemBodyText secondary hsae-group-desc">Create cards with custom images and links</div>
<div class="listItemBodyText secondary hsae-group-desc">${escapeHtml(groupDesc)}</div>
</div>
<div class="hsae-custom-items-stage">${html}</div>
</section>
@@ -2636,7 +2657,7 @@
const liveState = getLiveEditorState(dialog);
const customItemsGroup = dialog.querySelector('.hsae-custom-items-group');
if (isProfileFieldActive(root, customItemsGroup) && customEditor && liveState?.customItems) {
customEditor.collectCustomItemsFromForm(dialog, liveState, customEditor.getCustomItemsOptions());
customEditor.collectCustomItemsFromForm(dialog, liveState, getAdvancedCustomItemsOptions(liveState));
section.items = liveState.customItems.map(customEditor.buildStaticItemFromDraft).filter(Boolean);
} else if (!Array.isArray(section.items)) {
section.items = [];
@@ -3373,7 +3394,7 @@
if (action?.startsWith('hsae-')) {
const customEditor = getCustomItemsEditor();
if (customEditor && state.itemSource === 'custom') {
customEditor.handleAction(action, actionEl, root, state, customEditor.getCustomItemsOptions(), () => refreshEditorBody(modalInstance));
customEditor.handleAction(action, actionEl, root, state, getAdvancedCustomItemsOptions(state), () => refreshEditorBody(modalInstance));
}
return;
}
+197 -12
View File
@@ -37,9 +37,14 @@
addItem: 'wizard-add-custom-item',
removeItem: 'wizard-remove-custom-item',
toggleImages: 'wizard-toggle-custom-images',
toggleHide: 'wizard-toggle-custom-item-hide',
moveLeft: 'wizard-move-custom-item-left',
moveRight: 'wizard-move-custom-item-right',
...(options.actions || {})
},
intro: options.intro !== false
intro: options.intro !== false,
// Default Kefin sections pass false — hide only, never delete
allowRemove: options.allowRemove !== false
};
}
@@ -61,7 +66,8 @@
backdropUrl: '',
squareUrl: '',
bannerUrl: '',
logoUrl: ''
logoUrl: '',
hidden: false
};
}
@@ -77,6 +83,9 @@
draft.squareUrl = item.squareUrl || '';
draft.bannerUrl = item.bannerUrl || '';
draft.logoUrl = item.logoUrl || '';
draft.hidden = item.hidden === true;
if (item.Id || item.id) draft.id = item.Id || item.id;
if (item.Type) draft.type = item.Type;
return draft;
}
@@ -114,6 +123,33 @@
state.customItemAdditionalExpanded = next;
}
function swapCustomItemExpanded(state, fromIndex, toIndex) {
if (!state.customItemAdditionalExpanded) return;
const a = state.customItemAdditionalExpanded[fromIndex];
const b = state.customItemAdditionalExpanded[toIndex];
if (a === undefined && b === undefined) return;
if (b === undefined) delete state.customItemAdditionalExpanded[fromIndex];
else state.customItemAdditionalExpanded[fromIndex] = b;
if (a === undefined) delete state.customItemAdditionalExpanded[toIndex];
else state.customItemAdditionalExpanded[toIndex] = a;
}
function moveCustomItemExpanded(state, fromIndex, toIndex) {
if (!state.customItemAdditionalExpanded || fromIndex === toIndex) return;
const next = {};
const moved = state.customItemAdditionalExpanded[fromIndex];
Object.entries(state.customItemAdditionalExpanded).forEach(([key, value]) => {
const i = parseInt(key, 10);
if (Number.isNaN(i) || i === fromIndex) return;
let dest = i;
if (fromIndex < toIndex && i > fromIndex && i <= toIndex) dest = i - 1;
else if (fromIndex > toIndex && i >= toIndex && i < fromIndex) dest = i + 1;
next[dest] = value;
});
if (moved !== undefined) next[toIndex] = moved;
state.customItemAdditionalExpanded = next;
}
function hasValidCustomItems(items) {
return (items || []).some(item => item.name?.trim() && item.posterUrl?.trim());
}
@@ -138,11 +174,13 @@
if (value) provided[key] = value;
});
const item = { Name: name, Type: 'Folder' };
const item = { Name: name, Type: draft.type || 'Folder' };
if (draft.id) item.Id = draft.id;
const cardUrl = draft.cardUrl?.trim();
if (cardUrl) item.cardUrl = cardUrl;
const cardFooter = draft.cardFooter?.trim();
if (cardFooter) item.cardFooter = cardFooter;
if (draft.hidden === true) item.hidden = true;
const keys = Object.keys(provided);
if (keys.length === 1 && provided.posterUrl) {
@@ -177,9 +215,20 @@
}
}
function resolvePreviewUrl(url) {
const kefinTweaksRoot = window.KefinTweaksConfig?.kefinTweaksRoot || '';
const serverId = (typeof ApiClient !== 'undefined' && typeof ApiClient.serverId === 'function')
? ApiClient.serverId()
: '';
return String(url || '')
.replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot)
.replace(/\$\{serverId\}/g, serverId)
.trim();
}
function updateCustomImagePreview(imgEl, url) {
if (!imgEl) return;
const trimmed = (url || '').trim();
const trimmed = resolvePreviewUrl(url);
if (!trimmed) {
imgEl.removeAttribute('src');
imgEl.style.display = 'none';
@@ -268,19 +317,41 @@
`;
}
function renderCustomItemTileHTML(index, item, canRemove, options) {
function renderCustomItemTileHTML(index, item, canRemove, options, itemCount) {
const opts = getOptions(options);
const label = item.name?.trim() || `Item ${index + 1}`;
const isHidden = item.hidden === true;
const canMoveLeft = index > 0;
const canMoveRight = index < itemCount - 1;
const hideIcon = isHidden ? 'visibility_off' : 'visibility';
const hideLabel = isHidden ? `Show ${label}` : `Hide ${label}`;
const removeTitle = canRemove
? `Remove ${label}`
: (opts.allowRemove === false
? 'Default section items cannot be deleted — hide instead'
: `Remove ${label}`);
return `
<div class="hsse-custom-item-tile" data-custom-item-index="${index}">
<div class="hsse-custom-item-tile${isHidden ? ' is-hidden' : ''}" data-custom-item-index="${index}">
<button type="button" class="hsse-custom-item-tile-image hsse-image-preview hsse-item-tile-preview" ${actionAttr(opts, 'openEditor')} data-item-index="${index}" aria-label="Edit ${escapeHtml(label)}">
<img class="hsse-image-preview-img" alt="" data-tile-index="${index}">
<span class="hsse-custom-item-edit-badge">${renderMaterialIcon('edit')}</span>
</button>
<div class="listItemBodyText hsse-custom-item-tile-label">${escapeHtml(label)}</div>
<button type="button" class="emby-button raised hsse-custom-item-trash" ${actionAttr(opts, 'removeItem')} data-item-index="${index}"${canRemove ? '' : ' disabled'} aria-label="Remove ${escapeHtml(label)}">
${renderMaterialIcon('delete', 'hsse-custom-item-trash-icon')}
</button>
<div class="hsse-custom-item-actions">
<button type="button" class="emby-button raised hsse-custom-item-action" ${actionAttr(opts, 'moveLeft')} data-item-index="${index}"${canMoveLeft ? '' : ' disabled'} aria-label="Move ${escapeHtml(label)} left">
${renderMaterialIcon('chevron_left', 'hsse-custom-item-action-icon')}
</button>
<button type="button" class="emby-button raised hsse-custom-item-action hsse-custom-item-hide" ${actionAttr(opts, 'toggleHide')} data-item-index="${index}" aria-label="${escapeHtml(hideLabel)}" aria-pressed="${isHidden ? 'true' : 'false'}">
${renderMaterialIcon(hideIcon, 'hsse-custom-item-action-icon')}
</button>
<button type="button" class="emby-button raised hsse-custom-item-action hsse-custom-item-trash" ${actionAttr(opts, 'removeItem')} data-item-index="${index}"${canRemove ? '' : ' disabled'} title="${escapeHtml(removeTitle)}" aria-label="${escapeHtml(removeTitle)}">
${renderMaterialIcon('delete', 'hsse-custom-item-action-icon hsse-custom-item-trash-icon')}
</button>
<button type="button" class="emby-button raised hsse-custom-item-action" ${actionAttr(opts, 'moveRight')} data-item-index="${index}"${canMoveRight ? '' : ' disabled'} aria-label="Move ${escapeHtml(label)} right">
${renderMaterialIcon('chevron_right', 'hsse-custom-item-action-icon')}
</button>
</div>
</div>
`;
}
@@ -319,7 +390,7 @@
const opts = getOptions(options);
ensureCustomItemsInitialized(state);
const items = state.customItems;
const canRemove = items.length > 1;
const canRemove = opts.allowRemove !== false && items.length > 1;
return `
<div class="hsse-custom-items-stage">
@@ -329,7 +400,7 @@
<p class="listItemBodyText secondary hsse-field-hint">Name and poster required. Link and other images optional.</p>
</div>` : ''}
<div class="hsse-custom-items-grid">
${items.map((item, index) => renderCustomItemTileHTML(index, item, canRemove, opts)).join('')}
${items.map((item, index) => renderCustomItemTileHTML(index, item, canRemove, opts, items.length)).join('')}
<button type="button" class="hsse-custom-item-add-tile" ${actionAttr(opts, 'addItem')} aria-label="Add item">
${renderMaterialIcon('add', 'hsse-custom-item-add-icon')}
</button>
@@ -339,6 +410,48 @@
`;
}
function reindexTileDom(tile, index, itemCount) {
if (!tile) return;
tile.dataset.customItemIndex = String(index);
tile.querySelectorAll('[data-item-index]').forEach((el) => {
el.dataset.itemIndex = String(index);
});
const tileImg = tile.querySelector('img[data-tile-index]');
if (tileImg) tileImg.dataset.tileIndex = String(index);
// Action order: left | hide | delete | right
const actions = tile.querySelectorAll('.hsse-custom-item-actions .hsse-custom-item-action');
if (actions.length >= 4) {
actions[0].disabled = index <= 0;
actions[3].disabled = index >= itemCount - 1;
}
}
function swapAdjacentCustomItemTiles(root, fromIndex, toIndex) {
if (!root || fromIndex === toIndex) return false;
const grid = root.querySelector('.hsse-custom-items-grid');
if (!grid) return false;
const a = grid.querySelector(`[data-custom-item-index="${fromIndex}"]`);
const b = grid.querySelector(`[data-custom-item-index="${toIndex}"]`);
if (!a || !b) return false;
const aNext = a.nextSibling;
const bNext = b.nextSibling;
if (aNext === b) {
grid.insertBefore(b, a);
} else if (bNext === a) {
grid.insertBefore(a, b);
} else {
grid.insertBefore(a, bNext);
grid.insertBefore(b, aNext);
}
const itemCount = grid.querySelectorAll('[data-custom-item-index]').length;
reindexTileDom(a, toIndex, itemCount);
reindexTileDom(b, fromIndex, itemCount);
return true;
}
function handleAction(action, el, root, state, options, onRefresh) {
const opts = getOptions(options);
const actions = opts.actions;
@@ -371,6 +484,7 @@
break;
}
case actions.removeItem: {
if (opts.allowRemove === false) break;
const removeIndex = parseInt(el.dataset.itemIndex, 10);
if (state.customItems.length <= 1 || Number.isNaN(removeIndex)) break;
if (state.customItemEditorIndex === removeIndex) {
@@ -403,6 +517,74 @@
if (typeof onRefresh === 'function') onRefresh(root);
break;
}
case actions.toggleHide: {
const hideIndex = parseInt(el.dataset.itemIndex, 10);
if (Number.isNaN(hideIndex) || !state.customItems?.[hideIndex]) break;
collectCustomItemsFromForm(root, state, opts);
const item = state.customItems[hideIndex];
item.hidden = !item.hidden;
// Remove, then reinsert at the target slot among remaining items
state.customItems.splice(hideIndex, 1);
let insertAt;
if (item.hidden === true) {
// Hidden items stay at the end
insertAt = state.customItems.length;
} else {
// Unhide: last active item, immediately before the first still-hidden item
insertAt = state.customItems.findIndex((it) => it.hidden === true);
if (insertAt < 0) insertAt = state.customItems.length;
}
state.customItems.splice(insertAt, 0, item);
if (insertAt !== hideIndex) {
moveCustomItemExpanded(state, hideIndex, insertAt);
if (state.customItemEditorIndex === hideIndex) {
state.customItemEditorIndex = insertAt;
} else if (state.customItemEditorIndex != null) {
if (hideIndex < state.customItemEditorIndex && insertAt >= state.customItemEditorIndex) {
state.customItemEditorIndex -= 1;
} else if (hideIndex > state.customItemEditorIndex && insertAt <= state.customItemEditorIndex) {
state.customItemEditorIndex += 1;
}
}
}
if (typeof onRefresh === 'function') onRefresh(root);
break;
}
case actions.moveLeft: {
const leftIndex = parseInt(el.dataset.itemIndex, 10);
if (Number.isNaN(leftIndex) || leftIndex <= 0) break;
collectCustomItemsFromForm(root, state, opts);
const swapWith = leftIndex - 1;
const tmp = state.customItems[leftIndex];
state.customItems[leftIndex] = state.customItems[swapWith];
state.customItems[swapWith] = tmp;
swapCustomItemExpanded(state, leftIndex, swapWith);
if (state.customItemEditorIndex === leftIndex) state.customItemEditorIndex = swapWith;
else if (state.customItemEditorIndex === swapWith) state.customItemEditorIndex = leftIndex;
if (!swapAdjacentCustomItemTiles(root, leftIndex, swapWith) && typeof onRefresh === 'function') {
onRefresh(root);
}
break;
}
case actions.moveRight: {
const rightIndex = parseInt(el.dataset.itemIndex, 10);
if (Number.isNaN(rightIndex) || rightIndex >= state.customItems.length - 1) break;
collectCustomItemsFromForm(root, state, opts);
const swapWith = rightIndex + 1;
const tmp = state.customItems[rightIndex];
state.customItems[rightIndex] = state.customItems[swapWith];
state.customItems[swapWith] = tmp;
swapCustomItemExpanded(state, rightIndex, swapWith);
if (state.customItemEditorIndex === rightIndex) state.customItemEditorIndex = swapWith;
else if (state.customItemEditorIndex === swapWith) state.customItemEditorIndex = rightIndex;
if (!swapAdjacentCustomItemTiles(root, rightIndex, swapWith) && typeof onRefresh === 'function') {
onRefresh(root);
}
break;
}
default:
break;
}
@@ -431,7 +613,10 @@
closeEditor: 'hsae-close-custom-item-editor',
addItem: 'hsae-add-custom-item',
removeItem: 'hsae-remove-custom-item',
toggleImages: 'hsae-toggle-custom-images'
toggleImages: 'hsae-toggle-custom-images',
toggleHide: 'hsae-toggle-custom-item-hide',
moveLeft: 'hsae-move-custom-item-left',
moveRight: 'hsae-move-custom-item-right'
},
...overrides
})
+2 -2
View File
@@ -17,7 +17,7 @@
lockVisibility: true,
allowGroupChange: false,
allowItemSourceChange: false,
hideCustomItems: true
hideCustomItems: false
},
postProcessing: {
queryFields: ['limit'],
@@ -25,7 +25,7 @@
lockVisibility: true,
allowGroupChange: false,
allowItemSourceChange: false,
hideCustomItems: true
hideCustomItems: false
},
normal: {
queryFields: ['limit', 'sortBy', 'sortOrder'],
+21
View File
@@ -407,13 +407,34 @@ html:not(.layout-mobile) [data-modal-id="kefin-homescreen-section-create-flow"]
white-space: nowrap;
}
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-tile.is-hidden .hsse-custom-item-tile-image,
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-tile.is-hidden .hsse-custom-item-tile-label {
opacity: 0.4;
filter: grayscale(0.35);
}
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 0.2em;
}
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action,
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-trash {
font-size: 0;
padding: 0.3em;
min-height: 0;
min-width: 0;
line-height: 1;
}
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action:disabled {
opacity: 0.35;
cursor: default;
}
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-action-icon,
[data-modal-id="kefin-homescreen-section-create-flow"] .hsse-custom-item-trash-icon {
font-size: 1.1rem;
}
+19 -4
View File
@@ -164,6 +164,9 @@
}
function createBlankCustomItem() {
if (window.KefinHomeScreenCustomItemsEditor?.createBlankCustomItem) {
return window.KefinHomeScreenCustomItemsEditor.createBlankCustomItem();
}
return {
name: '',
cardFooter: '',
@@ -173,7 +176,8 @@
backdropUrl: '',
squareUrl: '',
bannerUrl: '',
logoUrl: ''
logoUrl: '',
hidden: false
};
}
@@ -186,7 +190,10 @@
closeEditor: 'wizard-close-custom-item-editor',
addItem: 'wizard-add-custom-item',
removeItem: 'wizard-remove-custom-item',
toggleImages: 'wizard-toggle-custom-images'
toggleImages: 'wizard-toggle-custom-images',
toggleHide: 'wizard-toggle-custom-item-hide',
moveLeft: 'wizard-move-custom-item-left',
moveRight: 'wizard-move-custom-item-right'
}
});
}
@@ -240,6 +247,9 @@
}
function buildStaticItemFromDraft(draft) {
if (window.KefinHomeScreenCustomItemsEditor?.buildStaticItemFromDraft) {
return window.KefinHomeScreenCustomItemsEditor.buildStaticItemFromDraft(draft);
}
const name = draft.name?.trim();
const posterUrl = draft.posterUrl?.trim();
if (!name || !posterUrl) return null;
@@ -251,11 +261,13 @@
if (value) provided[key] = value;
});
const item = { Name: name, Type: 'Folder' };
const item = { Name: name, Type: draft.type || 'Folder' };
if (draft.id) item.Id = draft.id;
const cardUrl = draft.cardUrl?.trim();
if (cardUrl) item.cardUrl = cardUrl;
const cardFooter = draft.cardFooter?.trim();
if (cardFooter) item.cardFooter = cardFooter;
if (draft.hidden === true) item.hidden = true;
const keys = Object.keys(provided);
if (keys.length === 1 && provided.posterUrl) {
@@ -4277,7 +4289,10 @@
'wizard-close-custom-item-editor',
'wizard-add-custom-item',
'wizard-remove-custom-item',
'wizard-toggle-custom-images'
'wizard-toggle-custom-images',
'wizard-toggle-custom-item-hide',
'wizard-move-custom-item-left',
'wizard-move-custom-item-right'
]);
if (wizardCustomActions.has(action) && window.KefinHomeScreenCustomItemsEditor) {
window.KefinHomeScreenCustomItemsEditor.handleAction(
+2 -1
View File
@@ -1936,7 +1936,8 @@
const normalizeTemplate = (value) =>
(value || '').replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot).replace(/\$\{serverId\}/g, serverId);
return sectionConfig.items.map((item, index) => ({
const visibleItems = (sectionConfig.items || []).filter((item) => item && item.hidden !== true);
return visibleItems.map((item, index) => ({
Name: item.Name,
Id: item.Id || 'static-' + sectionConfig.id + '-' + index,
Type: item.Type || 'Folder',
+2 -1
View File
@@ -245,7 +245,8 @@
.replace(/\$\{kefinTweaksRoot\}/g, kefinTweaksRoot)
.replace(/\$\{serverId\}/g, serverId);
const normalizedItems = sectionConfig.items.map((item, index) => {
const visibleItems = sectionConfig.items.filter((item) => item && item.hidden !== true);
const normalizedItems = visibleItems.map((item, index) => {
const posterUrl = normalizeTemplate(item.posterUrl);
const thumbUrl = normalizeTemplate(item.thumbUrl);
const squareUrl = normalizeTemplate(item.squareUrl);