People: Normalize names #22
This commit is contained in:
parent
ed962a36da
commit
1f92f294dd
25 changed files with 267 additions and 151 deletions
12
frontend/package-lock.json
generated
12
frontend/package-lock.json
generated
|
@ -1887,9 +1887,9 @@
|
|||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz",
|
||||
"integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w=="
|
||||
"version": "16.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.3.tgz",
|
||||
"integrity": "sha512-5UmMznRvrwKqisJ458JbNoq3AyXHxlAKMkGtNe143W1SkZ1BVgvCHYBzn7wD66J+smE+BolqA1mes5BeXlWY6w=="
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
|
@ -15535,9 +15535,9 @@
|
|||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz",
|
||||
"integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w=="
|
||||
"version": "16.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.3.tgz",
|
||||
"integrity": "sha512-5UmMznRvrwKqisJ458JbNoq3AyXHxlAKMkGtNe143W1SkZ1BVgvCHYBzn7wD66J+smE+BolqA1mes5BeXlWY6w=="
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
|
|
|
@ -181,6 +181,20 @@
|
|||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile v-show="$config.feature('people')" :to="{ name: 'people' }" class="nav-people" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('People')">
|
||||
<v-icon>person</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="People">People</translate>
|
||||
<span v-show="config.count.people > 0"
|
||||
:class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.people }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/favorites" class="nav-favorites" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('Favorites')">
|
||||
<v-icon>favorite</v-icon>
|
||||
|
@ -209,20 +223,6 @@
|
|||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile v-show="$config.feature('people')" :to="{ name: 'people' }" class="nav-people" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('People')">
|
||||
<v-icon>emoji_people</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="People">People</translate>
|
||||
<span v-show="config.count.people > 0"
|
||||
:class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.people }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile :to="{ name: 'calendar' }" class="nav-calendar" @click.stop="">
|
||||
<v-list-tile-action :title="$gettext('Calendar')">
|
||||
<v-icon>date_range</v-icon>
|
||||
|
|
|
@ -52,9 +52,9 @@
|
|||
</v-tab>
|
||||
|
||||
<v-tab id="tab-people" :disabled="!$config.feature('people')" ripple>
|
||||
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="$gettext('People')">emoji_people</v-icon>
|
||||
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="$gettext('People')">people_alt</v-icon>
|
||||
<template v-else>
|
||||
<v-icon :size="18" :left="!rtl" :right="rtl">emoji_people</v-icon>
|
||||
<v-icon :size="18" :left="!rtl" :right="rtl">people_alt</v-icon>
|
||||
<v-badge color="secondary-dark" :left="rtl" :right="!rtl">
|
||||
<template #badge>
|
||||
<span v-if="model.Faces">{{ model.Faces }}</span>
|
||||
|
|
Binary file not shown.
|
@ -334,13 +334,6 @@ msgstr ""
|
|||
"Videos und andere Bild-Formate nach JPEG konvertieren, damit sie indexiert "
|
||||
"und angezeigt werden können."
|
||||
|
||||
#: src/pages/settings/general.vue:429
|
||||
msgid ""
|
||||
"Automatically detects faces so that you can search your pictures for people."
|
||||
msgstr ""
|
||||
"Erkennt automatisch Gesichter, so dass Sie Ihre Bilder nach Personen "
|
||||
"durchsuchen können."
|
||||
|
||||
#: src/pages/people/subjects.vue:339
|
||||
msgid "Bio"
|
||||
msgstr "Biographie"
|
||||
|
@ -365,7 +358,7 @@ msgstr "Brasilianisches Portugiesisch"
|
|||
msgid "Brown"
|
||||
msgstr "Braun"
|
||||
|
||||
#: src/pages/settings/general.vue:361
|
||||
#: src/pages/settings/general.vue:386
|
||||
msgid "Browse and edit image classification labels."
|
||||
msgstr "Automatische Bild-Kategorisierung sehen und bearbeiten."
|
||||
|
||||
|
@ -435,7 +428,7 @@ msgid "Change"
|
|||
msgstr "Ändern"
|
||||
|
||||
#: src/pages/settings/general.vue:185
|
||||
msgid "Change photo titles, locations and other metadata."
|
||||
msgid "Change photo titles, locations, and other metadata."
|
||||
msgstr "Titel, Datum, Ort und andere Metadaten können geändert werden."
|
||||
|
||||
#: src/component/photo/clipboard.vue:146
|
||||
|
@ -860,8 +853,8 @@ msgstr "Jeden zweiten Tag"
|
|||
|
||||
#: src/pages/settings/general.vue:273
|
||||
msgid ""
|
||||
"Exclude content marked as private from search results, shared albums, labels "
|
||||
"and places."
|
||||
"Exclude content marked as private from search results, shared albums, "
|
||||
"labels, and places."
|
||||
msgstr ""
|
||||
"Als privat markierte Inhalte werden nicht in Suchergebnissen und geteilten "
|
||||
"Alben angezeigt."
|
||||
|
@ -890,7 +883,7 @@ msgstr "Belichtungszeit"
|
|||
msgid "F Number"
|
||||
msgstr "F Nummer"
|
||||
|
||||
#: src/model/face.js:129
|
||||
#: src/model/face.js:125
|
||||
msgid "Face"
|
||||
msgstr "Gesicht"
|
||||
|
||||
|
@ -922,7 +915,7 @@ msgstr "Schnell"
|
|||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: src/component/navigation.vue:176 src/component/navigation.vue:698
|
||||
#: src/component/navigation.vue:189 src/component/navigation.vue:743
|
||||
#: src/routes.js:180
|
||||
msgid "Favorites"
|
||||
msgstr "Favoriten"
|
||||
|
@ -1192,7 +1185,7 @@ msgstr "Name"
|
|||
|
||||
#: src/component/navigation.vue:262 src/component/navigation.vue:994
|
||||
#: src/dialog/photo/edit.vue:39 src/dialog/photo/edit.vue:6
|
||||
#: src/dialog/photo/edit.vue:216 src/pages/settings/general.vue:360
|
||||
#: src/dialog/photo/edit.vue:216 src/pages/settings/general.vue:385
|
||||
#: src/routes.js:259
|
||||
msgid "Labels"
|
||||
msgstr "Kategorien"
|
||||
|
@ -1225,8 +1218,8 @@ msgstr "Lavendel"
|
|||
msgid "Lens"
|
||||
msgstr "Objektiv"
|
||||
|
||||
#: src/pages/settings/general.vue:339
|
||||
msgid "Let PhotoPrism create albums from past events."
|
||||
#: src/pages/settings/general.vue:364
|
||||
msgid "Let PhotoPrism automatically create albums from past events."
|
||||
msgstr ""
|
||||
"PhotoPrism erstellt automatisch Alben mit besonderen Momenten, Reisen und "
|
||||
"Orten."
|
||||
|
@ -1237,7 +1230,7 @@ msgstr "Jetzt Unterstützer werden"
|
|||
|
||||
#: src/component/navigation.vue:301 src/component/navigation.vue:311
|
||||
#: src/component/navigation.vue:4 src/component/navigation.vue:1131
|
||||
#: src/pages/settings.vue:41 src/pages/settings/general.vue:382
|
||||
#: src/pages/settings.vue:41 src/pages/settings/general.vue:407
|
||||
#: src/routes.js:279 src/routes.js:286 src/routes.js:293
|
||||
msgid "Library"
|
||||
msgstr "Dateien"
|
||||
|
@ -1308,7 +1301,7 @@ msgstr "Anmelden"
|
|||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: src/pages/library.vue:55 src/pages/settings/general.vue:404
|
||||
#: src/pages/library.vue:55 src/pages/settings/general.vue:429
|
||||
msgid "Logs"
|
||||
msgstr "Logs"
|
||||
|
||||
|
@ -1352,8 +1345,8 @@ msgstr "Minimieren"
|
|||
msgid "Missing"
|
||||
msgstr "Fehlend"
|
||||
|
||||
#: src/component/navigation.vue:189 src/component/navigation.vue:741
|
||||
#: src/pages/settings/general.vue:338 src/routes.js:121 src/routes.js:128
|
||||
#: src/component/navigation.vue:202 src/component/navigation.vue:786
|
||||
#: src/pages/settings/general.vue:363 src/routes.js:121 src/routes.js:128
|
||||
msgid "Moments"
|
||||
msgstr "Erlebnisse"
|
||||
|
||||
|
@ -1431,7 +1424,7 @@ msgstr "Name zu lang"
|
|||
msgid "Never"
|
||||
msgstr "Nie"
|
||||
|
||||
#: src/pages/people.vue:42
|
||||
#: src/pages/people.vue:43
|
||||
msgid "New"
|
||||
msgstr "Neu"
|
||||
|
||||
|
@ -1650,9 +1643,9 @@ msgstr "Passwort geändert"
|
|||
msgid "pay for operating expenses and external services like satellite maps"
|
||||
msgstr "Betriebskosten und externe Dienste wie Satellitenkarten zu bezahlen"
|
||||
|
||||
#: src/component/navigation.vue:202 src/component/navigation.vue:786
|
||||
#: src/component/navigation.vue:176 src/component/navigation.vue:698
|
||||
#: src/dialog/photo/edit.vue:52 src/dialog/photo/edit.vue:6
|
||||
#: src/dialog/photo/edit.vue:267 src/pages/settings/general.vue:428
|
||||
#: src/dialog/photo/edit.vue:267 src/pages/settings/general.vue:340
|
||||
#: src/routes.js:265 src/routes.js:272
|
||||
msgid "People"
|
||||
msgstr "Personen"
|
||||
|
@ -1835,7 +1828,13 @@ msgstr "Zuletzt hinzugefügt"
|
|||
msgid "Recently edited"
|
||||
msgstr "Zuletzt bearbeitet"
|
||||
|
||||
#: src/pages/people.vue:31
|
||||
#: src/pages/settings/general.vue:341
|
||||
msgid ""
|
||||
"Recognize faces so that specific people can be found and albums created."
|
||||
msgstr ""
|
||||
"Gesichter erkennen, damit Personen gefunden und Alben erstellt werden können."
|
||||
|
||||
#: src/pages/people.vue:32
|
||||
msgid "Recognized"
|
||||
msgstr "Erkannt"
|
||||
|
||||
|
@ -2024,7 +2023,7 @@ msgstr "Mit dir geteilt."
|
|||
msgid "Show less"
|
||||
msgstr "Weniger zeigen"
|
||||
|
||||
#: src/pages/settings/general.vue:383
|
||||
#: src/pages/settings/general.vue:408
|
||||
msgid "Show Library in navigation menu."
|
||||
msgstr "Datei-Verwaltung in der Navigation anzeigen."
|
||||
|
||||
|
@ -2032,7 +2031,7 @@ msgstr "Datei-Verwaltung in der Navigation anzeigen."
|
|||
msgid "Show more"
|
||||
msgstr "Mehr zeigen"
|
||||
|
||||
#: src/pages/settings/general.vue:405
|
||||
#: src/pages/settings/general.vue:430
|
||||
msgid "Show server logs in Library."
|
||||
msgstr "Server-Ereignisprotokoll anzeigen, um Fehler zu finden."
|
||||
|
||||
|
@ -2155,7 +2154,7 @@ msgstr "Straßen"
|
|||
msgid "Style"
|
||||
msgstr "Style"
|
||||
|
||||
#: src/dialog/photo/details.vue:482 src/model/subject.js:137
|
||||
#: src/dialog/photo/details.vue:482 src/model/subject.js:143
|
||||
msgid "Subject"
|
||||
msgstr "Bildinhalt"
|
||||
|
||||
|
@ -2506,8 +2505,19 @@ msgstr "Deine Nachricht wurde gesendet"
|
|||
msgid "Zoom in/out"
|
||||
msgstr "Zoom in/out"
|
||||
|
||||
#~ msgid "Detect faces and search for people in your pictures."
|
||||
#~ msgstr "Findet Gesichter und aktiviert die Suche nach Personen."
|
||||
#~ msgid ""
|
||||
#~ "Detect faces so that you can search for specific people, and share photos "
|
||||
#~ "with them."
|
||||
#~ msgstr ""
|
||||
#~ "Erkennt Gesichter, damit bestimmte Personen gefunden und Alben erstellt "
|
||||
#~ "werden können."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Automatically detects faces so that you can search your pictures for "
|
||||
#~ "people."
|
||||
#~ msgstr ""
|
||||
#~ "Erkennt automatisch Gesichter, so dass Sie Ihre Bilder nach Personen "
|
||||
#~ "durchsuchen können."
|
||||
|
||||
#~ msgid "Not implemented yet"
|
||||
#~ msgstr "Noch nicht implementiert"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -346,10 +346,6 @@ msgstr ""
|
|||
msgid "Automatically create JPEGs for other file types so that they can be displayed in a browser."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:429
|
||||
msgid "Automatically detects faces so that you can search your pictures for people."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/people/subjects.vue:339
|
||||
msgid "Bio"
|
||||
msgstr ""
|
||||
|
@ -374,7 +370,7 @@ msgstr ""
|
|||
msgid "Brown"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:361
|
||||
#: src/pages/settings/general.vue:386
|
||||
msgid "Browse and edit image classification labels."
|
||||
msgstr ""
|
||||
|
||||
|
@ -467,7 +463,7 @@ msgid "Change"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:185
|
||||
msgid "Change photo titles, locations and other metadata."
|
||||
msgid "Change photo titles, locations, and other metadata."
|
||||
msgstr ""
|
||||
|
||||
#: src/component/photo/clipboard.vue:146
|
||||
|
@ -930,7 +926,7 @@ msgid "Every two days"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:273
|
||||
msgid "Exclude content marked as private from search results, shared albums, labels and places."
|
||||
msgid "Exclude content marked as private from search results, shared albums, labels, and places."
|
||||
msgstr ""
|
||||
|
||||
#: src/component/navigation.vue:248
|
||||
|
@ -958,7 +954,7 @@ msgstr ""
|
|||
msgid "F Number"
|
||||
msgstr ""
|
||||
|
||||
#: src/model/face.js:129
|
||||
#: src/model/face.js:125
|
||||
msgid "Face"
|
||||
msgstr ""
|
||||
|
||||
|
@ -991,8 +987,8 @@ msgstr ""
|
|||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/navigation.vue:176
|
||||
#: src/component/navigation.vue:698
|
||||
#: src/component/navigation.vue:189
|
||||
#: src/component/navigation.vue:743
|
||||
#: src/routes.js:180
|
||||
msgid "Favorites"
|
||||
msgstr ""
|
||||
|
@ -1269,7 +1265,7 @@ msgstr ""
|
|||
#: src/dialog/photo/edit.vue:39
|
||||
#: src/dialog/photo/edit.vue:6
|
||||
#: src/dialog/photo/edit.vue:216
|
||||
#: src/pages/settings/general.vue:360
|
||||
#: src/pages/settings/general.vue:385
|
||||
#: src/routes.js:259
|
||||
msgid "Labels"
|
||||
msgstr ""
|
||||
|
@ -1303,8 +1299,8 @@ msgstr ""
|
|||
msgid "Lens"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:339
|
||||
msgid "Let PhotoPrism create albums from past events."
|
||||
#: src/pages/settings/general.vue:364
|
||||
msgid "Let PhotoPrism automatically create albums from past events."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/sponsor.vue:7
|
||||
|
@ -1316,7 +1312,7 @@ msgstr ""
|
|||
#: src/component/navigation.vue:4
|
||||
#: src/component/navigation.vue:1131
|
||||
#: src/pages/settings.vue:41
|
||||
#: src/pages/settings/general.vue:382
|
||||
#: src/pages/settings/general.vue:407
|
||||
#: src/routes.js:279
|
||||
#: src/routes.js:286
|
||||
#: src/routes.js:293
|
||||
|
@ -1397,7 +1393,7 @@ msgid "Logout"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/library.vue:55
|
||||
#: src/pages/settings/general.vue:404
|
||||
#: src/pages/settings/general.vue:429
|
||||
msgid "Logs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1444,9 +1440,9 @@ msgstr ""
|
|||
msgid "Missing"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/navigation.vue:189
|
||||
#: src/component/navigation.vue:741
|
||||
#: src/pages/settings/general.vue:338
|
||||
#: src/component/navigation.vue:202
|
||||
#: src/component/navigation.vue:786
|
||||
#: src/pages/settings/general.vue:363
|
||||
#: src/routes.js:121
|
||||
#: src/routes.js:128
|
||||
msgid "Moments"
|
||||
|
@ -1544,7 +1540,7 @@ msgstr ""
|
|||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/people.vue:42
|
||||
#: src/pages/people.vue:43
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1760,12 +1756,12 @@ msgstr ""
|
|||
msgid "pay for operating expenses and external services like satellite maps"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/navigation.vue:202
|
||||
#: src/component/navigation.vue:786
|
||||
#: src/component/navigation.vue:176
|
||||
#: src/component/navigation.vue:698
|
||||
#: src/dialog/photo/edit.vue:52
|
||||
#: src/dialog/photo/edit.vue:6
|
||||
#: src/dialog/photo/edit.vue:267
|
||||
#: src/pages/settings/general.vue:428
|
||||
#: src/pages/settings/general.vue:340
|
||||
#: src/routes.js:265
|
||||
#: src/routes.js:272
|
||||
msgid "People"
|
||||
|
@ -1962,7 +1958,11 @@ msgstr ""
|
|||
msgid "Recently edited"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/people.vue:31
|
||||
#: src/pages/settings/general.vue:341
|
||||
msgid "Recognize faces so that specific people can be found and albums created."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/people.vue:32
|
||||
msgid "Recognized"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2172,7 +2172,7 @@ msgstr ""
|
|||
msgid "Show less"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:383
|
||||
#: src/pages/settings/general.vue:408
|
||||
msgid "Show Library in navigation menu."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2180,7 +2180,7 @@ msgstr ""
|
|||
msgid "Show more"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/general.vue:405
|
||||
#: src/pages/settings/general.vue:430
|
||||
msgid "Show server logs in Library."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2304,7 +2304,7 @@ msgid "Style"
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/photo/details.vue:482
|
||||
#: src/model/subject.js:137
|
||||
#: src/model/subject.js:143
|
||||
msgid "Subject"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -68,6 +68,15 @@ export class Rest extends Model {
|
|||
}
|
||||
|
||||
update() {
|
||||
// Get updated values.
|
||||
const values = this.getValues(true);
|
||||
|
||||
// Return if no values were changed.
|
||||
if (Object.keys(values).length === 0) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
// Send PUT request.
|
||||
return Api.put(this.getEntityResource(), this.getValues(true)).then((resp) =>
|
||||
Promise.resolve(this.setValues(resp.data))
|
||||
);
|
||||
|
|
|
@ -34,6 +34,8 @@ import { DateTime } from "luxon";
|
|||
import { config } from "../session";
|
||||
import { $gettext } from "common/vm";
|
||||
|
||||
const SubjPerson = "person";
|
||||
|
||||
export class Subject extends RestModel {
|
||||
getDefaults() {
|
||||
return {
|
||||
|
@ -61,6 +63,10 @@ export class Subject extends RestModel {
|
|||
}
|
||||
|
||||
route(view) {
|
||||
if (!this.Type || this.Type === SubjPerson) {
|
||||
return { name: view, query: { q: `people:"${this.Name}"` } };
|
||||
}
|
||||
|
||||
return { name: view, query: { q: "subject:" + this.UID } };
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export default {
|
|||
tab: String,
|
||||
},
|
||||
data() {
|
||||
let tabName = this.tab;
|
||||
const config = this.$config.values;
|
||||
const isDemo = this.$config.get("demo");
|
||||
const isPublic = this.$config.get("public");
|
||||
|
@ -76,10 +77,14 @@ export default {
|
|||
},
|
||||
];
|
||||
|
||||
if (config.count.people === 0) {
|
||||
tabName = "people-faces";
|
||||
}
|
||||
|
||||
let active = 0;
|
||||
|
||||
if (typeof this.tab === 'string' && this.tab !== '') {
|
||||
active = tabs.findIndex((t) => t.name === this.tab);
|
||||
if (typeof tabName === 'string' && tabName !== '') {
|
||||
active = tabs.findIndex((t) => t.name === tabName);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
class="ma-0 pa-0 input-edit"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Edit')"
|
||||
:hint="$gettext('Change photo titles, locations and other metadata.')"
|
||||
:hint="$gettext('Change photo titles, locations, and other metadata.')"
|
||||
prepend-icon="edit"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
|
@ -143,7 +143,7 @@
|
|||
class="ma-0 pa-0 input-private"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Private')"
|
||||
:hint="$gettext('Exclude content marked as private from search results, shared albums, labels and places.')"
|
||||
:hint="$gettext('Exclude content marked as private from search results, shared albums, labels, and places.')"
|
||||
prepend-icon="lock"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
|
@ -181,6 +181,21 @@
|
|||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex v-if="config.experimental" xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.people"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-people"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('People')"
|
||||
:hint="$gettext('Recognize faces so that specific people can be found and albums created.')"
|
||||
prepend-icon="person"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.moments"
|
||||
|
@ -188,7 +203,7 @@
|
|||
class="ma-0 pa-0 input-moments"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('Moments')"
|
||||
:hint="$gettext('Let PhotoPrism create albums from past events.')"
|
||||
:hint="$gettext('Let PhotoPrism automatically create albums from past events.')"
|
||||
prepend-icon="star"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
|
@ -241,21 +256,6 @@
|
|||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex v-if="config.experimental" xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.people"
|
||||
:disabled="busy"
|
||||
class="ma-0 pa-0 input-people"
|
||||
color="secondary-dark"
|
||||
:label="$gettext('People')"
|
||||
:hint="$gettext('Automatically detects faces so that you can search your pictures for people.')"
|
||||
prepend-icon="emoji_people"
|
||||
persistent-hint
|
||||
@change="onChange"
|
||||
>
|
||||
</v-checkbox>
|
||||
</v-flex>
|
||||
|
||||
<v-flex v-if="!config.disable.places" xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||
<v-checkbox
|
||||
v-model="settings.features.places"
|
||||
|
|
|
@ -53,6 +53,7 @@ describe("model/abstract", () => {
|
|||
const values = { id: 5, Name: "Christmas 2019", Slug: "christmas-2019", UID: 66 };
|
||||
const album = new Album(values);
|
||||
assert.equal(album.Description, undefined);
|
||||
album.Name = "Christmas 2020";
|
||||
await album.update();
|
||||
assert.equal(album.Description, "Test description");
|
||||
});
|
||||
|
@ -60,6 +61,7 @@ describe("model/abstract", () => {
|
|||
it("should save album", async () => {
|
||||
const values = { UID: "abc", Name: "Christmas 2019", Slug: "christmas-2019" };
|
||||
const album = new Album(values);
|
||||
album.Name = "Christmas 2020";
|
||||
assert.equal(album.Description, undefined);
|
||||
await album.save();
|
||||
assert.equal(album.Description, "Test description");
|
||||
|
|
4
go.mod
4
go.mod
|
@ -52,7 +52,7 @@ require (
|
|||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210630100626-7ff61aa87be8
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df
|
||||
github.com/tensorflow/tensorflow v1.15.2
|
||||
github.com/tidwall/gjson v1.9.1
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
|
@ -61,7 +61,7 @@ require (
|
|||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gonum.org/v1/gonum v0.9.3
|
||||
|
|
8
go.sum
8
go.sum
|
@ -284,8 +284,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210630100626-7ff61aa87be8 h1:ipNUBPHSUmHhhcLhvqC2vGZsJPzVuJap8rJx3uGAEco=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210630100626-7ff61aa87be8/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8gRPt1MdSzBNZP0OYuDm5wsmDKgwpLjYzo=
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||
github.com/tensorflow/tensorflow v1.15.2 h1:7/f/A664Tml/nRJg04+p3StcrsT53mkcvmxYHXI21Qo=
|
||||
github.com/tensorflow/tensorflow v1.15.2/go.mod h1:itOSERT4trABok4UOoG+X4BoKds9F3rIsySdn+Lvu90=
|
||||
github.com/tidwall/gjson v1.9.1 h1:wrrRk7TyL7MmKanNRck/Mcr3VU1sdMvJHvJXzqBIUNo=
|
||||
|
@ -384,8 +384,8 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf h1:R150MpwJIv1MpS0N/pc+NhTM8ajzvlmxlY5OYsrevXQ=
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
|
@ -27,7 +27,7 @@ func SearchPhotosGeo(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
|
||||
err := c.MustBindWith(&f, binding.Form)
|
||||
|
||||
|
|
|
@ -99,6 +99,12 @@ func UpdateSubject(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
if txt.NameSlug(f.SubjName) == "" {
|
||||
// Return unchanged model data if (normalized) name is empty.
|
||||
c.JSON(http.StatusOK, m)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := m.UpdateName(f.SubjName); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
return
|
||||
|
|
|
@ -161,14 +161,14 @@ func (m *Label) AfterCreate(scope *gorm.Scope) error {
|
|||
|
||||
// SetName changes the label name.
|
||||
func (m *Label) SetName(name string) {
|
||||
newName := txt.Clip(name, txt.ClipDefault)
|
||||
name = txt.NormalizeName(name)
|
||||
|
||||
if newName == "" {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m.LabelName = txt.Title(newName)
|
||||
m.CustomSlug = slug.Make(txt.Clip(name, txt.ClipSlug))
|
||||
m.LabelName = name
|
||||
m.CustomSlug = txt.NameSlug(name)
|
||||
}
|
||||
|
||||
// UpdateClassify updates a label if necessary
|
||||
|
|
|
@ -127,7 +127,7 @@ func (m *Marker) SaveForm(f form.Marker) (changed bool, err error) {
|
|||
|
||||
if f.SubjSrc == SrcManual && strings.TrimSpace(f.MarkerName) != "" && f.MarkerName != m.MarkerName {
|
||||
m.SubjSrc = SrcManual
|
||||
m.MarkerName = txt.Title(txt.Clip(f.MarkerName, txt.ClipDefault))
|
||||
m.MarkerName = txt.NormalizeName(f.MarkerName)
|
||||
|
||||
if err := m.SyncSubject(true); err != nil {
|
||||
return changed, err
|
||||
|
|
|
@ -202,15 +202,17 @@ func FindSubject(s string) *Subject {
|
|||
}
|
||||
|
||||
// FindSubjectByName find an existing subject by name.
|
||||
func FindSubjectByName(s string) *Subject {
|
||||
if s == "" {
|
||||
func FindSubjectByName(name string) *Subject {
|
||||
name = txt.NormalizeName(name)
|
||||
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := Subject{}
|
||||
|
||||
// Search database.
|
||||
db := UnscopedDb().Where("subj_name LIKE ?", s).First(&result)
|
||||
db := UnscopedDb().Where("subj_name LIKE ?", name).First(&result)
|
||||
|
||||
if err := db.First(&result).Error; err != nil {
|
||||
return nil
|
||||
|
@ -238,14 +240,14 @@ func (m *Subject) Person() *Person {
|
|||
|
||||
// SetName changes the subject's name.
|
||||
func (m *Subject) SetName(name string) error {
|
||||
newName := txt.Clip(name, txt.ClipDefault)
|
||||
name = txt.NormalizeName(name)
|
||||
|
||||
if newName == "" {
|
||||
if name == "" {
|
||||
return fmt.Errorf("subject: name must not be empty")
|
||||
}
|
||||
|
||||
m.SubjName = txt.Title(newName)
|
||||
m.SubjSlug = slug.Make(txt.Clip(name, txt.ClipSlug))
|
||||
m.SubjName = name
|
||||
m.SubjSlug = txt.NameSlug(name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package form
|
|||
|
||||
import "time"
|
||||
|
||||
// GeoSearch represents search form fields for "/api/v1/geo".
|
||||
type GeoSearch struct {
|
||||
// PhotoSearchGeo represents search form fields for "/api/v1/geo".
|
||||
type PhotoSearchGeo struct {
|
||||
Query string `form:"q"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
|
@ -42,17 +42,17 @@ type GeoSearch struct {
|
|||
}
|
||||
|
||||
// GetQuery returns the query parameter as string.
|
||||
func (f *GeoSearch) GetQuery() string {
|
||||
func (f *PhotoSearchGeo) GetQuery() string {
|
||||
return f.Query
|
||||
}
|
||||
|
||||
// SetQuery sets the query parameter.
|
||||
func (f *GeoSearch) SetQuery(q string) {
|
||||
func (f *PhotoSearchGeo) SetQuery(q string) {
|
||||
f.Query = q
|
||||
}
|
||||
|
||||
// ParseQueryString parses the query parameter if possible.
|
||||
func (f *GeoSearch) ParseQueryString() error {
|
||||
func (f *PhotoSearchGeo) ParseQueryString() error {
|
||||
err := ParseQueryString(f)
|
||||
|
||||
if f.Path == "" && f.Folder != "" {
|
||||
|
@ -67,15 +67,15 @@ func (f *GeoSearch) ParseQueryString() error {
|
|||
}
|
||||
|
||||
// Serialize returns a string containing non-empty fields and values of a struct.
|
||||
func (f *GeoSearch) Serialize() string {
|
||||
func (f *PhotoSearchGeo) Serialize() string {
|
||||
return Serialize(f, false)
|
||||
}
|
||||
|
||||
// SerializeAll returns a string containing all non-empty fields and values of a struct.
|
||||
func (f *GeoSearch) SerializeAll() string {
|
||||
func (f *PhotoSearchGeo) SerializeAll() string {
|
||||
return Serialize(f, true)
|
||||
}
|
||||
|
||||
func NewGeoSearch(query string) GeoSearch {
|
||||
return GeoSearch{Query: query}
|
||||
func NewGeoSearch(query string) PhotoSearchGeo {
|
||||
return PhotoSearchGeo{Query: query}
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestGeoSearch(t *testing.T) {
|
||||
t.Run("subjects", func(t *testing.T) {
|
||||
form := &GeoSearch{Query: "subjects:\"Jens Mander\""}
|
||||
form := &PhotoSearchGeo{Query: "subjects:\"Jens Mander\""}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -20,7 +20,7 @@ func TestGeoSearch(t *testing.T) {
|
|||
assert.Equal(t, "Jens Mander", form.Subjects)
|
||||
})
|
||||
t.Run("keywords", func(t *testing.T) {
|
||||
form := &GeoSearch{Query: "keywords:\"Foo Bar\""}
|
||||
form := &PhotoSearchGeo{Query: "keywords:\"Foo Bar\""}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -31,7 +31,7 @@ func TestGeoSearch(t *testing.T) {
|
|||
assert.Equal(t, "Foo Bar", form.Keywords)
|
||||
})
|
||||
t.Run("valid query", func(t *testing.T) {
|
||||
form := &GeoSearch{Query: "query:\"fooBar baz\" before:2019-01-15 dist:25000 lat:33.45343166666667"}
|
||||
form := &PhotoSearchGeo{Query: "query:\"fooBar baz\" before:2019-01-15 dist:25000 lat:33.45343166666667"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -47,7 +47,7 @@ func TestGeoSearch(t *testing.T) {
|
|||
assert.Equal(t, float32(33.45343), form.Lat)
|
||||
})
|
||||
t.Run("valid query path empty folder not empty", func(t *testing.T) {
|
||||
form := &GeoSearch{Query: "query:\"fooBar baz\" before:2019-01-15 dist:25000 lat:33.45343166666667 folder:test"}
|
||||
form := &PhotoSearchGeo{Query: "query:\"fooBar baz\" before:2019-01-15 dist:25000 lat:33.45343166666667 folder:test"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -67,18 +67,18 @@ func TestGeoSearch(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGeoSearch_Serialize(t *testing.T) {
|
||||
form := &GeoSearch{Query: "query:\"fooBar baz\"", Favorite: true}
|
||||
form := &PhotoSearchGeo{Query: "query:\"fooBar baz\"", Favorite: true}
|
||||
|
||||
assert.Equal(t, "q:\"query:fooBar baz\" favorite:true", form.Serialize())
|
||||
}
|
||||
|
||||
func TestGeoSearch_SerializeAll(t *testing.T) {
|
||||
form := &GeoSearch{Query: "query:\"fooBar baz\"", Favorite: true}
|
||||
form := &PhotoSearchGeo{Query: "query:\"fooBar baz\"", Favorite: true}
|
||||
|
||||
assert.Equal(t, "q:\"query:fooBar baz\" favorite:true", form.SerializeAll())
|
||||
}
|
||||
|
||||
func TestNewGeoSearch(t *testing.T) {
|
||||
r := NewGeoSearch("Berlin")
|
||||
assert.IsType(t, GeoSearch{}, r)
|
||||
assert.IsType(t, PhotoSearchGeo{}, r)
|
||||
}
|
|
@ -16,7 +16,7 @@ import (
|
|||
)
|
||||
|
||||
// PhotosGeo searches for photos based on Form values and returns GeoResults ([]GeoResult).
|
||||
func PhotosGeo(f form.GeoSearch) (results GeoResults, err error) {
|
||||
func PhotosGeo(f form.PhotoSearchGeo) (results GeoResults, err error) {
|
||||
start := time.Now()
|
||||
|
||||
if err := f.ParseQueryString(); err != nil {
|
||||
|
|
|
@ -66,7 +66,7 @@ func TestGeo(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("search for review true, quality 0", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "",
|
||||
Before: time.Time{},
|
||||
After: time.Time{},
|
||||
|
@ -94,7 +94,7 @@ func TestGeo(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("search for review false, quality > 0", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "",
|
||||
Before: time.Time{},
|
||||
After: time.Time{},
|
||||
|
@ -117,7 +117,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("search for s2", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "",
|
||||
Before: time.Time{},
|
||||
After: time.Time{},
|
||||
|
@ -140,7 +140,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("search for Olc", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "",
|
||||
Before: time.Time{},
|
||||
After: time.Time{},
|
||||
|
@ -162,7 +162,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("query for label flower", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "flower",
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("query for label landscape", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "landscape",
|
||||
Album: "test",
|
||||
Camera: 123,
|
||||
|
@ -199,7 +199,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("search with multiple parameters", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "landscape",
|
||||
Photo: true,
|
||||
Path: "/xxx,xxx",
|
||||
|
@ -217,7 +217,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("search for archived true", func(t *testing.T) {
|
||||
f := form.GeoSearch{
|
||||
f := form.PhotoSearchGeo{
|
||||
Query: "landscape",
|
||||
Photo: true,
|
||||
Path: "/xxx/xxx/",
|
||||
|
@ -233,7 +233,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.IsType(t, GeoResults{}, result)
|
||||
})
|
||||
t.Run("faces:true", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Query = "faces:true"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -245,7 +245,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 4)
|
||||
})
|
||||
t.Run("faces:yes", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Faces = "Yes"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -257,7 +257,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 4)
|
||||
})
|
||||
t.Run("faces:no", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Faces = "No"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -269,7 +269,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 8)
|
||||
})
|
||||
t.Run("faces:2", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Faces = "2"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -281,7 +281,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 1)
|
||||
})
|
||||
t.Run("day", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Day = 18
|
||||
f.Month = 4
|
||||
|
||||
|
@ -294,7 +294,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 1)
|
||||
})
|
||||
t.Run("subject uid in query", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Query = "Actress"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -306,7 +306,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 1)
|
||||
})
|
||||
t.Run("albums", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Albums = "2030"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -318,7 +318,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 10)
|
||||
})
|
||||
t.Run("path or path", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Path = "1990/04" + "|" + "2015/11"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
@ -330,7 +330,7 @@ func TestGeo(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, len(photos), 3)
|
||||
})
|
||||
t.Run("name or name", func(t *testing.T) {
|
||||
var f form.GeoSearch
|
||||
var f form.PhotoSearchGeo
|
||||
f.Name = "20151101_000000_51C501B5" + "|" + "Video"
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
|
|
@ -3,6 +3,8 @@ package txt
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
)
|
||||
|
||||
// UniqueNames removes exact duplicates from a list of strings without changing their order.
|
||||
|
@ -92,3 +94,38 @@ func NameKeywords(names, aliases string) (results []string) {
|
|||
|
||||
return UniqueNames(append(Words(names), Words(aliases)...))
|
||||
}
|
||||
|
||||
// NormalizeName sanitizes and capitalizes names.
|
||||
func NormalizeName(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Remove double quotes and other special characters.
|
||||
name = strings.Map(func(r rune) rune {
|
||||
switch r {
|
||||
case '"', '`', '~', '\\', '/', '*', '%', '&', '|', '+', '=', '$', '@', '!', '?', ':', ';', '<', '>', '{', '}':
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, name)
|
||||
|
||||
// Shorten.
|
||||
name = Clip(name, ClipDefault)
|
||||
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Capitalize.
|
||||
return Title(name)
|
||||
}
|
||||
|
||||
// NameSlug converts a name to a valid slug.
|
||||
func NameSlug(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return slug.Make(Clip(name, ClipSlug))
|
||||
}
|
||||
|
|
|
@ -105,3 +105,42 @@ func TestNameKeywords(t *testing.T) {
|
|||
assert.Equal(t, []string{"william", "henry", "gates", "iii", "windows", "guru"}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNormalizeName(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", NormalizeName(""))
|
||||
})
|
||||
t.Run("BillGates", func(t *testing.T) {
|
||||
assert.Equal(t, "William Henry Gates III", NormalizeName("William Henry Gates III"))
|
||||
})
|
||||
t.Run("Quotes", func(t *testing.T) {
|
||||
assert.Equal(t, "William HenRy Gates'", NormalizeName("william \"HenRy\" gates' "))
|
||||
})
|
||||
t.Run("Slash", func(t *testing.T) {
|
||||
assert.Equal(t, "William McCorn Gates'", NormalizeName("william\\ \"McCorn\" / gates' "))
|
||||
})
|
||||
t.Run("SpecialCharacters", func(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
"'', '', '', '', '', '', '', '', '', '', '', '', Foo '', '', '', '', '', '', '', McBar '', ''",
|
||||
NormalizeName("'\"', '`', '~', '\\\\', '/', '*', '%', '&', '|', '+', '=', '$', Foo '@', '!', '?', ':', ';', '<', '>', McBar '{', '}'"),
|
||||
)
|
||||
})
|
||||
t.Run("Chinese", func(t *testing.T) {
|
||||
assert.Equal(t, "陈 赵", NormalizeName(" 陈 赵"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNameSlug(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", NameSlug(""))
|
||||
})
|
||||
t.Run("BillGates", func(t *testing.T) {
|
||||
assert.Equal(t, "william-henry-gates-iii", NameSlug("William Henry Gates III"))
|
||||
})
|
||||
t.Run("Quotes", func(t *testing.T) {
|
||||
assert.Equal(t, "william-henry-gates", NameSlug("william \"HenRy\" gates' "))
|
||||
})
|
||||
t.Run("Chinese", func(t *testing.T) {
|
||||
assert.Equal(t, "chen-zhao", NameSlug(" 陈 赵"))
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue