diff --git a/assets/css/states.css b/assets/css/states.css index cba281a..596d88f 100755 --- a/assets/css/states.css +++ b/assets/css/states.css @@ -61,3 +61,7 @@ .noshrink { flex-shrink: 0; } + +.capitalize { + text-transform: capitalize; +} diff --git a/assets/css/theme.css b/assets/css/theme.css index 22aa4c1..88e1fa3 100644 --- a/assets/css/theme.css +++ b/assets/css/theme.css @@ -81,6 +81,9 @@ --success: #5c2; --notice: #25c; + --approve: #3a1; + --reject: #a22; + --gold: #d5b522; } diff --git a/assets/img/icons/bubble-blocked.svg b/assets/img/icons/bubble-blocked.svg new file mode 100755 index 0000000..790d466 --- /dev/null +++ b/assets/img/icons/bubble-blocked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-cancel.svg b/assets/img/icons/bubble-cancel.svg new file mode 100755 index 0000000..a4e20d9 --- /dev/null +++ b/assets/img/icons/bubble-cancel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-check.svg b/assets/img/icons/bubble-check.svg new file mode 100755 index 0000000..54bf80b --- /dev/null +++ b/assets/img/icons/bubble-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-lines3.svg b/assets/img/icons/bubble-lines3.svg new file mode 100755 index 0000000..635aab0 --- /dev/null +++ b/assets/img/icons/bubble-lines3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-link.svg b/assets/img/icons/bubble-link.svg new file mode 100755 index 0000000..700414a --- /dev/null +++ b/assets/img/icons/bubble-link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-notification2.svg b/assets/img/icons/bubble-notification2.svg new file mode 100755 index 0000000..ce6e5b3 --- /dev/null +++ b/assets/img/icons/bubble-notification2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/bubble-plus.svg b/assets/img/icons/bubble-plus.svg new file mode 100755 index 0000000..9d91556 --- /dev/null +++ b/assets/img/icons/bubble-plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/hammer2.svg b/assets/img/icons/hammer2.svg new file mode 100755 index 0000000..43b6463 --- /dev/null +++ b/assets/img/icons/hammer2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/hand.svg b/assets/img/icons/hand.svg new file mode 100755 index 0000000..778dc07 --- /dev/null +++ b/assets/img/icons/hand.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/spam.svg b/assets/img/icons/spam.svg new file mode 100755 index 0000000..73ccb6c --- /dev/null +++ b/assets/img/icons/spam.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/img/icons/user-block.svg b/assets/img/icons/user-block.svg new file mode 100755 index 0000000..25b2e44 --- /dev/null +++ b/assets/img/icons/user-block.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/icons/user-cancel.svg b/assets/img/icons/user-cancel.svg new file mode 100755 index 0000000..320ba94 --- /dev/null +++ b/assets/img/icons/user-cancel.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/icons/user-check.svg b/assets/img/icons/user-check.svg new file mode 100755 index 0000000..df273b4 --- /dev/null +++ b/assets/img/icons/user-check.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/icons/user-lock.svg b/assets/img/icons/user-lock.svg new file mode 100755 index 0000000..4f67f24 --- /dev/null +++ b/assets/img/icons/user-lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/icons/user-minus.svg b/assets/img/icons/user-minus.svg new file mode 100755 index 0000000..5f98fcb --- /dev/null +++ b/assets/img/icons/user-minus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/img/icons/user-plus.svg b/assets/img/icons/user-plus.svg new file mode 100755 index 0000000..6798cb1 --- /dev/null +++ b/assets/img/icons/user-plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/components/actors/bio.vue b/components/actors/bio.vue index 5ba1dcb..f04fe5b 100644 --- a/components/actors/bio.vue +++ b/components/actors/bio.vue @@ -8,7 +8,7 @@ class="avatar-container" > @@ -296,7 +296,7 @@ + + diff --git a/components/edit/actors.vue b/components/edit/actors.vue index 37c52ed..932afe8 100644 --- a/components/edit/actors.vue +++ b/components/edit/actors.vue @@ -85,11 +85,13 @@ watch(() => props.scene, () => { newActors.value = []; }); &.disabled { .actor { - color: var(--shadow); + background: var(--glass-weak-50); + color: var(--glass-strong-10); .remove, .add { - background: var(--shadow-weak-40); + fill: var(--shadow-weak-30); + background: var(--shadow-weak-50); } } @@ -103,9 +105,14 @@ watch(() => props.scene, () => { newActors.value = []; }); align-items: center; margin-left: .25rem; + &:hover { + box-shadow: 0 0 3px var(--shadow-weak-20); + } + .icon { height: 100%; padding: 0 .5rem; + background: var(--success); fill: var(--text-light); } } @@ -114,8 +121,9 @@ watch(() => props.scene, () => { newActors.value = []; }); .actor { display: flex; align-items: stretch; - background: var(--glass-weak-30); border-radius: .25rem; + background: var(--background); + box-shadow: 0 0 3px var(--shadow-weak-30); &.deleted { color: var(--glass); @@ -129,8 +137,8 @@ watch(() => props.scene, () => { newActors.value = []; }); .add { height: auto; padding: .25rem .3rem; - fill: var(--highlight-strong-10); border-radius: .25rem; + fill: var(--highlight-strong-10); &:hover { fill: var(--text-light); @@ -139,11 +147,19 @@ watch(() => props.scene, () => { newActors.value = []; }); } .remove { - background: var(--error); + fill: var(--error); + + &:hover { + background: var(--error); + } } .add { - background: var(--success); + fill: var(--success); + + &:hover { + background: var(--success); + } } } diff --git a/components/edit/movies.vue b/components/edit/movies.vue new file mode 100644 index 0000000..e4755a8 --- /dev/null +++ b/components/edit/movies.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/components/edit/revisions.vue b/components/edit/revisions.vue new file mode 100644 index 0000000..67c476a --- /dev/null +++ b/components/edit/revisions.vue @@ -0,0 +1,488 @@ + + + + + diff --git a/components/edit/tags.vue b/components/edit/tags.vue index 43eb3eb..65339a1 100644 --- a/components/edit/tags.vue +++ b/components/edit/tags.vue @@ -85,11 +85,13 @@ watch(() => props.scene, () => { newTags.value = []; }); &.disabled { .tag { - color: var(--shadow); + background: var(--glass-weak-50); + color: var(--glass-strong-10); .remove, .add { - background: var(--shadow-weak-40); + fill: var(--shadow-weak-30); + background: var(--shadow-weak-50); } } @@ -103,9 +105,14 @@ watch(() => props.scene, () => { newTags.value = []; }); align-items: center; margin-left: .25rem; + &:hover { + box-shadow: 0 0 3px var(--shadow-weak-20); + } + .icon { height: 100%; padding: 0 .5rem; + background: var(--success); fill: var(--text-light); } } @@ -114,8 +121,9 @@ watch(() => props.scene, () => { newTags.value = []; }); .tag { display: flex; align-items: stretch; - background: var(--glass-weak-30); border-radius: .25rem; + background: var(--background); + box-shadow: 0 0 3px var(--shadow-weak-30); &.deleted { color: var(--glass); @@ -129,8 +137,8 @@ watch(() => props.scene, () => { newTags.value = []; }); .add { height: auto; padding: .25rem .3rem; - fill: var(--highlight-strong-10); border-radius: .25rem; + fill: var(--highlight-strong-10); &:hover { fill: var(--text-light); @@ -139,11 +147,19 @@ watch(() => props.scene, () => { newTags.value = []; }); } .remove { - background: var(--error); + fill: var(--error); + + &:hover { + background: var(--error); + } } .add { - background: var(--success); + fill: var(--success); + + &:hover { + background: var(--success); + } } } diff --git a/components/form/checkbox.vue b/components/form/checkbox.vue index 5b26446..d317385 100755 --- a/components/form/checkbox.vue +++ b/components/form/checkbox.vue @@ -11,6 +11,7 @@ :checked="checked" type="checkbox" class="check-checkbox" + :disabled="disabled" @change="$emit('change', $event.target.checked)" > @@ -33,6 +34,10 @@ defineProps({ type: String, default: null, }, + disabled: { + type: Boolean, + default: false, + }, }); defineEmits(['change']); @@ -98,6 +103,10 @@ defineEmits(['change']); } } +.check-checkbox:disabled + .check { + background: var(--shadow); +} + .check-container.minus .check-checkbox:checked + .check { background: var(--error); @@ -108,7 +117,6 @@ defineEmits(['change']); .check-label { overflow: hidden; - text-transform: capitalize; text-overflow: ellipsis; margin: 0 .5rem 0 0; } diff --git a/components/header/header.vue b/components/header/header.vue index aaf6d1d..6b91741 100644 --- a/components/header/header.vue +++ b/components/header/header.vue @@ -189,6 +189,20 @@ Settings + +
  • + + + + + + + + + + diff --git a/config/default.cjs b/config/default.cjs index 6e919fd..4f6d127 100755 --- a/config/default.cjs +++ b/config/default.cjs @@ -63,6 +63,9 @@ module.exports = { usernameLength: [2, 24], usernamePattern: /^[a-zA-Z0-9_-]+$/, }, + bans: { + defaultExpiry: 60 * 24 * 3, // in minutes, 3 days + }, apiAccess: { graphqlEnabled: true, keySize: 24, // bytes diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 7f3a78f..846f10d 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -3,7 +3,7 @@ module.exports = { apps: [ { - name: 'newtraxxx', + name: 'traxxx', script: 'npm', args: 'run server:prod', exec_mode: 'cluster', diff --git a/package-lock.json b/package-lock.json index 5068a9b..81e82b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "manticoresearch": "^4.0.0", "markdown-it": "^14.0.0", "mathjs": "^12.2.1", + "merkle-json": "^2.6.0", "mitt": "^3.0.1", "mysql": "^2.18.1", "nanoid": "^5.0.4", @@ -7324,6 +7325,17 @@ "node": ">= 8" } }, + "node_modules/merkle-json": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/merkle-json/-/merkle-json-2.6.0.tgz", + "integrity": "sha512-sJM+SNINn3/5GzFyY8MMCj+647UbDVcZv3wcynX1vv9Vhnm1gWGI5ZPOA+EYm3iInITyQHKnmcpYKqZkeY+iAQ==", + "dependencies": { + "merkle-json": "^2.1.0" + }, + "engines": { + "node": ">=6.11.0" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -15647,6 +15659,14 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, + "merkle-json": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/merkle-json/-/merkle-json-2.6.0.tgz", + "integrity": "sha512-sJM+SNINn3/5GzFyY8MMCj+647UbDVcZv3wcynX1vv9Vhnm1gWGI5ZPOA+EYm3iInITyQHKnmcpYKqZkeY+iAQ==", + "requires": { + "merkle-json": "^2.1.0" + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", diff --git a/package.json b/package.json index eb9f69c..a87ab54 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "manticoresearch": "^4.0.0", "markdown-it": "^14.0.0", "mathjs": "^12.2.1", + "merkle-json": "^2.6.0", "mitt": "^3.0.1", "mysql": "^2.18.1", "nanoid": "^5.0.4", diff --git a/pages/admin/+Page.vue b/pages/admin/+Page.vue new file mode 100644 index 0000000..e81d112 --- /dev/null +++ b/pages/admin/+Page.vue @@ -0,0 +1,9 @@ + + + diff --git a/pages/admin/+onBeforeRender.js b/pages/admin/+onBeforeRender.js new file mode 100644 index 0000000..469e839 --- /dev/null +++ b/pages/admin/+onBeforeRender.js @@ -0,0 +1,13 @@ +import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */ + +export function onBeforeRender(pageContext) { + if (pageContext.user.role === 'user') { + throw render(404); + } + + return { + pageContext: { + title: pageContext.routeParams.section, + }, + }; +} diff --git a/pages/admin/revisions/+Page.vue b/pages/admin/revisions/+Page.vue new file mode 100644 index 0000000..bb1a79f --- /dev/null +++ b/pages/admin/revisions/+Page.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/pages/admin/revisions/+onBeforeRender.js b/pages/admin/revisions/+onBeforeRender.js new file mode 100644 index 0000000..87e97fb --- /dev/null +++ b/pages/admin/revisions/+onBeforeRender.js @@ -0,0 +1,30 @@ +import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */ +import { fetchSceneRevisions } from '#/src/scenes.js'; + +export async function onBeforeRender(pageContext) { + if (!pageContext.user || pageContext.user.role === 'user') { + throw render(404); + } + + const { + revisions, + actors, + tags, + movies, + } = await fetchSceneRevisions(null, { + isFinalized: false, + limit: 50, + }, pageContext.user); + + return { + pageContext: { + title: pageContext.routeParams.section, + pageProps: { + revisions, + actors, + tags, + movies, + }, + }, + }; +} diff --git a/pages/admin/revisions/+route.js b/pages/admin/revisions/+route.js new file mode 100644 index 0000000..d9cb684 --- /dev/null +++ b/pages/admin/revisions/+route.js @@ -0,0 +1 @@ +export default '/admin/@section/*'; diff --git a/pages/scene/+Page.vue b/pages/scene/+Page.vue index b426735..efa9f96 100644 --- a/pages/scene/+Page.vue +++ b/pages/scene/+Page.vue @@ -286,7 +286,7 @@ v-if="user" class="icon-link" target="_blank" - :href="`/user/${user.username}/summaries?t=${selectedTemplate}`" + :href="`/user/${user.username}/templates?t=${selectedTemplate}`" > {{ userTemplate.name }}
  • + +
    + Edit scene + + Revisions +
    @@ -670,6 +687,13 @@ function copySummary() { } } +.scene-actions { + display: flex; + justify-content: center; + gap: 2rem; + margin-top: 1rem; +} + .icon-link { display: flex; height: auto; diff --git a/pages/scene/edit/+Page.vue b/pages/scene/edit/+Page.vue index 6baf09d..3a91bed 100644 --- a/pages/scene/edit/+Page.vue +++ b/pages/scene/edit/+Page.vue @@ -1,6 +1,40 @@ @@ -56,6 +70,7 @@ import { formatDistanceStrict } from 'date-fns'; import Stashes from '#/components/stashes/stashes.vue'; import Alerts from '#/components/alerts/alerts.vue'; import Summaries from '#/components/scenes/summaries.vue'; +import Revisions from '#/components/edit/revisions.vue'; const pageContext = inject('pageContext'); const domain = pageContext.routeParams.domain; @@ -125,8 +140,9 @@ const mockupRelease = {