mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-05 10:11:12 +02:00
wip: render hashtags correctly
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, Activity } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useAccountSync } from "@/hooks/useAccountSync";
|
||||
import Feed from "./nostr/Feed";
|
||||
@@ -141,21 +141,16 @@ export default function Home() {
|
||||
<UserMenu />
|
||||
</header>
|
||||
<section className="flex-1 relative overflow-hidden">
|
||||
{Object.values(state.workspaces).map((workspace) => (
|
||||
<Activity
|
||||
key={workspace.id}
|
||||
mode={
|
||||
workspace.id === state.activeWorkspaceId ? "visible" : "hidden"
|
||||
}
|
||||
>
|
||||
{workspace.layout === null ? (
|
||||
{state.workspaces[state.activeWorkspaceId] && (
|
||||
<>
|
||||
{state.workspaces[state.activeWorkspaceId].layout === null ? (
|
||||
<GrimoireWelcome
|
||||
onLaunchCommand={() => setCommandLauncherOpen(true)}
|
||||
/>
|
||||
) : (
|
||||
<Mosaic
|
||||
renderTile={renderTile}
|
||||
value={workspace.layout}
|
||||
value={state.workspaces[state.activeWorkspaceId].layout}
|
||||
onChange={updateLayout}
|
||||
onRelease={(node) => {
|
||||
// When Mosaic removes a node from the layout, clean up the window
|
||||
@@ -166,8 +161,8 @@ export default function Home() {
|
||||
className="mosaic-blueprint-theme"
|
||||
/>
|
||||
)}
|
||||
</Activity>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
<TabBar />
|
||||
</main>
|
||||
|
||||
@@ -5,13 +5,5 @@ interface HashtagNodeProps {
|
||||
}
|
||||
|
||||
export function Hashtag({ node }: HashtagNodeProps) {
|
||||
return (
|
||||
<a
|
||||
href={`/t/${node.hashtag}`}
|
||||
className="text-muted-foreground hover:text-primary cursor-crosshair"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
#{node.hashtag}
|
||||
</a>
|
||||
);
|
||||
return <span className="text-muted-foreground">#{node.hashtag}</span>;
|
||||
}
|
||||
|
||||
@@ -12,23 +12,24 @@ function hasRTLCharacters(text: string): boolean {
|
||||
|
||||
export function Text({ node }: TextNodeProps) {
|
||||
const text = node.value;
|
||||
|
||||
|
||||
// If no newlines, render as inline span
|
||||
if (!text.includes("\n")) {
|
||||
const isRTL = hasRTLCharacters(text);
|
||||
return <span dir={isRTL ? "rtl" : "auto"}>{text || "\u00A0"}</span>;
|
||||
}
|
||||
|
||||
// If has newlines, use divs for proper RTL per line
|
||||
|
||||
// If has newlines, use spans with <br> tags
|
||||
const lines = text.split("\n");
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, idx) => {
|
||||
const isRTL = hasRTLCharacters(line);
|
||||
return (
|
||||
<div key={idx} dir={isRTL ? "rtl" : "auto"}>
|
||||
{line || "\u00A0"}
|
||||
</div>
|
||||
<span key={idx}>
|
||||
<span dir={isRTL ? "rtl" : "auto"}>{line || "\u00A0"}</span>
|
||||
{idx < lines.length - 1 && <br />}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function EventAuthor({ pubkey }: { pubkey: string }) {
|
||||
<div className="flex flex-col gap-0">
|
||||
<UserName
|
||||
pubkey={pubkey}
|
||||
className="text-md cursor-crosshair font-semibold hover:underline hover:decoration-dotted"
|
||||
className="text-md text-accent cursor-crosshair font-semibold hover:underline hover:decoration-dotted"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { useObservableMemo, useEventStore } from "applesauce-react/hooks";
|
||||
import { useEventStore, useObservableMemo } from "applesauce-react/hooks";
|
||||
import accounts from "@/services/accounts";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { getInboxes, getOutboxes } from "applesauce-core/helpers";
|
||||
|
||||
@@ -1,32 +1,54 @@
|
||||
import { useEffect } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { useState, useEffect } from "react";
|
||||
import { profileLoader } from "@/services/loaders";
|
||||
import { useEventStore, useObservableMemo } from "applesauce-react/hooks";
|
||||
import { ProfileContent } from "applesauce-core/helpers";
|
||||
import { ProfileModel } from "applesauce-core/models/profile";
|
||||
import { kinds } from "nostr-tools";
|
||||
import db from "@/services/db";
|
||||
|
||||
export function useProfile(pubkey: string): ProfileContent | undefined {
|
||||
const eventStore = useEventStore();
|
||||
const [profile, setProfile] = useState<ProfileContent | undefined>();
|
||||
|
||||
const profile = useObservableMemo(
|
||||
() => eventStore.model(ProfileModel, pubkey),
|
||||
[eventStore, pubkey],
|
||||
);
|
||||
|
||||
// Fetch profile if not in store (only runs once per pubkey)
|
||||
useEffect(() => {
|
||||
if (profile) return; // Already have the event
|
||||
let mounted = true;
|
||||
|
||||
// Load from IndexedDB first
|
||||
db.profiles.get(pubkey).then((cachedProfile) => {
|
||||
if (mounted && cachedProfile) {
|
||||
setProfile(cachedProfile);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch from network
|
||||
const sub = profileLoader({ kind: kinds.Metadata, pubkey }).subscribe({
|
||||
next: (fetchedEvent) => {
|
||||
if (fetchedEvent) {
|
||||
eventStore.add(fetchedEvent);
|
||||
next: async (fetchedEvent) => {
|
||||
if (!fetchedEvent || !fetchedEvent.content) return;
|
||||
|
||||
try {
|
||||
const profileData = JSON.parse(fetchedEvent.content) as ProfileContent;
|
||||
|
||||
// Save to IndexedDB
|
||||
await db.profiles.put({
|
||||
...profileData,
|
||||
pubkey,
|
||||
created_at: fetchedEvent.created_at,
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
setProfile(profileData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[useProfile] Failed to parse profile:", e);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error("[useProfile] Error fetching profile:", err);
|
||||
},
|
||||
});
|
||||
|
||||
return () => sub.unsubscribe();
|
||||
}, [pubkey, eventStore]); // Removed event and loading from deps
|
||||
return () => {
|
||||
mounted = false;
|
||||
sub.unsubscribe();
|
||||
};
|
||||
}, [pubkey]);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ProfileContent } from "applesauce-core/helpers";
|
||||
import { Dexie, Table } from "dexie";
|
||||
|
||||
export interface Profile extends ProfileContent {
|
||||
pubkey: string;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user