mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Batch Edit: Add utility functions for label normalization and slugification; integrate into chip selector and batch edit components UX: Add batch edit dialog to change the metadata of multiple pictures #271
This commit is contained in:
parent
59b3ffcdc6
commit
65ebaaf038
3 changed files with 61 additions and 24 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue