mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-27 19:09:12 +00:00
Merge branch 'main' into feat/default-to-user-primary-lang
This commit is contained in:
commit
5648843f4e
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -1 +1 @@
|
||||||
* @dessalines @SleeplessOne1917 @alectrocute
|
* @dessalines @SleeplessOne1917 @alectrocute @jsit
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "lemmy-ui",
|
"name": "lemmy-ui",
|
||||||
"version": "0.18.0-rc.5",
|
"version": "0.18.0-rc.6",
|
||||||
"description": "An isomorphic UI for lemmy",
|
"description": "An isomorphic UI for lemmy",
|
||||||
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
"repository": "https://github.com/LemmyNet/lemmy-ui",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { initializeSite, isAuthPath } from "@utils/app";
|
import { initializeSite, isAuthPath } from "@utils/app";
|
||||||
|
import { getHttpBaseInternal } from "@utils/env";
|
||||||
import { ErrorPageData } from "@utils/types";
|
import { ErrorPageData } from "@utils/types";
|
||||||
|
import fetch from "cross-fetch";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { StaticRouter, matchPath } from "inferno-router";
|
import { StaticRouter, matchPath } from "inferno-router";
|
||||||
import { renderToString } from "inferno-server";
|
import { renderToString } from "inferno-server";
|
||||||
import IsomorphicCookie from "isomorphic-cookie";
|
import IsomorphicCookie from "isomorphic-cookie";
|
||||||
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
||||||
import { App } from "../../shared/components/app/app";
|
import { App } from "../../shared/components/app/app";
|
||||||
import { getHttpBaseInternal } from "../../shared/env";
|
|
||||||
import {
|
import {
|
||||||
InitialFetchRequest,
|
InitialFetchRequest,
|
||||||
IsoDataOptionalSite,
|
IsoDataOptionalSite,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { getHttpBaseExternal, getHttpBaseInternal } from "@utils/env";
|
||||||
|
import fetch from "cross-fetch";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { LemmyHttp } from "lemmy-js-client";
|
import { LemmyHttp } from "lemmy-js-client";
|
||||||
import { getHttpBaseExternal, getHttpBaseInternal } from "../../shared/env";
|
|
||||||
import { wrapClient } from "../../shared/services/HttpService";
|
import { wrapClient } from "../../shared/services/HttpService";
|
||||||
import generateManifestJson from "../utils/generate-manifest-json";
|
import generateManifestJson from "../utils/generate-manifest-json";
|
||||||
import { setForwardedHeaders } from "../utils/set-forwarded-headers";
|
import { setForwardedHeaders } from "../utils/set-forwarded-headers";
|
||||||
|
|
|
@ -4,15 +4,20 @@ import { readdir } from "fs/promises";
|
||||||
const extraThemesFolder =
|
const extraThemesFolder =
|
||||||
process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes";
|
process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes";
|
||||||
|
|
||||||
const themes = ["darkly", "darkly-red", "litely", "litely-red"];
|
const themes: ReadonlyArray<string> = [
|
||||||
|
"darkly",
|
||||||
|
"darkly-red",
|
||||||
|
"litely",
|
||||||
|
"litely-red",
|
||||||
|
];
|
||||||
|
|
||||||
export async function buildThemeList(): Promise<string[]> {
|
export async function buildThemeList(): Promise<ReadonlyArray<string>> {
|
||||||
if (existsSync(extraThemesFolder)) {
|
if (existsSync(extraThemesFolder)) {
|
||||||
const dirThemes = await readdir(extraThemesFolder);
|
const dirThemes = await readdir(extraThemesFolder);
|
||||||
const cssThemes = dirThemes
|
const cssThemes = dirThemes
|
||||||
.filter(d => d.endsWith(".css"))
|
.filter(d => d.endsWith(".css"))
|
||||||
.map(d => d.replace(".css", ""));
|
.map(d => d.replace(".css", ""));
|
||||||
themes.push(...cssThemes);
|
return themes.concat(cssThemes);
|
||||||
}
|
}
|
||||||
return themes;
|
return themes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import serialize from "serialize-javascript";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
import { favIconPngUrl, favIconUrl } from "../../shared/config";
|
||||||
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
|
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
|
||||||
|
import { buildThemeList } from "./build-themes-list";
|
||||||
import { fetchIconPng } from "./fetch-icon-png";
|
import { fetchIconPng } from "./fetch-icon-png";
|
||||||
|
|
||||||
const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
|
const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
|
||||||
|
@ -16,6 +17,10 @@ export async function createSsrHtml(
|
||||||
) {
|
) {
|
||||||
const site = isoData.site_res;
|
const site = isoData.site_res;
|
||||||
|
|
||||||
|
const fallbackTheme = `<link rel="stylesheet" type="text/css" href="/css/themes/${
|
||||||
|
(await buildThemeList())[0]
|
||||||
|
}.css" />`;
|
||||||
|
|
||||||
if (!appleTouchIcon) {
|
if (!appleTouchIcon) {
|
||||||
appleTouchIcon = site?.site_view.site.icon
|
appleTouchIcon = site?.site_view.site.icon
|
||||||
? `data:image/png;base64,${sharp(
|
? `data:image/png;base64,${sharp(
|
||||||
|
@ -68,7 +73,7 @@ export async function createSsrHtml(
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
<meta name="Description" content="Lemmy">
|
<meta name="Description" content="Lemmy">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link
|
<link
|
||||||
id="favicon"
|
id="favicon"
|
||||||
rel="shortcut icon"
|
rel="shortcut icon"
|
||||||
|
@ -85,7 +90,7 @@ export async function createSsrHtml(
|
||||||
<link rel="stylesheet" type="text/css" href="/static/styles/styles.css" />
|
<link rel="stylesheet" type="text/css" href="/static/styles/styles.css" />
|
||||||
|
|
||||||
<!-- Current theme and more -->
|
<!-- Current theme and more -->
|
||||||
${helmet.link.toString()}
|
${helmet.link.toString() || fallbackTheme}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import fetch from "cross-fetch";
|
||||||
|
|
||||||
export async function fetchIconPng(iconUrl: string) {
|
export async function fetchIconPng(iconUrl: string) {
|
||||||
return await fetch(iconUrl)
|
return await fetch(iconUrl)
|
||||||
.then(res => res.blob())
|
.then(res => res.blob())
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { getHttpBaseExternal } from "@utils/env";
|
||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import { GetSiteResponse } from "lemmy-js-client";
|
import { GetSiteResponse } from "lemmy-js-client";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { getHttpBaseExternal } from "../../shared/env";
|
|
||||||
import { fetchIconPng } from "./fetch-icon-png";
|
import { fetchIconPng } from "./fetch-icon-png";
|
||||||
|
|
||||||
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { httpExternalPath } from "@utils/env";
|
||||||
import { htmlToText } from "html-to-text";
|
import { htmlToText } from "html-to-text";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { Helmet } from "inferno-helmet";
|
import { Helmet } from "inferno-helmet";
|
||||||
import { httpExternalPath } from "../../env";
|
|
||||||
import { md } from "../../markdown";
|
import { md } from "../../markdown";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<picture className="pictrs-image d-inline-block overflow-hidden">
|
<picture>
|
||||||
<source srcSet={this.src("webp")} type="image/webp" />
|
<source srcSet={this.src("webp")} type="image/webp" />
|
||||||
<source srcSet={this.props.src} />
|
<source srcSet={this.props.src} />
|
||||||
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
||||||
|
@ -31,7 +31,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
alt={this.alt()}
|
alt={this.alt()}
|
||||||
title={this.alt()}
|
title={this.alt()}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={classNames({
|
className={classNames("overflow-hidden pictrs-image", {
|
||||||
"img-fluid": !this.props.icon && !this.props.iconOverlay,
|
"img-fluid": !this.props.icon && !this.props.iconOverlay,
|
||||||
banner: this.props.banner,
|
banner: this.props.banner,
|
||||||
"thumbnail rounded":
|
"thumbnail rounded":
|
||||||
|
|
|
@ -67,6 +67,13 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
||||||
<option disabled aria-hidden="true">
|
<option disabled aria-hidden="true">
|
||||||
─────
|
─────
|
||||||
</option>
|
</option>
|
||||||
|
<option value={"TopHour"}>{I18NextService.i18n.t("top_hour")}</option>
|
||||||
|
<option value={"TopSixHour"}>
|
||||||
|
{I18NextService.i18n.t("top_six_hours")}
|
||||||
|
</option>
|
||||||
|
<option value={"TopTwelveHour"}>
|
||||||
|
{I18NextService.i18n.t("top_twelve_hours")}
|
||||||
|
</option>
|
||||||
<option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
|
<option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
|
||||||
<option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
|
<option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
|
||||||
<option value={"TopMonth"}>
|
<option value={"TopMonth"}>
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
updateCommunityBlock,
|
updateCommunityBlock,
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
|
|
||||||
import {
|
import {
|
||||||
getPageFromString,
|
getPageFromString,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
|
@ -229,10 +228,6 @@ export class Community extends Component<
|
||||||
setupTippy();
|
setupTippy();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
saveScrollPosition(this.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
path,
|
path,
|
||||||
|
@ -609,7 +604,6 @@ export class Community extends Component<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreScrollPosition(this.context);
|
|
||||||
setupTippy();
|
setupTippy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
showLocal,
|
showLocal,
|
||||||
updatePersonBlock,
|
updatePersonBlock,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
|
|
||||||
import {
|
import {
|
||||||
getPageFromString,
|
getPageFromString,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
|
@ -293,10 +292,6 @@ export class Home extends Component<any, HomeState> {
|
||||||
setupTippy();
|
setupTippy();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
saveScrollPosition(this.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
client,
|
client,
|
||||||
auth,
|
auth,
|
||||||
|
@ -800,7 +795,6 @@ export class Home extends Component<any, HomeState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreScrollPosition(this.context);
|
|
||||||
setupTippy();
|
setupTippy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { myAuthRequired, newVote, showScores } from "@utils/app";
|
import { myAuthRequired, newVote, showScores } from "@utils/app";
|
||||||
import { canShare, share } from "@utils/browser";
|
import { canShare, share } from "@utils/browser";
|
||||||
import { futureDaysToUnixTime, hostname, numToSI } from "@utils/helpers";
|
import { getExternalHost, getHttpBase } from "@utils/env";
|
||||||
|
import {
|
||||||
|
capitalizeFirstLetter,
|
||||||
|
futureDaysToUnixTime,
|
||||||
|
hostname,
|
||||||
|
numToSI,
|
||||||
|
} from "@utils/helpers";
|
||||||
import { isImage, isVideo } from "@utils/media";
|
import { isImage, isVideo } from "@utils/media";
|
||||||
import {
|
import {
|
||||||
amAdmin,
|
amAdmin,
|
||||||
|
@ -38,7 +44,6 @@ import {
|
||||||
TransferCommunity,
|
TransferCommunity,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { relTags } from "../../config";
|
import { relTags } from "../../config";
|
||||||
import { getExternalHost, getHttpBase } from "../../env";
|
|
||||||
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
|
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
|
||||||
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
|
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
|
@ -234,25 +239,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get img() {
|
get img() {
|
||||||
return this.imageSrc ? (
|
if (this.imageSrc) {
|
||||||
<>
|
return (
|
||||||
<div className="offset-sm-3 my-2 d-none d-sm-block">
|
<>
|
||||||
<a href={this.imageSrc} className="d-inline-block">
|
<div className="offset-sm-3 my-2 d-none d-sm-block">
|
||||||
<PictrsImage src={this.imageSrc} />
|
<a href={this.imageSrc} className="d-inline-block">
|
||||||
</a>
|
<PictrsImage src={this.imageSrc} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="my-2 d-block d-sm-none">
|
||||||
|
<a
|
||||||
|
className="d-inline-block"
|
||||||
|
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||||
|
>
|
||||||
|
<PictrsImage src={this.imageSrc} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { post } = this.postView;
|
||||||
|
const { url } = post;
|
||||||
|
|
||||||
|
if (url && isVideo(url)) {
|
||||||
|
return (
|
||||||
|
<div className="embed-responsive mt-3">
|
||||||
|
<video muted controls className="embed-responsive-item col-12">
|
||||||
|
<source src={url} type="video/mp4" />
|
||||||
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-2 d-block d-sm-none">
|
);
|
||||||
<a
|
}
|
||||||
className="d-inline-block"
|
|
||||||
onClick={linkEvent(this, this.handleImageExpandClick)}
|
return <></>;
|
||||||
>
|
|
||||||
<PictrsImage src={this.imageSrc} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imgThumb(src: string) {
|
imgThumb(src: string) {
|
||||||
|
@ -320,17 +340,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
} else if (url) {
|
} else if (url) {
|
||||||
if (!this.props.hideImage && isVideo(url)) {
|
if (!this.props.hideImage && isVideo(url)) {
|
||||||
return (
|
return (
|
||||||
<div className="embed-responsive embed-responsive-16by9">
|
<a
|
||||||
<video
|
className="text-body"
|
||||||
playsInline
|
href={url}
|
||||||
muted
|
title={url}
|
||||||
loop
|
rel={relTags}
|
||||||
controls
|
data-tippy-content={I18NextService.i18n.t("expand_here")}
|
||||||
className="embed-responsive-item"
|
onClick={linkEvent(this, this.handleImageExpandClick)}
|
||||||
>
|
aria-label={I18NextService.i18n.t("expand_here")}
|
||||||
<source src={url} type="video/mp4" />
|
>
|
||||||
</video>
|
<div className="thumbnail rounded bg-light d-flex justify-content-center">
|
||||||
</div>
|
<Icon icon="play" classes="d-flex align-items-center" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -981,7 +1003,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
classes={classNames("me-1", { "text-danger": locked })}
|
classes={classNames("me-1", { "text-danger": locked })}
|
||||||
inline
|
inline
|
||||||
/>
|
/>
|
||||||
{label}
|
{capitalizeFirstLetter(label)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -24,3 +24,15 @@ export const updateUnreadCountsInterval = 30000;
|
||||||
export const fetchLimit = 40;
|
export const fetchLimit = 40;
|
||||||
export const relTags = "noopener nofollow";
|
export const relTags = "noopener nofollow";
|
||||||
export const emDash = "\u2014";
|
export const emDash = "\u2014";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepted formats:
|
||||||
|
* !community@server.com
|
||||||
|
* /c/community@server.com
|
||||||
|
* /m/community@server.com
|
||||||
|
* /u/username@server.com
|
||||||
|
*/
|
||||||
|
export const instanceLinkRegex =
|
||||||
|
/(\/[cmu]\/|!)[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
||||||
|
|
||||||
|
export const testHost = "0.0.0.0:8536";
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
import { isBrowser } from "@utils/browser";
|
|
||||||
|
|
||||||
const testHost = "0.0.0.0:8536";
|
|
||||||
|
|
||||||
function getInternalHost() {
|
|
||||||
return !isBrowser()
|
|
||||||
? process.env.LEMMY_UI_LEMMY_INTERNAL_HOST ?? testHost
|
|
||||||
: testHost; // used for local dev
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getExternalHost() {
|
|
||||||
return isBrowser()
|
|
||||||
? `${window.location.hostname}${
|
|
||||||
["1234", "1235"].includes(window.location.port)
|
|
||||||
? ":8536"
|
|
||||||
: window.location.port == ""
|
|
||||||
? ""
|
|
||||||
: `:${window.location.port}`
|
|
||||||
}`
|
|
||||||
: process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST || testHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSecure() {
|
|
||||||
return (
|
|
||||||
isBrowser()
|
|
||||||
? window.location.protocol.includes("https")
|
|
||||||
: process.env.LEMMY_UI_HTTPS === "true"
|
|
||||||
)
|
|
||||||
? "s"
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHost() {
|
|
||||||
return isBrowser() ? getExternalHost() : getInternalHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBaseLocal(s = "") {
|
|
||||||
return `http${s}://${getHost()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHttpBaseInternal() {
|
|
||||||
return getBaseLocal(); // Don't use secure here
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHttpBaseExternal() {
|
|
||||||
return `http${getSecure()}://${getExternalHost()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHttpBase() {
|
|
||||||
return getBaseLocal(getSecure());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isHttps() {
|
|
||||||
return getSecure() === "s";
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`httpbase: ${getHttpBase()}`);
|
|
||||||
console.log(`isHttps: ${isHttps()}`);
|
|
||||||
|
|
||||||
// This is for html tags, don't include port
|
|
||||||
export function httpExternalPath(path: string) {
|
|
||||||
return `http${getSecure()}://${getExternalHost().replace(
|
|
||||||
/:\d+/g,
|
|
||||||
""
|
|
||||||
)}${path}`;
|
|
||||||
}
|
|
|
@ -14,6 +14,7 @@ import markdown_it_sub from "markdown-it-sub";
|
||||||
import markdown_it_sup from "markdown-it-sup";
|
import markdown_it_sup from "markdown-it-sup";
|
||||||
import Renderer from "markdown-it/lib/renderer";
|
import Renderer from "markdown-it/lib/renderer";
|
||||||
import Token from "markdown-it/lib/token";
|
import Token from "markdown-it/lib/token";
|
||||||
|
import { instanceLinkRegex } from "./config";
|
||||||
|
|
||||||
export let Tribute: any;
|
export let Tribute: any;
|
||||||
|
|
||||||
|
@ -72,6 +73,75 @@ const html5EmbedConfig = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function localInstanceLinkParser(md: MarkdownIt) {
|
||||||
|
md.core.ruler.push("replace-text", state => {
|
||||||
|
for (let i = 0; i < state.tokens.length; i++) {
|
||||||
|
if (state.tokens[i].type !== "inline") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const inlineTokens: Token[] = state.tokens[i].children || [];
|
||||||
|
for (let j = inlineTokens.length - 1; j >= 0; j--) {
|
||||||
|
if (
|
||||||
|
inlineTokens[j].type === "text" &&
|
||||||
|
new RegExp(instanceLinkRegex).test(inlineTokens[j].content)
|
||||||
|
) {
|
||||||
|
const text = inlineTokens[j].content;
|
||||||
|
const matches = Array.from(text.matchAll(instanceLinkRegex));
|
||||||
|
|
||||||
|
let lastIndex = 0;
|
||||||
|
const newTokens: Token[] = [];
|
||||||
|
|
||||||
|
let linkClass = "community-link";
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
// If there is plain text before the match, add it as a separate token
|
||||||
|
if (match.index !== undefined && match.index > lastIndex) {
|
||||||
|
const textToken = new state.Token("text", "", 0);
|
||||||
|
textToken.content = text.slice(lastIndex, match.index);
|
||||||
|
newTokens.push(textToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
let href;
|
||||||
|
if (match[0].startsWith("!")) {
|
||||||
|
href = "/c/" + match[0].substring(1);
|
||||||
|
} else if (match[0].startsWith("/m/")) {
|
||||||
|
href = "/c/" + match[0].substring(3);
|
||||||
|
} else {
|
||||||
|
href = match[0];
|
||||||
|
if (match[0].startsWith("/u/")) {
|
||||||
|
linkClass = "user-link";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkOpenToken = new state.Token("link_open", "a", 1);
|
||||||
|
linkOpenToken.attrs = [
|
||||||
|
["href", href],
|
||||||
|
["class", linkClass],
|
||||||
|
];
|
||||||
|
const textToken = new state.Token("text", "", 0);
|
||||||
|
textToken.content = match[0];
|
||||||
|
const linkCloseToken = new state.Token("link_close", "a", -1);
|
||||||
|
|
||||||
|
newTokens.push(linkOpenToken, textToken, linkCloseToken);
|
||||||
|
|
||||||
|
lastIndex =
|
||||||
|
(match.index !== undefined ? match.index : 0) + match[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is plain text after the last match, add it as a separate token
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
const textToken = new state.Token("text", "", 0);
|
||||||
|
textToken.content = text.slice(lastIndex);
|
||||||
|
newTokens.push(textToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
inlineTokens.splice(j, 1, ...newTokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function setupMarkdown() {
|
export function setupMarkdown() {
|
||||||
const markdownItConfig: MarkdownIt.Options = {
|
const markdownItConfig: MarkdownIt.Options = {
|
||||||
html: false,
|
html: false,
|
||||||
|
@ -88,7 +158,8 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_sup)
|
.use(markdown_it_sup)
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig);
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
|
.use(localInstanceLinkParser);
|
||||||
// .use(markdown_it_emoji, {
|
// .use(markdown_it_emoji, {
|
||||||
// defs: emojiDefs,
|
// defs: emojiDefs,
|
||||||
// });
|
// });
|
||||||
|
@ -99,6 +170,7 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig)
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
|
.use(localInstanceLinkParser)
|
||||||
// .use(markdown_it_emoji, {
|
// .use(markdown_it_emoji, {
|
||||||
// defs: emojiDefs,
|
// defs: emojiDefs,
|
||||||
// })
|
// })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { getHttpBase } from "@utils/env";
|
||||||
import { LemmyHttp } from "lemmy-js-client";
|
import { LemmyHttp } from "lemmy-js-client";
|
||||||
import { getHttpBase } from "../../shared/env";
|
|
||||||
import { toast } from "../../shared/toast";
|
import { toast } from "../../shared/toast";
|
||||||
import { I18NextService } from "./I18NextService";
|
import { I18NextService } from "./I18NextService";
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// import Cookies from 'js-cookie';
|
// import Cookies from 'js-cookie';
|
||||||
import { isAuthPath } from "@utils/app";
|
import { isAuthPath } from "@utils/app";
|
||||||
import { isBrowser } from "@utils/browser";
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { isHttps } from "@utils/env";
|
||||||
import IsomorphicCookie from "isomorphic-cookie";
|
import IsomorphicCookie from "isomorphic-cookie";
|
||||||
import jwt_decode from "jwt-decode";
|
import jwt_decode from "jwt-decode";
|
||||||
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
|
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
|
||||||
import { isHttps } from "../env";
|
|
||||||
import { toast } from "../toast";
|
import { toast } from "../toast";
|
||||||
import { I18NextService } from "./I18NextService";
|
import { I18NextService } from "./I18NextService";
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ export default function convertCommentSortType(
|
||||||
): CommentSortType {
|
): CommentSortType {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case "TopAll":
|
case "TopAll":
|
||||||
|
case "TopHour":
|
||||||
|
case "TopSixHour":
|
||||||
|
case "TopTwelveHour":
|
||||||
case "TopDay":
|
case "TopDay":
|
||||||
case "TopWeek":
|
case "TopWeek":
|
||||||
case "TopMonth":
|
case "TopMonth":
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export default function restoreScrollPosition(context: any) {
|
export default function restoreScrollPosition(context: any) {
|
||||||
const path: string = context.router.route.location.pathname;
|
const path: string = context.router.route.location.pathname;
|
||||||
const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
|
const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
|
||||||
|
|
||||||
window.scrollTo(0, y);
|
window.scrollTo(0, y);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export default function saveScrollPosition(context: any) {
|
export default function saveScrollPosition(context: any) {
|
||||||
const path: string = context.router.route.location.pathname;
|
const path: string = context.router.route.location.pathname;
|
||||||
const y = window.scrollY;
|
const y = window.scrollY;
|
||||||
|
|
||||||
sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
|
sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
|
||||||
}
|
}
|
||||||
|
|
5
src/shared/utils/env/get-base-local.ts
vendored
Normal file
5
src/shared/utils/env/get-base-local.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { getHost } from "@utils/env";
|
||||||
|
|
||||||
|
export default function getBaseLocal(s = "") {
|
||||||
|
return `http${s}://${getHost()}`;
|
||||||
|
}
|
14
src/shared/utils/env/get-external-host.ts
vendored
Normal file
14
src/shared/utils/env/get-external-host.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { testHost } from "../../config";
|
||||||
|
|
||||||
|
export default function getExternalHost() {
|
||||||
|
return isBrowser()
|
||||||
|
? `${window.location.hostname}${
|
||||||
|
["1234", "1235"].includes(window.location.port)
|
||||||
|
? ":8536"
|
||||||
|
: window.location.port == ""
|
||||||
|
? ""
|
||||||
|
: `:${window.location.port}`
|
||||||
|
}`
|
||||||
|
: process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST || testHost;
|
||||||
|
}
|
6
src/shared/utils/env/get-host.ts
vendored
Normal file
6
src/shared/utils/env/get-host.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { getExternalHost, getInternalHost } from "@utils/env";
|
||||||
|
|
||||||
|
export default function getHost() {
|
||||||
|
return isBrowser() ? getExternalHost() : getInternalHost();
|
||||||
|
}
|
5
src/shared/utils/env/get-http-base-external.ts
vendored
Normal file
5
src/shared/utils/env/get-http-base-external.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { getExternalHost, getSecure } from "@utils/env";
|
||||||
|
|
||||||
|
export default function getHttpBaseExternal() {
|
||||||
|
return `http${getSecure()}://${getExternalHost()}`;
|
||||||
|
}
|
5
src/shared/utils/env/get-http-base-internal.ts
vendored
Normal file
5
src/shared/utils/env/get-http-base-internal.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { getBaseLocal } from "@utils/env";
|
||||||
|
|
||||||
|
export default function getHttpBaseInternal() {
|
||||||
|
return getBaseLocal(); // Don't use secure here
|
||||||
|
}
|
5
src/shared/utils/env/get-http-base.ts
vendored
Normal file
5
src/shared/utils/env/get-http-base.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { getBaseLocal, getSecure } from "@utils/env";
|
||||||
|
|
||||||
|
export default function getHttpBase() {
|
||||||
|
return getBaseLocal(getSecure());
|
||||||
|
}
|
8
src/shared/utils/env/get-internal-host.ts
vendored
Normal file
8
src/shared/utils/env/get-internal-host.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { testHost } from "../../config";
|
||||||
|
|
||||||
|
export default function getInternalHost() {
|
||||||
|
return !isBrowser()
|
||||||
|
? process.env.LEMMY_UI_LEMMY_INTERNAL_HOST ?? testHost
|
||||||
|
: testHost; // used for local dev
|
||||||
|
}
|
11
src/shared/utils/env/get-secure.ts
vendored
Normal file
11
src/shared/utils/env/get-secure.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
|
export default function getSecure() {
|
||||||
|
return (
|
||||||
|
isBrowser()
|
||||||
|
? window.location.protocol.includes("https")
|
||||||
|
: process.env.LEMMY_UI_HTTPS === "true"
|
||||||
|
)
|
||||||
|
? "s"
|
||||||
|
: "";
|
||||||
|
}
|
9
src/shared/utils/env/http-external-path.ts
vendored
Normal file
9
src/shared/utils/env/http-external-path.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { getExternalHost, getSecure } from "@utils/env";
|
||||||
|
|
||||||
|
// This is for html tags, don't include port
|
||||||
|
export default function httpExternalPath(path: string) {
|
||||||
|
return `http${getSecure()}://${getExternalHost().replace(
|
||||||
|
/:\d+/g,
|
||||||
|
""
|
||||||
|
)}${path}`;
|
||||||
|
}
|
23
src/shared/utils/env/index.ts
vendored
Normal file
23
src/shared/utils/env/index.ts
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import getBaseLocal from "./get-base-local";
|
||||||
|
import getExternalHost from "./get-external-host";
|
||||||
|
import getHost from "./get-host";
|
||||||
|
import getHttpBase from "./get-http-base";
|
||||||
|
import getHttpBaseExternal from "./get-http-base-external";
|
||||||
|
import getHttpBaseInternal from "./get-http-base-internal";
|
||||||
|
import getInternalHost from "./get-internal-host";
|
||||||
|
import getSecure from "./get-secure";
|
||||||
|
import httpExternalPath from "./http-external-path";
|
||||||
|
import isHttps from "./is-https";
|
||||||
|
|
||||||
|
export {
|
||||||
|
getBaseLocal,
|
||||||
|
getExternalHost,
|
||||||
|
getHost,
|
||||||
|
getHttpBase,
|
||||||
|
getHttpBaseExternal,
|
||||||
|
getHttpBaseInternal,
|
||||||
|
getInternalHost,
|
||||||
|
getSecure,
|
||||||
|
httpExternalPath,
|
||||||
|
isHttps,
|
||||||
|
};
|
5
src/shared/utils/env/is-https.ts
vendored
Normal file
5
src/shared/utils/env/is-https.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { getSecure } from "@utils/env";
|
||||||
|
|
||||||
|
export default function isHttps() {
|
||||||
|
return getSecure() === "s";
|
||||||
|
}
|
Loading…
Reference in a new issue