feat: npub permalink

This commit is contained in:
Shusui MOYATANI
2023-07-09 14:43:36 +09:00
parent dca108084a
commit 0189016a8b
10 changed files with 190 additions and 118 deletions

View File

@@ -10,6 +10,7 @@ import i18nextInstance from '@/i18n/i18n';
import { I18NextProvider } from '@/i18n/useTranslation';
const Home = lazy(() => import('@/pages/Home'));
const Permalink = lazy(() => import('@/pages/Permalink'));
const Hello = lazy(() => import('@/pages/Hello'));
const NotFound = lazy(() => import('@/pages/NotFound'));
@@ -52,6 +53,7 @@ const App: Component = () => {
<Routes>
<Route path="/hello" element={<Hello />} />
<Route path="/" element={<Home />} />
<Route path="/:id" element={<Permalink />} />
<Route path="/*" element={<NotFound />} />
</Routes>
</QueryClientProvider>

View File

@@ -13,7 +13,7 @@ const Columns = () => {
const { config } = useConfig();
return (
<div class="scrollbar flex h-full snap-x snap-mandatory flex-row overflow-y-hidden overflow-x-scroll">
<div class="flex h-full snap-x snap-mandatory flex-row overflow-scroll scroll-smooth">
<For each={config().columns}>
{(column, index) => {
const columnIndex = () => index() + 1;

View File

@@ -1,4 +1,4 @@
import { Show, Switch, Match } from 'solid-js';
import { Show, Switch, Match, Component } from 'solid-js';
import About from '@/components/modal/About';
import AddColumn from '@/components/modal/AddColumn';
@@ -8,7 +8,7 @@ import useModalState from '@/hooks/useModalState';
import usePubkey from '@/nostr/usePubkey';
import ensureNonNull from '@/utils/ensureNonNull';
const GlobalModal = () => {
const GlobalModal: Component = () => {
const pubkey = usePubkey();
const { modalState, showProfile, closeModal } = useModalState();

View File

@@ -243,8 +243,11 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
return (
<BasicModal onClose={() => props.onClose?.()}>
<Show when={profileQuery.isFetched} fallback={<>loading</>}>
<Show when={profile()?.banner} fallback={<div class="h-12 shrink-0" />} keyed>
<Show
when={profileQuery.isFetched && profile()?.banner}
fallback={<div class="h-12 shrink-0" />}
keyed
>
{(bannerUrl) => (
<div class="h-40 w-full shrink-0 sm:h-52">
<img src={bannerUrl} alt="header" class="h-full w-full object-cover" />
@@ -254,7 +257,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
<div class="mt-[-54px] flex items-end gap-4 px-4 pt-4">
<div class="flex-1 shrink-0">
<div class="h-28 w-28 rounded-lg shadow-md">
<Show when={profile()?.picture} keyed>
<Show when={profileQuery.isFetched && profile()?.picture} keyed>
{(pictureUrl) => (
<img
src={pictureUrl}
@@ -265,6 +268,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</Show>
</div>
</div>
<Show when={myPubkey() != null}>
<div class="flex shrink-0 flex-col items-center gap-1">
<div class="flex flex-row justify-start gap-1">
<Switch>
@@ -284,7 +288,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</Match>
<Match when={myFollowingQuery.isLoading || myFollowingQuery.isFetching}>
<span class="rounded-full border border-primary px-4 py-2 text-primary sm:text-base">
{i18n()('profile.loading')}
{i18n()('general.loading')}
</span>
</Match>
<Match when={following()}>
@@ -323,16 +327,18 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</div>
<Switch>
<Match when={userFollowingQuery.isLoading}>
<div class="shrink-0 text-xs">{i18n()('profile.loading')}</div>
<div class="shrink-0 text-xs">{i18n()('general.loading')}</div>
</Match>
<Match when={followed()}>
<div class="shrink-0 text-xs">{i18n()('profile.followsYou')}</div>
</Match>
</Switch>
</div>
</Show>
</div>
<div class="flex items-start px-4 pt-2">
<div class="h-16 shrink overflow-hidden">
<Show when={profileQuery.isLoading}>{i18n()('general.loading')}</Show>
<Show when={(profile()?.display_name?.length ?? 0) > 0}>
<div class="truncate text-xl font-bold">{profile()?.display_name}</div>
</Show>
@@ -422,7 +428,6 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
</Show>
</ul>
</Show>
</Show>
<Switch>
<Match when={modal() === 'Following'}>
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />

View File

@@ -78,8 +78,8 @@ const InitialConfig = (): Config => ({
customEmojis: {},
dateFormat: 'relative',
keepOpenPostForm: false,
useEmojiReaction: false,
showEmojiReaction: false,
useEmojiReaction: true,
showEmojiReaction: true,
showMedia: true,
hideCount: false,
mutedPubkeys: [],
@@ -92,7 +92,7 @@ const deserializer = (json: string): Config =>
({
...InitialConfig(),
...JSON.parse(json),
} as Config);
}) as Config;
const storage = createStorageWithSerializer(() => window.localStorage, serializer, deserializer);
const [config, setConfig] = createStoreWithStorage('RabbitConfig', InitialConfig(), storage);

View File

@@ -1,6 +1,7 @@
import { createSignal } from 'solid-js';
type ModalState =
| { type: 'Login' }
| { type: 'Profile'; pubkey: string }
| { type: 'ProfileEdit' }
| { type: 'UserTimeline'; pubkey: string }
@@ -11,6 +12,9 @@ type ModalState =
const [modalState, setModalState] = createSignal<ModalState>({ type: 'Closed' });
const useModalState = () => {
const showLogin = () => {
setModalState({ type: 'Login' });
};
const showProfile = (pubkey: string) => {
setModalState({ type: 'Profile', pubkey });
};
@@ -29,6 +33,7 @@ const useModalState = () => {
return {
modalState,
setModalState,
showLogin,
showProfile,
showProfileEdit,
showAddColumn,

View File

@@ -1,6 +1,10 @@
import ja from '@/locales/ja';
export default {
general: {
loading: 'Loading',
updating: 'Updating',
},
posting: {
placeholder: "What's happening?",
contentWarning: 'Content warning',
@@ -34,8 +38,6 @@ export default {
following: 'Following',
followers: 'Followers',
loadFollowers: 'Load',
loading: 'Loading',
updating: 'Updating',
editProfile: 'Edit',
follow: 'Follow',
unfollow: 'Unfollow',

View File

@@ -1,4 +1,8 @@
export default {
general: {
loading: '読み込み中',
updating: '更新中',
},
posting: {
placeholder: 'いまどうしてる?',
contentWarning: 'コンテンツ警告を設定',
@@ -32,8 +36,6 @@ export default {
following: 'フォロー',
followers: 'フォロワー',
loadFollowers: '読み込む',
loading: '読み込み中',
updating: '更新中',
editProfile: '編集',
follow: 'フォロー',
unfollow: 'フォロー解除',

View File

@@ -6,6 +6,7 @@ import Columns from '@/components/column/Columns';
import GlobalModal from '@/components/modal/GlobalModal';
import SideBar from '@/components/SideBar';
import useConfig from '@/core/useConfig';
import useModalState from '@/hooks/useModalState';
import usePersistStatus from '@/hooks/usePersistStatus';
import { useMountShortcutKeys } from '@/hooks/useShortcutKeys';
import usePool from '@/nostr/usePool';

55
src/pages/Permalink.tsx Normal file
View File

@@ -0,0 +1,55 @@
import { createEffect, onMount } from 'solid-js';
import { useNavigate, useParams } from '@solidjs/router';
import { nip19 } from 'nostr-tools';
import GlobalModal from '@/components/modal/GlobalModal';
import SideBar from '@/components/SideBar';
import useModalState from '@/hooks/useModalState';
import usePersistStatus from '@/hooks/usePersistStatus';
const Permalink = () => {
const navigate = useNavigate();
const params = useParams();
const { modalState, showProfile } = useModalState();
const { persistStatus } = usePersistStatus();
const navigateTop = () => {
if (persistStatus().loggedIn) {
navigate('/');
} else {
navigate('/hello');
}
};
onMount(() => {
if (params.id != null) {
try {
const decoded = nip19.decode(params.id);
if (decoded.type === 'npub') {
showProfile(decoded.data);
}
} catch (err) {
window.alert('Invalid ID');
navigateTop();
}
} else {
navigateTop();
}
});
createEffect(() => {
if (modalState().type === 'Closed') {
navigateTop();
}
});
return (
<div class="absolute inset-0 flex w-screen touch-manipulation flex-row overflow-hidden">
<SideBar />
<GlobalModal />
</div>
);
};
export default Permalink;