Added socials.
This commit is contained in:
109
components/edit/revision-socials.vue
Normal file
109
components/edit/revision-socials.vue
Normal 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>
|
||||
</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>
|
||||
@@ -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
289
components/edit/socials.vue
Normal 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>
|
||||
Reference in New Issue
Block a user