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 | ||||
| 					v-close-popper | ||||
| 					icon="plus3" | ||||
| 					icon="alarm-add" | ||||
| 					@click="emit('addAlert')" | ||||
| 				/> | ||||
| 
 | ||||
| 				<a | ||||
| 					:href="`/user/${user.username}/alerts`" | ||||
| 				> | ||||
| 					<Icon | ||||
| 						icon="alarm" | ||||
| 					/> | ||||
| 				</a> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ async function createStash() { | |||
| 		name: stashName.value, | ||||
| 		public: false, | ||||
| 	}, { | ||||
| 		successFeedback: `Created stash '${stashName.value}'`, | ||||
| 		successFeedback: `Stash '${stashName.value}' created`, | ||||
| 		errorFeedback: `Failed to create stash '${stashName.value}'`, | ||||
| 		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> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<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="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 | ||||
| 			<nav | ||||
| 				v-if="profile.id === user?.id" | ||||
| 				class="profile-section" | ||||
| 				class="domains" | ||||
| 			> | ||||
| 				<div class="section-header"> | ||||
| 					<h3 class="heading">Alerts</h3> | ||||
| 				<a | ||||
| 					:href="`/user/${profile.username}/stashes`" | ||||
| 					class="domain nolink" | ||||
| 					:class="{ active: domain === 'stashes' }" | ||||
| 				>Stashes</a> | ||||
| 
 | ||||
| 					<button | ||||
| 						class="button" | ||||
| 						@click="showAlertDialog = true" | ||||
| 					> | ||||
| 						<Icon icon="plus3" /> | ||||
| 						<span class="button-label">New alert</span> | ||||
| 					</button> | ||||
| 				</div> | ||||
| 				<a | ||||
| 					:href="`/user/${profile.username}/alerts`" | ||||
| 					class="domain nolink" | ||||
| 					:class="{ active: domain === 'alerts' }" | ||||
| 				>Alerts</a> | ||||
| 			</nav> | ||||
| 
 | ||||
| 				<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> | ||||
| 			<Stashes v-if="domain === 'stashes'" /> | ||||
| 			<Alerts v-if="domain === 'alerts' && profile.id === user?.id" /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
|  | @ -217,59 +42,42 @@ | |||
| import { ref, inject } from 'vue'; | ||||
| import { formatDistanceStrict } from 'date-fns'; | ||||
| 
 | ||||
| import { get, del } from '#/src/api.js'; | ||||
| 
 | ||||
| import StashTile from '#/components/stashes/tile.vue'; | ||||
| import StashDialog from '#/components/stashes/create.vue'; | ||||
| import AlertDialog from '#/components/alerts/create.vue'; | ||||
| import Stashes from '#/components/stashes/stashes.vue'; | ||||
| import Alerts from '#/components/alerts/alerts.vue'; | ||||
| 
 | ||||
| const pageContext = inject('pageContext'); | ||||
| const domain = pageContext.routeParams.domain; | ||||
| const user = pageContext.user; | ||||
| 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> | ||||
| 
 | ||||
| <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> | ||||
| .page { | ||||
| 	display: flex; | ||||
|  | @ -288,6 +96,7 @@ async function removeAlert(alert) { | |||
| 	justify-content: space-between; | ||||
| 	align-items: center; | ||||
| 	padding: .5rem 1rem; | ||||
| 	margin-bottom: .5rem; | ||||
|     color: var(--highlight-strong-30); | ||||
|     background: var(--shadow-strong-30); | ||||
| 	border-radius: 0 0 .5rem .5rem; | ||||
|  | @ -323,170 +132,28 @@ async function removeAlert(alert) { | |||
| 	margin-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .section-header { | ||||
| .domains { | ||||
| 	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; | ||||
| 	margin-right: .75rem; | ||||
| 
 | ||||
| 	.icon { | ||||
| 		fill: var(--glass-weak-40); | ||||
| 	} | ||||
| 
 | ||||
| 	.icon.trigger { | ||||
| 		fill: var(--glass-weak-10); | ||||
| 	} | ||||
| 	padding: .5rem 0; | ||||
| 	margin-top: .5rem; | ||||
| } | ||||
| 
 | ||||
| .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; | ||||
| .domain { | ||||
| 	color: var(--glass-strong-20); | ||||
| 	background: var(--background-dark-20); | ||||
| 	padding: .5rem 1rem; | ||||
| 	border-radius: 1rem; | ||||
| 	font-size: .9rem; | ||||
| 	color: var(--glass-weak-10); | ||||
| 	font-weight: bold; | ||||
| 
 | ||||
| 	.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; | ||||
| 	&.active { | ||||
| 		background: var(--primary); | ||||
| 		color: var(--text-light); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @media(--small-30) { | ||||
| 	.section-header { | ||||
| 		padding: .5rem .5rem .5rem .5rem; | ||||
| 	} | ||||
| 
 | ||||
| 	.stashes { | ||||
| 		padding: 0 .5rem 1rem .5rem; | ||||
| 	} | ||||
| 
 | ||||
| 	.age .icon { | ||||
| 		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