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