mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 06:24:25 +01:00
feat: color themes
This commit is contained in:
BIN
public/images/rabbit_muted_256.png
Normal file
BIN
public/images/rabbit_muted_256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -6,6 +6,7 @@ import { persistQueryClient } from '@tanstack/query-persist-client-core';
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
|
||||||
import { get as getItem, set as setItem, del as removeItem } from 'idb-keyval';
|
import { get as getItem, set as setItem, del as removeItem } from 'idb-keyval';
|
||||||
|
|
||||||
|
import useColorTheme from '@/hooks/useColorTheme';
|
||||||
import i18nextInstance from '@/i18n/i18n';
|
import i18nextInstance from '@/i18n/i18n';
|
||||||
import { I18NextProvider } from '@/i18n/useTranslation';
|
import { I18NextProvider } from '@/i18n/useTranslation';
|
||||||
|
|
||||||
@@ -47,6 +48,8 @@ const App: Component = () => {
|
|||||||
onCleanup(() => unsubscribe());
|
onCleanup(() => unsubscribe());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useColorTheme(document.body);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18NextProvider i18next={i18next}>
|
<I18NextProvider i18next={i18next}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ const ReactionAction = (props: { event: NostrEvent }) => {
|
|||||||
<div
|
<div
|
||||||
class="flex shrink-0 items-center gap-1"
|
class="flex shrink-0 items-center gap-1"
|
||||||
classList={{
|
classList={{
|
||||||
'text-zinc-400': !isReactedByMe() || isReactedByMeWithEmoji(),
|
'text-fg-tertiary': !isReactedByMe() || isReactedByMeWithEmoji(),
|
||||||
'hover:text-rose-400': !isReactedByMe() || isReactedByMeWithEmoji(),
|
'hover:text-r-reaction': !isReactedByMe() || isReactedByMeWithEmoji(),
|
||||||
'text-rose-400':
|
'text-r-reaction':
|
||||||
(isReactedByMe() && !isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
|
(isReactedByMe() && !isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -130,7 +130,7 @@ const ReactionAction = (props: { event: NostrEvent }) => {
|
|||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
<Show when={!config().hideCount && !config().showEmojiReaction && reactions().length > 0}>
|
<Show when={!config().hideCount && !config().showEmojiReaction && reactions().length > 0}>
|
||||||
<div class="text-sm text-zinc-400">
|
<div class="text-sm text-fg-tertiary">
|
||||||
{formatSiPrefix(reactions().length, { minDigits: 4 })}
|
{formatSiPrefix(reactions().length, { minDigits: 4 })}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -139,9 +139,9 @@ const ReactionAction = (props: { event: NostrEvent }) => {
|
|||||||
<div
|
<div
|
||||||
class="flex shrink-0 items-center gap-1"
|
class="flex shrink-0 items-center gap-1"
|
||||||
classList={{
|
classList={{
|
||||||
'text-zinc-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
'text-fg-tertiary': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
||||||
'hover:text-rose-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
'hover:text-r-reaction': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
||||||
'text-rose-400':
|
'text-r-reaction':
|
||||||
(isReactedByMe() && isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
|
(isReactedByMe() && isReactedByMeWithEmoji()) || publishReactionMutation.isPending,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -199,16 +199,18 @@ const RepostAction = (props: { event: NostrEvent }) => {
|
|||||||
<div
|
<div
|
||||||
class="flex shrink-0 items-center gap-1"
|
class="flex shrink-0 items-center gap-1"
|
||||||
classList={{
|
classList={{
|
||||||
'text-zinc-400': !isRepostedByMe(),
|
'text-fg-tertiary': !isRepostedByMe(),
|
||||||
'hover:text-green-400': !isRepostedByMe(),
|
'hover:text-r-repost': !isRepostedByMe(),
|
||||||
'text-green-400': isRepostedByMe() || publishRepostMutation.isPending,
|
'text-r-repost': isRepostedByMe() || publishRepostMutation.isPending,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button class="h-4 w-4" onClick={handleRepost} disabled={publishRepostMutation.isPending}>
|
<button onClick={handleRepost} disabled={publishRepostMutation.isPending}>
|
||||||
<ArrowPathRoundedSquare />
|
<span class="flex h-4 w-4">
|
||||||
|
<ArrowPathRoundedSquare />
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<Show when={!config().hideCount && reposts().length > 0}>
|
<Show when={!config().hideCount && reposts().length > 0}>
|
||||||
<div class="text-sm text-zinc-400">
|
<div class="text-sm text-fg-tertiary">
|
||||||
{formatSiPrefix(reposts().length, { minDigits: 4 })}
|
{formatSiPrefix(reposts().length, { minDigits: 4 })}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -293,13 +295,14 @@ const EmojiReactions: Component<{ event: NostrEvent }> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class="flex h-6 max-w-[128px] items-center rounded border px-1"
|
class="flex h-8 max-w-[128px] items-center rounded border border-border px-1 sm:h-6"
|
||||||
classList={{
|
classList={{
|
||||||
'text-zinc-400': !isReactedByMeWithThisContent,
|
'text-fg-tertiary': !isReactedByMeWithThisContent,
|
||||||
'hover:bg-zinc-50': !isReactedByMeWithThisContent,
|
'hover:bg-r-reaction/10': !isReactedByMeWithThisContent,
|
||||||
'bg-rose-50': isReactedByMeWithThisContent,
|
'hover:border-r-reaction/40': !isReactedByMeWithThisContent,
|
||||||
'border-rose-200': isReactedByMeWithThisContent,
|
'bg-r-reaction/10': isReactedByMeWithThisContent,
|
||||||
'text-rose-400': isReactedByMeWithThisContent,
|
'border-r-reaction/40': isReactedByMeWithThisContent,
|
||||||
|
'text-r-reaction': isReactedByMeWithThisContent,
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
@@ -401,18 +404,20 @@ const Actions: Component<ActionProps> = (props) => {
|
|||||||
<EmojiReactions event={props.event} />
|
<EmojiReactions event={props.event} />
|
||||||
<div class="actions flex w-52 items-center justify-between gap-8 pt-1">
|
<div class="actions flex w-52 items-center justify-between gap-8 pt-1">
|
||||||
<button
|
<button
|
||||||
class="h-4 w-4 shrink-0 text-zinc-400 hover:text-zinc-500"
|
class="shrink-0 text-fg-tertiary hover:text-fg-tertiary/70"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
props.onClickReply();
|
props.onClickReply();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChatBubbleLeft />
|
<span class="flex h-4 w-4">
|
||||||
|
<ChatBubbleLeft />
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<RepostAction event={props.event} />
|
<RepostAction event={props.event} />
|
||||||
<ReactionAction event={props.event} />
|
<ReactionAction event={props.event} />
|
||||||
<ContextMenu menu={menu}>
|
<ContextMenu menu={menu}>
|
||||||
<span class="inline-block h-4 w-4 text-zinc-400 hover:text-zinc-500">
|
<span class="inline-block h-4 w-4 text-fg-tertiary hover:text-fg-tertiary/70">
|
||||||
<EllipsisHorizontal />
|
<EllipsisHorizontal />
|
||||||
</span>
|
</span>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type ColumnItemProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ColumnItem: Component<ColumnItemProps> = (props) => (
|
const ColumnItem: Component<ColumnItemProps> = (props) => (
|
||||||
<div class="block shrink-0 overflow-hidden border-b p-1">{props.children}</div>
|
<div class="block shrink-0 overflow-hidden border-b border-border p-1">{props.children}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ColumnItem;
|
export default ColumnItem;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li class="border-b hover:bg-stone-200">
|
<li class="border-b border-border hover:bg-bg-tertiary">
|
||||||
<button class="w-full px-4 py-1 text-start" onClick={handleClick}>
|
<button class="w-full px-4 py-1 text-start" onClick={handleClick}>
|
||||||
{props.item.content()}
|
{props.item.content()}
|
||||||
</button>
|
</button>
|
||||||
@@ -46,7 +46,7 @@ const ContextMenu: Component<ContextMenuProps> = (props) => {
|
|||||||
button={props.children}
|
button={props.children}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
>
|
>
|
||||||
<ul class="min-w-[96px] rounded border bg-white shadow-md">
|
<ul class="min-w-[96px] rounded border border-border bg-bg shadow-md">
|
||||||
<For each={props.menu.filter((e) => e.when == null || e.when())}>
|
<For each={props.menu.filter((e) => e.when == null || e.when())}>
|
||||||
{(item: MenuItem) => <MenuItemDisplay item={item} onClose={close} />}
|
{(item: MenuItem) => <MenuItemDisplay item={item} onClose={close} />}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const isPlus = (r: ReactionTypes) => r.type === 'LikeDislike' && r.content === '
|
|||||||
const EmojiDisplay: Component<EmojiDisplayProps> = (props) => (
|
const EmojiDisplay: Component<EmojiDisplayProps> = (props) => (
|
||||||
<Switch fallback={<span class="truncate">{props.reactionTypes.content}</span>}>
|
<Switch fallback={<span class="truncate">{props.reactionTypes.content}</span>}>
|
||||||
<Match when={isPlus(props.reactionTypes)}>
|
<Match when={isPlus(props.reactionTypes)}>
|
||||||
<span class="inline-block h-4 w-4 pt-[1px] text-rose-400">
|
<span class="inline-block h-4 w-4 pt-[1px] text-r-reaction">
|
||||||
<HeartSolid />
|
<HeartSolid />
|
||||||
</span>
|
</span>
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const tryEncodeNevent = (eventId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EventLink: Component<EventLinkProps> = (props) => (
|
const EventLink: Component<EventLinkProps> = (props) => (
|
||||||
<button class="text-blue-500 underline">
|
<button class="text-link underline">
|
||||||
<Show when={props.kind == null || props.kind === 1} fallback={tryEncodeNevent(props.eventId)}>
|
<Show when={props.kind == null || props.kind === 1} fallback={tryEncodeNevent(props.eventId)}>
|
||||||
{tryEncodeNote(props.eventId)}
|
{tryEncodeNote(props.eventId)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createSignal, createMemo, onMount, Show, For, type Component, type JSX } from 'solid-js';
|
import { createSignal, createMemo, onMount, Show, For, type Component, type JSX } from 'solid-js';
|
||||||
|
|
||||||
import { createMutation } from '@tanstack/solid-query';
|
import { createMutation } from '@tanstack/solid-query';
|
||||||
|
import ExclamationTriangle from 'heroicons/24/outline/exclamation-triangle.svg';
|
||||||
import FaceSmile from 'heroicons/24/outline/face-smile.svg';
|
import FaceSmile from 'heroicons/24/outline/face-smile.svg';
|
||||||
import Photo from 'heroicons/24/outline/photo.svg';
|
import Photo from 'heroicons/24/outline/photo.svg';
|
||||||
import XMark from 'heroicons/24/outline/x-mark.svg';
|
import XMark from 'heroicons/24/outline/x-mark.svg';
|
||||||
@@ -365,7 +366,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
<Show when={contentWarning()}>
|
<Show when={contentWarning()}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="rounded"
|
class="rounded-md border border-border bg-bg ring-border placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
placeholder={i18n()('posting.contentWarningReason')}
|
placeholder={i18n()('posting.contentWarningReason')}
|
||||||
maxLength={32}
|
maxLength={32}
|
||||||
onInput={(ev) => setContentWarningReason(ev.currentTarget.value)}
|
onInput={(ev) => setContentWarningReason(ev.currentTarget.value)}
|
||||||
@@ -379,7 +380,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
emojiTextAreaRef(el);
|
emojiTextAreaRef(el);
|
||||||
}}
|
}}
|
||||||
name="text"
|
name="text"
|
||||||
class="min-h-[40px] rounded-md border-none focus:ring-rose-300"
|
class="min-h-[40px] rounded-md border border-border bg-bg ring-border placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder={placeholder(mode())}
|
placeholder={placeholder(mode())}
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
@@ -392,42 +393,56 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
<div class="flex items-end justify-end gap-1">
|
<div class="flex items-end justify-end gap-1">
|
||||||
<Show when={mode() === 'reply'}>
|
<Show when={mode() === 'reply'}>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<button class="h-5 w-5 text-stone-500" onClick={() => close()}>
|
<button class="h-5 w-5 text-fg-secondary" onClick={() => close()}>
|
||||||
<XMark />
|
<XMark />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<EmojiPicker customEmojis={true} onEmojiSelect={handleEmojiSelect}>
|
<EmojiPicker customEmojis={true} onEmojiSelect={handleEmojiSelect}>
|
||||||
<span class="inline-block h-8 w-8 rounded bg-primary p-2 font-bold text-white">
|
<span
|
||||||
|
class="inline-block rounded bg-primary font-bold text-primary-fg"
|
||||||
|
classList={{
|
||||||
|
'h-8': mode() === 'normal',
|
||||||
|
'w-8': mode() === 'normal',
|
||||||
|
'p-2': mode() === 'normal',
|
||||||
|
'h-7': mode() === 'reply',
|
||||||
|
'w-7': mode() === 'reply',
|
||||||
|
'p-[6px]': mode() === 'reply',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FaceSmile />
|
<FaceSmile />
|
||||||
</span>
|
</span>
|
||||||
</EmojiPicker>
|
</EmojiPicker>
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-center rounded p-2 text-xs font-bold text-white"
|
class="flex items-center justify-center rounded p-2 text-xs font-bold text-primary-fg"
|
||||||
classList={{
|
classList={{
|
||||||
'bg-rose-300': !contentWarning(),
|
'bg-primary': !contentWarning(),
|
||||||
'bg-rose-400': contentWarning(),
|
'bg-primary-hover': contentWarning(),
|
||||||
'h-8': mode() === 'normal',
|
'h-8': mode() === 'normal',
|
||||||
'w-8': mode() === 'normal',
|
'w-8': mode() === 'normal',
|
||||||
|
'p-2': mode() === 'normal',
|
||||||
'h-7': mode() === 'reply',
|
'h-7': mode() === 'reply',
|
||||||
'w-7': mode() === 'reply',
|
'w-7': mode() === 'reply',
|
||||||
|
'p-[6px]': mode() === 'reply',
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={i18n()('posting.contentWarning')}
|
aria-label={i18n()('posting.contentWarning')}
|
||||||
title={i18n()('posting.contentWarning')}
|
title={i18n()('posting.contentWarning')}
|
||||||
onClick={() => setContentWarning((e) => !e)}
|
onClick={() => setContentWarning((e) => !e)}
|
||||||
>
|
>
|
||||||
<span>CW</span>
|
<ExclamationTriangle />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded bg-primary p-2 font-bold text-white"
|
class="rounded font-bold text-primary-fg"
|
||||||
classList={{
|
classList={{
|
||||||
'bg-primary-disabled': fileUploadDisabled(),
|
'bg-primary-disabled': fileUploadDisabled(),
|
||||||
'bg-primary': !fileUploadDisabled(),
|
'bg-primary': !fileUploadDisabled(),
|
||||||
'h-8': mode() === 'normal',
|
'h-8': mode() === 'normal',
|
||||||
'w-8': mode() === 'normal',
|
'w-8': mode() === 'normal',
|
||||||
|
'p-2': mode() === 'normal',
|
||||||
'h-7': mode() === 'reply',
|
'h-7': mode() === 'reply',
|
||||||
'w-7': mode() === 'reply',
|
'w-7': mode() === 'reply',
|
||||||
|
'p-[6px]': mode() === 'reply',
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
title={i18n()('posting.uploadImage')}
|
title={i18n()('posting.uploadImage')}
|
||||||
@@ -438,7 +453,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
<Photo />
|
<Photo />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded bg-primary p-2 font-bold text-white"
|
class="rounded p-2 font-bold text-primary-fg"
|
||||||
classList={{
|
classList={{
|
||||||
'bg-primary-disabled': submitDisabled(),
|
'bg-primary-disabled': submitDisabled(),
|
||||||
'bg-primary': !submitDisabled(),
|
'bg-primary': !submitDisabled(),
|
||||||
@@ -457,7 +472,6 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
class="rounded bg-primary"
|
|
||||||
type="file"
|
type="file"
|
||||||
hidden
|
hidden
|
||||||
name="image"
|
name="image"
|
||||||
|
|||||||
@@ -53,19 +53,19 @@ const Post: Component<PostProps> = (props) => {
|
|||||||
<div class="flex justify-between gap-1 text-xs">
|
<div class="flex justify-between gap-1 text-xs">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="author flex min-w-0 select-text truncate hover:text-blue-500"
|
class="author flex min-w-0 select-text truncate hover:text-link"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
props?.onShowProfile?.();
|
props?.onShowProfile?.();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="author flex min-w-0 truncate hover:text-blue-500">
|
<span class="author flex min-w-0 truncate hover:text-link">
|
||||||
<Show when={(author()?.display_name?.length ?? 0) > 0}>
|
<Show when={(author()?.display_name?.length ?? 0) > 0}>
|
||||||
<div class="author-name truncate pr-1 font-bold hover:underline">
|
<div class="author-name truncate pr-1 font-bold hover:underline">
|
||||||
{author()?.display_name}
|
{author()?.display_name}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="author-username truncate text-zinc-600">
|
<div class="author-username truncate text-fg-secondary">
|
||||||
<Show
|
<Show
|
||||||
when={author()?.name != null}
|
when={author()?.name != null}
|
||||||
fallback={`@${npubEncodeFallback(props.authorPubkey)}`}
|
fallback={`@${npubEncodeFallback(props.authorPubkey)}`}
|
||||||
@@ -99,7 +99,7 @@ const Post: Component<PostProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={overflow()}>
|
<Show when={overflow()}>
|
||||||
<button
|
<button
|
||||||
class="mt-2 w-full rounded border p-2 text-center text-xs text-stone-600 shadow-sm hover:shadow"
|
class="mt-2 w-full rounded border border-border p-2 text-center text-xs text-fg-secondary shadow-sm hover:bg-bg-tertiary hover:shadow"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
setShowOverflow((current) => !current);
|
setShowOverflow((current) => !current);
|
||||||
|
|||||||
@@ -39,24 +39,24 @@ const SearchButton = () => {
|
|||||||
}}
|
}}
|
||||||
position="right"
|
position="right"
|
||||||
button={
|
button={
|
||||||
<span class="inline-block h-9 w-9 rounded-full border border-primary p-2 text-2xl font-bold text-primary">
|
<span class="inline-block h-9 w-9 rounded-full border border-primary p-2 text-2xl font-bold text-primary hover:border-primary-hover hover:text-primary-hover">
|
||||||
<MagnifyingGlass />
|
<MagnifyingGlass />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
onOpen={() => inputRef?.focus()}
|
onOpen={() => inputRef?.focus()}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
class="flex w-72 items-center gap-1 rounded-md bg-white p-4 shadow-md"
|
class="flex w-72 items-center gap-1 rounded-md border border-border bg-bg p-4 shadow-md"
|
||||||
onSubmit={handleSearchSubmit}
|
onSubmit={handleSearchSubmit}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
class="h-8 w-full rounded border border-stone-300 focus:border-rose-100 focus:ring-rose-300"
|
class="h-8 w-full rounded border border-border bg-bg focus:border-primary focus:ring-border"
|
||||||
type="text"
|
type="text"
|
||||||
value={query()}
|
value={query()}
|
||||||
onChange={(ev) => setQuery(ev.currentTarget.value)}
|
onChange={(ev) => setQuery(ev.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<button class="h-8 w-8 rounded bg-primary p-1 text-white" type="submit">
|
<button class="h-8 w-8 rounded bg-primary p-1 text-primary-fg" type="submit">
|
||||||
<MagnifyingGlass />
|
<MagnifyingGlass />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -68,7 +68,7 @@ const SideBar: Component = () => {
|
|||||||
let textAreaRef: HTMLTextAreaElement | undefined;
|
let textAreaRef: HTMLTextAreaElement | undefined;
|
||||||
|
|
||||||
const { showAddColumn, showAbout } = useModalState();
|
const { showAddColumn, showAbout } = useModalState();
|
||||||
const { config } = useConfig();
|
const { config, getColorTheme } = useConfig();
|
||||||
|
|
||||||
const [formOpened, setFormOpened] = createSignal(false);
|
const [formOpened, setFormOpened] = createSignal(false);
|
||||||
const [configOpened, setConfigOpened] = createSignal(false);
|
const [configOpened, setConfigOpened] = createSignal(false);
|
||||||
@@ -93,11 +93,11 @@ const SideBar: Component = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex shrink-0 flex-row border-r bg-sidebar-bg">
|
<div class="flex shrink-0 flex-row bg-r-sidebar">
|
||||||
<div class="flex w-14 flex-auto flex-col items-center gap-3 border-r border-rose-200 pt-5">
|
<div class="flex w-14 flex-auto flex-col items-center gap-3 border-r border-border pt-5">
|
||||||
<div class="flex flex-col items-center gap-3">
|
<div class="flex flex-col items-center gap-3">
|
||||||
<button
|
<button
|
||||||
class="h-9 w-9 rounded-full border border-primary bg-primary p-2 text-2xl text-white"
|
class="h-9 w-9 rounded-full border border-primary bg-primary p-2 text-2xl text-primary-fg hover:border-primary-hover hover:bg-primary-hover"
|
||||||
onClick={() => toggleForm()}
|
onClick={() => toggleForm()}
|
||||||
>
|
>
|
||||||
<PencilSquare />
|
<PencilSquare />
|
||||||
@@ -107,13 +107,13 @@ const SideBar: Component = () => {
|
|||||||
<div class="grow" />
|
<div class="grow" />
|
||||||
<div class="flex flex-col items-center pb-2">
|
<div class="flex flex-col items-center pb-2">
|
||||||
<button
|
<button
|
||||||
class="h-10 w-12 rounded-full p-3 text-2xl text-primary"
|
class="h-10 w-12 rounded-full p-3 text-2xl text-primary hover:border-primary-hover hover:text-primary-hover"
|
||||||
onClick={() => showAddColumn()}
|
onClick={() => showAddColumn()}
|
||||||
>
|
>
|
||||||
<Plus />
|
<Plus />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="h-10 w-12 p-3 text-primary"
|
class="h-10 w-12 p-3 text-primary hover:border-primary-hover hover:text-primary-hover"
|
||||||
onClick={() => setConfigOpened((current) => !current)}
|
onClick={() => setConfigOpened((current) => !current)}
|
||||||
>
|
>
|
||||||
<Cog6Tooth />
|
<Cog6Tooth />
|
||||||
@@ -121,13 +121,14 @@ const SideBar: Component = () => {
|
|||||||
<button class="pt-2" onClick={() => showAbout()}>
|
<button class="pt-2" onClick={() => showAbout()}>
|
||||||
<img
|
<img
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
src={resolveAsset('images/rabbit_app_256.png')}
|
src={resolveAsset(getColorTheme().rabbitIconPath)}
|
||||||
alt="About rabbit"
|
alt="About rabbit"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
class="border-r border-border"
|
||||||
classList={{
|
classList={{
|
||||||
static: formOpened() || config().keepOpenPostForm,
|
static: formOpened() || config().keepOpenPostForm,
|
||||||
hidden: !(formOpened() || config().keepOpenPostForm),
|
hidden: !(formOpened() || config().keepOpenPostForm),
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ const BasicColumnHeader: Component<BasicColumnHeaderProps> = (props) => {
|
|||||||
<div class="flex h-8 items-center gap-1 px-2">
|
<div class="flex h-8 items-center gap-1 px-2">
|
||||||
<h2 class="flex min-w-0 flex-1 items-center gap-1">
|
<h2 class="flex min-w-0 flex-1 items-center gap-1">
|
||||||
<Show when={props.icon} keyed>
|
<Show when={props.icon} keyed>
|
||||||
{(icon) => <span class="inline-block h-4 w-4 shrink-0 text-gray-700">{icon}</span>}
|
{(icon) => <span class="inline-block h-4 w-4 shrink-0 text-fg-secondary">{icon}</span>}
|
||||||
</Show>
|
</Show>
|
||||||
<span class="column-name truncate">{props.name}</span>
|
<span class="truncate">{props.name}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<button class="h-4 w-4" onClick={() => toggleSettingsOpened()}>
|
<button class="flex h-full place-items-center" onClick={() => toggleSettingsOpened()}>
|
||||||
<EllipsisVertical />
|
<span class="inline-block h-4 w-4">
|
||||||
|
<EllipsisVertical />
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Show when={isSettingsOpened()}>{props.settings()}</Show>
|
<Show when={isSettingsOpened()}>{props.settings()}</Show>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const Column: Component<ColumnProps> = (props) => {
|
|||||||
<TimelineContext.Provider value={timelineState}>
|
<TimelineContext.Provider value={timelineState}>
|
||||||
<div
|
<div
|
||||||
ref={columnDivRef}
|
ref={columnDivRef}
|
||||||
class="flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r sm:snap-align-none"
|
class="flex w-[80vw] shrink-0 snap-center snap-always flex-col border-r border-border sm:snap-align-none"
|
||||||
classList={{
|
classList={{
|
||||||
'sm:w-[500px]': width() === 'widest',
|
'sm:w-[500px]': width() === 'widest',
|
||||||
'sm:w-[360px]': width() === 'wide',
|
'sm:w-[360px]': width() === 'wide',
|
||||||
@@ -58,7 +58,7 @@ const Column: Component<ColumnProps> = (props) => {
|
|||||||
keyed
|
keyed
|
||||||
fallback={
|
fallback={
|
||||||
<>
|
<>
|
||||||
<div class="shrink-0 border-b">{props.header}</div>
|
<div class="shrink-0 border-b border-border">{props.header}</div>
|
||||||
<div class="scrollbar flex flex-col overflow-y-scroll scroll-smooth pb-16">
|
<div class="scrollbar flex flex-col overflow-y-scroll scroll-smooth pb-16">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
@@ -67,7 +67,7 @@ const Column: Component<ColumnProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{(timeline) => (
|
{(timeline) => (
|
||||||
<>
|
<>
|
||||||
<div class="flex shrink-0 items-center border-b bg-white px-2">
|
<div class="flex shrink-0 items-center border-b border-border px-2">
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center gap-1"
|
class="flex w-full items-center gap-1"
|
||||||
onClick={() => timelineState?.clearTimeline()}
|
onClick={() => timelineState?.clearTimeline()}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type ColumnSettingsSectionProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) => (
|
const ColumnSettingsSection: Component<ColumnSettingsSectionProps> = (props) => (
|
||||||
<div class="flex flex-col gap-2 border-b p-2">
|
<div class="flex flex-col gap-2 border-b border-border p-2">
|
||||||
<div>{props.title}</div>
|
<div>{props.title}</div>
|
||||||
<div>{props.children}</div>
|
<div>{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,29 +41,29 @@ const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col border-t">
|
<div class="flex flex-col border-t border-border">
|
||||||
<ColumnSettingsSection title={i18n()('column.config.columnWidth')}>
|
<ColumnSettingsSection title={i18n()('column.config.columnWidth')}>
|
||||||
<div class="scrollbar flex h-9 gap-2 overflow-x-scroll">
|
<div class="scrollbar flex h-9 gap-2 overflow-x-scroll">
|
||||||
<button
|
<button
|
||||||
class="rounded-md border px-4 hover:bg-stone-100"
|
class="rounded-md border border-border px-4"
|
||||||
onClick={() => setColumnWidth('widest')}
|
onClick={() => setColumnWidth('widest')}
|
||||||
>
|
>
|
||||||
{i18n()('column.config.widest')}
|
{i18n()('column.config.widest')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded-md border px-4 hover:bg-stone-100"
|
class="rounded-md border border-border px-4"
|
||||||
onClick={() => setColumnWidth('wide')}
|
onClick={() => setColumnWidth('wide')}
|
||||||
>
|
>
|
||||||
{i18n()('column.config.wide')}
|
{i18n()('column.config.wide')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded-md border px-4 hover:bg-stone-100"
|
class="rounded-md border border-border px-4"
|
||||||
onClick={() => setColumnWidth('medium')}
|
onClick={() => setColumnWidth('medium')}
|
||||||
>
|
>
|
||||||
{i18n()('column.config.medium')}
|
{i18n()('column.config.medium')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded-md border px-4 hover:bg-stone-100"
|
class="rounded-md border border-border px-4"
|
||||||
onClick={() => setColumnWidth('narrow')}
|
onClick={() => setColumnWidth('narrow')}
|
||||||
>
|
>
|
||||||
{i18n()('column.config.narrow')}
|
{i18n()('column.config.narrow')}
|
||||||
@@ -91,7 +91,7 @@ const ColumnSettings: Component<ColumnSettingsProps> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<button
|
<button
|
||||||
class="px-2 py-4 text-rose-500 hover:text-rose-600"
|
class="px-2 py-4 text-danger hover:text-rose-600"
|
||||||
title={i18n()('column.config.removeColumn')}
|
title={i18n()('column.config.removeColumn')}
|
||||||
onClick={() => removeColumn(props.column.id)}
|
onClick={() => removeColumn(props.column.id)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ const SearchColumnHeader: Component<SearchColumnHeaderProps> = (props) => {
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex h-8 items-center gap-1 px-2">
|
<div class="flex h-8 items-center gap-1 px-2">
|
||||||
<h2 class="flex items-center gap-1">
|
<h2 class="flex items-center gap-1">
|
||||||
<span class="inline-block h-4 w-4 text-gray-700">
|
<span class="inline-block h-4 w-4 text-fg-secondary">
|
||||||
<MagnifyingGlass />
|
<MagnifyingGlass />
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<form class="flex-1" onSubmit={handleSubmit}>
|
<form class="flex-1" onSubmit={handleSubmit}>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded border border-stone-300 px-1 py-0 focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded border border-border bg-bg px-1 py-0 ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="query"
|
name="query"
|
||||||
value={query()}
|
value={query()}
|
||||||
|
|||||||
@@ -31,19 +31,19 @@ const ProfileListItem: Component<ProfileListItemProps> = (props) => {
|
|||||||
<div class="flex justify-between gap-1 text-xs">
|
<div class="flex justify-between gap-1 text-xs">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="profile flex min-w-0 select-text truncate hover:text-blue-500"
|
class="profile flex min-w-0 select-text truncate hover:text-link"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
props?.onShowProfile?.();
|
props?.onShowProfile?.();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="profile flex min-w-0 truncate hover:text-blue-500">
|
<span class="profile flex min-w-0 truncate hover:text-link">
|
||||||
<Show when={(profile()?.display_name?.length ?? 0) > 0}>
|
<Show when={(profile()?.display_name?.length ?? 0) > 0}>
|
||||||
<div class="profile-name truncate pr-1 font-bold hover:underline">
|
<div class="profile-name truncate pr-1 font-bold hover:underline">
|
||||||
{profile()?.display_name}
|
{profile()?.display_name}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="profile-username truncate text-zinc-600">
|
<div class="profile-username truncate text-fg-secondary">
|
||||||
<Show
|
<Show
|
||||||
when={profile()?.name}
|
when={profile()?.name}
|
||||||
fallback={`@${npubEncodeFallback(props.pubkey)}`}
|
fallback={`@${npubEncodeFallback(props.pubkey)}`}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const ReactionDisplay: Component<ReactionDisplayProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-1 overflow-hidden">
|
<div class="flex min-w-0 flex-1 overflow-hidden">
|
||||||
<button
|
<button
|
||||||
class="select-text truncate font-bold hover:text-blue-500 hover:underline"
|
class="select-text truncate font-bold hover:text-link hover:underline"
|
||||||
onClick={() => showProfile(props.event.pubkey)}
|
onClick={() => showProfile(props.event.pubkey)}
|
||||||
>
|
>
|
||||||
<UserDisplayName pubkey={props.event.pubkey} />
|
<UserDisplayName pubkey={props.event.pubkey} />
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ const Repost: Component<RepostProps> = (props) => {
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div class="flex shrink-0 place-items-center pl-[2px]" aria-hidden="true">
|
<div class="flex shrink-0 place-items-center pl-[2px]" aria-hidden="true">
|
||||||
<span class="h-4 w-4 text-green-500">
|
<span class="h-4 w-4 text-r-repost">
|
||||||
<ArrowPathRoundedSquare />
|
<ArrowPathRoundedSquare />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification-user flex min-w-0 flex-1 overflow-hidden text-xs">
|
<div class="notification-user flex min-w-0 flex-1 overflow-hidden text-xs">
|
||||||
<button
|
<button
|
||||||
class="select-text truncate hover:text-blue-500 hover:underline"
|
class="select-text truncate hover:text-link hover:underline"
|
||||||
onClick={() => showProfile(props.event.pubkey)}
|
onClick={() => showProfile(props.event.pubkey)}
|
||||||
>
|
>
|
||||||
<UserDisplayName pubkey={props.event.pubkey} />
|
<UserDisplayName pubkey={props.event.pubkey} />
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
<div class="textnote-content">
|
<div class="textnote-content">
|
||||||
<Show when={showReplyEvent()} keyed>
|
<Show when={showReplyEvent()} keyed>
|
||||||
{(id) => (
|
{(id) => (
|
||||||
<div class="mt-1 rounded border p-1">
|
<div class="mt-1 rounded border border-border p-1">
|
||||||
<LazyLoad fallback={<div class="h-12" />}>
|
<LazyLoad fallback={<div class="h-12" />}>
|
||||||
{() => <EventDisplayById eventId={id} actions={false} embedding={false} />}
|
{() => <EventDisplayById eventId={id} actions={false} embedding={false} />}
|
||||||
</LazyLoad>
|
</LazyLoad>
|
||||||
@@ -79,7 +79,7 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
<For each={event().taggedPubkeys()}>
|
<For each={event().taggedPubkeys()}>
|
||||||
{(replyToPubkey: string) => (
|
{(replyToPubkey: string) => (
|
||||||
<button
|
<button
|
||||||
class="select-text pr-1 text-blue-500 hover:underline"
|
class="select-text pr-1 text-link hover:underline"
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
showProfile(replyToPubkey);
|
showProfile(replyToPubkey);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const ZapReceipt: Component<ZapReceiptProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-1 overflow-hidden">
|
<div class="flex min-w-0 flex-1 overflow-hidden">
|
||||||
<button
|
<button
|
||||||
class="select-text truncate font-bold hover:text-blue-500 hover:underline"
|
class="select-text truncate font-bold hover:text-link hover:underline"
|
||||||
onClick={() => showProfile(event().senderPubkey())}
|
onClick={() => showProfile(event().senderPubkey())}
|
||||||
>
|
>
|
||||||
<UserDisplayName pubkey={event().senderPubkey()} />
|
<UserDisplayName pubkey={event().senderPubkey()} />
|
||||||
@@ -101,7 +101,7 @@ const ZapReceipt: Component<ZapReceiptProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={event().description().content.length > 0}>
|
<Show when={event().description().content.length > 0}>
|
||||||
<div class="ml-7 whitespace-pre-wrap break-all rounded border border-zinc-300 px-1 text-sm">
|
<div class="ml-7 whitespace-pre-wrap break-all rounded border border-border px-1 text-sm">
|
||||||
{event().description().content}
|
{event().description().content}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { createSignal, type Component, type JSX, Show } from 'solid-js';
|
import { createSignal, type Component, type JSX, Show } from 'solid-js';
|
||||||
|
|
||||||
|
import ExclamationTriangle from 'heroicons/24/outline/exclamation-triangle.svg';
|
||||||
|
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
import { ContentWarning } from '@/nostr/event/TextNoteLike';
|
import { ContentWarning } from '@/nostr/event/TextNoteLike';
|
||||||
|
|
||||||
@@ -17,12 +19,14 @@ const ContentWarningDisplay: Component<ContentWarningDisplayProps> = (props) =>
|
|||||||
when={!props.contentWarning.contentWarning || showContentWarning()}
|
when={!props.contentWarning.contentWarning || showContentWarning()}
|
||||||
fallback={
|
fallback={
|
||||||
<button
|
<button
|
||||||
class="mt-2 w-full rounded border p-2 text-center text-xs text-stone-600 shadow-sm hover:shadow"
|
class="mt-2 flex w-full flex-col items-center rounded border border-border p-2 text-center text-xs text-fg-secondary"
|
||||||
onClick={() => setShowContentWarning(true)}
|
onClick={() => setShowContentWarning(true)}
|
||||||
>
|
>
|
||||||
{i18n()('post.contentWarning.show')}
|
<span class="inline-block h-4 w-4">
|
||||||
|
<ExclamationTriangle />
|
||||||
|
</span>
|
||||||
|
<span>{i18n()('post.contentWarning.show')}</span>
|
||||||
<Show when={props.contentWarning.reason != null}>
|
<Show when={props.contentWarning.reason != null}>
|
||||||
<br />
|
|
||||||
<span>
|
<span>
|
||||||
{i18n()('post.contentWarning.reason')}: {props.contentWarning.reason}
|
{i18n()('post.contentWarning.reason')}: {props.contentWarning.reason}
|
||||||
</span>
|
</span>
|
||||||
@@ -33,7 +37,7 @@ const ContentWarningDisplay: Component<ContentWarningDisplayProps> = (props) =>
|
|||||||
<div>{props.children}</div>
|
<div>{props.children}</div>
|
||||||
<Show when={props.contentWarning.contentWarning}>
|
<Show when={props.contentWarning.contentWarning}>
|
||||||
<button
|
<button
|
||||||
class="text-xs text-stone-600 hover:text-stone-800"
|
class="text-xs text-fg-secondary hover:text-fg-secondary/70"
|
||||||
onClick={() => setShowContentWarning(false)}
|
onClick={() => setShowContentWarning(false)}
|
||||||
>
|
>
|
||||||
隠す
|
隠す
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
|
|||||||
when={!hidden()}
|
when={!hidden()}
|
||||||
fallback={
|
fallback={
|
||||||
<button
|
<button
|
||||||
class="rounded bg-stone-300 p-3 text-xs text-stone-600 hover:shadow"
|
class="rounded bg-bg-tertiary p-3 text-xs text-fg-secondary hover:shadow"
|
||||||
onClick={() => setHidden(false)}
|
onClick={() => setHidden(false)}
|
||||||
>
|
>
|
||||||
{i18n()('post.showImage')}
|
{i18n()('post.showImage')}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const MentionedUserDisplay = (props: MentionedUserDisplayProps) => {
|
|||||||
showProfile(props.pubkey);
|
showProfile(props.pubkey);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<button class="inline select-text text-blue-500 underline" onClick={handleClick}>
|
<button class="inline select-text text-link underline" onClick={handleClick}>
|
||||||
<GeneralUserMentionDisplay pubkey={props.pubkey} />
|
<GeneralUserMentionDisplay pubkey={props.pubkey} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,14 +32,24 @@ const youtubeUrl = (videoId: string): string => {
|
|||||||
const TwitterEmbed: Component<{ class?: string; href: string }> = (props) => {
|
const TwitterEmbed: Component<{ class?: string; href: string }> = (props) => {
|
||||||
let twitterRef: HTMLQuoteElement | undefined;
|
let twitterRef: HTMLQuoteElement | undefined;
|
||||||
|
|
||||||
|
const { getColorTheme } = useConfig();
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (isTwitterUrl(props.href)) {
|
if (isTwitterUrl(props.href)) {
|
||||||
window.twttr?.widgets?.load(twitterRef);
|
window.twttr?.widgets?.load(twitterRef);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dataTheme = () => {
|
||||||
|
const colorTheme = getColorTheme();
|
||||||
|
if (colorTheme.brightness === 'dark') {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<blockquote class="twitter-tweet" ref={twitterRef}>
|
<blockquote ref={twitterRef} class="twitter-tweet" data-theme={dataTheme()}>
|
||||||
<a
|
<a
|
||||||
class={props.class}
|
class={props.class}
|
||||||
href={twitterUrl(props.href)}
|
href={twitterUrl(props.href)}
|
||||||
@@ -61,16 +71,16 @@ const OgpEmbed: Component<{ class?: string; url: string }> = (props) => {
|
|||||||
<Show when={ogp()} fallback={<SafeLink class={props.class} href={props.url} />} keyed>
|
<Show when={ogp()} fallback={<SafeLink class={props.class} href={props.url} />} keyed>
|
||||||
{(ogpProps) => (
|
{(ogpProps) => (
|
||||||
<SafeLink href={props.url}>
|
<SafeLink href={props.url}>
|
||||||
<div class="my-2 rounded-lg border transition-colors hover:bg-slate-100">
|
<div class="my-2 rounded-lg border border-border transition-colors hover:bg-bg-tertiary">
|
||||||
<img
|
<img
|
||||||
alt={ogpProps.title}
|
alt={ogpProps.title}
|
||||||
class="max-w-full rounded-t-lg object-contain shadow"
|
class="max-w-full rounded-t-lg object-contain shadow"
|
||||||
src={ogpProps.image}
|
src={ogpProps.image}
|
||||||
/>
|
/>
|
||||||
<div class="mb-1 p-1">
|
<div class="mb-1 p-1">
|
||||||
<div class="text-xs text-slate-500">{new URL(ogpProps.url).host}</div>
|
<div class="text-xs text-fg-secondary">{new URL(ogpProps.url).host}</div>
|
||||||
<div class="text-sm">{ogpProps.title}</div>
|
<div class="text-sm">{ogpProps.title}</div>
|
||||||
<div class="text-xs text-slate-500">{ogpProps.description}</div>
|
<div class="text-xs text-fg-secondary">{ogpProps.description}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SafeLink>
|
</SafeLink>
|
||||||
|
|||||||
@@ -57,14 +57,14 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
|||||||
if (isWebSocketUrl(item.content)) {
|
if (isWebSocketUrl(item.content)) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class="select-text text-blue-500 underline"
|
class="select-text text-link underline"
|
||||||
onClick={() => addRelayColumn(item.content)}
|
onClick={() => addRelayColumn(item.content)}
|
||||||
>
|
>
|
||||||
{item.content}
|
{item.content}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <PreviewedLink class="text-blue-500 underline" href={item.content} />;
|
return <PreviewedLink class="text-link underline" href={item.content} />;
|
||||||
}
|
}
|
||||||
if (item.type === 'TagReferenceResolved') {
|
if (item.type === 'TagReferenceResolved') {
|
||||||
if (item.reference == null) {
|
if (item.reference == null) {
|
||||||
@@ -83,7 +83,7 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
|||||||
if (item.type === 'Bech32Entity') {
|
if (item.type === 'Bech32Entity') {
|
||||||
if (item.data.type === 'note' && props.embedding) {
|
if (item.data.type === 'note' && props.embedding) {
|
||||||
return (
|
return (
|
||||||
<div class="my-1 rounded border p-1">
|
<div class="my-1 rounded border border-border p-1">
|
||||||
<EventDisplayById
|
<EventDisplayById
|
||||||
eventId={item.data.data}
|
eventId={item.data.data}
|
||||||
actions={false}
|
actions={false}
|
||||||
@@ -95,7 +95,7 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
|||||||
}
|
}
|
||||||
if (item.data.type === 'nevent' && props.embedding) {
|
if (item.data.type === 'nevent' && props.embedding) {
|
||||||
return (
|
return (
|
||||||
<div class="my-1 rounded border p-1">
|
<div class="my-1 rounded border border-border p-1">
|
||||||
<EventDisplayById eventId={item.data.data.id} actions={false} embedding={false} />
|
<EventDisplayById eventId={item.data.data.id} actions={false} embedding={false} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -109,20 +109,17 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
|||||||
if (item.data.type === 'nrelay') {
|
if (item.data.type === 'nrelay') {
|
||||||
const url: string = item.data.data;
|
const url: string = item.data.data;
|
||||||
return (
|
return (
|
||||||
<button
|
<button class="select-text text-link underline" onClick={() => addRelayColumn(url)}>
|
||||||
class="select-text text-blue-500 underline"
|
|
||||||
onClick={() => addRelayColumn(url)}
|
|
||||||
>
|
|
||||||
{url} ({item.content})
|
{url} ({item.content})
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <span class="text-blue-500 underline">{item.content}</span>;
|
return <span class="text-link underline">{item.content}</span>;
|
||||||
}
|
}
|
||||||
if (item.type === 'HashTag') {
|
if (item.type === 'HashTag') {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class="select-text text-blue-500 underline"
|
class="select-text text-link underline"
|
||||||
onClick={() => addHashTagColumn(item.content)}
|
onClick={() => addHashTagColumn(item.content)}
|
||||||
>
|
>
|
||||||
{item.content}
|
{item.content}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const VideoDisplay: Component<VideoDisplayProps> = (props) => {
|
|||||||
when={!hidden()}
|
when={!hidden()}
|
||||||
fallback={
|
fallback={
|
||||||
<button
|
<button
|
||||||
class="rounded bg-stone-300 p-3 text-xs text-stone-600 hover:shadow"
|
class="rounded bg-bg-tertiary p-3 text-xs text-fg-secondary hover:shadow"
|
||||||
onClick={() => setHidden(false)}
|
onClick={() => setHidden(false)}
|
||||||
>
|
>
|
||||||
{i18n()('post.showVideo')}
|
{i18n()('post.showVideo')}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const About: Component<AboutProps> = (props) => {
|
|||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
おかしな動作を見つけたら
|
おかしな動作を見つけたら
|
||||||
<a
|
<a
|
||||||
class="text-blue-500 underline"
|
class="text-link underline"
|
||||||
href="https://github.com/syusui-s/rabbit/issues/new/choose"
|
href="https://github.com/syusui-s/rabbit/issues/new/choose"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -70,7 +70,7 @@ const About: Component<AboutProps> = (props) => {
|
|||||||
|
|
||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
ソースコードは
|
ソースコードは
|
||||||
<SafeLink class="text-blue-400 underline" href="https://github.com/syusui-s/rabbit">
|
<SafeLink class="text-link underline" href="https://github.com/syusui-s/rabbit">
|
||||||
GitHub
|
GitHub
|
||||||
</SafeLink>
|
</SafeLink>
|
||||||
で入手できます。
|
で入手できます。
|
||||||
@@ -81,7 +81,7 @@ const About: Component<AboutProps> = (props) => {
|
|||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
Copyright (C) 2023 Shusui Moyatani and{' '}
|
Copyright (C) 2023 Shusui Moyatani and{' '}
|
||||||
<SafeLink
|
<SafeLink
|
||||||
class="text-blue-400 underline"
|
class="text-link underline"
|
||||||
href="https://github.com/syusui-s/rabbit/graphs/contributors"
|
href="https://github.com/syusui-s/rabbit/graphs/contributors"
|
||||||
>
|
>
|
||||||
Rabbit contributors
|
Rabbit contributors
|
||||||
@@ -104,17 +104,15 @@ const About: Component<AboutProps> = (props) => {
|
|||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
あなたは、このプログラムに付随してGNUアフェロー一般公衆ライセンスのコピーを受け取っていることでしょう。
|
あなたは、このプログラムに付随してGNUアフェロー一般公衆ライセンスのコピーを受け取っていることでしょう。
|
||||||
そうでなければ、
|
そうでなければ、
|
||||||
<a class="link" href="https://www.gnu.org/licenses/">
|
<SafeLink href="https://www.gnu.org/licenses/" />
|
||||||
https://www.gnu.org/licenses/
|
|
||||||
</a>
|
|
||||||
をご参照ください。
|
をご参照ください。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a class="text-blue-500 underline" href="https://gpl.mhatta.org/agpl.ja.html">
|
<a class="text-link underline" href="https://gpl.mhatta.org/agpl.ja.html">
|
||||||
参考訳
|
参考訳
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<pre class="max-h-96 overflow-scroll rounded bg-zinc-100 p-4 text-xs">
|
<pre class="scorllbar max-h-96 overflow-scroll rounded bg-bg-secondary p-4 text-xs">
|
||||||
{packageInfo()?.self.licenseText}
|
{packageInfo()?.self.licenseText}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@@ -126,7 +124,7 @@ const About: Component<AboutProps> = (props) => {
|
|||||||
<h3 class="mb-2 mt-4 font-mono">
|
<h3 class="mb-2 mt-4 font-mono">
|
||||||
{p.name}@{p.version} ({p.licenseSpdx})
|
{p.name}@{p.version} ({p.licenseSpdx})
|
||||||
</h3>
|
</h3>
|
||||||
<pre class="max-h-96 overflow-scroll rounded bg-zinc-100 p-4 text-xs">
|
<pre class="scrollbar max-h-96 overflow-scroll rounded bg-bg-secondary p-4 text-xs">
|
||||||
{p.licenseText}
|
{p.licenseText}
|
||||||
</pre>
|
</pre>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
<BasicModal onClose={props.onClose}>
|
<BasicModal onClose={props.onClose}>
|
||||||
<div class="flex flex-wrap p-4">
|
<div class="flex flex-wrap p-4">
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addFollowingColumn()}
|
onClick={() => addFollowingColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
@@ -90,7 +90,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
{i18n()('column.home')}
|
{i18n()('column.home')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addNotificationColumn()}
|
onClick={() => addNotificationColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
@@ -99,7 +99,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
{i18n()('column.notification')}
|
{i18n()('column.notification')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addJapanRelaysColumn()}
|
onClick={() => addJapanRelaysColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
@@ -130,7 +130,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
*/}
|
*/}
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addSearchColumn()}
|
onClick={() => addSearchColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
@@ -139,7 +139,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
{i18n()('column.search')}
|
{i18n()('column.search')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addMyPostsColumn()}
|
onClick={() => addMyPostsColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
@@ -148,7 +148,7 @@ const AddColumn: Component<AddColumnProps> = (props) => {
|
|||||||
{i18n()('column.myPosts')}
|
{i18n()('column.myPosts')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="flex basis-1/2 flex-col items-center gap-2 py-8 sm:basis-1/4"
|
class="flex basis-1/2 flex-col items-center gap-2 py-8 hover:text-primary sm:basis-1/4"
|
||||||
onClick={() => addMyReactionsColumn()}
|
onClick={() => addMyReactionsColumn()}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-8 w-8">
|
<span class="inline-block h-8 w-8">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const BasicModal: Component<BasicModalProps> = (props) => (
|
|||||||
<Modal onClose={() => props.onClose?.()}>
|
<Modal onClose={() => props.onClose?.()}>
|
||||||
<div class="w-[640px] max-w-full">
|
<div class="w-[640px] max-w-full">
|
||||||
<button
|
<button
|
||||||
class="w-full pt-1 text-start text-stone-800"
|
class="w-full pt-1 text-start text-fg-secondary/50"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
onClick={() => props.onClose?.()}
|
onClick={() => props.onClose?.()}
|
||||||
>
|
>
|
||||||
@@ -24,7 +24,7 @@ const BasicModal: Component<BasicModalProps> = (props) => (
|
|||||||
</Show>
|
</Show>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex max-h-[calc(100vh-6em)] flex-col overflow-y-scroll rounded-xl border bg-white text-stone-700 shadow-lg">
|
<div class="scrollbar flex max-h-[calc(100vh-6em)] flex-col overflow-y-scroll rounded-xl border border-border bg-bg text-fg shadow-lg">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import XMark from 'heroicons/24/outline/x-mark.svg';
|
|||||||
|
|
||||||
import BasicModal from '@/components/modal/BasicModal';
|
import BasicModal from '@/components/modal/BasicModal';
|
||||||
import UserNameDisplay from '@/components/UserDisplayName';
|
import UserNameDisplay from '@/components/UserDisplayName';
|
||||||
|
import { colorThemes } from '@/core/colorThemes';
|
||||||
import useConfig, { type Config } from '@/core/useConfig';
|
import useConfig, { type Config } from '@/core/useConfig';
|
||||||
import useModalState from '@/hooks/useModalState';
|
import useModalState from '@/hooks/useModalState';
|
||||||
import { useTranslation } from '@/i18n/useTranslation';
|
import { useTranslation } from '@/i18n/useTranslation';
|
||||||
@@ -36,7 +37,7 @@ const ProfileSection = () => {
|
|||||||
<h3 class="font-bold">{i18n()('config.profile.profile')}</h3>
|
<h3 class="font-bold">{i18n()('config.profile.profile')}</h3>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
class="rounded border border-rose-300 px-4 py-2 font-bold text-rose-300"
|
class="rounded border border-primary px-4 py-2 font-bold text-primary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
ensureNonNull([pubkey()])(([pubkeyNonNull]) => {
|
ensureNonNull([pubkey()])(([pubkeyNonNull]) => {
|
||||||
showProfile(pubkeyNonNull);
|
showProfile(pubkeyNonNull);
|
||||||
@@ -46,7 +47,7 @@ const ProfileSection = () => {
|
|||||||
{i18n()('config.profile.openProfile')}
|
{i18n()('config.profile.openProfile')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="rounded border border-rose-300 px-4 py-2 font-bold text-rose-300"
|
class="rounded border border-primary px-4 py-2 font-bold text-primary"
|
||||||
onClick={() => showProfileEdit()}
|
onClick={() => showProfileEdit()}
|
||||||
>
|
>
|
||||||
{i18n()('config.profile.editProfile')}
|
{i18n()('config.profile.editProfile')}
|
||||||
@@ -116,14 +117,15 @@ const RelayConfig = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
<form class="flex gap-2" onSubmit={handleClickAddRelay}>
|
<form class="flex gap-2" onSubmit={handleClickAddRelay}>
|
||||||
<input
|
<input
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border border-border bg-bg ring-border placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="relayUrl"
|
name="relayUrl"
|
||||||
|
placeholder="wss://..."
|
||||||
value={relayUrlInput()}
|
value={relayUrlInput()}
|
||||||
pattern={RelayUrlRegex}
|
pattern={RelayUrlRegex}
|
||||||
onChange={(ev) => setRelayUrlInput(ev.currentTarget.value)}
|
onChange={(ev) => setRelayUrlInput(ev.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="rounded bg-rose-300 p-2 font-bold text-white">
|
<button type="submit" class="rounded bg-primary p-2 font-bold text-primary-fg">
|
||||||
{i18n()('config.relays.addRelay')}
|
{i18n()('config.relays.addRelay')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -132,7 +134,7 @@ const RelayConfig = () => {
|
|||||||
<h3 class="pb-1 font-bold">{i18n()('config.relays.importRelays')}</h3>
|
<h3 class="pb-1 font-bold">{i18n()('config.relays.importRelays')}</h3>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded bg-rose-300 p-2 font-bold text-white"
|
class="rounded bg-primary p-2 font-bold text-primary-fg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
importFromNIP07().catch((err) => {
|
importFromNIP07().catch((err) => {
|
||||||
console.error('failed to import relays', err);
|
console.error('failed to import relays', err);
|
||||||
@@ -147,6 +149,47 @@ const RelayConfig = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ColorThemeConfig = () => {
|
||||||
|
const i18n = useTranslation();
|
||||||
|
const { config, setConfig } = useConfig();
|
||||||
|
|
||||||
|
const isCurrentlyUsing = (id: string) => {
|
||||||
|
const colorThemeConfig = config().colorTheme;
|
||||||
|
if (colorThemeConfig.type === 'specific') {
|
||||||
|
return colorThemeConfig.id === id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateColorTheme = (id: string) => {
|
||||||
|
setConfig((current) => ({ ...current, colorTheme: { type: 'specific', id } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="py-2">
|
||||||
|
<h3 class="font-bold">{i18n()('config.display.colorTheme')}</h3>
|
||||||
|
<div class="flex max-h-[25vh] flex-col overflow-scroll rounded-md border border-border">
|
||||||
|
<For each={Object.values(colorThemes)}>
|
||||||
|
{(colorTheme) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="border-t border-border px-2 py-1 text-left"
|
||||||
|
classList={{
|
||||||
|
'bg-primary': isCurrentlyUsing(colorTheme.id),
|
||||||
|
'text-primary-fg': isCurrentlyUsing(colorTheme.id),
|
||||||
|
'text-fg': !isCurrentlyUsing(colorTheme.id),
|
||||||
|
}}
|
||||||
|
onClick={() => updateColorTheme(colorTheme.id)}
|
||||||
|
>
|
||||||
|
{colorTheme.name}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const DateFormatConfig = () => {
|
const DateFormatConfig = () => {
|
||||||
const i18n = useTranslation();
|
const i18n = useTranslation();
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
@@ -186,12 +229,12 @@ const DateFormatConfig = () => {
|
|||||||
<div class="flex flex-1 flex-row items-center gap-1 sm:flex-col">
|
<div class="flex flex-1 flex-row items-center gap-1 sm:flex-col">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-48 rounded border border-rose-300 p-2 font-bold sm:w-full"
|
class="w-48 rounded border border-primary p-2 font-bold sm:w-full"
|
||||||
classList={{
|
classList={{
|
||||||
'bg-rose-300': config().dateFormat === id,
|
'bg-primary': config().dateFormat === id,
|
||||||
'text-white': config().dateFormat === id,
|
'text-primary-fg': config().dateFormat === id,
|
||||||
'bg-white': config().dateFormat !== id,
|
'bg-bg': config().dateFormat !== id,
|
||||||
'text-rose-300': config().dateFormat !== id,
|
'text-primary': config().dateFormat !== id,
|
||||||
}}
|
}}
|
||||||
onClick={() => updateDateFormat(id)}
|
onClick={() => updateDateFormat(id)}
|
||||||
>
|
>
|
||||||
@@ -211,17 +254,17 @@ const ToggleButton = (props: {
|
|||||||
onClick: JSX.EventHandler<HTMLButtonElement, MouseEvent>;
|
onClick: JSX.EventHandler<HTMLButtonElement, MouseEvent>;
|
||||||
}) => (
|
}) => (
|
||||||
<button
|
<button
|
||||||
class="flex h-[24px] w-[48px] items-center rounded-full border border-primary px-1"
|
class="flex h-[24px] w-[48px] items-center rounded-full border border-primary/80 px-1"
|
||||||
classList={{
|
classList={{
|
||||||
'bg-white': !props.value,
|
'bg-bg-tertiary': !props.value,
|
||||||
'justify-start': !props.value,
|
'justify-start': !props.value,
|
||||||
'bg-rose-300': props.value,
|
'bg-primary': props.value,
|
||||||
'justify-end': props.value,
|
'justify-end': props.value,
|
||||||
}}
|
}}
|
||||||
area-label={props.value}
|
area-label={props.value}
|
||||||
onClick={(event) => props.onClick(event)}
|
onClick={(event) => props.onClick(event)}
|
||||||
>
|
>
|
||||||
<span class="m-[-2px] inline-block h-5 w-5 rounded-full border border-primary bg-white shadow" />
|
<span class="m-[-3px] inline-block h-5 w-5 rounded-full border bg-primary-fg shadow" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -301,7 +344,7 @@ const EmojiConfig = () => {
|
|||||||
<label class="flex flex-1 items-center gap-1">
|
<label class="flex flex-1 items-center gap-1">
|
||||||
<div class="w-9">{i18n()('config.customEmoji.shortcode')}</div>
|
<div class="w-9">{i18n()('config.customEmoji.shortcode')}</div>
|
||||||
<input
|
<input
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border-border bg-bg placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="shortcode"
|
name="shortcode"
|
||||||
placeholder="smiley"
|
placeholder="smiley"
|
||||||
@@ -314,7 +357,7 @@ const EmojiConfig = () => {
|
|||||||
<label class="flex flex-1 items-center gap-1">
|
<label class="flex flex-1 items-center gap-1">
|
||||||
<div class="w-9">{i18n()('config.customEmoji.url')}</div>
|
<div class="w-9">{i18n()('config.customEmoji.url')}</div>
|
||||||
<input
|
<input
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border-border bg-bg placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="url"
|
name="url"
|
||||||
value={urlInput()}
|
value={urlInput()}
|
||||||
@@ -324,7 +367,10 @@ const EmojiConfig = () => {
|
|||||||
onChange={(ev) => setUrlInput(ev.currentTarget.value)}
|
onChange={(ev) => setUrlInput(ev.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" class="w-24 self-end rounded bg-rose-300 p-2 font-bold text-white">
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-24 self-end rounded bg-primary p-2 font-bold text-primary-fg"
|
||||||
|
>
|
||||||
{i18n()('config.customEmoji.addEmoji')}
|
{i18n()('config.customEmoji.addEmoji')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -359,13 +405,16 @@ const EmojiImport = () => {
|
|||||||
<p>{i18n()('config.customEmoji.emojiImportDescription')}</p>
|
<p>{i18n()('config.customEmoji.emojiImportDescription')}</p>
|
||||||
<form class="flex flex-col gap-2" onSubmit={handleClickSaveEmoji}>
|
<form class="flex flex-col gap-2" onSubmit={handleClickSaveEmoji}>
|
||||||
<textarea
|
<textarea
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border-border bg-bg placeholder:text-fg-secondary focus:border-border focus:ring-primary"
|
||||||
name="json"
|
name="json"
|
||||||
value={jsonInput()}
|
value={jsonInput()}
|
||||||
placeholder='{ "smiley": "https://example.com/smiley.png" }'
|
placeholder='{ "smiley": "https://example.com/smiley.png" }'
|
||||||
onChange={(ev) => setJSONInput(ev.currentTarget.value)}
|
onChange={(ev) => setJSONInput(ev.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="w-24 self-end rounded bg-rose-300 p-2 font-bold text-white">
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-24 self-end rounded bg-primary p-2 font-bold text-primary-fg"
|
||||||
|
>
|
||||||
{i18n()('config.customEmoji.importEmoji')}
|
{i18n()('config.customEmoji.importEmoji')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -421,13 +470,13 @@ const MuteConfig = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
<form class="flex gap-2" onSubmit={handleClickAddKeyword}>
|
<form class="flex gap-2" onSubmit={handleClickAddKeyword}>
|
||||||
<input
|
<input
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="keyword"
|
name="keyword"
|
||||||
value={keywordInput()}
|
value={keywordInput()}
|
||||||
onChange={(ev) => setKeywordInput(ev.currentTarget.value)}
|
onChange={(ev) => setKeywordInput(ev.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="rounded bg-rose-300 p-2 font-bold text-white">
|
<button type="submit" class="rounded bg-primary p-2 font-bold text-primary-fg">
|
||||||
{i18n()('config.mute.add')}
|
{i18n()('config.mute.add')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -551,6 +600,7 @@ const ConfigUI = (props: ConfigProps) => {
|
|||||||
icon: () => <PaintBrush />,
|
icon: () => <PaintBrush />,
|
||||||
render: () => (
|
render: () => (
|
||||||
<>
|
<>
|
||||||
|
<ColorThemeConfig />
|
||||||
<DateFormatConfig />
|
<DateFormatConfig />
|
||||||
<ReactionConfig />
|
<ReactionConfig />
|
||||||
<EmbeddingConfig />
|
<EmbeddingConfig />
|
||||||
@@ -594,7 +644,7 @@ const ConfigUI = (props: ConfigProps) => {
|
|||||||
{(menuItem, i) => (
|
{(menuItem, i) => (
|
||||||
<li class="w-full">
|
<li class="w-full">
|
||||||
<button
|
<button
|
||||||
class="flex w-full gap-2 py-3 hover:text-rose-400"
|
class="flex w-full gap-2 py-3 hover:text-primary"
|
||||||
onClick={() => setMenuIndex(i)}
|
onClick={() => setMenuIndex(i)}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-6 w-6">{menuItem.icon()}</span>
|
<span class="inline-block h-6 w-6">{menuItem.icon()}</span>
|
||||||
|
|||||||
@@ -24,14 +24,18 @@ const EventDebugModal: Component<EventDebugModalProps> = (props) => {
|
|||||||
<BasicModal onClose={props.onClose}>
|
<BasicModal onClose={props.onClose}>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<h2 class="text-lg font-bold">JSON</h2>
|
<h2 class="text-lg font-bold">JSON</h2>
|
||||||
<pre class="whitespace-pre-wrap break-all rounded-lg border p-4 text-xs">{json()}</pre>
|
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
|
||||||
|
{json()}
|
||||||
|
</pre>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<Copy class="h-4 w-4" text={json()} />
|
<Copy class="h-4 w-4 hover:text-primary" text={json()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<h2 class="text-lg font-bold">Found in these relays</h2>
|
<h2 class="text-lg font-bold">Found in these relays</h2>
|
||||||
<pre class="whitespace-pre-wrap break-all rounded-lg border p-2 text-xs">{seenOn()}</pre>
|
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-2 text-xs">
|
||||||
|
{seenOn()}
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
<Match when={props.pubkey === myPubkey()}>
|
<Match when={props.pubkey === myPubkey()}>
|
||||||
<button
|
<button
|
||||||
class="rounded-full border border-primary px-4 py-2
|
class="rounded-full border border-primary px-4 py-2
|
||||||
text-center font-bold text-primary hover:bg-primary hover:text-white sm:w-20"
|
text-center font-bold text-primary hover:bg-primary hover:text-primary-fg sm:w-20"
|
||||||
onClick={() => showProfileEdit()}
|
onClick={() => showProfileEdit()}
|
||||||
>
|
>
|
||||||
{i18n()('profile.editProfile')}
|
{i18n()('profile.editProfile')}
|
||||||
@@ -320,8 +320,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</Match>
|
</Match>
|
||||||
<Match when={following()}>
|
<Match when={following()}>
|
||||||
<button
|
<button
|
||||||
class="rounded-full border border-primary bg-primary px-4 py-2
|
class="rounded-full border border-primary bg-primary px-4 py-2 text-center font-bold text-primary-fg hover:bg-primary-hover sm:w-36"
|
||||||
text-center font-bold text-white hover:bg-rose-500 sm:w-36"
|
|
||||||
onMouseEnter={() => setHoverFollowButton(true)}
|
onMouseEnter={() => setHoverFollowButton(true)}
|
||||||
onMouseLeave={() => setHoverFollowButton(false)}
|
onMouseLeave={() => setHoverFollowButton(false)}
|
||||||
onClick={() => unfollow()}
|
onClick={() => unfollow()}
|
||||||
@@ -334,8 +333,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</Match>
|
</Match>
|
||||||
<Match when={!following()}>
|
<Match when={!following()}>
|
||||||
<button
|
<button
|
||||||
class="w-28 rounded-full border border-primary px-4 py-2 text-primary
|
class="w-28 rounded-full border border-primary px-4 py-2 text-primary hover:border-primary-hover hover:text-primary-hover"
|
||||||
hover:border-rose-400 hover:text-rose-400"
|
|
||||||
onClick={() => follow()}
|
onClick={() => follow()}
|
||||||
disabled={updateContactsMutation.isPending}
|
disabled={updateContactsMutation.isPending}
|
||||||
>
|
>
|
||||||
@@ -344,10 +342,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
<ContextMenu menu={menu}>
|
<ContextMenu menu={menu}>
|
||||||
<button
|
<button class="w-10 rounded-full border border-primary p-2 text-primary hover:border-primary-hover hover:text-primary-hover">
|
||||||
class="w-10 rounded-full border border-primary p-2 text-primary
|
|
||||||
hover:border-rose-400 hover:text-rose-400"
|
|
||||||
>
|
|
||||||
<EllipsisHorizontal />
|
<EllipsisHorizontal />
|
||||||
</button>
|
</button>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
@@ -378,7 +373,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
{nip05Identifier()?.ident}
|
{nip05Identifier()?.ident}
|
||||||
<Switch
|
<Switch
|
||||||
fallback={
|
fallback={
|
||||||
<span class="inline-block h-4 w-4 text-rose-500">
|
<span class="inline-block h-4 w-4 text-danger">
|
||||||
<ExclamationCircle />
|
<ExclamationCircle />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -389,7 +384,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={isVerified()}>
|
<Match when={isVerified()}>
|
||||||
<span class="inline-block h-4 w-4 text-blue-400">
|
<span class="inline-block h-4 w-4 text-link">
|
||||||
<CheckCircle />
|
<CheckCircle />
|
||||||
</span>
|
</span>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -409,7 +404,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex border-t px-4 py-2">
|
<div class="flex border-t border-border px-4 py-2">
|
||||||
<button class="flex flex-1 flex-col items-start" onClick={() => setModal('Following')}>
|
<button class="flex flex-1 flex-col items-start" onClick={() => setModal('Following')}>
|
||||||
<div class="text-sm">{i18n()('profile.following')}</div>
|
<div class="text-sm">{i18n()('profile.following')}</div>
|
||||||
<div class="text-xl">
|
<div class="text-xl">
|
||||||
@@ -429,7 +424,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
when={showFollowers()}
|
when={showFollowers()}
|
||||||
fallback={
|
fallback={
|
||||||
<button
|
<button
|
||||||
class="text-sm hover:text-stone-800 hover:underline"
|
class="text-sm hover:text-fg-secondary"
|
||||||
onClick={() => setShowFollowers(true)}
|
onClick={() => setShowFollowers(true)}
|
||||||
>
|
>
|
||||||
{i18n()('profile.loadFollowers')}
|
{i18n()('profile.loadFollowers')}
|
||||||
@@ -444,14 +439,14 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={(profile()?.website ?? '').length > 0}>
|
<Show when={(profile()?.website ?? '').length > 0}>
|
||||||
<ul class="border-t px-5 py-2 text-xs">
|
<ul class="border-t border-border px-5 py-2 text-xs">
|
||||||
<Show when={profile()?.website} keyed>
|
<Show when={profile()?.website} keyed>
|
||||||
{(website) => (
|
{(website) => (
|
||||||
<li class="flex items-center gap-1">
|
<li class="flex items-center gap-1">
|
||||||
<span class="inline-block h-4 w-4" area-label="website" title="website">
|
<span class="inline-block h-4 w-4" area-label="website" title="website">
|
||||||
<GlobeAlt />
|
<GlobeAlt />
|
||||||
</span>
|
</span>
|
||||||
<SafeLink class="text-blue-500 underline" href={website} />
|
<SafeLink class="text-link underline" href={website} />
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
@@ -462,7 +457,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
|||||||
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
|
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
<ul class="border-t p-1">
|
<ul class="border-t border-border p-1">
|
||||||
<Timeline events={events()} />
|
<Timeline events={events()} />
|
||||||
</ul>
|
</ul>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
const loading = () => query.isPending || mutation.isPending;
|
const loading = () => query.isPending || mutation.isPending;
|
||||||
const disabled = () => loading();
|
const disabled = () => loading();
|
||||||
|
|
||||||
setInterval(() => console.log(query.isPending, mutation.isPending), 1000);
|
|
||||||
|
|
||||||
const otherProperties = () =>
|
const otherProperties = () =>
|
||||||
omit(profile(), [
|
omit(profile(), [
|
||||||
'picture',
|
'picture',
|
||||||
@@ -170,7 +168,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.icon')}
|
{i18n()('profile.edit.icon')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
id="picture"
|
id="picture"
|
||||||
name="picture"
|
name="picture"
|
||||||
@@ -186,7 +184,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.banner')}
|
{i18n()('profile.edit.banner')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
id="banner"
|
id="banner"
|
||||||
name="banner"
|
name="banner"
|
||||||
@@ -204,12 +202,11 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
<div class="flex w-full items-center gap-2">
|
<div class="flex w-full items-center gap-2">
|
||||||
<span>@</span>
|
<span>@</span>
|
||||||
<input
|
<input
|
||||||
class="flex-1 rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="flex-1 rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
value={name()}
|
value={name()}
|
||||||
// pattern="^[a-zA-Z_][a-zA-Z0-9_]+$"
|
|
||||||
maxlength="32"
|
maxlength="32"
|
||||||
required
|
required
|
||||||
disabled={disabled()}
|
disabled={disabled()}
|
||||||
@@ -223,7 +220,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.displayName')}
|
{i18n()('profile.edit.displayName')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="displayName"
|
name="displayName"
|
||||||
value={displayName()}
|
value={displayName()}
|
||||||
@@ -238,7 +235,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.about')}
|
{i18n()('profile.edit.about')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
name="about"
|
name="about"
|
||||||
value={about()}
|
value={about()}
|
||||||
rows="5"
|
rows="5"
|
||||||
@@ -251,7 +248,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.website')}
|
{i18n()('profile.edit.website')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="website"
|
name="website"
|
||||||
value={website()}
|
value={website()}
|
||||||
@@ -266,7 +263,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
{i18n()('profile.edit.nip05')}
|
{i18n()('profile.edit.nip05')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="nip05"
|
name="nip05"
|
||||||
value={nip05()}
|
value={nip05()}
|
||||||
@@ -283,7 +280,7 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
</label>
|
</label>
|
||||||
<span class="text-xs">{i18n()('profile.edit.lightningAddressDescription')}</span>
|
<span class="text-xs">{i18n()('profile.edit.lightningAddressDescription')}</span>
|
||||||
<input
|
<input
|
||||||
class="w-full rounded-md focus:border-rose-100 focus:ring-rose-300"
|
class="w-full rounded-md border-border bg-bg ring-border focus:border-border focus:ring-primary"
|
||||||
type="text"
|
type="text"
|
||||||
name="website"
|
name="website"
|
||||||
value={lightningAddress()}
|
value={lightningAddress()}
|
||||||
@@ -312,14 +309,18 @@ const ProfileEdit: Component<ProfileEditProps> = (props) => {
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded bg-rose-300 p-2 font-bold text-white hover:bg-rose-400"
|
class="rounded p-2 font-bold text-primary-fg hover:bg-primary-hover"
|
||||||
|
classList={{
|
||||||
|
'bg-primary': !mutation.isPending,
|
||||||
|
'bg-primary-disabled': mutation.isPending,
|
||||||
|
}}
|
||||||
disabled={mutation.isPending}
|
disabled={mutation.isPending}
|
||||||
>
|
>
|
||||||
{i18n()('profile.edit.save')}
|
{i18n()('profile.edit.save')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded border border-rose-300 p-2 font-bold text-rose-300 hover:border-rose-400 hover:text-rose-400"
|
class="rounded border border-primary p-2 font-bold text-primary hover:border-primary-hover hover:text-primary-hover"
|
||||||
onClick={() => props.onClose()}
|
onClick={() => props.onClose()}
|
||||||
>
|
>
|
||||||
{i18n()('profile.edit.cancel')}
|
{i18n()('profile.edit.cancel')}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const UserList = <T,>(props: UserListProps<T>): JSX.Element => {
|
|||||||
{(e) => {
|
{(e) => {
|
||||||
const pubkey = () => props.pubkeyExtractor(e);
|
const pubkey = () => props.pubkeyExtractor(e);
|
||||||
return (
|
return (
|
||||||
<div class="flex border-t py-1">
|
<div class="flex border-t border-border py-1">
|
||||||
<Show when={props.renderInfo} keyed>
|
<Show when={props.renderInfo} keyed>
|
||||||
{(render) => render(e)}
|
{(render) => render(e)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ const Copy: Component<CopyProps> = (props) => {
|
|||||||
<ClipboardDocument />
|
<ClipboardDocument />
|
||||||
</button>
|
</button>
|
||||||
<Show when={showPopup()}>
|
<Show when={showPopup()}>
|
||||||
<div
|
<div class="absolute left-[-2.5rem] top-[-1.5rem] rounded bg-primary p-1 text-xs font-bold text-primary-fg shadow">
|
||||||
class="absolute left-[-2.5rem] top-[-1.5rem] rounded
|
|
||||||
bg-rose-300 p-1 text-xs font-bold text-white shadow"
|
|
||||||
>
|
|
||||||
Copied!
|
Copied!
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
31
src/core/colorThemes.ts
Normal file
31
src/core/colorThemes.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export type ColorTheme = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
brightness: 'light' | 'dark';
|
||||||
|
className?: string;
|
||||||
|
rabbitIconPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const colorThemes: Record<string, ColorTheme> = {
|
||||||
|
sakura: {
|
||||||
|
id: 'sakura',
|
||||||
|
name: 'Sakura',
|
||||||
|
brightness: 'light',
|
||||||
|
className: 'theme-sakura',
|
||||||
|
rabbitIconPath: 'images/rabbit_app_256.png',
|
||||||
|
},
|
||||||
|
cinnamon: {
|
||||||
|
id: 'cinnamon',
|
||||||
|
name: 'Cinnamon',
|
||||||
|
brightness: 'light',
|
||||||
|
className: 'theme-cinnamon',
|
||||||
|
rabbitIconPath: 'images/rabbit_muted_256.png',
|
||||||
|
},
|
||||||
|
yozakura: {
|
||||||
|
id: 'yozakura',
|
||||||
|
name: 'Yozakura',
|
||||||
|
brightness: 'dark',
|
||||||
|
className: 'theme-yozakura',
|
||||||
|
rabbitIconPath: 'images/rabbit_256.png',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import uniq from 'lodash/uniq';
|
|||||||
import * as Kind from 'nostr-tools/kinds';
|
import * as Kind from 'nostr-tools/kinds';
|
||||||
import { type Event as NostrEvent } from 'nostr-tools/pure';
|
import { type Event as NostrEvent } from 'nostr-tools/pure';
|
||||||
|
|
||||||
|
import { colorThemes, type ColorTheme } from '@/core/colorThemes';
|
||||||
import {
|
import {
|
||||||
ColumnType,
|
ColumnType,
|
||||||
createFollowingColumn,
|
createFollowingColumn,
|
||||||
@@ -26,10 +27,16 @@ export type CustomEmojiConfig = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ColorThemeConfig = {
|
||||||
|
type: 'specific';
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
relayUrls: string[];
|
relayUrls: string[];
|
||||||
columns: ColumnType[];
|
columns: ColumnType[];
|
||||||
customEmojis: Record<string, CustomEmojiConfig>;
|
customEmojis: Record<string, CustomEmojiConfig>;
|
||||||
|
colorTheme: ColorThemeConfig;
|
||||||
dateFormat: 'relative' | 'absolute-long' | 'absolute-short';
|
dateFormat: 'relative' | 'absolute-long' | 'absolute-short';
|
||||||
keepOpenPostForm: boolean;
|
keepOpenPostForm: boolean;
|
||||||
useEmojiReaction: boolean;
|
useEmojiReaction: boolean;
|
||||||
@@ -48,6 +55,8 @@ export type Config = {
|
|||||||
type UseConfig = {
|
type UseConfig = {
|
||||||
config: Accessor<Config>;
|
config: Accessor<Config>;
|
||||||
setConfig: Setter<Config>;
|
setConfig: Setter<Config>;
|
||||||
|
// display
|
||||||
|
getColorTheme: () => ColorTheme;
|
||||||
// relay
|
// relay
|
||||||
addRelay: (url: string) => void;
|
addRelay: (url: string) => void;
|
||||||
removeRelay: (url: string) => void;
|
removeRelay: (url: string) => void;
|
||||||
@@ -83,6 +92,7 @@ const InitialConfig = (): Config => ({
|
|||||||
relayUrls: initialRelays(),
|
relayUrls: initialRelays(),
|
||||||
columns: [],
|
columns: [],
|
||||||
customEmojis: {},
|
customEmojis: {},
|
||||||
|
colorTheme: { type: 'specific', id: 'sakura' },
|
||||||
dateFormat: 'relative',
|
dateFormat: 'relative',
|
||||||
keepOpenPostForm: false,
|
keepOpenPostForm: false,
|
||||||
useEmojiReaction: true,
|
useEmojiReaction: true,
|
||||||
@@ -114,6 +124,14 @@ const [config, setConfig] = createRoot(() =>
|
|||||||
const useConfig = (): UseConfig => {
|
const useConfig = (): UseConfig => {
|
||||||
const i18n = useTranslation();
|
const i18n = useTranslation();
|
||||||
|
|
||||||
|
const getColorTheme = (): ColorTheme | null => {
|
||||||
|
const colorThemeConfig = config.colorTheme;
|
||||||
|
if (colorThemeConfig.type === 'specific' && colorThemes[colorThemeConfig.id] != null) {
|
||||||
|
return colorThemes[colorThemeConfig.id];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const addRelay = (relayUrl: string) => {
|
const addRelay = (relayUrl: string) => {
|
||||||
setConfig('relayUrls', (current) => uniq([...current, relayUrl]));
|
setConfig('relayUrls', (current) => uniq([...current, relayUrl]));
|
||||||
};
|
};
|
||||||
@@ -233,6 +251,8 @@ const useConfig = (): UseConfig => {
|
|||||||
return {
|
return {
|
||||||
config: () => config,
|
config: () => config,
|
||||||
setConfig,
|
setConfig,
|
||||||
|
// display
|
||||||
|
getColorTheme,
|
||||||
// relay
|
// relay
|
||||||
addRelay,
|
addRelay,
|
||||||
removeRelay,
|
removeRelay,
|
||||||
|
|||||||
23
src/hooks/useColorTheme.ts
Normal file
23
src/hooks/useColorTheme.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createEffect, onCleanup } from 'solid-js';
|
||||||
|
|
||||||
|
import useConfig from '@/core/useConfig';
|
||||||
|
|
||||||
|
export const useColorTheme = (el: HTMLElement) => {
|
||||||
|
const { getColorTheme } = useConfig();
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const colorTheme = getColorTheme();
|
||||||
|
if (colorTheme == null) return;
|
||||||
|
|
||||||
|
const { className } = colorTheme;
|
||||||
|
if (className != null) {
|
||||||
|
el.classList.add(className);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
el.classList.remove(className);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColorTheme;
|
||||||
@@ -26,7 +26,7 @@ const useEmojiComplete = () => {
|
|||||||
},
|
},
|
||||||
template: (config: CustomEmojiConfig) => {
|
template: (config: CustomEmojiConfig) => {
|
||||||
const e = (
|
const e = (
|
||||||
<div class="flex gap-1 border-b px-2 py-1">
|
<div class="flex gap-1 border-b border-border px-2 py-1">
|
||||||
<img class="h-6 max-w-[3rem]" src={config.url} alt={config.shortcode} />
|
<img class="h-6 max-w-[3rem]" src={config.url} alt={config.shortcode} />
|
||||||
<div>{config.shortcode}</div>
|
<div>{config.shortcode}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,10 +38,10 @@ const useEmojiComplete = () => {
|
|||||||
],
|
],
|
||||||
{
|
{
|
||||||
dropdown: {
|
dropdown: {
|
||||||
className: 'bg-white shadow rounded',
|
className: 'bg-bg shadow rounded',
|
||||||
item: {
|
item: {
|
||||||
className: 'cursor-pointer',
|
className: 'cursor-pointer',
|
||||||
activeClassName: 'bg-rose-100 cursor-pointer',
|
activeClassName: 'bg-bg-tertiary cursor-pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
105
src/index.css
105
src/index.css
@@ -2,36 +2,99 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* LIGHT Sakura */
|
||||||
|
.theme-sakura {
|
||||||
|
--color-fg: 38 25 23;
|
||||||
|
--color-fg-secondary: 87 83 78;
|
||||||
|
--color-fg-tertiary: 168 162 158;
|
||||||
|
--color-bg: 255 255 255;
|
||||||
|
--color-bg-secondary: 168 162 158;
|
||||||
|
--color-bg-tertiary: 235 235 233;
|
||||||
|
--color-primary: 253 164 175;
|
||||||
|
--color-primary-fg: 255 255 255;
|
||||||
|
--color-primary-disabled: 254 205 211;
|
||||||
|
--color-primary-hover: 253 155 167;
|
||||||
|
--color-danger: 244 63 94;
|
||||||
|
--color-link: 59 130 246;
|
||||||
|
--color-border: 231 229 228;
|
||||||
|
--color-r-sidebar: 255 228 230;
|
||||||
|
--color-r-reaction: 251 113 133;
|
||||||
|
--color-r-repost: 74 222 128;
|
||||||
|
--color-scroll-thumb: 254 205 211;
|
||||||
|
--color-scroll-bg: 255 255 255 / 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIGHT Cinnamon */
|
||||||
|
.theme-cinnamon {
|
||||||
|
--color-fg: 49 38 38;
|
||||||
|
--color-fg-secondary: 87 80 58;
|
||||||
|
--color-fg-tertiary: 168 162 158;
|
||||||
|
--color-bg: 234 220 211;
|
||||||
|
--color-bg-secondary: 168 162 158;
|
||||||
|
--color-bg-tertiary: 220 205 190;
|
||||||
|
--color-primary: 67 57 56;
|
||||||
|
--color-primary-fg: 255 255 255;
|
||||||
|
--color-primary-disabled: 100 90 90;
|
||||||
|
--color-primary-hover: 80 64 63;
|
||||||
|
--color-danger: 244 63 94;
|
||||||
|
--color-link: 74 149 188;
|
||||||
|
--color-border: 215 198 189;
|
||||||
|
--color-r-sidebar: 133 118 106;
|
||||||
|
--color-r-reaction: 249 110 130;
|
||||||
|
--color-r-repost: 0 190 0;
|
||||||
|
--color-scroll-thumb: 49 38 38;
|
||||||
|
--color-scroll-bg: 245 235 222 / 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK Yozakura */
|
||||||
|
.theme-yozakura {
|
||||||
|
--color-fg: 255 235 235;
|
||||||
|
--color-fg-secondary: 220 180 180;
|
||||||
|
--color-fg-tertiary: 160 130 130;
|
||||||
|
--color-bg: 35 27 33;
|
||||||
|
--color-bg-secondary: 50 40 50;
|
||||||
|
--color-bg-tertiary: 63 40 50;
|
||||||
|
--color-primary: 217 121 133;
|
||||||
|
--color-primary-fg: var(--color-fg);
|
||||||
|
--color-primary-disabled: 158 93 102;
|
||||||
|
--color-primary-hover: 220 110 120;
|
||||||
|
--color-danger: 244 63 94;
|
||||||
|
--color-link: 59 130 246;
|
||||||
|
--color-border: 70 50 60;
|
||||||
|
--color-r-sidebar: 35 27 33;
|
||||||
|
--color-r-reaction: 251 113 133;
|
||||||
|
--color-r-repost: 139 191 67;
|
||||||
|
--color-scroll-thumb: var(--color-primary-fg);
|
||||||
|
--color-scroll-bg: var(--color-bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
||||||
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
@apply bg-bg text-fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
|
||||||
@apply underline text-blue-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
em-emoji-picker {
|
em-emoji-picker {
|
||||||
--background-rgb: 85, 170, 255;
|
--background-rgb: 85, 170, 255;
|
||||||
--border-radius: 8px;
|
--border-radius: 8px;
|
||||||
--color-border-over: rgba(0, 0, 0, 0.1);
|
--color-border-over: rgba(0, 0, 0, 0.3);
|
||||||
--color-border: rgba(0, 0, 0, 0.05);
|
--color-border: rgba(0, 0, 0, 0.2);
|
||||||
--category-icon-size: 20px;
|
--category-icon-size: 20px;
|
||||||
--font-size: 16px;
|
--font-size: 14px;
|
||||||
--rgb-accent: 253, 164, 175;
|
--rgb-accent: var(--color-primary);
|
||||||
--rgb-background: 255, 255, 255;
|
--rgb-background: var(--color-bg);
|
||||||
--rgb-color: 28, 25, 23;
|
--rgb-color: var(--color-fg);
|
||||||
--rgb-input: 255, 255, 255;
|
--rgb-input: var(--color-bg);
|
||||||
--shadow: 0 5px 8px -8px #222;
|
--shadow: 0 8px 8px -8px #000;
|
||||||
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(var(--color-border));
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
max-height: 800px;
|
max-height: 800px;
|
||||||
@@ -39,19 +102,23 @@ em-emoji-picker {
|
|||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrollbar {
|
||||||
|
scrollbar-color: rgb(var(--color-scroll-thumb)) rgb(var(--color-scroll-bg));
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar::-webkit-scrollbar:vertical {
|
.scrollbar::-webkit-scrollbar:vertical {
|
||||||
width: 8px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
.scrollbar::-webkit-scrollbar:horizontal {
|
.scrollbar::-webkit-scrollbar:horizontal {
|
||||||
height: 8px;
|
height: 5px;
|
||||||
}
|
}
|
||||||
.scrollbar::-webkit-scrollbar {
|
.scrollbar::-webkit-scrollbar {
|
||||||
background-color: #fbf9f9;
|
background-color: rgb(var(--color-scroll-bg));
|
||||||
}
|
}
|
||||||
.scrollbar::-webkit-scrollbar-thumb {
|
.scrollbar::-webkit-scrollbar-thumb {
|
||||||
background-color: #fce9ec;
|
background-color: rgb(var(--color-scroll-thumb));
|
||||||
border-radius: 8px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.scrollbar::-webkit-scrollbar-thumb:hover {
|
.scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #fcd9dc;
|
background-color: rgb(var(--color-scroll-thumb));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export default {
|
|||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
display: 'Display',
|
display: 'Display',
|
||||||
|
colorTheme: 'Color theme',
|
||||||
timeNotation: 'Time notation',
|
timeNotation: 'Time notation',
|
||||||
relativeTimeNotation: 'Relative',
|
relativeTimeNotation: 'Relative',
|
||||||
relativeTimeNotationExample: '7s',
|
relativeTimeNotationExample: '7s',
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ export default {
|
|||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
display: '表示',
|
display: '表示',
|
||||||
|
colorTheme: 'カラーテーマ',
|
||||||
timeNotation: '時刻の表記',
|
timeNotation: '時刻の表記',
|
||||||
relativeTimeNotation: '相対表記',
|
relativeTimeNotation: '相対表記',
|
||||||
relativeTimeNotationExample: '7秒前',
|
relativeTimeNotationExample: '7秒前',
|
||||||
|
|||||||
@@ -49,16 +49,11 @@ const Hello: Component = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="mx-auto flex max-w-[640px] flex-col items-center p-4 text-stone-600">
|
<div class="mx-auto flex max-w-[640px] flex-col items-center p-4 text-fg">
|
||||||
<div class="flex flex-col items-center gap-4 rounded bg-white p-4">
|
<div class="flex flex-col items-center gap-4 rounded p-4">
|
||||||
<img src={resolveAsset('images/rabbit_256.png')} width="96" alt="logo" height="96" />
|
<img src={resolveAsset('images/rabbit_256.png')} width="96" alt="logo" height="96" />
|
||||||
<h1 class="text-5xl font-black text-rose-300">Rabbit</h1>
|
<h1 class="text-5xl font-black text-primary">Rabbit</h1>
|
||||||
<div>Rabbit is a Web client for Nostr.</div>
|
<div>Rabbit is a Web client for Nostr.</div>
|
||||||
<p class="text-center">
|
|
||||||
<span class="font-bold text-rose-400">注意: 現在ベータ版です。</span>
|
|
||||||
<br />
|
|
||||||
未実装の機能やバグがあることを承知の上でご利用ください。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-md p-8 shadow-md">
|
<div class="rounded-md p-8 shadow-md">
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -72,7 +67,7 @@ const Hello: Component = () => {
|
|||||||
初めて利用する方も、他のクライアントをつかっている方も
|
初めて利用する方も、他のクライアントをつかっている方も
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
class="text-blue-500 underline"
|
class="text-link underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href="https://scrapbox.io/nostr/NIP-07#63e1c10c8b8fcb00000584fc"
|
href="https://scrapbox.io/nostr/NIP-07#63e1c10c8b8fcb00000584fc"
|
||||||
@@ -86,7 +81,7 @@ const Hello: Component = () => {
|
|||||||
</Match>
|
</Match>
|
||||||
<Match when={signerStatus() === 'available'}>
|
<Match when={signerStatus() === 'available'}>
|
||||||
<button
|
<button
|
||||||
class="rounded bg-rose-400 p-4 text-lg font-bold text-white hover:shadow-md"
|
class="rounded bg-primary p-4 text-lg font-bold text-primary-fg hover:shadow-md"
|
||||||
onClick={handleLogin}
|
onClick={handleLogin}
|
||||||
>
|
>
|
||||||
{i18n()('hello.loginWithSigner')}
|
{i18n()('hello.loginWithSigner')}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { Component } from 'solid-js';
|
|||||||
|
|
||||||
const NotFound: Component = () => (
|
const NotFound: Component = () => (
|
||||||
<div class="container mx-auto max-w-[640px] py-10">
|
<div class="container mx-auto max-w-[640px] py-10">
|
||||||
<h1 class="text-4xl font-bold text-stone-700">お探しのページは見つかりませんでした</h1>
|
<h1 class="text-4xl font-bold text-fg">お探しのページは見つかりませんでした</h1>
|
||||||
<p class="pt-4">
|
<p class="pt-4">
|
||||||
<a class="text-blue-500 underline" href="/">
|
<a class="text-link underline" href="/">
|
||||||
← トップに戻る
|
← トップに戻る
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,16 +1,41 @@
|
|||||||
import tailwindForms from '@tailwindcss/forms';
|
import tailwindForms from '@tailwindcss/forms';
|
||||||
import { type Config } from 'tailwindcss';
|
import { type Config } from 'tailwindcss';
|
||||||
import colors from 'tailwindcss/colors';
|
|
||||||
|
|
||||||
|
// 参考
|
||||||
|
// https://github.com/shadcn-ui/taxonomy/blob/main/tailwind.config.js
|
||||||
|
// https://github.com/shadcn-ui/taxonomy/blob/651f984e52edd65d40ccd55e299c1baeea3ff017/styles/globals.css#L78
|
||||||
export default {
|
export default {
|
||||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
// a color for primary actions like a submit button.
|
fg: {
|
||||||
primary: colors.rose['300'],
|
DEFAULT: 'rgb(var(--color-fg))',
|
||||||
'primary-disabled': colors.rose['200'],
|
secondary: 'rgb(var(--color-fg-secondary))',
|
||||||
'sidebar-bg': colors.rose['100'],
|
tertiary: 'rgb(var(--color-fg-tertiary))',
|
||||||
|
},
|
||||||
|
bg: {
|
||||||
|
DEFAULT: 'rgb(var(--color-bg))',
|
||||||
|
secondary: 'rgb(var(--color-bg-secondary))',
|
||||||
|
tertiary: 'rgb(var(--color-bg-tertiary))',
|
||||||
|
},
|
||||||
|
border: 'rgb(var(--color-border))',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'rgb(var(--color-primary))',
|
||||||
|
disabled: 'rgb(var(--color-primary-disabled))',
|
||||||
|
hover: 'rgb(var(--color-primary-hover))',
|
||||||
|
fg: 'rgb(var(--color-primary-fg))',
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
DEFAULT: 'rgb(var(--color-danger))',
|
||||||
|
fg: 'rgb(var(--color-danger-fg))',
|
||||||
|
},
|
||||||
|
link: 'rgb(var(--color-link))',
|
||||||
|
r: {
|
||||||
|
sidebar: 'rgb(var(--color-r-sidebar))',
|
||||||
|
reaction: 'rgb(var(--color-r-reaction))',
|
||||||
|
repost: 'rgb(var(--color-r-repost))',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user