Batch Edit: Add utility functions for label normalization and slugification; integrate into chip selector and batch edit components #271

This commit is contained in:
Ömer Duran 2025-11-13 21:29:34 +01:00
parent 59b3ffcdc6
commit 16073448c5
No known key found for this signature in database
GPG key ID: 2550B0D579890013
3 changed files with 61 additions and 24 deletions

View file

@ -49,6 +49,26 @@ export const tokenLength = 7;
const debug = window.__CONFIG__?.debug || window.__CONFIG__?.trace;
export default class $util {
static normalizeLabelTitle(s) {
if (s === null || s === undefined) return "";
return String(s)
.toLowerCase()
.replace(/&/g, "and")
.replace(/[+_\-]+/g, " ")
.replace(/[^a-z0-9 ]+/g, "")
.replace(/\s+/g, " ")
.trim();
}
static slugifyLabelTitle(s) {
if (s === null || s === undefined) return "";
return String(s)
.toLowerCase()
.replace(/&/g, "and")
.replace(/[^a-z0-9]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
// formatBytes returns a human-readable size string for a byte count.
static formatBytes(b) {
if (!b) {

View file

@ -74,6 +74,10 @@ export default {
type: Array,
default: () => [],
},
normalizeTitleForCompare: {
type: Function,
default: null,
},
availableItems: {
type: Array,
default: () => [],
@ -132,6 +136,18 @@ export default {
},
},
methods: {
normalizeTitle(text) {
const input = text == null ? "" : String(text);
if (typeof this.normalizeTitleForCompare === "function") {
try {
return this.normalizeTitleForCompare(input);
} catch (e) {
return input.toLowerCase();
}
}
return input.toLowerCase();
},
getChipClasses(item) {
const baseClass = "chip";
const classes = [baseClass];
@ -266,15 +282,27 @@ export default {
}
}
const normalizedTitle = title.toLowerCase();
const normalizedTitle = this.normalizeTitle(title);
const existingItem = this.items.find(
(item) => (item.value && value && item.value === value) || item.title.toLowerCase() === normalizedTitle
(item) => (item.value && value && item.value === value) || this.normalizeTitle(item.title) === normalizedTitle
);
if (existingItem) {
if (resolvedApplied && (existingItem.mixed || existingItem.action !== "add")) {
this.updateItemAction(existingItem, "add");
}
const updatedItems = this.items.map((item) => {
const isSame =
(item.value && value && item.value === value) || this.normalizeTitle(item.title) === normalizedTitle;
if (!isSame) return item;
const next = { ...item };
if (resolvedApplied) {
if (value) next.value = value;
if (title) next.title = title;
}
if (item.mixed || item.action !== "add") {
next.action = "add";
}
return next;
});
this.$emit("update:items", updatedItems);
this.menuOpen = false;
this.$nextTick(() => {
this.newItemTitle = "";

View file

@ -501,6 +501,7 @@
v-model:items="labelItems"
:available-items="availableLabelOptions"
:resolve-item-from-text="resolveLabelFromText"
:normalize-title-for-compare="normalizeLabelTitleForCompare"
:input-placeholder="$gettext('Enter label name...')"
:empty-text="$gettext('No labels assigned')"
:loading="loading"
@ -616,6 +617,7 @@ import Thumb from "../../../model/thumb";
import PLocationDialog from "component/location/dialog.vue";
import PLocationInput from "component/location/input.vue";
import BatchChipSelector from "component/file/chip-selector.vue";
import $util from "common/util";
export default {
name: "PPhotoEditBatch",
@ -854,6 +856,9 @@ export default {
afterLeave() {
this.$view.leave(this);
},
normalizeLabelTitleForCompare(s) {
return $util.normalizeLabelTitle(s);
},
resolveLabelFromText(inputTitle) {
if (!inputTitle || !Array.isArray(this.availableLabelOptions)) {
return null;
@ -862,24 +867,8 @@ export default {
const t = String(inputTitle).trim();
if (!t) return null;
const normalize = (s) =>
s
.toLowerCase()
.replace(/&/g, "and")
.replace(/[+_\-]+/g, " ")
.replace(/[^a-z0-9 ]+/g, "")
.replace(/\s+/g, " ")
.trim();
const toSlug = (s) =>
s
.toLowerCase()
.replace(/&/g, "and")
.replace(/[^a-z0-9]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
const nt = normalize(t);
const st = toSlug(t);
const nt = $util.normalizeLabelTitle(t);
const st = $util.slugifyLabelTitle(t);
let found = this.availableLabelOptions.find((o) => o.title.toLowerCase() === t.toLowerCase());
if (found) return { value: found.value, title: found.title };
@ -887,7 +876,7 @@ export default {
found = this.availableLabelOptions.find((o) => o.slug === st || o.customSlug === st);
if (found) return { value: found.value, title: found.title };
found = this.availableLabelOptions.find((o) => normalize(o.title) === nt);
found = this.availableLabelOptions.find((o) => $util.normalizeLabelTitle(o.title) === nt);
if (found) return { value: found.value, title: found.title };
return { value: "", title: t };