mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
feat: emoji reaction
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"@tanstack/solid-virtual": "^3.0.0-beta.6",
|
||||
"@thisbeyond/solid-dnd": "^0.7.4",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"heroicons": "^2.0.17",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.10.1",
|
||||
@@ -2699,6 +2700,11 @@
|
||||
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emoji-mart": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz",
|
||||
"integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
@@ -9181,6 +9187,11 @@
|
||||
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-mart": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz",
|
||||
"integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"@tanstack/solid-virtual": "^3.0.0-beta.6",
|
||||
"@thisbeyond/solid-dnd": "^0.7.4",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"heroicons": "^2.0.17",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.10.1",
|
||||
|
||||
58
src/components/EmojiPicker.tsx
Normal file
58
src/components/EmojiPicker.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Component, JSX, createSignal } from 'solid-js';
|
||||
|
||||
import { Picker } from 'emoji-mart';
|
||||
|
||||
import Popup, { PopupRef } from '@/components/utils/Popup';
|
||||
|
||||
type EmojiPickerProps = {
|
||||
onEmojiSelect?: (emoji: string) => void;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const EmojiPicker: Component<EmojiPickerProps> = (props) => {
|
||||
let popupRef: PopupRef | undefined;
|
||||
|
||||
const [pickerElement, setPickerElement] = createSignal<HTMLElement | undefined>(undefined);
|
||||
|
||||
const handleOpen = () => {
|
||||
const picker = new Picker({
|
||||
data: async () => {
|
||||
const response = await fetch('https://cdn.jsdelivr.net/npm/@emoji-mart/data');
|
||||
return response.json();
|
||||
},
|
||||
i18n: async () => {
|
||||
const response = await fetch('https://cdn.jsdelivr.net/npm/@emoji-mart/data/i18n/ja.json');
|
||||
return response.json();
|
||||
},
|
||||
autoFocus: true,
|
||||
locale: 'ja',
|
||||
theme: 'light',
|
||||
onEmojiSelect: (emoji: { id: string; native: string }) => {
|
||||
props.onEmojiSelect?.(emoji.native);
|
||||
popupRef?.close();
|
||||
},
|
||||
});
|
||||
|
||||
setPickerElement(picker as any as HTMLElement);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setPickerElement(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popup
|
||||
ref={(e) => {
|
||||
popupRef = e;
|
||||
}}
|
||||
position="bottom"
|
||||
button={props.children}
|
||||
onOpen={handleOpen}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{pickerElement()}
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmojiPicker;
|
||||
@@ -72,7 +72,7 @@ const extract = (parsed: ParsedTextNote) => {
|
||||
};
|
||||
|
||||
const format = (parsed: ParsedTextNote) => {
|
||||
const content = [];
|
||||
const content: string[] = [];
|
||||
parsed.forEach((node) => {
|
||||
if (node.type === 'Bech32Entity' && !node.isNIP19) {
|
||||
content.push(`nostr:${node.content}`);
|
||||
@@ -205,6 +205,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
};
|
||||
}
|
||||
publishTextNoteMutation.mutate(textNote);
|
||||
close();
|
||||
};
|
||||
|
||||
const handleInput: JSX.EventHandler<HTMLTextAreaElement, InputEvent> = (ev) => {
|
||||
|
||||
@@ -166,6 +166,46 @@ const ToggleButton = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
const EmojiConfig = () => {
|
||||
const { config, setConfig } = useConfig();
|
||||
|
||||
const toggleUseEmojiReaction = () => {
|
||||
setConfig((current) => ({
|
||||
...current,
|
||||
useEmojiReaction: !(current.useEmojiReaction ?? false),
|
||||
}));
|
||||
};
|
||||
|
||||
const toggleShowEmojiReaction = () => {
|
||||
setConfig((current) => ({
|
||||
...current,
|
||||
showEmojiReaction: !(current.showEmojiReaction ?? false),
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="py-2">
|
||||
<h3 class="font-bold">リアクション</h3>
|
||||
<div class="flex flex-col justify-evenly gap-2">
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">絵文字を選べるようにする</div>
|
||||
<ToggleButton
|
||||
value={config().useEmojiReaction}
|
||||
onClick={() => toggleUseEmojiReaction()}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">投稿にリアクションされた絵文字を表示する</div>
|
||||
<ToggleButton
|
||||
value={config().showEmojiReaction}
|
||||
onClick={() => toggleShowEmojiReaction()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MuteConfig = () => {
|
||||
const { config, removeMutedPubkey, addMutedKeyword, removeMutedKeyword } = useConfig();
|
||||
|
||||
@@ -295,6 +335,7 @@ const ConfigUI = (props: ConfigProps) => {
|
||||
<ProfileSection />
|
||||
<RelayConfig />
|
||||
<DateFormatConfig />
|
||||
<EmojiConfig />
|
||||
<OtherConfig />
|
||||
<MuteConfig />
|
||||
</div>
|
||||
|
||||
@@ -267,9 +267,14 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
</button>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
<Show when={followed()}>
|
||||
<div class="shrink-0 text-xs">フォローされています</div>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={userFollowingQuery.isLoading}>
|
||||
<div class="shrink-0 text-xs">読み込み中</div>
|
||||
</Match>
|
||||
<Match when={followed()}>
|
||||
<div class="shrink-0 text-xs">フォローされています</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start px-4 pt-2">
|
||||
|
||||
@@ -17,7 +17,7 @@ export type ProfileEditProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const LNURLRegexString = 'LNURL1[AC-HJ-NP-Zac-hj-np-z02-9]+';
|
||||
const LNURLRegexString = '(LNURL1[AC-HJ-NP-Z02-9]+|lnurl1[ac-hj-np-z02-9]+)';
|
||||
const InternetIdentiferRegexString = '[-_a-zA-Z0-9.]+@[-a-zA-Z0-9.]+';
|
||||
const LUDAddressRegexString = `^(${LNURLRegexString}|${InternetIdentiferRegexString})$`;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-squa
|
||||
import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg';
|
||||
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
|
||||
import HeartOutlined from 'heroicons/24/outline/heart.svg';
|
||||
import Plus from 'heroicons/24/outline/plus.svg';
|
||||
import HeartSolid from 'heroicons/24/solid/heart.svg';
|
||||
import { nip19, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import ContextMenu, { MenuItem } from '@/components/ContextMenu';
|
||||
import EmojiPicker from '@/components/EmojiPicker';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
@@ -25,7 +27,6 @@ import useProfile from '@/nostr/useProfile';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
import useReactions from '@/nostr/useReactions';
|
||||
import useReposts from '@/nostr/useReposts';
|
||||
import useSubscription from '@/nostr/useSubscription';
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import timeout from '@/utils/timeout';
|
||||
@@ -65,6 +66,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
|
||||
const {
|
||||
reactions,
|
||||
reactionsGroupedByContent,
|
||||
isReactedBy,
|
||||
invalidateReactions,
|
||||
query: reactionsQuery,
|
||||
@@ -212,9 +214,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||
ev.stopPropagation();
|
||||
|
||||
const doReaction = (emoji?: string) => {
|
||||
if (isReactedByMe()) {
|
||||
// TODO remove reaction
|
||||
return;
|
||||
@@ -224,7 +224,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
publishReactionMutation.mutate({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: pubkeyNonNull,
|
||||
content: '+',
|
||||
content: emoji ?? '+',
|
||||
eventId: eventIdNonNull,
|
||||
notifyPubkey: props.event.pubkey,
|
||||
});
|
||||
@@ -232,6 +232,11 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||
ev.stopPropagation();
|
||||
doReaction();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (contentRef != null) {
|
||||
setOverflow(contentRef.scrollHeight > contentRef.clientHeight);
|
||||
@@ -249,7 +254,6 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<Show when={author()?.picture}>
|
||||
{/* TODO 画像は脆弱性回避のためにimgじゃない方法で読み込みたい */}
|
||||
<img src={author()?.picture} alt="icon" class="h-full w-full rounded object-cover" />
|
||||
</Show>
|
||||
</button>
|
||||
@@ -262,7 +266,6 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
showProfile(event().pubkey);
|
||||
}}
|
||||
>
|
||||
{/* TODO link to author */}
|
||||
<Show when={(author()?.display_name?.length ?? 0) > 0}>
|
||||
<div class="author-name truncate pr-1 font-bold hover:underline">
|
||||
{author()?.display_name}
|
||||
@@ -345,6 +348,42 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={actions()}>
|
||||
<Show when={config().showEmojiReaction && reactions().length > 0}>
|
||||
<div class="flex gap-2 pt-1">
|
||||
<For each={[...reactionsGroupedByContent().entries()]}>
|
||||
{([content, events]) => {
|
||||
const isReactedByMeWithThisContent =
|
||||
events.findIndex((ev) => ev.pubkey === pubkey()) >= 0;
|
||||
|
||||
return (
|
||||
<button
|
||||
class="flex items-center rounded border px-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isReactedByMeWithThisContent,
|
||||
'bg-rose-50': isReactedByMeWithThisContent,
|
||||
'border-rose-200': isReactedByMeWithThisContent,
|
||||
'text-rose-400': isReactedByMeWithThisContent,
|
||||
}}
|
||||
type="button"
|
||||
onClick={() => doReaction(content)}
|
||||
>
|
||||
<Show
|
||||
when={content === '+'}
|
||||
fallback={<span class="text-xs">{content}</span>}
|
||||
>
|
||||
<span class="inline-block h-3 w-3 pt-[1px] text-rose-400">
|
||||
<HeartSolid />
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!config().hideCount}>
|
||||
<span class="ml-1 text-sm">{events.length}</span>
|
||||
</Show>
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="actions flex w-48 items-center justify-between gap-8 pt-1">
|
||||
<button
|
||||
class="h-4 w-4 shrink-0 text-zinc-400"
|
||||
@@ -380,16 +419,31 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
'text-rose-400': isReactedByMe() || publishReactionMutation.isLoading,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="h-4 w-4"
|
||||
onClick={handleReaction}
|
||||
disabled={publishReactionMutation.isLoading}
|
||||
<Show
|
||||
when={!config().useEmojiReaction}
|
||||
fallback={
|
||||
<EmojiPicker onEmojiSelect={(emoji) => doReaction(emoji)}>
|
||||
<span class="inline-block h-4 w-4">
|
||||
<Plus />
|
||||
</span>
|
||||
</EmojiPicker>
|
||||
}
|
||||
>
|
||||
<button
|
||||
class="h-4 w-4"
|
||||
onClick={handleReaction}
|
||||
disabled={publishReactionMutation.isLoading}
|
||||
>
|
||||
<Show when={isReactedByMe()} fallback={<HeartOutlined />}>
|
||||
<HeartSolid />
|
||||
</Show>
|
||||
</button>
|
||||
</Show>
|
||||
<Show
|
||||
when={
|
||||
!config().hideCount && !config().showEmojiReaction && reactions().length > 0
|
||||
}
|
||||
>
|
||||
<Show when={isReactedByMe()} fallback={<HeartOutlined />}>
|
||||
<HeartSolid />
|
||||
</Show>
|
||||
</button>
|
||||
<Show when={!config().hideCount && reactions().length > 0}>
|
||||
<div class="text-sm text-zinc-400">{reactions().length}</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { createSignal, createEffect, type Component, type JSX, onCleanup, onMount } from 'solid-js';
|
||||
import {
|
||||
createSignal,
|
||||
createEffect,
|
||||
type Component,
|
||||
type JSX,
|
||||
on,
|
||||
onCleanup,
|
||||
onMount,
|
||||
children,
|
||||
} from 'solid-js';
|
||||
|
||||
export type PopupRef = {
|
||||
close: () => void;
|
||||
@@ -9,6 +18,7 @@ export type PopupProps = {
|
||||
button: JSX.Element;
|
||||
position?: 'left' | 'bottom' | 'right' | 'top';
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
ref?: (ref: PopupRef) => void;
|
||||
};
|
||||
|
||||
@@ -17,6 +27,7 @@ const Popup: Component<PopupProps> = (props) => {
|
||||
let popupRef: HTMLDivElement | undefined;
|
||||
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
const resolvedChildren = children(() => props.children);
|
||||
|
||||
const handleClickOutside = (ev: MouseEvent) => {
|
||||
const target = ev.target as HTMLElement;
|
||||
@@ -40,34 +51,42 @@ const Popup: Component<PopupProps> = (props) => {
|
||||
createEffect(() => {
|
||||
if (isOpen()) {
|
||||
addClickOutsideHandler();
|
||||
props.onOpen?.();
|
||||
} else {
|
||||
removeClickOutsideHandler();
|
||||
props.onClose?.();
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (isOpen()) props.onOpen?.();
|
||||
});
|
||||
createEffect(
|
||||
on(resolvedChildren, () => {
|
||||
if (buttonRef == null || popupRef == null) return;
|
||||
|
||||
createEffect(() => {
|
||||
if (buttonRef == null || popupRef == null) return;
|
||||
const buttonRect = buttonRef?.getBoundingClientRect();
|
||||
const popupRect = popupRef?.getBoundingClientRect();
|
||||
|
||||
const buttonRect = buttonRef?.getBoundingClientRect();
|
||||
let { top, left } = buttonRect;
|
||||
|
||||
if (props.position === 'left') {
|
||||
popupRef.style.left = `${buttonRect.left - buttonRect.width}px`;
|
||||
popupRef.style.top = `${buttonRect.top}px`;
|
||||
} else if (props.position === 'right') {
|
||||
popupRef.style.left = `${buttonRect.left + buttonRect.width}px`;
|
||||
popupRef.style.top = `${buttonRect.top}px`;
|
||||
} else if (props.position === 'top') {
|
||||
popupRef.style.left = `${buttonRect.left + buttonRect.width}px`;
|
||||
popupRef.style.top = `${buttonRect.top - buttonRect.height}px`;
|
||||
} else {
|
||||
popupRef.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
||||
popupRef.style.top = `${buttonRect.top + buttonRect.height}px`;
|
||||
}
|
||||
});
|
||||
if (props.position === 'left') {
|
||||
left -= buttonRect.width;
|
||||
} else if (props.position === 'right') {
|
||||
left += buttonRect.width;
|
||||
} else if (props.position === 'top') {
|
||||
top -= buttonRect.height;
|
||||
left -= buttonRect.left + buttonRect.width / 2;
|
||||
} else {
|
||||
top += buttonRect.height;
|
||||
left += buttonRect.width / 2;
|
||||
}
|
||||
|
||||
top = Math.min(top, window.innerHeight - popupRect.height);
|
||||
left = Math.min(left, window.innerWidth - popupRect.width);
|
||||
console.log(popupRect);
|
||||
|
||||
popupRef.style.left = `${left}px`;
|
||||
popupRef.style.top = `${top}px`;
|
||||
}),
|
||||
);
|
||||
|
||||
onMount(() => {
|
||||
props.ref?.({ close });
|
||||
@@ -77,11 +96,11 @@ const Popup: Component<PopupProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button ref={buttonRef} onClick={handleClick}>
|
||||
<button ref={buttonRef} class="flex items-center" onClick={handleClick}>
|
||||
{props.button}
|
||||
</button>
|
||||
<div ref={popupRef} class="absolute z-20" classList={{ hidden: !isOpen(), block: isOpen() }}>
|
||||
{props.children}
|
||||
{resolvedChildren()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ export const relaysGlobal: string[] = [
|
||||
|
||||
export const relaysOnlyAvailableInJP: string[] = [
|
||||
'wss://relay-jp.nostr.wirednet.jp',
|
||||
'wss://nostr.h3z.jp',
|
||||
// 'wss://nostr.h3z.jp',
|
||||
'wss://nostr.holybea.com',
|
||||
];
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export type Config = {
|
||||
columns: ColumnType[];
|
||||
dateFormat: 'relative' | 'absolute-long' | 'absolute-short';
|
||||
keepOpenPostForm: boolean;
|
||||
useEmojiReaction: boolean;
|
||||
showEmojiReaction: boolean;
|
||||
showImage: boolean;
|
||||
hideCount: boolean;
|
||||
mutedPubkeys: string[];
|
||||
@@ -62,6 +64,8 @@ const InitialConfig = (): Config => ({
|
||||
columns: [],
|
||||
dateFormat: 'relative',
|
||||
keepOpenPostForm: false,
|
||||
useEmojiReaction: false,
|
||||
showEmojiReaction: false,
|
||||
showImage: true,
|
||||
hideCount: false,
|
||||
mutedPubkeys: [],
|
||||
|
||||
@@ -16,10 +16,27 @@ code {
|
||||
monospace;
|
||||
}
|
||||
|
||||
.navigationButton {
|
||||
@apply inline-block py-4 px-1 text-xl font-bold text-blue-500 hover:text-blue-600;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply underline text-blue-500;
|
||||
}
|
||||
|
||||
em-emoji-picker {
|
||||
--background-rgb: 85, 170, 255;
|
||||
--border-radius: 8px;
|
||||
--color-border-over: rgba(0, 0, 0, 0.1);
|
||||
--color-border: rgba(0, 0, 0, 0.05);
|
||||
--category-icon-size: 20px;
|
||||
--font-size: 16px;
|
||||
--rgb-accent: 253, 164, 175;
|
||||
--rgb-background: 255, 255, 255;
|
||||
--rgb-color: 28, 25, 23;
|
||||
--rgb-input: 255, 255, 255;
|
||||
--shadow: 0 5px 8px -8px #222;
|
||||
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
height: 50vh;
|
||||
min-height: 400px;
|
||||
max-height: 800px;
|
||||
width: 360px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ export const useProfile = (propsProvider: () => UseProfileProps | null): UseProf
|
||||
try {
|
||||
return JSON.parse(content) as Profile;
|
||||
} catch (err) {
|
||||
console.error('failed to parse profile (kind 0): ', err, content);
|
||||
console.warn('failed to parse profile (kind 0): ', err, content);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user