Added actors admin panel with bulk merge. Fixed merge failing if source and target actor have conflicting network profiles.
@@ -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 {
|
||||
|
||||
4
assets/img/icons/cancel.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M11.5 0h-7l-4.5 4.5v7l4.5 4.5h7l4.5-4.5v-7l-4.5-4.5zM12.5 11l-1.5 1.5-3-3-3 3-1.5-1.5 3-3-3-3 1.5-1.5 3 3 3-3 1.5 1.5-3 3 3 3z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
5
assets/img/icons/checkbox-checked.svg
Executable file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M0 0v16h16v-16h-16zM15 15h-14v-14h14v14z"></path>
|
||||
<path d="M2.5 8l1.5-1.5 2.5 2.5 5.5-5.5 1.5 1.5-7 7z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 259 B |
4
assets/img/icons/checkbox-checked2.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M14 0h-12c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2v-12c0-1.1-0.9-2-2-2zM7 12.414l-3.707-3.707 1.414-1.414 2.293 2.293 4.793-4.793 1.414 1.414-6.207 6.207z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 325 B |
4
assets/img/icons/checkbox-partial.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M0 0v16h16v-16h-16zM15 15h-14v-14h14v14zM4 4h8v8h-8z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 209 B |
4
assets/img/icons/checkbox-partial2.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M14.5 0h-13c-0.825 0-1.5 0.675-1.5 1.5v13c0 0.825 0.675 1.5 1.5 1.5h13c0.825 0 1.5-0.675 1.5-1.5v-13c0-0.825-0.675-1.5-1.5-1.5zM14 14h-12v-12h12v12zM5 12h6c0.55 0 1-0.45 1-1v-6c0-0.55-0.45-1-1-1h-6c-0.55 0-1 0.45-1 1v6c0 0.55 0.45 1 1 1zM5 10.875h6v0.123c-0.001 0.001-0.001 0.001-0.002 0.002h-5.996c-0.001-0.001-0.001-0.001-0.002-0.002v-0.123zM5.002 5h5.996c0.001 0.001 0.001 0.001 0.002 0.002v0.123h-6v-0.123c0.001-0.001 0.001-0.001 0.002-0.002zM11 5.375v0.25h-6v-0.25h6zM11 5.875v0.25h-6v-0.25h6zM11 6.375v0.25h-6v-0.25h6zM11 6.875v0.25h-6v-0.25h6zM11 7.375v0.25h-6v-0.25h6zM11 7.875v0.25h-6v-0.25h6zM11 8.375v0.25h-6v-0.25h6zM11 8.875v0.25h-6v-0.25h6zM11 9.375v0.25h-6v-0.25h6zM11 9.875v0.25h-6v-0.25h6zM11 10.375v0.25h-6v-0.25h6z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 890 B |
4
assets/img/icons/checkbox-unchecked.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M0 0v16h16v-16h-16zM15 15h-14v-14h14v14z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 197 B |
4
assets/img/icons/checkbox-unchecked2.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M14 0h-12c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h12c1.1 0 2-0.9 2-2v-12c0-1.1-0.9-2-2-2zM14 14h-12v-12h12v12z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 263 B |
12
assets/img/icons/collaboration.svg
Executable file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M4 3.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
|
||||
<path d="M2.5 5c-1.381 0-2.5 0.448-2.5 1v1h5v-1c0-0.552-1.119-1-2.5-1z"></path>
|
||||
<path d="M14 3.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
|
||||
<path d="M12.5 5c-1.381 0-2.5 0.448-2.5 1v1h5v-1c0-0.552-1.119-1-2.5-1z"></path>
|
||||
<path d="M9 11.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
|
||||
<path d="M7.5 13c-1.381 0-2.5 0.448-2.5 1v1h5v-1c0-0.552-1.119-1-2.5-1z"></path>
|
||||
<path d="M9.5 4h-4c-0.276 0-0.5-0.224-0.5-0.5s0.224-0.5 0.5-0.5h4c0.276 0 0.5 0.224 0.5 0.5s-0.224 0.5-0.5 0.5z"></path>
|
||||
<path d="M10.5 12c-0.095 0-0.192-0.027-0.277-0.084-0.23-0.153-0.292-0.464-0.139-0.693l2-3c0.153-0.23 0.464-0.292 0.693-0.139s0.292 0.464 0.139 0.693l-2 3c-0.096 0.145-0.255 0.223-0.416 0.223z"></path>
|
||||
<path d="M4.5 12c-0.162 0-0.32-0.078-0.417-0.223l-2-3c-0.153-0.23-0.091-0.54 0.139-0.693s0.54-0.091 0.693 0.139l2 3c0.153 0.23 0.091 0.54-0.139 0.693-0.085 0.057-0.182 0.084-0.277 0.084z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
assets/img/icons/exclude.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M11 4v-4h-11v11h4v5h12v-12h-5zM14 6.125h-3v-0.125h3v0.125zM6 14v-0.125h8v0.125h-8zM6 13.625v-0.25h8v0.25h-8zM6 13.125v-0.25h8v0.25h-8zM6 12.625v-0.25h8v0.25h-8zM6 12.125v-0.25h8v0.25h-8zM6 11.625v-0.25h8v0.25h-8zM6 11.125v-0.125h5v-0.125h3v0.25h-8zM11 10.625v-0.25h3v0.25h-3zM11 10.125v-0.25h3v0.25h-3zM11 9.625v-0.25h3v0.25h-3zM11 9.125v-0.25h3v0.25h-3zM11 8.625v-0.25h3v0.25h-3zM11 8.125v-0.25h3v0.25h-3zM11 7.625v-0.25h3v0.25h-3zM11 7.125v-0.25h3v0.25h-3zM11 6.625v-0.25h3v0.25h-3zM9 4h-5v0.125h-2v-0.25h7v0.125zM2 9v-0.125h2v0.125h-2zM2 8.625v-0.25h2v0.25h-2zM2 8.125v-0.25h2v0.25h-2zM2 7.625v-0.25h2v0.25h-2zM2 7.125v-0.25h2v0.25h-2zM2 6.625v-0.25h2v0.25h-2zM2 6.125v-0.25h2v0.25h-2zM2 5.625v-0.25h2v0.25h-2zM2 5.125v-0.25h2v0.25h-2zM2 4.625v-0.25h2v0.25h-2zM9 3.625h-7v-0.25h7v0.25zM9 3.125h-7v-0.25h7v0.25zM9 2.625h-7v-0.25h7v0.25zM9 2.125h-7v-0.125h7v0.125zM5 5h5v5h-5v-5z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
4
assets/img/icons/interset.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M11 4v-4h-11v11h4v5h12v-12h-5zM10 6.125h-4v-0.125h4v0.125zM10 10h-4v-0.125h4v0.125zM10 9.625h-4v-0.25h4v0.25zM10 9.125h-4v-0.25h4v0.25zM10 8.625h-4v-0.25h4v0.25zM10 8.125h-4v-0.25h4v0.25zM10 7.625h-4v-0.25h4v0.25zM10 7.125h-4v-0.25h4v0.25zM10 6.625h-4v-0.25h4v0.25zM4 10h-3v-9h9v3h-6v6zM15 15h-10v-3h7v-7h3v10z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 467 B |
5
assets/img/icons/merge.svg
Executable file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M9 5h3.5l-4.5-4.5-4.5 4.5h3.5v3.586l-5.957 5.957 1.414 1.414 6.543-6.543z"></path>
|
||||
<path d="M8.543 11.957l1.414-1.414 4 4-1.414 1.414-4-4z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 295 B |
4
assets/img/icons/popout.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M14.5 2h-9c-0.825 0-1.5 0.675-1.5 1.5v1.5h-2.5c-0.825 0-1.5 0.675-1.5 1.5v6c0 0.825 0.675 1.5 1.5 1.5h9c0.825 0 1.5-0.675 1.5-1.5v-1.5h2.5c0.825 0 1.5-0.675 1.5-1.5v-6c0-0.825-0.675-1.5-1.5-1.5zM11 12.5c0 0.271-0.229 0.5-0.5 0.5h-9c-0.271 0-0.5-0.229-0.5-0.5v-6c0-0.271 0.229-0.5 0.5-0.5h2.5v3.5c0 0.825 0.675 1.5 1.5 1.5h5.5v1.5zM15 9.5c0 0.271-0.229 0.5-0.5 0.5h-9c-0.271 0-0.5-0.229-0.5-0.5v-6c0-0.271 0.229-0.5 0.5-0.5h9c0.271 0 0.5 0.229 0.5 0.5v6z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 610 B |
4
assets/img/icons/stack-plus.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M5 1v1.155l-2.619 0.368 0.17 1.211-2.551 0.732 3.308 11.535 10.189-2.921 0.558-0.079h1.945v-12h-11zM3.929 14.879l-2.808-9.793 1.558-0.447 1.373 9.766 2.997-0.421-3.119 0.894zM4.822 13.382l-1.418-10.088 1.595-0.224v9.93h2.543l-2.721 0.382zM15 12h-9v-10h9v10zM13 8h-2v2h-1v-2h-2v-1h2v-2h1v2h2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
4
assets/img/icons/stack.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M13.75 4h-0.75v-0.75c0-0.689-0.561-1.25-1.25-1.25h-0.75v-0.75c0-0.689-0.561-1.25-1.25-1.25h-7.5c-0.689 0-1.25 0.561-1.25 1.25v9.5c0 0.689 0.561 1.25 1.25 1.25h0.75v0.75c0 0.689 0.561 1.25 1.25 1.25h0.75v0.75c0 0.689 0.561 1.25 1.25 1.25h7.5c0.689 0 1.25-0.561 1.25-1.25v-9.5c0-0.689-0.561-1.25-1.25-1.25zM2.25 11c-0.138 0-0.25-0.113-0.25-0.25v-9.5c0-0.137 0.112-0.25 0.25-0.25h7.5c0.137 0 0.25 0.113 0.25 0.25v0.75h-5.75c-0.689 0-1.25 0.561-1.25 1.25v7.75h-0.75zM4.25 13c-0.138 0-0.25-0.113-0.25-0.25v-9.5c0-0.138 0.112-0.25 0.25-0.25h7.5c0.137 0 0.25 0.112 0.25 0.25v0.75h-5.75c-0.689 0-1.25 0.561-1.25 1.25v7.75h-0.75zM14 14.75c0 0.137-0.113 0.25-0.25 0.25h-7.5c-0.138 0-0.25-0.113-0.25-0.25v-9.5c0-0.138 0.112-0.25 0.25-0.25h7.5c0.137 0 0.25 0.112 0.25 0.25v9.5z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 922 B |
4
assets/img/icons/stack2.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M16 5l-8-4-8 4 8 4 8-4zM8 2.328l5.345 2.672-5.345 2.672-5.345-2.672 5.345-2.672zM14.398 7.199l1.602 0.801-8 4-8-4 1.602-0.801 6.398 3.199zM14.398 10.199l1.602 0.801-8 4-8-4 1.602-0.801 6.398 3.199z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 354 B |
4
assets/img/icons/stack3.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M8 9l-8-4 8-4 8 4zM14.398 7.199l1.602 0.801-8 4-8-4 1.602-0.801 6.398 3.199zM14.398 10.199l1.602 0.801-8 4-8-4 1.602-0.801 6.398 3.199z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 292 B |
4
assets/img/icons/unite.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M11 4v-4h-11v11h4v5h12v-12h-5zM10 5.125h-5v-0.125h5v0.125zM14 6.125h-3v-0.125h3v0.125zM5 9.875h5v0.125h-5v-0.125zM2 8.875h2v0.125h-2v-0.125zM4 5.875v0.25h-2v-0.25h2zM2 5.625v-0.25h2v0.25h-2zM4 6.375v0.25h-2v-0.25h2zM5 8.125v-0.25h5v0.25h-5zM10 8.375v0.25h-5v-0.25h5zM5 7.625v-0.25h5v0.25h-5zM5 7.125v-0.25h5v0.25h-5zM5 6.625v-0.25h5v0.25h-5zM4 6.875v0.25h-2v-0.25h2zM4 7.375v0.25h-2v-0.25h2zM4 7.875v0.25h-2v-0.25h2zM4 8.375v0.25h-2v-0.25h2zM5 8.875h5v0.25h-5v-0.25zM10 9.375v0.25h-5v-0.25h5zM6 14v-0.125h8v0.125h-8zM6 13.625v-0.25h8v0.25h-8zM6 13.125v-0.25h8v0.25h-8zM6 12.625v-0.25h8v0.25h-8zM6 12.125v-0.25h8v0.25h-8zM6 11.625v-0.25h8v0.25h-8zM6 11.125v-0.125h5v-0.125h3v0.25h-8zM11 10.625v-0.25h3v0.25h-3zM11 10.125v-0.25h3v0.25h-3zM11 9.625v-0.25h3v0.25h-3zM11 9.125v-0.25h3v0.25h-3zM11 8.625v-0.25h3v0.25h-3zM11 8.125v-0.25h3v0.25h-3zM11 7.625v-0.25h3v0.25h-3zM11 7.125v-0.25h3v0.25h-3zM11 6.625v-0.25h3v0.25h-3zM10 6.125h-5v-0.25h5v0.25zM5 5.625v-0.25h5v0.25h-5zM4 5.125h-2v-0.25h2v0.25zM9 2v0.125h-7v-0.125h7zM9 2.375v0.25h-7v-0.25h7zM9 2.875v0.25h-7v-0.25h7zM9 3.375v0.25h-7v-0.25h7zM9 3.875v0.125h-5v0.125h-2v-0.25h7zM4 4.375v0.25h-2v-0.25h2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
11
assets/img/icons/unlink5.svg
Executable file
@@ -0,0 +1,11 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16">
|
||||
<path d="M7 12h-3c-2.206 0-4-1.794-4-4s1.794-4 4-4h3c0.552 0 1 0.448 1 1s-0.448 1-1 1h-3c-1.103 0-2 0.897-2 2s0.897 2 2 2h3c0.552 0 1 0.448 1 1s-0.448 1-1 1z"></path>
|
||||
<path d="M13 12h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1h3c1.103 0 2-0.897 2-2s-0.897-2-2-2h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1h3c2.206 0 4 1.794 4 4s-1.794 4-4 4z"></path>
|
||||
<path d="M8.5 3c-0.276 0-0.5-0.224-0.5-0.5v-2c0-0.276 0.224-0.5 0.5-0.5s0.5 0.224 0.5 0.5v2c0 0.276-0.224 0.5-0.5 0.5z"></path>
|
||||
<path d="M11.5 3c-0.128 0-0.256-0.049-0.354-0.146-0.195-0.195-0.195-0.512 0-0.707l2-2c0.195-0.195 0.512-0.195 0.707 0s0.195 0.512 0 0.707l-2 2c-0.098 0.098-0.226 0.146-0.354 0.146z"></path>
|
||||
<path d="M5.5 3c-0.128 0-0.256-0.049-0.354-0.146l-2-2c-0.195-0.195-0.195-0.512 0-0.707s0.512-0.195 0.707 0l2 2c0.195 0.195 0.195 0.512 0 0.707-0.098 0.098-0.226 0.146-0.354 0.146z"></path>
|
||||
<path d="M8.5 13c0.276 0 0.5 0.224 0.5 0.5v2c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-2c0-0.276 0.224-0.5 0.5-0.5z"></path>
|
||||
<path d="M5.5 13c0.128 0 0.256 0.049 0.354 0.146 0.195 0.195 0.195 0.512 0 0.707l-2 2c-0.195 0.195-0.512 0.195-0.707 0s-0.195-0.512 0-0.707l2-2c0.098-0.098 0.226-0.146 0.354-0.146z"></path>
|
||||
<path d="M11.5 13c0.128 0 0.256 0.049 0.354 0.146l2 2c0.195 0.195 0.195 0.512 0 0.707s-0.512 0.195-0.707 0l-2-2c-0.195-0.195-0.195-0.512 0-0.707 0.098-0.098 0.226-0.146 0.354-0.146z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -384,7 +384,7 @@
|
||||
|
||||
<Merge
|
||||
v-if="showMergeDialog"
|
||||
:actor="actor"
|
||||
:actors="[actor]"
|
||||
@close="showMergeDialog = false"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="`Merge '${actor.name}'`"
|
||||
:title="`Merge ${actors.length === 1 ? `'${actors[0].name}'` : `${actors.length} actors`}`"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<div
|
||||
@@ -12,9 +12,9 @@
|
||||
<ul class="options">
|
||||
<li class="option">
|
||||
<a
|
||||
:href="`/actor/${actor?.id}/${actor?.slug}`"
|
||||
href=""
|
||||
class="link"
|
||||
>Reload #{{ actor.id }} {{ actor.name }} ({{ actor.entity.name }})</a>
|
||||
>Reload page</a>
|
||||
</li>
|
||||
|
||||
<li class="option">
|
||||
@@ -30,7 +30,18 @@
|
||||
v-else
|
||||
class="dialog-body"
|
||||
>
|
||||
<strong class="source">#{{ actor.id }} {{ actor.name }}<span v-if="actor.entity"> ({{ actor.entity.name }})</span></strong>
|
||||
<ul class="actors nolist">
|
||||
<li
|
||||
v-for="actor in actors"
|
||||
:key="`actor-${actor.id}`"
|
||||
class="actor"
|
||||
>
|
||||
<span class="source">
|
||||
<strong class="source-name">{{ actor.name }}<template v-if="actor.entity"> ({{ actor.entity.name }})</template></strong>
|
||||
<span class="source-id">#{{ actor.id }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span class="path">merging into</span>
|
||||
|
||||
@@ -88,9 +99,10 @@
|
||||
|
||||
<button
|
||||
v-else
|
||||
v-tooltip="sourceTargetConflict && 'Cannot merge actor profile into itself'"
|
||||
type="submit"
|
||||
class="button button-primary"
|
||||
:disabled="!targetActor"
|
||||
:disabled="!targetActor || sourceTargetConflict"
|
||||
@click="merge"
|
||||
>Merge</button>
|
||||
</div>
|
||||
@@ -99,7 +111,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
|
||||
import Dialog from '#/components/dialog/dialog.vue';
|
||||
import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
@@ -107,9 +119,9 @@ import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
import { get, post } from '#/src/api.js';
|
||||
|
||||
const props = defineProps({
|
||||
actor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
actors: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -122,9 +134,11 @@ const actorResults = ref([]);
|
||||
const submitted = ref(false);
|
||||
const merged = ref(false);
|
||||
|
||||
const sourceTargetConflict = computed(() => props.actors.some((actor) => actor.id === targetActor.value?.id));
|
||||
|
||||
async function searchActors() {
|
||||
const res = await get('/actors', {
|
||||
q: actorQuery.value.charAt(0) === '#' ? actorQuery.value : `${actorQuery.value}*`, // return partial matches
|
||||
q: actorQuery.value, // return partial matches
|
||||
limit: 10,
|
||||
global: true,
|
||||
});
|
||||
@@ -132,12 +146,24 @@ async function searchActors() {
|
||||
actorResults.value = res.actors;
|
||||
}
|
||||
|
||||
function getActorNames() {
|
||||
if (props.actors.length > 1) {
|
||||
return `${props.actors.length} actors`;
|
||||
}
|
||||
|
||||
if (props.actors[0].entity) {
|
||||
return `${props.actors[0].name} (${props.actors[0].entity.name})`;
|
||||
}
|
||||
|
||||
return props.actors[0].name;
|
||||
}
|
||||
|
||||
async function merge() {
|
||||
submitted.value = true;
|
||||
|
||||
await post(`/actors/${targetActor.value.id}/merge/${props.actor.id}`, null, {
|
||||
successFeedback: `Merged ${props.actor.entity ? `${props.actor.name} (${props.actor.entity.name})` : props.actor.name} into ${targetActor.value.name}`,
|
||||
errorFeedback: `Failed to merge ${props.actor.entity ? `${props.actor.name} (${props.actor.entity.name})` : props.actor.name} into ${targetActor.value.name}`,
|
||||
await post(`/actors/${targetActor.value.id}/merge/${props.actors.map((actor) => actor.id).join(',')}`, null, {
|
||||
successFeedback: `Merged ${getActorNames()} into ${targetActor.value.name}`,
|
||||
errorFeedback: `Failed to merge ${getActorNames()} into ${targetActor.value.name}`,
|
||||
appendErrorMessage: true,
|
||||
});
|
||||
|
||||
@@ -167,6 +193,7 @@ onMounted(() => {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
@@ -175,6 +202,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.actors {
|
||||
max-height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.actor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -199,11 +235,20 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.source-id,
|
||||
.target-id {
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.source {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.source-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.results {
|
||||
padding: .25rem 0;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,10 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
|
||||
:deep(.bookmarks) .icon:not(.favorited):not(:hover) {
|
||||
fill: var(--text-light);
|
||||
}
|
||||
|
||||
.menu {
|
||||
fill: var(--text-light);
|
||||
}
|
||||
}
|
||||
|
||||
&.unstashed {
|
||||
@@ -135,6 +139,14 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
padding: .5rem .75rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
fill: var(--highlight-strong-20);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
<div class="page">
|
||||
<nav class="nav">
|
||||
<ul class="nav-items nolist">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="/admin/actors"
|
||||
class="nav-link nolink"
|
||||
:class="{ active: pageContext.routeParams.section === 'actors' }"
|
||||
>Actors</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="/admin/revisions/scenes"
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
<h2 class="heading">Admin Panel</h2>
|
||||
|
||||
<ul class="menu">
|
||||
<li>
|
||||
<a
|
||||
href="/admin/actors"
|
||||
class="link"
|
||||
>Actors</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/admin/revisions/scenes"
|
||||
|
||||
305
pages/admin/actors/+Page.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<Admin class="page">
|
||||
<div class="header">
|
||||
<input
|
||||
v-model="actorQuery"
|
||||
type="search"
|
||||
placeholder="Search actors"
|
||||
class="input search"
|
||||
@search="searchActors"
|
||||
>
|
||||
|
||||
<div class="header-actions">
|
||||
<button
|
||||
class="button"
|
||||
:disabled="selectedActors.size === 0"
|
||||
@click="selectedActors = new Set()"
|
||||
><Icon icon="cancel-square" />Deselect</button>
|
||||
|
||||
<button
|
||||
class="button"
|
||||
:disabled="selectedActors.size === 0"
|
||||
@click="showMergeDialog = true"
|
||||
><Icon icon="make-group" />Merge</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="actors">
|
||||
<thead class="actors-header">
|
||||
<tr>
|
||||
<th class="actor-id">ID</th>
|
||||
<th class="actor-name">Entity</th>
|
||||
<th class="actor-avatar">Avatar</th>
|
||||
<th class="actor-name">Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="actors-body">
|
||||
<tr
|
||||
v-for="(actor, index) in actors"
|
||||
:key="`actor-${actor.id}`"
|
||||
class="actor"
|
||||
>
|
||||
<td class="actor-id ellipsis">{{ actor.id }}</td>
|
||||
|
||||
<td
|
||||
v-tooltip="actor.entity?.name || 'Global'"
|
||||
class="actor-entity ellipsis"
|
||||
>
|
||||
<img
|
||||
v-if="actor.entity"
|
||||
:src="`/logos/${actor.entity.slug}/favicon_dark.png`"
|
||||
class="actor-favicon"
|
||||
>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="device_hub"
|
||||
class="actor-global"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td class="actor-avatar">
|
||||
<img
|
||||
v-if="actor.avatar"
|
||||
:src="getPath(actor.avatar, 'lazy')"
|
||||
loading="lazy"
|
||||
class="avatar"
|
||||
>
|
||||
|
||||
<img
|
||||
v-if="actor.avatar"
|
||||
:src="getPath(actor.avatar)"
|
||||
loading="lazy"
|
||||
class="avatar-zoom"
|
||||
>
|
||||
</td>
|
||||
|
||||
<td class="actor-name ellipsis">{{ actor.name }}</td>
|
||||
|
||||
<td class="actor-actions">
|
||||
<div class="actions">
|
||||
<Checkbox
|
||||
:checked="selectedActors.has(actor.id)"
|
||||
@change="(isChecked) => selectActors(actor, isChecked, index)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-tooltip="'Merge'"
|
||||
icon="make-group"
|
||||
class="actor-action action-merge"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-tooltip="'Delete'"
|
||||
icon="bin"
|
||||
class="actor-action action-delete"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Merge
|
||||
v-if="showMergeDialog"
|
||||
:actor="actors[0]"
|
||||
:actors="actors.filter((actor) => selectedActors.has(actor.id))"
|
||||
@close="showMergeDialog = false"
|
||||
/>
|
||||
</Admin>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from 'vue';
|
||||
|
||||
import Admin from '#/components/admin/admin.vue';
|
||||
import Checkbox from '#/components/form/checkbox.vue';
|
||||
import Merge from '#/components/actors/merge.vue';
|
||||
|
||||
import getPath from '#/src/get-path.js';
|
||||
import navigate from '#/src/navigate.js';
|
||||
// import { get } from '#/src/api.js';
|
||||
|
||||
const { pageProps, urlParsed } = inject('pageContext');
|
||||
|
||||
const actors = ref(pageProps.actors);
|
||||
const selectedActors = ref(new Set([]));
|
||||
const actorQuery = ref(urlParsed.search.q || null);
|
||||
|
||||
const lastSelectedIndex = ref(null);
|
||||
const holdingShift = ref(false);
|
||||
const showMergeDialog = ref(false);
|
||||
|
||||
console.log(actors.value);
|
||||
|
||||
function selectActors(selectedActor, isChecked, index) {
|
||||
const [start, end] = holdingShift.value
|
||||
? [index, lastSelectedIndex.value].toSorted((indexA, indexB) => indexA - indexB)
|
||||
: [index, index];
|
||||
|
||||
const actorIds = actors.value
|
||||
.slice(start, end + 1)
|
||||
.map((actor) => actor.id);
|
||||
|
||||
actorIds.forEach((actorId) => {
|
||||
if (isChecked) {
|
||||
selectedActors.value.add(actorId);
|
||||
} else {
|
||||
selectedActors.value.delete(actorId);
|
||||
}
|
||||
});
|
||||
|
||||
lastSelectedIndex.value = index;
|
||||
}
|
||||
|
||||
async function searchActors() {
|
||||
navigate('/admin/actors', { q: actorQuery.value }, { redirect: true });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Shift') {
|
||||
holdingShift.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', (event) => {
|
||||
if (event.key === 'Shift') {
|
||||
holdingShift.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('blur', () => {
|
||||
holdingShift.value = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
max-width: 20rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.actors-header tr {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.actors, .actors th, .actors td {
|
||||
border-collapse: collapse;
|
||||
padding: .5rem;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.actor {
|
||||
&:nth-child(2n) {
|
||||
background: var(--glass-weak-50);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--glass-weak-40);
|
||||
}
|
||||
}
|
||||
|
||||
.actor-id {
|
||||
width: 6rem;
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.actor-entity {
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.actor-favicon {
|
||||
width: 1rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.actor-global {
|
||||
background: var(--primary);
|
||||
fill: var(--text-light);
|
||||
padding: .5rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
th.actor-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
|
||||
.button:first-child {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.actor-actions {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actor-action {
|
||||
padding: .5rem .75rem;
|
||||
fill: var(--glass);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover.action-delete {
|
||||
fill: var(--error);
|
||||
}
|
||||
}
|
||||
|
||||
.actor-avatar {
|
||||
width: 5rem;
|
||||
position: relative;
|
||||
line-height: 0;
|
||||
|
||||
&:hover .avatar-zoom,
|
||||
&:active .avatar-zoom {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 3.5rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.avatar-zoom {
|
||||
display: none;
|
||||
height: 50vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.check-container {
|
||||
display: inline-flex;
|
||||
padding: .5rem .75rem;
|
||||
}
|
||||
</style>
|
||||
19
pages/admin/actors/+onBeforeRender.js
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||