mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-30 00:41:00 +00:00
feat: improve official columns
This commit is contained in:
parent
f3c52237fa
commit
cd31b99559
@ -1,6 +1,6 @@
|
|||||||
import { CheckCircleIcon } from "@lume/icons";
|
import { CheckCircleIcon } from "@lume/icons";
|
||||||
import { ColumnRouteSearch } from "@lume/types";
|
import { ColumnRouteSearch } from "@lume/types";
|
||||||
import { Column, User } from "@lume/ui";
|
import { Column, Spinner, User } from "@lume/ui";
|
||||||
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -30,6 +30,7 @@ function Screen() {
|
|||||||
|
|
||||||
const [title, setTitle] = useState<string>("Just a new group");
|
const [title, setTitle] = useState<string>("Just a new group");
|
||||||
const [users, setUsers] = useState<Array<string>>([]);
|
const [users, setUsers] = useState<Array<string>>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const [isDone, setIsDone] = useState(false);
|
const [isDone, setIsDone] = useState(false);
|
||||||
|
|
||||||
const toggleUser = (pubkey: string) => {
|
const toggleUser = (pubkey: string) => {
|
||||||
@ -43,13 +44,22 @@ function Screen() {
|
|||||||
try {
|
try {
|
||||||
if (isDone) return router.history.push(redirect);
|
if (isDone) return router.history.push(redirect);
|
||||||
|
|
||||||
|
// start loading
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const groups = await ark.set_nstore(
|
const groups = await ark.set_nstore(
|
||||||
`lume_group_${label}`,
|
`lume_group_${label}`,
|
||||||
JSON.stringify(users),
|
JSON.stringify(users),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (groups) setIsDone(true);
|
if (groups) {
|
||||||
|
toast.success("Group has been created successfully.");
|
||||||
|
// start loading
|
||||||
|
setIsDone(true);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
setLoading(false);
|
||||||
toast.error(e);
|
toast.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -101,14 +111,14 @@ function Screen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="fixed z-10 flex items-center justify-center w-full bottom-3">
|
<div className="fixed z-10 flex items-center justify-center w-full bottom-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={users.length < 1}
|
disabled={users.length < 1}
|
||||||
className="inline-flex items-center justify-center px-4 font-medium text-white transform bg-blue-500 rounded-full active:translate-y-1 w-36 h-11 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
|
className="inline-flex items-center justify-center px-4 font-medium text-white transform bg-blue-500 rounded-full active:translate-y-1 w-26 h-9 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isDone ? "Back" : "Update"}
|
{isDone ? "Back" : loading ? <Spinner /> : "Update"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Column.Content>
|
</Column.Content>
|
||||||
|
@ -18,6 +18,7 @@ export const Route = createFileRoute("/foryou")({
|
|||||||
beforeLoad: async ({ search, context }) => {
|
beforeLoad: async ({ search, context }) => {
|
||||||
const ark = context.ark;
|
const ark = context.ark;
|
||||||
const interests = await ark.get_interest();
|
const interests = await ark.get_interest();
|
||||||
|
const settings = await ark.get_settings();
|
||||||
|
|
||||||
if (!interests) {
|
if (!interests) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@ -31,6 +32,7 @@ export const Route = createFileRoute("/foryou")({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
interests,
|
interests,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
@ -48,7 +50,6 @@ export function Screen() {
|
|||||||
interests.hashtags,
|
interests.hashtags,
|
||||||
20,
|
20,
|
||||||
pageParam,
|
pageParam,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
return events;
|
return events;
|
||||||
},
|
},
|
||||||
@ -87,13 +88,12 @@ export function Screen() {
|
|||||||
{data.map((item) => renderItem(item))}
|
{data.map((item) => renderItem(item))}
|
||||||
</Virtualizer>
|
</Virtualizer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data?.length && hasNextPage ? (
|
{data?.length && hasNextPage ? (
|
||||||
<div className="flex h-20 items-center justify-center">
|
<div className="flex h-20 items-center justify-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fetchNextPage()}
|
onClick={() => fetchNextPage()}
|
||||||
disabled={isFetchingNextPage || isFetchingNextPage}
|
disabled={isFetchingNextPage}
|
||||||
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
{isFetchingNextPage ? (
|
{isFetchingNextPage ? (
|
||||||
|
@ -17,7 +17,10 @@ export const Route = createFileRoute("/group")({
|
|||||||
},
|
},
|
||||||
beforeLoad: async ({ search, context }) => {
|
beforeLoad: async ({ search, context }) => {
|
||||||
const ark = context.ark;
|
const ark = context.ark;
|
||||||
const groups = await ark.get_nstore(`lume_group_${search.label}`);
|
const groups = (await ark.get_nstore(
|
||||||
|
`lume_group_${search.label}`,
|
||||||
|
)) as string[];
|
||||||
|
const settings = await ark.get_settings();
|
||||||
|
|
||||||
if (!groups) {
|
if (!groups) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@ -31,6 +34,7 @@ export const Route = createFileRoute("/group")({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
groups,
|
groups,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
component: Screen,
|
component: Screen,
|
||||||
@ -38,13 +42,13 @@ export const Route = createFileRoute("/group")({
|
|||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
const { label, name, account } = Route.useSearch();
|
const { label, name, account } = Route.useSearch();
|
||||||
const { ark } = Route.useRouteContext();
|
const { ark, groups } = Route.useRouteContext();
|
||||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
|
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: [name, account],
|
queryKey: [name, account],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const events = await ark.get_events(20, pageParam);
|
const events = await ark.get_events(20, pageParam, groups);
|
||||||
return events;
|
return events;
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => {
|
getNextPageParam: (lastPage) => {
|
||||||
|
@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export const Route = createFileRoute("/interests")({
|
export const Route = createFileRoute("/interests")({
|
||||||
component: Screen,
|
|
||||||
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
|
validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
|
||||||
return {
|
return {
|
||||||
account: search.account,
|
account: search.account,
|
||||||
@ -15,6 +14,7 @@ export const Route = createFileRoute("/interests")({
|
|||||||
name: search.name,
|
name: search.name,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
component: Screen,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
@ -6,8 +6,16 @@ export const Route = createFileRoute("/store/community")({
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 p-3">
|
<div className="flex h-full flex-col items-center justify-center gap-3 p-3">
|
||||||
<p>Coming Soon</p>
|
<div className="size-24 bg-blue-100 flex flex-col items-center justify-end overflow-hidden dark:bg-blue-900 rounded-full">
|
||||||
|
<div className="w-12 h-16 bg-gradient-to-b from-blue-500 dark:from-blue-200 to-blue-50 dark:to-blue-900 rounded-t-lg" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="font-semibold text-lg">Coming Soon</h1>
|
||||||
|
<p className="text-sm text-neutral-700 dark:text-neutral-300 leading-tight">
|
||||||
|
Enhance your experience <br /> by adding column shared by community.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -195,45 +195,39 @@ export class Ark {
|
|||||||
hashtags: string[],
|
hashtags: string[],
|
||||||
limit: number,
|
limit: number,
|
||||||
asOf?: number,
|
asOf?: number,
|
||||||
global?: boolean,
|
|
||||||
) {
|
) {
|
||||||
let until: string = undefined;
|
let until: string = undefined;
|
||||||
if (asOf && asOf > 0) until = asOf.toString();
|
if (asOf && asOf > 0) until = asOf.toString();
|
||||||
|
|
||||||
const dedup = true;
|
|
||||||
const seenIds = new Set<string>();
|
const seenIds = new Set<string>();
|
||||||
const dedupQueue = new Set<string>();
|
const dedupQueue = new Set<string>();
|
||||||
|
const nostrTags = hashtags.map((tag) => tag.replace("#", "").toLowerCase());
|
||||||
|
|
||||||
const nostrEvents: Event[] = await invoke("get_events_from_interests", {
|
const nostrEvents: Event[] = await invoke("get_events_from_interests", {
|
||||||
hashtags,
|
hashtags: nostrTags,
|
||||||
limit,
|
limit,
|
||||||
until,
|
until,
|
||||||
global,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dedup) {
|
for (const event of nostrEvents) {
|
||||||
for (const event of nostrEvents) {
|
const tags = event.tags
|
||||||
const tags = event.tags
|
.filter((el) => el[0] === "e")
|
||||||
.filter((el) => el[0] === "e")
|
?.map((item) => item[1]);
|
||||||
?.map((item) => item[1]);
|
|
||||||
|
|
||||||
if (tags.length) {
|
if (tags.length) {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (seenIds.has(tag)) {
|
if (seenIds.has(tag)) {
|
||||||
dedupQueue.add(event.id);
|
dedupQueue.add(event.id);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
seenIds.add(tag);
|
|
||||||
}
|
}
|
||||||
|
seenIds.add(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nostrEvents
|
|
||||||
.filter((event) => !dedupQueue.has(event.id))
|
|
||||||
.sort((a, b) => b.created_at - a.created_at);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nostrEvents.sort((a, b) => b.created_at - a.created_at);
|
return nostrEvents
|
||||||
|
.filter((event) => !dedupQueue.has(event.id))
|
||||||
|
.sort((a, b) => b.created_at - a.created_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async publish(
|
public async publish(
|
||||||
|
@ -126,7 +126,7 @@ export function NoteContent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (compact) {
|
if (compact) {
|
||||||
parsedContent = reactStringReplace(parsedContent, /[\r\n]{2,}/g, () => (
|
parsedContent = reactStringReplace(parsedContent, /\n*\n/g, () => (
|
||||||
<div key={nanoid()} className="h-1.5" />
|
<div key={nanoid()} className="h-1.5" />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export function MentionNote({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
className="my-1 flex w-full cursor-default items-center justify-between rounded-2xl border border-black/10 p-3 dark:border-white/10"
|
className="my-1 flex w-full cursor-default items-center justify-between rounded-xl border border-black/10 p-3 dark:border-white/10"
|
||||||
>
|
>
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
@ -30,7 +30,7 @@ export function MentionNote({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
className="my-1 w-full cursor-default rounded-2xl border border-black/10 p-3 dark:border-white/10"
|
className="my-1 w-full cursor-default rounded-xl border border-black/10 p-3 dark:border-white/10"
|
||||||
>
|
>
|
||||||
{t("note.error")}
|
{t("note.error")}
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,16 +19,6 @@
|
|||||||
"author": "Lume",
|
"author": "Lume",
|
||||||
"description": "Collective of people you're interested in."
|
"description": "Collective of people you're interested in."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "sDbO6XxAGnW5XuUZEgZMn",
|
|
||||||
"name": "Antenas",
|
|
||||||
"content": "/antenas",
|
|
||||||
"logo": "",
|
|
||||||
"cover": "/antenas.png",
|
|
||||||
"coverRetina": "/antenas@2x.png",
|
|
||||||
"author": "Lume",
|
|
||||||
"description": "Keep track to specific content."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "gxtcIbgD8YNPbeI5o92I8",
|
"label": "gxtcIbgD8YNPbeI5o92I8",
|
||||||
"name": "Trending",
|
"name": "Trending",
|
||||||
|
@ -155,7 +155,6 @@ pub async fn get_events_from_interests(
|
|||||||
hashtags: Vec<&str>,
|
hashtags: Vec<&str>,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
until: Option<&str>,
|
until: Option<&str>,
|
||||||
global: Option<bool>,
|
|
||||||
state: State<'_, Nostr>,
|
state: State<'_, Nostr>,
|
||||||
) -> Result<Vec<Event>, String> {
|
) -> Result<Vec<Event>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
@ -163,34 +162,11 @@ pub async fn get_events_from_interests(
|
|||||||
Some(until) => Timestamp::from_str(until).unwrap(),
|
Some(until) => Timestamp::from_str(until).unwrap(),
|
||||||
None => Timestamp::now(),
|
None => Timestamp::now(),
|
||||||
};
|
};
|
||||||
let authors = match global {
|
let filter = Filter::new()
|
||||||
Some(val) => match val {
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
true => None,
|
.limit(limit)
|
||||||
false => {
|
.until(as_of)
|
||||||
match client
|
.hashtags(hashtags);
|
||||||
.get_contact_list_public_keys(Some(Duration::from_secs(10)))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(val) => Some(val),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let filter = match authors {
|
|
||||||
Some(val) => Filter::new()
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
|
||||||
.authors(val)
|
|
||||||
.limit(limit)
|
|
||||||
.until(as_of)
|
|
||||||
.hashtags(hashtags),
|
|
||||||
None => Filter::new()
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
|
||||||
.limit(limit)
|
|
||||||
.until(as_of)
|
|
||||||
.hashtags(hashtags),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(events) = client
|
if let Ok(events) = client
|
||||||
.get_events_of(vec![filter], Some(Duration::from_secs(15)))
|
.get_events_of(vec![filter], Some(Duration::from_secs(15)))
|
||||||
|
Loading…
Reference in New Issue
Block a user