mirror of
https://github.com/LemmyNet/joinlemmy-site.git
synced 2024-11-25 22:01:17 +00:00
Finishing up helper modal.
This commit is contained in:
parent
709612005b
commit
7241dfaedf
11 changed files with 200 additions and 106 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit afcb1684e80a42a6fecd6c0cebdf4caa64f92440
|
Subproject commit fd888ab941a2f77e30ebafda065289fa0542d23e
|
|
@ -1 +1 @@
|
||||||
Subproject commit b9a566d8376c1ca4d122d63cb921accc3db6c79f
|
Subproject commit d0f3548379e446d2c333e582734bc68f8d684f4d
|
|
@ -19,4 +19,8 @@ const wrapper = (
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
hydrate(wrapper, document.getElementById("root"));
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
hydrate(wrapper, root);
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class App extends Component<any, any> {
|
||||||
key={path}
|
key={path}
|
||||||
path={path}
|
path={path}
|
||||||
exact={exact}
|
exact={exact}
|
||||||
render={props => <C {...props} {...rest} />}
|
render={props => C && <C {...props} {...rest} />}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Route render={props => <NoMatch {...props} />} />
|
<Route render={props => <NoMatch {...props} />} />
|
||||||
|
|
|
@ -32,33 +32,32 @@ export const INSTANCE_HELPERS: InstanceHelper[] = [
|
||||||
|
|
||||||
// TODO add i18n strings, Icons
|
// TODO add i18n strings, Icons
|
||||||
// DO this as an interface and const list
|
// DO this as an interface and const list
|
||||||
|
export interface Topic {
|
||||||
export interface Category {
|
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const All_CATEGORY: Category = {
|
export const All_TOPIC: Topic = {
|
||||||
name: "all",
|
name: "all_topics",
|
||||||
icon: "TBD",
|
icon: "TBD",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SPORTS: Category = {
|
export const SPORTS: Topic = {
|
||||||
name: "sports",
|
name: "sports",
|
||||||
icon: "TBD",
|
icon: "TBD",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TECH: Category = {
|
export const TECH: Topic = {
|
||||||
name: "tech",
|
name: "tech",
|
||||||
icon: "TBD",
|
icon: "TBD",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CATEGORIES: Category[] = [All_CATEGORY, TECH, SPORTS];
|
export const TOPICS: Topic[] = [All_TOPIC, TECH, SPORTS];
|
||||||
|
|
||||||
export interface RecommendedInstance {
|
export interface RecommendedInstance {
|
||||||
domain: string;
|
domain: string;
|
||||||
languages: string[];
|
languages: string[];
|
||||||
categories: Category[];
|
topics: Topic[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO fix these up
|
// TODO fix these up
|
||||||
|
@ -66,86 +65,86 @@ export const RECOMMENDED_INSTANCES: RecommendedInstance[] = [
|
||||||
{
|
{
|
||||||
domain: "lemmy.ml",
|
domain: "lemmy.ml",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "lemmy.world",
|
domain: "lemmy.world",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "lemmy.fmhy.ml",
|
domain: "lemmy.fmhy.ml",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "discuss.tchncs.de",
|
domain: "discuss.tchncs.de",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "lemm.ee",
|
domain: "lemm.ee",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "reddthat.com",
|
domain: "reddthat.com",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "discuss.online",
|
domain: "discuss.online",
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "feddit.dk",
|
domain: "feddit.dk",
|
||||||
languages: ["da"],
|
languages: ["da"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "feddit.de",
|
domain: "feddit.de",
|
||||||
languages: ["de"],
|
languages: ["de"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "discuss.tchncs.de",
|
domain: "discuss.tchncs.de",
|
||||||
languages: ["de"],
|
languages: ["de"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domain: "feddit.nl",
|
domain: "feddit.nl",
|
||||||
languages: ["nl"],
|
languages: ["nl"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
domain: "lemmy.pt",
|
domain: "lemmy.pt",
|
||||||
languages: ["pt"],
|
languages: ["pt"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
domain: "lemmy.eus",
|
domain: "lemmy.eus",
|
||||||
languages: ["eu"],
|
languages: ["eu"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
domain: "tabinezumi.net",
|
domain: "tabinezumi.net",
|
||||||
languages: ["ja"],
|
languages: ["ja"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
domain: "lm.korako.me",
|
domain: "lm.korako.me",
|
||||||
languages: ["ja"],
|
languages: ["ja"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
domain: "feddit.it",
|
domain: "feddit.it",
|
||||||
languages: ["it"],
|
languages: ["it"],
|
||||||
categories: [TECH],
|
topics: [TECH],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -3,16 +3,17 @@ import { Helmet } from "inferno-helmet";
|
||||||
import { i18n, LANGUAGES } from "../i18next";
|
import { i18n, LANGUAGES } from "../i18next";
|
||||||
import { T } from "inferno-i18next";
|
import { T } from "inferno-i18next";
|
||||||
import { instance_stats } from "../instance_stats";
|
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 { Badge, SELECT_CLASSES, TEXT_GRADIENT } from "./common";
|
||||||
import {
|
import {
|
||||||
INSTANCE_HELPERS,
|
INSTANCE_HELPERS,
|
||||||
Category,
|
Topic,
|
||||||
RECOMMENDED_INSTANCES,
|
RECOMMENDED_INSTANCES,
|
||||||
All_CATEGORY,
|
All_TOPIC,
|
||||||
CATEGORIES,
|
TOPICS,
|
||||||
} from "./instances-definitions";
|
} from "./instances-definitions";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
|
import { I18nKeys } from "i18next";
|
||||||
|
|
||||||
const TitleBlock = () => (
|
const TitleBlock = () => (
|
||||||
<div className="flex flex-col items-center pt-16 mb-16">
|
<div className="flex flex-col items-center pt-16 mb-16">
|
||||||
|
@ -64,9 +65,6 @@ interface InstanceCardGridProps {
|
||||||
instances: any[];
|
instances: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO create the instance picker helper
|
|
||||||
|
|
||||||
// - Language, Categories, and Sort Order (active, random)
|
|
||||||
const InstanceCardGrid = ({ instances }: InstanceCardGridProps) => (
|
const InstanceCardGrid = ({ instances }: InstanceCardGridProps) => (
|
||||||
<div className="grid md:grid-cols-3 grid-cols-1 gap-4">
|
<div className="grid md:grid-cols-3 grid-cols-1 gap-4">
|
||||||
{instances.map(i => (
|
{instances.map(i => (
|
||||||
|
@ -282,23 +280,67 @@ function sortActive(instances: any[]): any[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
instances: any[];
|
||||||
sort: Sort;
|
sort: Sort;
|
||||||
language: string;
|
language: string;
|
||||||
category: Category;
|
topic: Topic;
|
||||||
|
scroll: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Instances extends Component<any, State> {
|
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<Props>({
|
||||||
|
sort: getSortFromQuery,
|
||||||
|
language: d => d || "all",
|
||||||
|
topic: getTopicFromQuery,
|
||||||
|
scroll: d => !!d,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Instances extends Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
sort: SORTS[0],
|
instances: [],
|
||||||
language: i18n.language.split("-")[0],
|
sort: RANDOM_SORT,
|
||||||
category: All_CATEGORY,
|
language: "all",
|
||||||
|
topic: All_TOPIC,
|
||||||
|
scroll: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
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;
|
let instances = instance_stats.stats.instance_details;
|
||||||
const recommended = RECOMMENDED_INSTANCES;
|
const recommended = RECOMMENDED_INSTANCES;
|
||||||
|
|
||||||
|
@ -312,26 +354,26 @@ export class Instances extends Component<any, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category filter
|
// Topic filter
|
||||||
if (this.state.category !== All_CATEGORY) {
|
if (this.state.topic !== All_TOPIC) {
|
||||||
const categoryRecs = recommended.filter(r =>
|
const topicRecs = recommended.filter(r =>
|
||||||
r.categories.includes(this.state.category),
|
r.topics.includes(this.state.topic),
|
||||||
);
|
);
|
||||||
instances = instances.filter(i =>
|
instances = instances.filter(i =>
|
||||||
categoryRecs.map(c => c.domain).includes(i.domain),
|
topicRecs.map(c => c.domain).includes(i.domain),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
if (this.state.sort == SORTS[0]) {
|
if (this.state.sort == RANDOM_SORT) {
|
||||||
instances = sortRandom(instances);
|
instances = sortRandom(instances);
|
||||||
} else if (this.state.sort == SORTS[1]) {
|
} else if (this.state.sort == MOST_ACTIVE_SORT) {
|
||||||
instances = sortActive(instances);
|
instances = sortActive(instances);
|
||||||
} else {
|
} else {
|
||||||
instances = sortActive(instances).reverse();
|
instances = sortActive(instances).reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
return instances;
|
this.setState({ instances });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -345,18 +387,37 @@ export class Instances extends Component<any, State> {
|
||||||
<TitleBlock />
|
<TitleBlock />
|
||||||
<ComparisonBlock />
|
<ComparisonBlock />
|
||||||
{this.filterAndTitleBlock()}
|
{this.filterAndTitleBlock()}
|
||||||
|
<div className="mt-4">
|
||||||
|
{this.state.instances.length > 0 ? (
|
||||||
<InstanceCardGrid
|
<InstanceCardGrid
|
||||||
title={i18n.t("popular_instances")}
|
title={i18n.t("popular_instances")}
|
||||||
instances={this.buildInstanceList()}
|
instances={this.state.instances}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
this.seeAllBtn()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
seeAllBtn() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-300 mb-4">{i18n.t("none_found")}</p>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-secondary text-white normal-case"
|
||||||
|
onClick={linkEvent(this, handleSeeAll)}
|
||||||
|
>
|
||||||
|
{i18n.t("see_all_servers")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO i18n these
|
|
||||||
filterAndTitleBlock() {
|
filterAndTitleBlock() {
|
||||||
return (
|
return (
|
||||||
<div className="my-16">
|
<div id="search" className="mt-16">
|
||||||
<div className="flex flex-row flex-wrap gap-4">
|
<div className="flex flex-row flex-wrap gap-4">
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<SectionTitle title={i18n.t("join_title")} />
|
<SectionTitle title={i18n.t("join_title")} />
|
||||||
|
@ -365,20 +426,19 @@ export class Instances extends Component<any, State> {
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<select
|
<select
|
||||||
className={`${SELECT_CLASSES} mr-2`}
|
className={`${SELECT_CLASSES} mr-2`}
|
||||||
value={this.state.category.name}
|
value={this.state.topic.name}
|
||||||
onChange={linkEvent(this, handleCategoryChange)}
|
onChange={linkEvent(this, handleTopicChange)}
|
||||||
name="category_select"
|
name="topic_select"
|
||||||
>
|
>
|
||||||
<option disabled selected>
|
<option disabled selected>
|
||||||
Category
|
{i18n.t("topic")}
|
||||||
</option>
|
</option>
|
||||||
{CATEGORIES.map(c => (
|
{TOPICS.map(c => (
|
||||||
<option key={c.name} value={c.name}>
|
<option key={c.name} value={c.name}>
|
||||||
{c.name}
|
{i18n.t(c.name as I18nKeys)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={this.state.language}
|
value={this.state.language}
|
||||||
onChange={linkEvent(this, handleLanguageChange)}
|
onChange={linkEvent(this, handleLanguageChange)}
|
||||||
|
@ -386,11 +446,11 @@ export class Instances extends Component<any, State> {
|
||||||
>
|
>
|
||||||
<option disabled>Languages</option>
|
<option disabled>Languages</option>
|
||||||
<option key="all" value="all">
|
<option key="all" value="all">
|
||||||
all
|
{i18n.t("all_languages")}
|
||||||
</option>
|
</option>
|
||||||
{languageList().map((language, i) => (
|
{LANGUAGES.map((l, i) => (
|
||||||
<option key={i} value={language}>
|
<option key={i} value={l.code}>
|
||||||
{LANGUAGES.find(l => l.code.startsWith(language)).name}
|
{l.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
@ -400,10 +460,10 @@ export class Instances extends Component<any, State> {
|
||||||
className={SELECT_CLASSES}
|
className={SELECT_CLASSES}
|
||||||
onChange={linkEvent(this, handleSortChange)}
|
onChange={linkEvent(this, handleSortChange)}
|
||||||
>
|
>
|
||||||
<option disabled>Sort TODO</option>
|
<option disabled>{i18n.t("sort")}</option>
|
||||||
{SORTS.map(s => (
|
{SORTS.map(s => (
|
||||||
<option key={s.name} value={s.name}>
|
<option key={s.name} value={s.name}>
|
||||||
{s.name}
|
{i18n.t(s.name as I18nKeys)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
@ -415,13 +475,29 @@ export class Instances extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSortChange(i: Instances, event: any) {
|
function handleSortChange(i: Instances, event: any) {
|
||||||
i.setState({ sort: SORTS.find(s => s.name == event.target.value) });
|
i.setState({
|
||||||
|
sort: SORTS.find(s => s.name == event.target.value) ?? RANDOM_SORT,
|
||||||
|
});
|
||||||
|
i.buildInstanceList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCategoryChange(i: Instances, event: any) {
|
function handleTopicChange(i: Instances, event: any) {
|
||||||
i.setState({ category: CATEGORIES.find(c => c.name == event.target.value) });
|
i.setState({
|
||||||
|
topic: TOPICS.find(c => c.name == event.target.value) ?? All_TOPIC,
|
||||||
|
});
|
||||||
|
i.buildInstanceList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLanguageChange(i: Instances, event: any) {
|
function handleLanguageChange(i: Instances, event: any) {
|
||||||
i.setState({ language: event.target.value });
|
i.setState({ language: event.target.value });
|
||||||
|
i.buildInstanceList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSeeAll(i: Instances) {
|
||||||
|
i.setState({
|
||||||
|
sort: RANDOM_SORT,
|
||||||
|
language: "all",
|
||||||
|
topic: All_TOPIC,
|
||||||
|
});
|
||||||
|
i.buildInstanceList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
SupportDonateBlock,
|
SupportDonateBlock,
|
||||||
TEXT_GRADIENT,
|
TEXT_GRADIENT,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
|
import { InstancePicker } from "./instance-picker";
|
||||||
|
|
||||||
const TitleBlock = () => (
|
const TitleBlock = () => (
|
||||||
<div className="py-16 flex flex-col items-center">
|
<div className="py-16 flex flex-col items-center">
|
||||||
|
@ -18,9 +19,9 @@ const TitleBlock = () => (
|
||||||
<p className={`text-6xl font-bold ${TEXT_GRADIENT}`}>Lemmy</p>
|
<p className={`text-6xl font-bold ${TEXT_GRADIENT}`}>Lemmy</p>
|
||||||
<p className="text-3xl font-medium text-center">{i18n.t("lemmy_desc")}</p>
|
<p className="text-3xl font-medium text-center">{i18n.t("lemmy_desc")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-around gap-2">
|
<div className="flex flex-row justify-around gap-4">
|
||||||
<JoinServerButton />
|
<JoinServerButton />
|
||||||
<RunServerButton />
|
<SeeAllServersButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -62,18 +63,18 @@ const CarouselBlock = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const JoinServerButton = () => (
|
const JoinServerButton = () => (
|
||||||
<Link className="btn btn-primary text-white normal-case" to="/instances">
|
<button
|
||||||
|
className="btn btn-primary text-white normal-case"
|
||||||
|
onClick={() => (document.getElementById("picker") as any).showModal()}
|
||||||
|
>
|
||||||
{i18n.t("join_a_server")}
|
{i18n.t("join_a_server")}
|
||||||
</Link>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RunServerButton = () => (
|
const SeeAllServersButton = () => (
|
||||||
<a
|
<Link to="/instances" className="btn btn-secondary text-white normal-case">
|
||||||
class="btn btn-secondary text-white normal-case"
|
{i18n.t("see_all_servers")}
|
||||||
href={`/docs/administration/administration.html`}
|
</Link>
|
||||||
>
|
|
||||||
{i18n.t("run_a_server")}
|
|
||||||
</a>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const FollowCommunitiesBlock = () => (
|
const FollowCommunitiesBlock = () => (
|
||||||
|
@ -86,7 +87,7 @@ const FollowCommunitiesBlock = () => (
|
||||||
>
|
>
|
||||||
#<span className={TEXT_GRADIENT}>#</span>
|
#<span className={TEXT_GRADIENT}>#</span>
|
||||||
</T>
|
</T>
|
||||||
<p class="text-sm text-gray-300 text-center mb-6">
|
<p className="text-sm text-gray-300 text-center mb-6">
|
||||||
{i18n.t("lemmy_long_desc")}
|
{i18n.t("lemmy_long_desc")}
|
||||||
</p>
|
</p>
|
||||||
<JoinServerButton />
|
<JoinServerButton />
|
||||||
|
@ -220,12 +221,12 @@ const DiscussionPlatformBlock = () => (
|
||||||
#
|
#
|
||||||
</a>
|
</a>
|
||||||
</T>
|
</T>
|
||||||
<Link
|
<a
|
||||||
className="btn btn-primary bg-white text-primary normal-case"
|
className="btn btn-primary bg-white text-primary normal-case"
|
||||||
to="/instances"
|
href={`/docs/administration/administration.html`}
|
||||||
>
|
>
|
||||||
{i18n.t("join_a_server")}
|
{i18n.t("run_a_server")}
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -272,7 +273,7 @@ const MoreFeaturesBlock = () => (
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
text={
|
text={
|
||||||
<Link class="link link-primary" to="/apps">
|
<Link className="link link-primary" to="/apps">
|
||||||
{i18n.t("mobile_apps_for_ios_and_android")}
|
{i18n.t("mobile_apps_for_ios_and_android")}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
|
@ -293,7 +294,7 @@ const MoreFeaturesBlock = () => (
|
||||||
}
|
}
|
||||||
text={
|
text={
|
||||||
<T i18nKey="full_vote_scores">
|
<T i18nKey="full_vote_scores">
|
||||||
#<code class="text-primary">#</code>#
|
#<code className="text-primary">#</code>#
|
||||||
</T>
|
</T>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -309,7 +310,7 @@ const MoreFeaturesBlock = () => (
|
||||||
icons={<div>:</div>}
|
icons={<div>:</div>}
|
||||||
text={
|
text={
|
||||||
<T i18nKey="emojis_autocomplete">
|
<T i18nKey="emojis_autocomplete">
|
||||||
#<code class="text-primary">#</code>
|
#<code className="text-primary">#</code>
|
||||||
</T>
|
</T>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -321,8 +322,8 @@ const MoreFeaturesBlock = () => (
|
||||||
}
|
}
|
||||||
text={
|
text={
|
||||||
<T i18nKey="user_tagging">
|
<T i18nKey="user_tagging">
|
||||||
#<code class="text-primary">#</code>
|
#<code className="text-primary">#</code>
|
||||||
<code class="text-primary">#</code>
|
<code className="text-primary">#</code>
|
||||||
</T>
|
</T>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -368,11 +369,11 @@ const MoreFeaturesBlock = () => (
|
||||||
}
|
}
|
||||||
text={
|
text={
|
||||||
<T i18nKey="rss_feeds">
|
<T i18nKey="rss_feeds">
|
||||||
#<code class="text-primary">#</code>
|
#<code className="text-primary">#</code>
|
||||||
<code class="text-primary">#</code>
|
<code className="text-primary">#</code>
|
||||||
<code class="text-primary">#</code>
|
<code className="text-primary">#</code>
|
||||||
<code class="text-primary">#</code>
|
<code className="text-primary">#</code>
|
||||||
<code class="text-primary">#</code>
|
<code className="text-primary">#</code>
|
||||||
</T>
|
</T>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -428,6 +429,7 @@ export class Main extends Component<any, any> {
|
||||||
const title = i18n.t("lemmy_title");
|
const title = i18n.t("lemmy_title");
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<InstancePicker />
|
||||||
<Helmet title={title}>
|
<Helmet title={title}>
|
||||||
<meta property={"title"} content={title} />
|
<meta property={"title"} content={title} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Icon, IconSize } from "./icon";
|
||||||
import { i18n, LANGUAGES } from "../i18next";
|
import { i18n, LANGUAGES } from "../i18next";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { SELECT_CLASSES } from "./common";
|
import { SELECT_CLASSES } from "./common";
|
||||||
import { languageList } from "../utils";
|
|
||||||
|
|
||||||
const NavLink = ({ content }) => <li className="text-gray-400">{content}</li>;
|
const NavLink = ({ content }) => <li className="text-gray-400">{content}</li>;
|
||||||
|
|
||||||
|
@ -52,13 +51,13 @@ export const Navbar = ({ footer = false }) => (
|
||||||
onChange={linkEvent(this, handleLanguageChange)}
|
onChange={linkEvent(this, handleLanguageChange)}
|
||||||
className={SELECT_CLASSES}
|
className={SELECT_CLASSES}
|
||||||
>
|
>
|
||||||
{languageList().map((language, i) => (
|
{LANGUAGES.map((l, i) => (
|
||||||
<option
|
<option
|
||||||
key={i}
|
key={i}
|
||||||
value={language}
|
value={l.code}
|
||||||
selected={i18n.language.startsWith(language)}
|
selected={i18n.language.startsWith(l.code)}
|
||||||
>
|
>
|
||||||
{LANGUAGES.find(l => l.code.startsWith(language)).name}
|
{l.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class NewsItem extends Component<any, any> {
|
||||||
get markdown(): string {
|
get markdown(): string {
|
||||||
let title = decodeURIComponent(this.props.match.params.title);
|
let title = decodeURIComponent(this.props.match.params.title);
|
||||||
title = title.replace(/_/g, " ");
|
title = title.replace(/_/g, " ");
|
||||||
return news_md.find(v => v.title == title).markdown;
|
return news_md.find(v => v.title == title)?.markdown ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import markdown_it from "markdown-it";
|
import markdown_it from "markdown-it";
|
||||||
import { i18n } from "./i18next";
|
|
||||||
|
|
||||||
let SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
|
let SHORTNUM_SI_FORMAT = new Intl.NumberFormat("en-US", {
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
|
@ -26,6 +25,20 @@ export function mdToHtml(text: string) {
|
||||||
return { __html: md.render(text) };
|
return { __html: md.render(text) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function languageList() {
|
export function getQueryParams<T extends Record<string, any>>(processors: {
|
||||||
return Object.keys(i18n.services.resourceStore.data).sort();
|
[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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
"strictNullChecks": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|
Loading…
Reference in a new issue