diff --git a/joinlemmy-translations b/joinlemmy-translations index afcb168..fd888ab 160000 --- a/joinlemmy-translations +++ b/joinlemmy-translations @@ -1 +1 @@ -Subproject commit afcb1684e80a42a6fecd6c0cebdf4caa64f92440 +Subproject commit fd888ab941a2f77e30ebafda065289fa0542d23e diff --git a/lemmy-translations b/lemmy-translations index b9a566d..d0f3548 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit b9a566d8376c1ca4d122d63cb921accc3db6c79f +Subproject commit d0f3548379e446d2c333e582734bc68f8d684f4d diff --git a/src/client/index.tsx b/src/client/index.tsx index 71a3ec4..0726802 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -19,4 +19,8 @@ const wrapper = ( ); -hydrate(wrapper, document.getElementById("root")); +const root = document.getElementById("root"); + +if (root) { + hydrate(wrapper, root); +} diff --git a/src/shared/components/app.tsx b/src/shared/components/app.tsx index 9fc26d4..8fe3a6b 100644 --- a/src/shared/components/app.tsx +++ b/src/shared/components/app.tsx @@ -25,7 +25,7 @@ export class App extends Component { key={path} path={path} exact={exact} - render={props => } + render={props => C && } /> ))} } /> diff --git a/src/shared/components/instances-definitions.ts b/src/shared/components/instances-definitions.ts index cdb9aa9..bf3483b 100644 --- a/src/shared/components/instances-definitions.ts +++ b/src/shared/components/instances-definitions.ts @@ -32,33 +32,32 @@ export const INSTANCE_HELPERS: InstanceHelper[] = [ // TODO add i18n strings, Icons // DO this as an interface and const list - -export interface Category { +export interface Topic { name: string; icon: string; } -export const All_CATEGORY: Category = { - name: "all", +export const All_TOPIC: Topic = { + name: "all_topics", icon: "TBD", }; -export const SPORTS: Category = { +export const SPORTS: Topic = { name: "sports", icon: "TBD", }; -export const TECH: Category = { +export const TECH: Topic = { name: "tech", icon: "TBD", }; -export const CATEGORIES: Category[] = [All_CATEGORY, TECH, SPORTS]; +export const TOPICS: Topic[] = [All_TOPIC, TECH, SPORTS]; export interface RecommendedInstance { domain: string; languages: string[]; - categories: Category[]; + topics: Topic[]; } // TODO fix these up @@ -66,86 +65,86 @@ export const RECOMMENDED_INSTANCES: RecommendedInstance[] = [ { domain: "lemmy.ml", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "lemmy.world", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "lemmy.fmhy.ml", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "discuss.tchncs.de", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "lemm.ee", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "reddthat.com", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "discuss.online", languages: ["en"], - categories: [TECH], + topics: [TECH], }, { domain: "feddit.dk", languages: ["da"], - categories: [TECH], + topics: [TECH], }, { domain: "feddit.de", languages: ["de"], - categories: [TECH], + topics: [TECH], }, { domain: "discuss.tchncs.de", languages: ["de"], - categories: [TECH], + topics: [TECH], }, { domain: "feddit.nl", languages: ["nl"], - categories: [TECH], + topics: [TECH], }, { domain: "lemmy.pt", languages: ["pt"], - categories: [TECH], + topics: [TECH], }, { domain: "lemmy.eus", languages: ["eu"], - categories: [TECH], + topics: [TECH], }, { domain: "tabinezumi.net", languages: ["ja"], - categories: [TECH], + topics: [TECH], }, { domain: "lm.korako.me", languages: ["ja"], - categories: [TECH], + topics: [TECH], }, { domain: "feddit.it", languages: ["it"], - categories: [TECH], + topics: [TECH], }, ]; diff --git a/src/shared/components/instances.tsx b/src/shared/components/instances.tsx index 812f904..61564ba 100644 --- a/src/shared/components/instances.tsx +++ b/src/shared/components/instances.tsx @@ -3,16 +3,17 @@ import { Helmet } from "inferno-helmet"; import { i18n, LANGUAGES } from "../i18next"; import { T } from "inferno-i18next"; import { instance_stats } from "../instance_stats"; -import { languageList, mdToHtml, numToSI } from "../utils"; +import { getQueryParams, mdToHtml, numToSI } from "../utils"; import { Badge, SELECT_CLASSES, TEXT_GRADIENT } from "./common"; import { INSTANCE_HELPERS, - Category, + Topic, RECOMMENDED_INSTANCES, - All_CATEGORY, - CATEGORIES, + All_TOPIC, + TOPICS, } from "./instances-definitions"; import { Icon } from "./icon"; +import { I18nKeys } from "i18next"; const TitleBlock = () => (
@@ -64,9 +65,6 @@ interface InstanceCardGridProps { instances: any[]; } -// TODO create the instance picker helper - -// - Language, Categories, and Sort Order (active, random) const InstanceCardGrid = ({ instances }: InstanceCardGridProps) => (
{instances.map(i => ( @@ -282,23 +280,67 @@ function sortActive(instances: any[]): any[] { } interface State { + instances: any[]; sort: Sort; language: string; - category: Category; + topic: Topic; + scroll: boolean; } -export class Instances extends Component { +interface Props { + sort: Sort; + language: string; + topic: Topic; + scroll: boolean; +} + +function getSortFromQuery(sort?: string): Sort { + return SORTS.find(s => s.name == sort) ?? RANDOM_SORT; +} + +function getTopicFromQuery(topic?: string): Topic { + return TOPICS.find(c => c.name == topic) ?? All_TOPIC; +} + +function getInstancesQueryParams() { + return getQueryParams({ + sort: getSortFromQuery, + language: d => d || "all", + topic: getTopicFromQuery, + scroll: d => !!d, + }); +} + +export class Instances extends Component { state: State = { - sort: SORTS[0], - language: i18n.language.split("-")[0], - category: All_CATEGORY, + instances: [], + sort: RANDOM_SORT, + language: "all", + topic: All_TOPIC, + scroll: false, }; constructor(props: any, context: any) { super(props, context); } - buildInstanceList(): any[] { + // Set the filters by the query params if they exist + componentDidMount(): void { + this.setState(getInstancesQueryParams()); + this.buildInstanceList(); + this.scrollToSearch(); + } + + scrollToSearch() { + if (this.state.scroll) { + const el = document.getElementById("search")?.offsetTop; + if (el) { + window.scrollTo({ top: el, behavior: "smooth" }); + } + } + } + + buildInstanceList() { let instances = instance_stats.stats.instance_details; const recommended = RECOMMENDED_INSTANCES; @@ -312,26 +354,26 @@ export class Instances extends Component { ); } - // Category filter - if (this.state.category !== All_CATEGORY) { - const categoryRecs = recommended.filter(r => - r.categories.includes(this.state.category), + // Topic filter + if (this.state.topic !== All_TOPIC) { + const topicRecs = recommended.filter(r => + r.topics.includes(this.state.topic), ); instances = instances.filter(i => - categoryRecs.map(c => c.domain).includes(i.domain), + topicRecs.map(c => c.domain).includes(i.domain), ); } // Sort - if (this.state.sort == SORTS[0]) { + if (this.state.sort == RANDOM_SORT) { instances = sortRandom(instances); - } else if (this.state.sort == SORTS[1]) { + } else if (this.state.sort == MOST_ACTIVE_SORT) { instances = sortActive(instances); } else { instances = sortActive(instances).reverse(); } - return instances; + this.setState({ instances }); } render() { @@ -345,18 +387,37 @@ export class Instances extends Component { {this.filterAndTitleBlock()} - +
+ {this.state.instances.length > 0 ? ( + + ) : ( + this.seeAllBtn() + )} +
+
+ ); + } + + seeAllBtn() { + return ( +
+

{i18n.t("none_found")}

+
); } - // TODO i18n these filterAndTitleBlock() { return ( -
+ } text={ - + {i18n.t("mobile_apps_for_ios_and_android")} } @@ -293,7 +294,7 @@ const MoreFeaturesBlock = () => ( } text={ - ### + ### } /> @@ -309,7 +310,7 @@ const MoreFeaturesBlock = () => ( icons={
:
} text={ - ## + ## } /> @@ -321,8 +322,8 @@ const MoreFeaturesBlock = () => ( } text={ - ## - # + ## + # } /> @@ -368,11 +369,11 @@ const MoreFeaturesBlock = () => ( } text={ - ## - # - # - # - # + ## + # + # + # + # } /> @@ -428,6 +429,7 @@ export class Main extends Component { const title = i18n.t("lemmy_title"); return (
+ diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx index cdb9395..c88a0c0 100644 --- a/src/shared/components/navbar.tsx +++ b/src/shared/components/navbar.tsx @@ -4,7 +4,6 @@ import { Icon, IconSize } from "./icon"; import { i18n, LANGUAGES } from "../i18next"; import classNames from "classnames"; import { SELECT_CLASSES } from "./common"; -import { languageList } from "../utils"; const NavLink = ({ content }) =>
  • {content}
  • ; @@ -52,13 +51,13 @@ export const Navbar = ({ footer = false }) => ( onChange={linkEvent(this, handleLanguageChange)} className={SELECT_CLASSES} > - {languageList().map((language, i) => ( + {LANGUAGES.map((l, i) => ( ))} diff --git a/src/shared/components/news-item.tsx b/src/shared/components/news-item.tsx index ae09471..5f82b67 100644 --- a/src/shared/components/news-item.tsx +++ b/src/shared/components/news-item.tsx @@ -21,7 +21,7 @@ export class NewsItem extends Component { get markdown(): string { let title = decodeURIComponent(this.props.match.params.title); title = title.replace(/_/g, " "); - return news_md.find(v => v.title == title).markdown; + return news_md.find(v => v.title == title)?.markdown ?? ""; } render() { diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 5eb2bde..d45e644 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,5 +1,4 @@ import markdown_it from "markdown-it"; -import { i18n } from "./i18next"; let SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1, @@ -26,6 +25,20 @@ export function mdToHtml(text: string) { return { __html: md.render(text) }; } -export function languageList() { - return Object.keys(i18n.services.resourceStore.data).sort(); +export function getQueryParams>(processors: { + [K in keyof T]: (param: string) => T[K]; +}): T { + if (isBrowser()) { + const searchParams = new URLSearchParams(window.location.search); + + return Array.from(Object.entries(processors)).reduce( + (acc, [key, process]) => ({ + ...acc, + [key]: process(searchParams.get(key)), + }), + {} as T, + ); + } + + return {} as T; } diff --git a/tsconfig.json b/tsconfig.json index 9926c3d..1afc588 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "pretty": true, "target": "esnext", "module": "esnext", + "strictNullChecks": true, "allowSyntheticDefaultImports": true, "preserveConstEnums": true, "sourceMap": true,