Added socials.

This commit is contained in:
2024-11-04 02:36:30 +01:00
parent 208f1dfde4
commit de60b67cb9
20 changed files with 945 additions and 142 deletions

View File

@@ -0,0 +1,109 @@
<template>
<ul class="socials nolist">
<li
v-for="social in socials"
:key="`social-${rev.id}-${index}-${social.id}`"
class="delta-item"
:class="{ modified: social.modified }"
>
<a
:href="getUrl(social)"
target="_blank"
class="link"
>
<Icon
v-if="social.platform && env.socials.urls[social.platform]"
:icon="iconMap[social.platform] || social.platform"
:title="social.platform"
:class="`icon-social icon-${social.platform}`"
/>
<Icon
v-else-if="social.platform"
icon="bubbles10"
:title="social.platform"
:class="`icon-social icon-${social.platform} icon-generic`"
/>
<Icon
v-else-if="social.url"
icon="sphere"
:title="social.platform"
:class="`icon-social icon-${social.platform} icon-generic`"
/>
{{ social.handle || social.url }}
</a>
</li>&nbsp;
</ul>
</template>
<script setup>
import { inject } from 'vue';
import formatTemplate from 'template-format';
const pageContext = inject('pageContext');
const { env } = pageContext;
defineProps({
rev: {
type: Object,
default: null,
},
index: {
type: Number,
default: null,
},
socials: {
type: Array,
default: () => [],
},
});
const iconMap = {
twitter: 'twitter-x',
};
function getUrl(social) {
if (social.url) {
return social.url;
}
if (env.socials.urls[social.platform]) {
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
}
return null;
}
</script>
<style scoped>
.socials {
display: flex;
flex-wrap: wrap;
gap: .5rem;
}
.delta-item .link {
display: inline-flex;
align-items: center;
padding: .25rem .5rem;
border-radius: .25rem;
box-shadow: 0 0 3px var(--shadow-weak-20);
color: inherit;
.icon {
margin-right: .5rem;
height: auto;
}
.icon-generic {
fill: var(--glass-strong-10);
}
}
.delta-item.modified {
font-weight: bold;
}
</style>

View File

@@ -104,8 +104,15 @@
<div class="delta-deltas">
<span class="delta-from delta-value">
<Socials
v-if="delta.key === 'socials'"
:rev="rev"
:index="index"
:socials="rev.base[delta.key]"
/>
<ul
v-if="Array.isArray(rev.base[delta.key])"
v-else-if="Array.isArray(rev.base[delta.key])"
class="nolist"
>[
<li
@@ -129,8 +136,15 @@
<span class="delta-arrow"></span>
<span class="delta-to delta-value">
<Socials
v-if="delta.key === 'socials'"
:rev="rev"
:index="index"
:socials="delta.value"
/>
<ul
v-if="Array.isArray(delta.value)"
v-else-if="Array.isArray(delta.value)"
class="nolist"
>[
<li
@@ -218,6 +232,7 @@ import { ref, computed, inject } from 'vue';
import { format } from 'date-fns';
import Avatar from '#/components/edit/avatar.vue';
import Socials from '#/components/edit/revision-socials.vue';
import Checkbox from '#/components/form/checkbox.vue';
import { get, post } from '#/src/api.js';
@@ -277,6 +292,14 @@ const curatedRevisions = computed(() => revisions.value.map((revision) => {
}))];
}
if (key === 'socials') {
// new socials don't have IDs yet, so we need to compare the values
return [key, value.map((item) => ({
...item,
modified: revision.deltas.some((delta) => delta.key === key && !delta.value.some((deltaItem) => deltaItem.url === item.url || `${deltaItem.platform}:${deltaItem.handle}` === `${item.platform}:${item.handle}`)),
}))];
}
if (dateKeys.includes(key)) {
return [key, new Date(value)];
}
@@ -300,6 +323,17 @@ const curatedRevisions = computed(() => revisions.value.map((revision) => {
};
}
if (delta.key === 'socials') {
// new socials don't have IDs yet, so we need to compare the values
return {
...delta,
value: delta.value.map((social) => ({
...social,
modified: !revision.base[delta.key].some((baseItem) => baseItem.url === social.url || `${baseItem.platform}:${baseItem.handle}` === `${social.platform}:${social.handle}`),
})),
};
}
if (dateKeys.includes(delta.key)) {
return {
...delta,

289
components/edit/socials.vue Normal file
View File

@@ -0,0 +1,289 @@
<template>
<ul
class="list nolist"
:class="{ disabled: !editing.has('socials') }"
>
<li
v-for="(social, index) in socials"
:key="`socials-${social}`"
class="list-item"
:class="{ deleted: !socials.some((listItem) => listItem.social === social.social || listItem.url === social.url) }"
>
<a
:href="getUrl(social)"
target="_blank"
class="link"
>
<Icon
v-if="social.platform && env.socials.urls[social.platform]"
:icon="iconMap[social.platform] || social.platform"
:title="social.platform"
:class="`icon-social icon-${social.platform}`"
/>
<Icon
v-else-if="social.platform"
icon="bubbles10"
:title="social.platform"
:class="`icon-social icon-${social.platform} icon-generic`"
/>
<Icon
v-else-if="social.url"
icon="sphere"
:title="social.platform"
:class="`icon-social icon-${social.platform} icon-generic`"
/>
{{ social.handle || social.url }}
</a>
<Icon
v-if="!socials.some((listItem) => listItem.social === social.social || listItem.url === social.url)"
icon="checkmark"
class="add noselect"
@click="socials = socials.concat(social)"
/>
<Icon
v-else
icon="cross2"
class="remove noselect"
@click="socials = socials.filter((listItem, listIndex) => listIndex !== index)"
/>
</li>
<li class="list-new">
<VDropdown>
<Icon
icon="plus2"
class="add noselect"
/>
<template #popper>
<form
class="new"
@submit.prevent="addSocial"
>
<div class="new-section">
<input
v-model="platform"
list="platforms"
class="input"
placeholder="Platform"
pattern="[a-z]+"
>
<datalist id="platforms">
<option value="onlyfans">OnlyFans</option>
<option value="twitter">Twitter/X</option>
<option value="instagram">Instagram</option>
<option value="pornhub">PornHub</option>
<option value="linktree">Linktree</option>
<option value="fansly">Fansly</option>
<option value="loyalfans">LoyalFans</option>
<option value="manyvids">ManyVids</option>
<option value="cashapp">Cash App</option>
</datalist>
<input
v-model="handle"
class="input"
placeholder="Handle"
pattern="[\w-]+"
:disabled="!!url"
>
</div>
<div class="new-section">
OR<input
v-model="url"
class="input"
placeholder="Website URL"
:disabled="!!handle"
>
<button
class="button"
>Add</button>
</div>
</form>
</template>
</VDropdown>
</li>
</ul>
</template>
<script setup>
import {
ref,
watch,
inject,
} from 'vue';
import formatTemplate from 'template-format';
const pageContext = inject('pageContext');
const { env } = pageContext;
const props = defineProps({
edits: {
type: Object,
default: null,
},
editing: {
type: Set,
default: null,
},
});
const emit = defineEmits(['socials']);
const socials = ref(props.edits.socials);
const platform = ref('');
const handle = ref('');
const url = ref('');
watch(socials, () => emit('socials', socials));
const iconMap = {
twitter: 'twitter-x',
};
function addSocial() {
if (!handle.value && !url.value) {
return;
}
if (handle.value && !platform.value) {
return;
}
socials.value = socials.value.concat({
platform: platform.value || null,
handle: handle.value || null,
url: url.value || null,
});
emit('socials', socials.value);
platform.value = 'onlyfans';
handle.value = '';
url.value = '';
}
function getUrl(social) {
if (social.url) {
return url;
}
if (env.socials.urls[social.platform]) {
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
}
return null;
}
</script>
<style scoped>
.list {
display: flex;
gap: .5rem;
&.disabled {
opacity: .5;
}
}
.list-item {
.icon-social {
margin-right: .5rem;
}
.icon-generic {
fill: var(--glass-strong-20);
}
&.deleted {
color: var(--glass);
text-decoration: line-through;
.icon.icon-social {
fill: var(--glass-weak-10);
}
}
}
.list-item,
.list-new {
display: inline-flex;
align-items: stretch;
border-radius: .25rem;
box-shadow: 0 0 3px var(--shadow-weak-30);
background: var(--background);
.link {
padding: .25rem 0 .25rem .5rem;
display: inline-flex;
align-items: center;
color: inherit;
}
.icon {
height: auto;
}
.add,
.remove {
padding: 0 .3rem;
margin-left: .5rem;
border-radius: .25rem;
&:hover {
fill: var(--text-light);
cursor: pointer;
}
}
.add {
fill: var(--success);
&:hover {
background: var(--success);
}
}
.remove {
fill: var(--error);
&:hover {
background: var(--error);
}
}
}
.list-new .add {
padding: .25rem .5rem;
background: var(--background);
margin: 0;
}
.new {
padding: .25rem;
}
.new-section {
display: flex;
align-items: center;
gap: .5rem;
padding: .25rem;
.input {
flex-grow: 1;
&:disabled {
opacity: .5;
}
}
}
</style>