Split profile sections into different pages.
This commit is contained in:
parent
8fba01dbdd
commit
2cf7f2a692
|
@ -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="M15 4h-14l1-2h5.5l0.5 1h6.5zM0 5l1 10h14l1-10h-16zM6.719 13.352l-3.207-3.707 0.914-0.914 2.293 1.793 4.293-3.793 0.914 0.914-5.207 5.707z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 294 B |
|
@ -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="M15 4h-14l1-2h5.5l0.5 1h6.5zM7.813 12.5c0 0.918 0.266 1.776 0.724 2.5h-7.537l-1-10h16l-0.399 3.988c-0.827-0.731-1.913-1.176-3.101-1.176-2.585 0-4.688 2.103-4.688 4.687zM12.5 9c-1.933 0-3.5 1.567-3.5 3.5s1.567 3.5 3.5 3.5 3.5-1.567 3.5-3.5-1.567-3.5-3.5-3.5zM10 13v-1h5v1h-5z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 431 B |
|
@ -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="M15 4h-14l1-2h5.5l0.5 1h6.5zM0 5l1 10h14l1-10h-16zM11 11h-6v-2h6v2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 224 B |
|
@ -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="M15 4h-14l1-2h5.5l0.5 1h6.5zM7.813 12.5c0 0.918 0.266 1.776 0.724 2.5h-7.537l-1-10h16l-0.399 3.988c-0.827-0.731-1.913-1.176-3.101-1.176-2.585 0-4.688 2.103-4.688 4.687zM12.5 9c-1.933 0-3.5 1.567-3.5 3.5s1.567 3.5 3.5 3.5 3.5-1.567 3.5-3.5-1.567-3.5-3.5-3.5zM13 13v2h-1v-2h-2v-1h2v-2h1v2h2v1h-2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 451 B |
|
@ -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="M15 4h-14l1-2h5.5l0.5 1h6.5zM0 5l1 10h14l1-10h-16zM11 11h-2v2h-2v-2h-2v-2h2v-2h2v2h2v2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 244 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<!-- 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-6.5l-0.5-1h-5.5l-1 2h14z"></path>
|
||||||
|
<path d="M14.13 11h1.17l0.7-7h-16l1 10h7.564c-1.639-0.59-2.814-2.16-2.814-4 0-2.343 1.907-4.25 4.25-4.25s4.25 1.907 4.25 4.25c0 0.339-0.041 0.674-0.12 1z"></path>
|
||||||
|
<path d="M15.672 14.277l-3.101-2.73c0.273-0.452 0.43-0.981 0.43-1.548 0-1.657-1.343-3-3-3s-3 1.343-3 3 1.343 3 3 3c0.566 0 1.096-0.157 1.548-0.43l2.73 3.101c0.359 0.417 0.971 0.44 1.359 0.051l0.086-0.086c0.389-0.389 0.366-1.001-0.051-1.359zM10 11.938c-1.070 0-1.938-0.867-1.938-1.938s0.867-1.938 1.938-1.938 1.938 0.867 1.938 1.938-0.867 1.938-1.938 1.938z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 719 B |
|
@ -0,0 +1,344 @@
|
||||||
|
<template>
|
||||||
|
<section class="profile-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h3 class="heading">Alerts</h3>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
@click="showAlertDialog = true"
|
||||||
|
>
|
||||||
|
<Icon icon="alarm-add" />
|
||||||
|
<span class="button-label">New alert</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="alerts nolist">
|
||||||
|
<li
|
||||||
|
v-for="alert in alerts"
|
||||||
|
:key="`alert-${alert.id}`"
|
||||||
|
class="alert"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="alert-details"
|
||||||
|
:class="{ and: alert.and.fields, or: !alert.and.fields }"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="alert.tags.length > 0"
|
||||||
|
class="alert-detail alert-tags"
|
||||||
|
:class="{ and: alert.and.tags, or: !alert.and.tags }"
|
||||||
|
>
|
||||||
|
<span class="alert-values">
|
||||||
|
<span
|
||||||
|
v-for="tag in alert.tags"
|
||||||
|
:key="`tag-${alert.id}-${tag.id}`"
|
||||||
|
class="alert-key"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="`/tag/${tag.slug}`"
|
||||||
|
class="alert-value link"
|
||||||
|
>{{ tag.name }}</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="alert.actors.length > 0"
|
||||||
|
class="alert-detail alert-actors"
|
||||||
|
:class="{ and: alert.and.actors, or: !alert.and.actors }"
|
||||||
|
>
|
||||||
|
<span class="alert-values">with
|
||||||
|
<span
|
||||||
|
v-for="actor in alert.actors"
|
||||||
|
:key="`actor-${alert.id}-${actor.id}`"
|
||||||
|
class="alert-key"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="`/actor/${actor.id}/${actor.slug}`"
|
||||||
|
class="alert-value link"
|
||||||
|
>{{ actor.name }}</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="alert.entities.length > 0"
|
||||||
|
class="alert-detail alert-entities or"
|
||||||
|
>
|
||||||
|
<span class="alert-values">for
|
||||||
|
<span
|
||||||
|
v-for="entity in alert.entities"
|
||||||
|
:key="`entity-${alert.id}-${entity.id}`"
|
||||||
|
class="alert-key"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="`/${entity.type}/${entity.slug}`"
|
||||||
|
class="alert-value link"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="entity.type === 'network'"
|
||||||
|
icon="device_hub"
|
||||||
|
/>{{ entity.name }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="alert.matches.length > 0"
|
||||||
|
class="alert-detail alert-matches"
|
||||||
|
:class="{ and: alert.and.matches, or: !alert.and.matches }"
|
||||||
|
>
|
||||||
|
<span class="alert-values">matching
|
||||||
|
<span
|
||||||
|
v-for="match in alert.matches"
|
||||||
|
:key="`match-${alert.id}-${match.id}`"
|
||||||
|
class="alert-key"
|
||||||
|
>
|
||||||
|
<span class="alert-value">{{ match.property }}:
|
||||||
|
<span
|
||||||
|
class="alert-regex"
|
||||||
|
title="If your original expression was not a /regular expression/, it was converted, and new characters may have been added for syntactical purposes. These characters do not alter the function of the expression; they ensure it."
|
||||||
|
>{{ match.expression }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert-meta">
|
||||||
|
<div class="alert-triggers">
|
||||||
|
<Icon
|
||||||
|
v-tooltip="alert.notify ? 'Notify in traxxx' : undefined"
|
||||||
|
icon="bell2"
|
||||||
|
:class="{ trigger: alert.notify }"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-if="alert.stashes.some((stash) => !stash.isPrimary)"
|
||||||
|
v-tooltip="`Add to ${alert.stashes.map((stash) => stash.name).join(', ')}`"
|
||||||
|
icon="folder-heart"
|
||||||
|
class="trigger"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
v-tooltip="alert.stashes.length > 0 ? 'Add to Favorites' : undefined"
|
||||||
|
icon="heart7"
|
||||||
|
:class="{ trigger: alert.stashes.length > 0 }"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<Icon
|
||||||
|
icon="envelop5"
|
||||||
|
title="E-mail me"
|
||||||
|
:class="{ trigger: alert.email }"
|
||||||
|
/>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert-actions">
|
||||||
|
<span
|
||||||
|
class="alert-id"
|
||||||
|
title="Alert ID"
|
||||||
|
>#{{ alert.id }}</span>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon="bin"
|
||||||
|
@click="removeAlert(alert)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<AlertDialog
|
||||||
|
v-if="showAlertDialog"
|
||||||
|
@close="showAlertDialog = false; reloadAlerts();"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
|
|
||||||
|
import AlertDialog from '#/components/alerts/create.vue';
|
||||||
|
|
||||||
|
import { get, del } from '#/src/api.js';
|
||||||
|
|
||||||
|
const pageContext = inject('pageContext');
|
||||||
|
|
||||||
|
const alerts = ref(pageContext.pageProps.alerts);
|
||||||
|
const showAlertDialog = ref(false);
|
||||||
|
const done = ref(true);
|
||||||
|
|
||||||
|
async function reloadAlerts() {
|
||||||
|
alerts.value = await get('/alerts');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAlert(alert) {
|
||||||
|
if (done.value === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify(alert, null, 4));
|
||||||
|
if (!confirm(`Are you sure you want to remove alert #${alert.id}?`)) { // eslint-disable-line no-restricted-globals, no-alert
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
done.value = false;
|
||||||
|
|
||||||
|
const alertLabel = [
|
||||||
|
...alert.actors.map((actor) => actor.name),
|
||||||
|
...alert.tags.map((tag) => tag.name),
|
||||||
|
...alert.entities.map((entity) => entity.name),
|
||||||
|
...alert.matches.map((match) => match.expression),
|
||||||
|
].filter(Boolean).join(', ');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await del(`/alerts/${alert.id}`, {
|
||||||
|
undoFeedback: `Removed alert for '${alertLabel}'`,
|
||||||
|
errorFeedback: `Failed to remove alert for '${alertLabel}'`,
|
||||||
|
appendErrorMessage: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await reloadAlerts();
|
||||||
|
} finally {
|
||||||
|
done.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alerts {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 0 0 0 .5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
border-bottom: solid 1px var(--glass-weak-40);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--glass-weak-30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-triggers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-right: .75rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--glass-weak-40);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.trigger {
|
||||||
|
fill: var(--glass-weak-10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-details {
|
||||||
|
padding: .25rem 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
line-height: 2.5;
|
||||||
|
color: var(--glass);
|
||||||
|
|
||||||
|
&.and .alert-detail:not(:last-child):after {
|
||||||
|
content: ' and ';
|
||||||
|
}
|
||||||
|
|
||||||
|
&.or .alert-detail:not(:last-child):after {
|
||||||
|
content: ' or ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-value {
|
||||||
|
color: var(--text);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: .25rem;
|
||||||
|
fill: var(--glass);
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-values {
|
||||||
|
padding: .5rem .5rem;
|
||||||
|
border-bottom: solid 1px var(--primary-light-20);
|
||||||
|
border-radius: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-detail {
|
||||||
|
&.and .alert-key:not(:last-child):after {
|
||||||
|
content: ' and ';
|
||||||
|
}
|
||||||
|
|
||||||
|
&.or .alert-key:not(:last-child):after {
|
||||||
|
content: ' or ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-regex {
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '╱';
|
||||||
|
padding: 0 .1rem;
|
||||||
|
color: var(--primary-light-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-meta {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: .9rem;
|
||||||
|
color: var(--glass-weak-10);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 .75rem;
|
||||||
|
fill: var(--glass);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(--compact) {
|
||||||
|
.profile-header {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
padding: .5rem 1rem .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stashes {
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 0 .5rem 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(--small-20) {
|
||||||
|
.alert {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-meta {
|
||||||
|
padding: .5rem 0 .75rem 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -13,9 +13,17 @@
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
v-close-popper
|
v-close-popper
|
||||||
icon="plus3"
|
icon="alarm-add"
|
||||||
@click="emit('addAlert')"
|
@click="emit('addAlert')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`/user/${user.username}/alerts`"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="alarm"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ async function createStash() {
|
||||||
name: stashName.value,
|
name: stashName.value,
|
||||||
public: false,
|
public: false,
|
||||||
}, {
|
}, {
|
||||||
successFeedback: `Created stash '${stashName.value}'`,
|
successFeedback: `Stash '${stashName.value}' created`,
|
||||||
errorFeedback: `Failed to create stash '${stashName.value}'`,
|
errorFeedback: `Failed to create stash '${stashName.value}'`,
|
||||||
appendErrorMessage: true,
|
appendErrorMessage: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<section class="profile-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h3 class="heading">Stashes</h3>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="profile.id === user?.id"
|
||||||
|
class="button"
|
||||||
|
@click="showStashDialog = true"
|
||||||
|
>
|
||||||
|
<Icon icon="folder-plus2" />
|
||||||
|
<span class="button-label">New stash</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StashDialog
|
||||||
|
v-if="showStashDialog"
|
||||||
|
@created="showStashDialog = false; reloadStashes();"
|
||||||
|
@close="showStashDialog = false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ul class="stashes nolist">
|
||||||
|
<li
|
||||||
|
v-for="stash in stashes"
|
||||||
|
:key="`stash-${stash.id}`"
|
||||||
|
>
|
||||||
|
<StashTile
|
||||||
|
:stash="stash"
|
||||||
|
:profile="profile"
|
||||||
|
@reload="reloadStashes"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
|
|
||||||
|
import StashTile from '#/components/stashes/tile.vue';
|
||||||
|
import StashDialog from '#/components/stashes/create.vue';
|
||||||
|
|
||||||
|
import { get } from '#/src/api.js';
|
||||||
|
|
||||||
|
const pageContext = inject('pageContext');
|
||||||
|
|
||||||
|
const user = pageContext.user;
|
||||||
|
const stashes = ref(pageContext.pageProps.stashes);
|
||||||
|
const profile = ref(pageContext.pageProps.profile);
|
||||||
|
|
||||||
|
const showStashDialog = ref(false);
|
||||||
|
|
||||||
|
async function reloadStashes() {
|
||||||
|
// profile.value = await get(`/users/${profile.value.id}`);
|
||||||
|
stashes.value = await get(`/users/${profile.value.id}/stashes`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.stashes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0 .5rem 1rem .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(--small-30) {
|
||||||
|
.stashes {
|
||||||
|
padding: 0 .5rem 1rem .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,200 +15,25 @@
|
||||||
<span class="age">{{ formatDistanceStrict(Date.now(), profile.createdAt) }}</span>
|
<span class="age">{{ formatDistanceStrict(Date.now(), profile.createdAt) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="profile-section">
|
<nav
|
||||||
<div class="section-header">
|
|
||||||
<h3 class="heading">Stashes</h3>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="profile.id === user?.id"
|
|
||||||
class="button"
|
|
||||||
@click="showStashDialog = true"
|
|
||||||
>
|
|
||||||
<Icon icon="plus3" />
|
|
||||||
<span class="button-label">New stash</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StashDialog
|
|
||||||
v-if="showStashDialog"
|
|
||||||
@created="showStashDialog = false; reloadStashes();"
|
|
||||||
@close="showStashDialog = false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ul class="stashes nolist">
|
|
||||||
<li
|
|
||||||
v-for="stash in stashes"
|
|
||||||
:key="`stash-${stash.id}`"
|
|
||||||
>
|
|
||||||
<StashTile
|
|
||||||
:stash="stash"
|
|
||||||
:profile="profile"
|
|
||||||
@reload="reloadStashes"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section
|
|
||||||
v-if="profile.id === user?.id"
|
v-if="profile.id === user?.id"
|
||||||
class="profile-section"
|
class="domains"
|
||||||
>
|
>
|
||||||
<div class="section-header">
|
<a
|
||||||
<h3 class="heading">Alerts</h3>
|
:href="`/user/${profile.username}/stashes`"
|
||||||
|
class="domain nolink"
|
||||||
|
:class="{ active: domain === 'stashes' }"
|
||||||
|
>Stashes</a>
|
||||||
|
|
||||||
<button
|
<a
|
||||||
class="button"
|
:href="`/user/${profile.username}/alerts`"
|
||||||
@click="showAlertDialog = true"
|
class="domain nolink"
|
||||||
>
|
:class="{ active: domain === 'alerts' }"
|
||||||
<Icon icon="plus3" />
|
>Alerts</a>
|
||||||
<span class="button-label">New alert</span>
|
</nav>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="alerts nolist">
|
<Stashes v-if="domain === 'stashes'" />
|
||||||
<li
|
<Alerts v-if="domain === 'alerts' && profile.id === user?.id" />
|
||||||
v-for="alert in alerts"
|
|
||||||
:key="`alert-${alert.id}`"
|
|
||||||
class="alert"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="alert-details"
|
|
||||||
:class="{ and: alert.and.fields, or: !alert.and.fields }"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="alert.tags.length > 0"
|
|
||||||
class="alert-detail alert-tags"
|
|
||||||
:class="{ and: alert.and.tags, or: !alert.and.tags }"
|
|
||||||
>
|
|
||||||
<span class="alert-values">
|
|
||||||
<span
|
|
||||||
v-for="tag in alert.tags"
|
|
||||||
:key="`tag-${alert.id}-${tag.id}`"
|
|
||||||
class="alert-key"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
:href="`/tag/${tag.slug}`"
|
|
||||||
class="alert-value link"
|
|
||||||
>{{ tag.name }}</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="alert.actors.length > 0"
|
|
||||||
class="alert-detail alert-actors"
|
|
||||||
:class="{ and: alert.and.actors, or: !alert.and.actors }"
|
|
||||||
>
|
|
||||||
<span class="alert-values">with
|
|
||||||
<span
|
|
||||||
v-for="actor in alert.actors"
|
|
||||||
:key="`actor-${alert.id}-${actor.id}`"
|
|
||||||
class="alert-key"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
:href="`/actor/${actor.id}/${actor.slug}`"
|
|
||||||
class="alert-value link"
|
|
||||||
>{{ actor.name }}</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="alert.entities.length > 0"
|
|
||||||
class="alert-detail alert-entities or"
|
|
||||||
>
|
|
||||||
<span class="alert-values">for
|
|
||||||
<span
|
|
||||||
v-for="entity in alert.entities"
|
|
||||||
:key="`entity-${alert.id}-${entity.id}`"
|
|
||||||
class="alert-key"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
:href="`/${entity.type}/${entity.slug}`"
|
|
||||||
class="alert-value link"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
v-if="entity.type === 'network'"
|
|
||||||
icon="device_hub"
|
|
||||||
/>{{ entity.name }}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="alert.matches.length > 0"
|
|
||||||
class="alert-detail alert-matches"
|
|
||||||
:class="{ and: alert.and.matches, or: !alert.and.matches }"
|
|
||||||
>
|
|
||||||
<span class="alert-values">matching
|
|
||||||
<span
|
|
||||||
v-for="match in alert.matches"
|
|
||||||
:key="`match-${alert.id}-${match.id}`"
|
|
||||||
class="alert-key"
|
|
||||||
>
|
|
||||||
<span class="alert-value">{{ match.property }}:
|
|
||||||
<span
|
|
||||||
class="alert-regex"
|
|
||||||
title="If your original expression was not a /regular expression/, it was converted, and new characters may have been added for syntactical purposes. These characters do not alter the function of the expression; they ensure it."
|
|
||||||
>{{ match.expression }}</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert-meta">
|
|
||||||
<div class="alert-triggers">
|
|
||||||
<Icon
|
|
||||||
v-tooltip="alert.notify ? 'Notify in traxxx' : undefined"
|
|
||||||
icon="bell2"
|
|
||||||
:class="{ trigger: alert.notify }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
v-if="alert.stashes.some((stash) => !stash.isPrimary)"
|
|
||||||
v-tooltip="`Add to ${alert.stashes.map((stash) => stash.name).join(', ')}`"
|
|
||||||
icon="folder-heart"
|
|
||||||
class="trigger"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
v-else
|
|
||||||
v-tooltip="alert.stashes.length > 0 ? 'Add to Favorites' : undefined"
|
|
||||||
icon="heart7"
|
|
||||||
:class="{ trigger: alert.stashes.length > 0 }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<Icon
|
|
||||||
icon="envelop5"
|
|
||||||
title="E-mail me"
|
|
||||||
:class="{ trigger: alert.email }"
|
|
||||||
/>
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert-actions">
|
|
||||||
<span
|
|
||||||
class="alert-id"
|
|
||||||
title="Alert ID"
|
|
||||||
>#{{ alert.id }}</span>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
icon="bin"
|
|
||||||
@click="removeAlert(alert)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<AlertDialog
|
|
||||||
v-if="showAlertDialog"
|
|
||||||
@close="showAlertDialog = false; reloadAlerts();"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -217,59 +42,42 @@
|
||||||
import { ref, inject } from 'vue';
|
import { ref, inject } from 'vue';
|
||||||
import { formatDistanceStrict } from 'date-fns';
|
import { formatDistanceStrict } from 'date-fns';
|
||||||
|
|
||||||
import { get, del } from '#/src/api.js';
|
import Stashes from '#/components/stashes/stashes.vue';
|
||||||
|
import Alerts from '#/components/alerts/alerts.vue';
|
||||||
import StashTile from '#/components/stashes/tile.vue';
|
|
||||||
import StashDialog from '#/components/stashes/create.vue';
|
|
||||||
import AlertDialog from '#/components/alerts/create.vue';
|
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
|
const domain = pageContext.routeParams.domain;
|
||||||
const user = pageContext.user;
|
const user = pageContext.user;
|
||||||
const profile = ref(pageContext.pageProps.profile);
|
const profile = ref(pageContext.pageProps.profile);
|
||||||
const stashes = ref(pageContext.pageProps.stashes);
|
|
||||||
const alerts = ref(pageContext.pageProps.alerts);
|
|
||||||
|
|
||||||
const done = ref(true);
|
|
||||||
const showStashDialog = ref(false);
|
|
||||||
const showAlertDialog = ref(false);
|
|
||||||
|
|
||||||
async function reloadStashes() {
|
|
||||||
// profile.value = await get(`/users/${profile.value.id}`);
|
|
||||||
stashes.value = await get(`/users/${profile.value.id}/stashes`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reloadAlerts() {
|
|
||||||
alerts.value = await get('/alerts');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeAlert(alert) {
|
|
||||||
if (done.value === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
done.value = false;
|
|
||||||
|
|
||||||
const alertLabel = [
|
|
||||||
...alert.actors.map((actor) => actor.name),
|
|
||||||
...alert.tags.map((tag) => tag.name),
|
|
||||||
...alert.entities.map((entity) => entity.name),
|
|
||||||
...alert.matches.map((match) => match.expression),
|
|
||||||
].filter(Boolean).join(', ');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await del(`/alerts/${alert.id}`, {
|
|
||||||
undoFeedback: `Removed alert for '${alertLabel}'`,
|
|
||||||
errorFeedback: `Failed to remove alert for '${alertLabel}'`,
|
|
||||||
appendErrorMessage: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await reloadAlerts();
|
|
||||||
} finally {
|
|
||||||
done.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.profile-section {
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: .5rem .5rem .5rem .5rem;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(--small-30) {
|
||||||
|
.profile-section .section-header {
|
||||||
|
padding: .5rem .5rem .5rem .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page {
|
.page {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -288,6 +96,7 @@ async function removeAlert(alert) {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
color: var(--highlight-strong-30);
|
color: var(--highlight-strong-30);
|
||||||
background: var(--shadow-strong-30);
|
background: var(--shadow-strong-30);
|
||||||
border-radius: 0 0 .5rem .5rem;
|
border-radius: 0 0 .5rem .5rem;
|
||||||
|
@ -323,170 +132,28 @@ async function removeAlert(alert) {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.domains {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: .5rem .5rem .5rem .5rem;
|
|
||||||
|
|
||||||
.button {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stashes {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0 .5rem 1rem .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alerts {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 0 0 0 .5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
border-bottom: solid 1px var(--glass-weak-40);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--glass-weak-30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-triggers {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
margin-right: .75rem;
|
padding: .5rem 0;
|
||||||
|
margin-top: .5rem;
|
||||||
.icon {
|
|
||||||
fill: var(--glass-weak-40);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.trigger {
|
|
||||||
fill: var(--glass-weak-10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-details {
|
.domain {
|
||||||
padding: .25rem 0;
|
color: var(--glass-strong-20);
|
||||||
flex-grow: 1;
|
background: var(--background-dark-20);
|
||||||
line-height: 2.5;
|
padding: .5rem 1rem;
|
||||||
color: var(--glass);
|
border-radius: 1rem;
|
||||||
|
|
||||||
&.and .alert-detail:not(:last-child):after {
|
|
||||||
content: ' and ';
|
|
||||||
}
|
|
||||||
|
|
||||||
&.or .alert-detail:not(:last-child):after {
|
|
||||||
content: ' or ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-value {
|
|
||||||
color: var(--text);
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-right: .25rem;
|
|
||||||
fill: var(--glass);
|
|
||||||
transform: translateY(2px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-values {
|
|
||||||
padding: .5rem .5rem;
|
|
||||||
border-bottom: solid 1px var(--primary-light-20);
|
|
||||||
border-radius: .3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-detail {
|
|
||||||
&.and .alert-key:not(:last-child):after {
|
|
||||||
content: ' and ';
|
|
||||||
}
|
|
||||||
|
|
||||||
&.or .alert-key:not(:last-child):after {
|
|
||||||
content: ' or ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-regex {
|
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
content: '╱';
|
|
||||||
padding: 0 .1rem;
|
|
||||||
color: var(--primary-light-20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-meta {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
color: var(--glass-weak-10);
|
font-weight: bold;
|
||||||
|
|
||||||
.icon {
|
&.active {
|
||||||
height: 100%;
|
background: var(--primary);
|
||||||
padding: 0 .75rem;
|
color: var(--text-light);
|
||||||
fill: var(--glass);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(--compact) {
|
|
||||||
.profile-header {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
padding: .5rem 1rem .5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stashes {
|
|
||||||
padding: 0 1rem 1rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 0 .5rem 0 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(--small-20) {
|
|
||||||
.alert {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-meta {
|
|
||||||
padding: .5rem 0 .75rem 0;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(--small-30) {
|
@media(--small-30) {
|
||||||
.section-header {
|
|
||||||
padding: .5rem .5rem .5rem .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stashes {
|
|
||||||
padding: 0 .5rem 1rem .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.age .icon {
|
.age .icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,25 @@
|
||||||
export default '/user/@username';
|
import { redirect } from 'vike/abort'; /* eslint-disable-line import/extensions */
|
||||||
|
import { match } from 'path-to-regexp';
|
||||||
|
// import { resolveRoute } from 'vike/routing'; // eslint-disable-line import/extensions
|
||||||
|
|
||||||
|
const path = '/user/:username/:domain?';
|
||||||
|
const urlMatch = match(path, { decode: decodeURIComponent });
|
||||||
|
|
||||||
|
export default (pageContext) => {
|
||||||
|
const matched = urlMatch(pageContext.urlPathname);
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
if (![undefined, 'stashes'].includes(matched.params.domain) && pageContext.user?.username !== matched.params.username) {
|
||||||
|
throw redirect(`/user/${matched.params.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
routeParams: {
|
||||||
|
username: matched.params.username,
|
||||||
|
domain: matched.params.domain || 'stashes',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue