diff --git a/assets/css/inputs.css b/assets/css/inputs.css
index bf83556..0eb5573 100644
--- a/assets/css/inputs.css
+++ b/assets/css/inputs.css
@@ -42,7 +42,7 @@
fill: var(--glass);
}
- &:hover {
+ &:hover:not(:disabled) {
cursor: pointer;
background: var(--primary);
color: var(--text-light);
@@ -55,6 +55,10 @@
&:focus {
outline: none;
}
+
+ &:disabled {
+ background: var(--glass-weak-30);
+ }
}
.button-label {
diff --git a/assets/img/icons/cancel.svg b/assets/img/icons/cancel.svg
new file mode 100755
index 0000000..dbf440b
--- /dev/null
+++ b/assets/img/icons/cancel.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/checkbox-checked.svg b/assets/img/icons/checkbox-checked.svg
new file mode 100755
index 0000000..1375fb6
--- /dev/null
+++ b/assets/img/icons/checkbox-checked.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/assets/img/icons/checkbox-checked2.svg b/assets/img/icons/checkbox-checked2.svg
new file mode 100755
index 0000000..1df05a8
--- /dev/null
+++ b/assets/img/icons/checkbox-checked2.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/checkbox-partial.svg b/assets/img/icons/checkbox-partial.svg
new file mode 100755
index 0000000..760dfa0
--- /dev/null
+++ b/assets/img/icons/checkbox-partial.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/checkbox-partial2.svg b/assets/img/icons/checkbox-partial2.svg
new file mode 100755
index 0000000..2d3fd04
--- /dev/null
+++ b/assets/img/icons/checkbox-partial2.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/checkbox-unchecked.svg b/assets/img/icons/checkbox-unchecked.svg
new file mode 100755
index 0000000..731e074
--- /dev/null
+++ b/assets/img/icons/checkbox-unchecked.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/checkbox-unchecked2.svg b/assets/img/icons/checkbox-unchecked2.svg
new file mode 100755
index 0000000..84606ac
--- /dev/null
+++ b/assets/img/icons/checkbox-unchecked2.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/collaboration.svg b/assets/img/icons/collaboration.svg
new file mode 100755
index 0000000..ab6db47
--- /dev/null
+++ b/assets/img/icons/collaboration.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/assets/img/icons/exclude.svg b/assets/img/icons/exclude.svg
new file mode 100755
index 0000000..e6578f3
--- /dev/null
+++ b/assets/img/icons/exclude.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/interset.svg b/assets/img/icons/interset.svg
new file mode 100755
index 0000000..49e5b20
--- /dev/null
+++ b/assets/img/icons/interset.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/merge.svg b/assets/img/icons/merge.svg
new file mode 100755
index 0000000..93b2e2c
--- /dev/null
+++ b/assets/img/icons/merge.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/assets/img/icons/popout.svg b/assets/img/icons/popout.svg
new file mode 100755
index 0000000..555c0b4
--- /dev/null
+++ b/assets/img/icons/popout.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/stack-plus.svg b/assets/img/icons/stack-plus.svg
new file mode 100755
index 0000000..d6bc1b3
--- /dev/null
+++ b/assets/img/icons/stack-plus.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/stack.svg b/assets/img/icons/stack.svg
new file mode 100755
index 0000000..2a0e859
--- /dev/null
+++ b/assets/img/icons/stack.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/stack2.svg b/assets/img/icons/stack2.svg
new file mode 100755
index 0000000..e621c85
--- /dev/null
+++ b/assets/img/icons/stack2.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/stack3.svg b/assets/img/icons/stack3.svg
new file mode 100755
index 0000000..a6f10fd
--- /dev/null
+++ b/assets/img/icons/stack3.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/unite.svg b/assets/img/icons/unite.svg
new file mode 100755
index 0000000..3b52a14
--- /dev/null
+++ b/assets/img/icons/unite.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/assets/img/icons/unlink5.svg b/assets/img/icons/unlink5.svg
new file mode 100755
index 0000000..d9332f6
--- /dev/null
+++ b/assets/img/icons/unlink5.svg
@@ -0,0 +1,11 @@
+
+
diff --git a/components/actors/bio.vue b/components/actors/bio.vue
index 6e0b0d3..5752447 100644
--- a/components/actors/bio.vue
+++ b/components/actors/bio.vue
@@ -384,7 +384,7 @@
diff --git a/components/actors/merge.vue b/components/actors/merge.vue
index 5db0ab0..bcaa9b4 100644
--- a/components/actors/merge.vue
+++ b/components/actors/merge.vue
@@ -1,6 +1,6 @@
+
+
diff --git a/pages/admin/actors/+onBeforeRender.js b/pages/admin/actors/+onBeforeRender.js
new file mode 100644
index 0000000..ae2e493
--- /dev/null
+++ b/pages/admin/actors/+onBeforeRender.js
@@ -0,0 +1,19 @@
+import { fetchActors } from '#/src/actors.js';
+
+export default async function onBeforeRender(pageContext) {
+ const { actors } = await fetchActors({
+ query: pageContext.urlParsed.search.q,
+ }, {
+ limit: 100,
+ // order: pageContext.urlParsed.search.order?.split('.') || ['likes', 'desc'],
+ }, pageContext.user);
+
+ return {
+ pageContext: {
+ title: 'Actors',
+ pageProps: {
+ actors,
+ },
+ },
+ };
+}
diff --git a/src/actors.js b/src/actors.js
index 2e93447..e3471f1 100644
--- a/src/actors.js
+++ b/src/actors.js
@@ -378,7 +378,7 @@ async function queryManticoreSql(filters, options, _reqUser) {
if (filters.query.charAt(0) === '#') {
builder.where('id', Number(escape(filters.query.slice(1))));
} else {
- builder.whereRaw('match(\'@(name,aliases) :query:\', actors)', { query: escape(filters.query) });
+ builder.whereRaw(`match('@(name,aliases) :query:${filters.query.charAt(0) === '=' ? '' : '*'}', actors)`, { query: escape(filters.query) });
}
}
@@ -447,8 +447,13 @@ async function queryManticoreSql(filters, options, _reqUser) {
builder.where('has_avatar', 1);
}
+ console.log('ACTOR OPTIONS', options);
+
if (options.order?.[0] === 'name') {
- builder.orderBy('actors.slug', options.order[1]);
+ builder.orderBy([
+ { column: 'actors.slug', order: options.order[1] },
+ { column: 'actors.entity_id', order: 'asc' },
+ ]);
} else if (options.order?.[0] === 'likes') {
builder.orderBy([
{ column: 'actors.stashed', order: options.order[1] },
@@ -477,6 +482,10 @@ async function queryManticoreSql(filters, options, _reqUser) {
]);
} else {
builder.orderBy('actors.slug', 'asc');
+ builder.orderBy([
+ { column: 'actors.slug', order: 'asc' },
+ { column: 'actors.entity_id', order: 'asc' },
+ ]);
}
})
.limit(options.limit)
@@ -558,72 +567,70 @@ export async function createActor(newActor, context, reqUser) {
return curateActor(actorEntry);
}
-export async function mergeActors(targetActorId, sourceActorId, reqUser) {
+export async function mergeActors(targetActorId, sourceActorIds, reqUser) {
if (!verifyAbility(reqUser, 'actor', 'merge')) {
throw new HttpError('You are not permitted to merge actors', 403);
}
- const [targetActor, sourceActor] = await Promise.all([
+ if (sourceActorIds.includes(targetActorId)) {
+ throw new HttpError('Cannot merge actor profile into itself', 400);
+ }
+
+ const [targetActor, sourceActors] = await Promise.all([
knex('actors')
.where('id', targetActorId)
.whereNull('entity_id')
.whereNull('alias_for')
.first(),
knex('actors')
- .where('id', sourceActorId)
- .first(),
+ .whereIn('id', sourceActorIds),
]);
if (!targetActor) {
throw new HttpError('Target actor not found', 404);
}
- if (!sourceActor) {
+ if (sourceActors.length < sourceActorIds.length) {
throw new HttpError('Source actor not found', 404);
}
- if (targetActor.entity_id) {
- throw new HttpError('Target actor is not global', 400);
- }
-
- if (targetActor.alias_for) {
- throw new HttpError('Target actor is aliased', 400);
- }
-
const trx = await knex.transaction();
let mergedProfiles;
let mergedScenes;
try {
- await trx('actors')
- .update('alias_for', targetActorId)
- .where('id', sourceActorId)
- .returning(['id', 'alias_for']);
+ const [existingProfiles] = await Promise.all([
+ trx('actors_profiles')
+ .where('actor_id', targetActorId),
+ trx('actors')
+ .update('alias_for', targetActorId)
+ .whereIn('id', sourceActorIds)
+ .returning(['id', 'alias_for']),
+ // some avatars are not matched to a profile, need to investigate why this happens and the avatar table needs a dedicated actor field
+ trx('actors_avatars')
+ .update('actor_id', targetActorId)
+ .whereIn('actor_id', sourceActorIds),
+ trx('stashes_actors')
+ .update('actor_id', targetActorId)
+ .whereIn('actor_id', sourceActorIds)
+ .returning('id'),
+ ]);
mergedProfiles = await trx('actors_profiles')
.update('actor_id', targetActorId)
- .where('actor_id', sourceActorId)
+ .whereIn('actor_id', sourceActorIds)
+ .whereNotIn('entity_id', existingProfiles.map((profile) => profile.entity_id))
.returning('id');
- // some avatars are not matched to a profile, need to investigate why this happens and the avatar table needs a dedicated actor field
- await trx('actors_avatars')
- .update('actor_id', targetActorId)
- .where('actor_id', sourceActorId);
-
mergedScenes = await trx('releases_actors')
.update({
actor_id: targetActorId,
- alias_id: sourceActorId,
+ alias_id: knex.raw('actor_id'),
})
- .where('actor_id', sourceActorId)
+ .whereIn('actor_id', sourceActorIds)
.returning('release_id');
- await trx('stashes_actors')
- .update('actor_id', targetActorId)
- .where('actor_id', sourceActorId)
- .returning('id');
-
await trx.commit();
} catch (error) {
await trx.rollback();
@@ -631,7 +638,7 @@ export async function mergeActors(targetActorId, sourceActorId, reqUser) {
throw error;
}
- await interpolateProfiles([targetActorId, sourceActorId], {
+ await interpolateProfiles([targetActorId, ...sourceActorIds], {
knex,
logger,
moment,
@@ -641,8 +648,8 @@ export async function mergeActors(targetActorId, sourceActorId, reqUser) {
await Promise.all([
syncScenes(mergedScenes.map((scene) => scene.release_id)),
- syncActors([targetActorId, sourceActorId]),
- syncStashes('actor', [targetActorId, sourceActorId]),
+ syncActors([targetActorId, ...sourceActorIds]),
+ syncStashes('actor', [targetActorId, ...sourceActorIds]),
]);
return {
diff --git a/src/web/actors.js b/src/web/actors.js
index 3497d3b..09e89b4 100644
--- a/src/web/actors.js
+++ b/src/web/actors.js
@@ -180,7 +180,7 @@ export async function createActorApi(req, res) {
}
export async function mergeActorsApi(req, res) {
- const result = await mergeActors(Number(req.params.targetActorId), Number(req.params.sourceActorId), req.user);
+ const result = await mergeActors(Number(req.params.targetActorId), req.params.sourceActorIds.split(',').map((actorId) => Number(actorId)), req.user);
res.send(result);
}
@@ -208,7 +208,7 @@ export const actorsRouter = Router();
actorsRouter.get('/api/actors', fetchActorsApi);
actorsRouter.post('/api/actors', createActorApi);
-actorsRouter.post('/api/actors/:targetActorId/merge/:sourceActorId', mergeActorsApi);
+actorsRouter.post('/api/actors/:targetActorId/merge/:sourceActorIds', mergeActorsApi);
actorsRouter.get('/api/revisions/actors', fetchActorRevisionsApi);
actorsRouter.get('/api/revisions/actors/:revisionId', fetchActorRevisionsApi);