aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/AniList/activity.ts21
-rw-r--r--src/lib/Locale/english.ts7
-rw-r--r--src/lib/Locale/japanese.ts7
-rw-r--r--src/lib/Locale/layout.ts7
-rw-r--r--src/routes/user/[user]/badges/+page.svelte153
-rw-r--r--src/styles/popup.scss7
6 files changed, 199 insertions, 3 deletions
diff --git a/src/lib/AniList/activity.ts b/src/lib/AniList/activity.ts
index 0b0929d6..f6209b57 100644
--- a/src/lib/AniList/activity.ts
+++ b/src/lib/AniList/activity.ts
@@ -279,3 +279,24 @@ export const activityLikes = async (id: number): Promise<Partial<User>[]> => {
return activityResponse['data']['Activity']['likes'];
};
+
+export const activityText = async (id: number): Promise<string> => {
+ const activityResponse = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
+ Activity(id: ${id}) {
+ ... on TextActivity { text(asHtml: true) }
+ }
+ }`
+ })
+ })
+ ).json();
+
+ return activityResponse['data']['Activity']['text'];
+};
diff --git a/src/lib/Locale/english.ts b/src/lib/Locale/english.ts
index 92a1f608..f562dc71 100644
--- a/src/lib/Locale/english.ts
+++ b/src/lib/Locale/english.ts
@@ -149,6 +149,13 @@ const English: Locale = {
update: 'Update',
or: 'or',
delete: 'Delete (Click Twice)'
+ },
+ importMode: {
+ enable: 'Enable Import Mode',
+ disable: 'Disable Import Mode',
+ cancel: 'Cancel',
+ import: 'Import',
+ fetch: 'Fetch'
}
},
profile: {
diff --git a/src/lib/Locale/japanese.ts b/src/lib/Locale/japanese.ts
index 1f92279d..a8cbb7f2 100644
--- a/src/lib/Locale/japanese.ts
+++ b/src/lib/Locale/japanese.ts
@@ -150,6 +150,13 @@ const Japanese: Locale = {
update: 'バッジを更新',
or: 'または',
delete: 'バッジを削除する(2回クリック)'
+ },
+ importMode: {
+ enable: 'インポートモードを有効にする',
+ disable: 'インポートモードを無効にする',
+ cancel: 'キャンセル',
+ import: 'インポート',
+ fetch: 'フェッチ'
}
},
profile: {
diff --git a/src/lib/Locale/layout.ts b/src/lib/Locale/layout.ts
index 00c19ea9..ec44391b 100644
--- a/src/lib/Locale/layout.ts
+++ b/src/lib/Locale/layout.ts
@@ -154,6 +154,13 @@ export interface Locale {
or: LocaleValue;
delete: LocaleValue;
};
+ importMode: {
+ enable: LocaleValue;
+ disable: LocaleValue;
+ cancel: LocaleValue;
+ import: LocaleValue;
+ fetch: LocaleValue;
+ };
};
profile: {
statistics: LocaleValue;
diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte
index a5ae83fe..523f9030 100644
--- a/src/routes/user/[user]/badges/+page.svelte
+++ b/src/routes/user/[user]/badges/+page.svelte
@@ -15,11 +15,19 @@
import Message from '$lib/Loading/Message.svelte';
import Dropdown from '$lib/Dropdown.svelte';
import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte';
+ import { activityText } from '$lib/AniList/activity.js';
+ import SettingHint from '$lib/Settings/SettingHint.svelte';
// import { io } from 'socket.io-client';
export let data;
+ interface ImportImage {
+ link?: string;
+ image: string;
+ }
+
let editMode = false;
+ let importMode = false;
let currentUserIdentity: ReturnType<typeof userIdentity>;
let error: null | string;
// const socket = io();
@@ -30,7 +38,11 @@
let confirmDelete = 0;
let selectedBadge: Badge | undefined = undefined;
let loadError: string | null = null;
+ const isId = /^\d+$/.test(data.username);
+ let importImages: ImportImage[] | undefined = undefined;
// let badgeCount = 0;
+ let importLinks = false;
+ let importCategory = '';
// $: downloadDisabled = badgeCount > 20;
@@ -50,7 +62,6 @@
onMount(async () => {
// socket.on('badges', (message) => (badges = message));
- const isId = /^\d+$/.test(data.username);
const badger = isId
? {
id: parseInt(data.username),
@@ -67,7 +78,7 @@
badgesPromise = fetch(root(`/api/badges?id=${badger.id}`));
awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`));
- if (data.user) {
+ if (data.user && !isId) {
currentUserIdentity = userIdentity(data.user);
// socket.emit('badges', data.user);
@@ -311,6 +322,71 @@
return url;
};
+
+ const parsePost = async () => {
+ if (importImages && importImages.length > 0) importImages = undefined;
+
+ const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value;
+ const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
+ const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
+
+ if (type !== 'activity') return null;
+
+ let text = await activityText(parseInt(id));
+
+ const images: ImportImage[] = [];
+
+ if (importLinks) {
+ Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach(
+ (a) => {
+ const anchor = a as HTMLAnchorElement;
+
+ if (anchor.querySelector('img')) {
+ images.push({
+ link: anchor.href,
+ image: (anchor.querySelector('img') as HTMLImageElement).src
+ });
+ }
+ }
+ );
+
+ text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, '');
+
+ Array.from(
+ new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+ ).forEach((img) => {
+ const image = img as HTMLImageElement;
+
+ images.push({
+ image: image.src
+ });
+ });
+ } else {
+ Array.from(
+ new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+ ).forEach((img) => {
+ const image = img as HTMLImageElement;
+
+ images.push({
+ image: image.src
+ });
+ });
+ }
+
+ importImages = images;
+ };
+
+ const importBadges = () =>
+ importImages?.forEach((image) => {
+ badgesPromise = fetch(
+ `/api/badges?image=${encodeURIComponent(image.image)}&post=${encodeURIComponent(
+ image.link || '#'
+ )}${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ''}`,
+ {
+ method: 'PUT'
+ }
+ );
+ });
</script>
<HeadTitle route={`${data.username}'s Badge Wall`} path={`/user/${data.username}`} />
@@ -325,7 +401,7 @@
<Skeleton grid={true} count={100} width="150px" height="170px" />
{:then identity}
- {@const isOwner = identity && identity.name === data.username}
+ {@const isOwner = identity && (isId ? identity.id : identity.name) === data.username}
{#await badgesPromise}
<Message message="Loading badges ..." />
@@ -399,6 +475,18 @@
? $locale().user.badges.editMode.disable
: $locale().user.badges.editMode.enable}
</button>
+ <span style="margin: 0 0.625rem;">•</span>
+ <button
+ on:click={() => {
+ if (importMode) selectedBadge = undefined;
+
+ importMode = !importMode;
+ }}
+ >
+ {importMode
+ ? $locale().user.badges.importMode.disable
+ : $locale().user.badges.importMode.enable}
+ </button>
{#if editMode && isOwner}
{@const groups = groupedBadges
@@ -581,6 +669,65 @@
{/await}
{/if}
+{#if importMode}
+ <div class="popup popup-fullscreen">
+ <div class="card">
+ <SettingHint>Import badges from an activity post</SettingHint>
+
+ <p />
+
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.activityURL}
+ id="import_activity_url"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+ <input
+ type="text"
+ placeholder={$locale().user.badges.editMode.category}
+ id="import_category"
+ minlength="1"
+ maxlength="1000"
+ size="20"
+ />
+
+ <p />
+
+ <input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} />
+ Import Links
+ <SettingHint lineBreak>
+ If your images are nested in links, check this box to import the links as well.
+ </SettingHint>
+
+ <p />
+
+ <button
+ on:click={() => {
+ importMode = false;
+ importImages = undefined;
+ }}
+ class="button-lined"
+ >
+ {$locale().user.badges.importMode.cancel}
+ </button>
+ <button on:click={() => parsePost()} class="button-lined" style="float: right;">
+ {$locale().user.badges.importMode.fetch}
+ </button>
+
+ {#if importImages && importImages.length > 0}
+ <p />
+
+ Import {importImages.length} badges?&nbsp;
+ <button on:click={() => importBadges()} class="button-lined no-shadow">
+ {$locale().user.badges.importMode.import}
+ </button>
+ {/if}
+ </div>
+ </div>
+{/if}
+
<style>
/* body {
margin: 0;
diff --git a/src/styles/popup.scss b/src/styles/popup.scss
index 645e9d23..fda4831e 100644
--- a/src/styles/popup.scss
+++ b/src/styles/popup.scss
@@ -9,3 +9,10 @@
justify-content: center;
align-items: center;
}
+
+.popup-fullscreen {
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 9999;
+}