Merge branch 'main' into i386
This commit is contained in:
commit
2b8752b51e
14 changed files with 12038 additions and 77 deletions
117
src/assets/css/themes/_variables.darkly-pureblack.scss
Normal file
117
src/assets/css/themes/_variables.darkly-pureblack.scss
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
@import "./variables";
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
$white: #f3f3f3;
|
||||||
|
$gray-200: #ebebeb;
|
||||||
|
$gray-300: #dee2e6;
|
||||||
|
$gray-500: #adb5bd;
|
||||||
|
$gray-600: #666;
|
||||||
|
$gray-700: #333;
|
||||||
|
$gray-800: #202020;
|
||||||
|
$gray-900: #111;
|
||||||
|
$black: #000;
|
||||||
|
|
||||||
|
$blue: #375a7f;
|
||||||
|
$red: #e74c3c;
|
||||||
|
$yellow: #f39c12;
|
||||||
|
$green: #00bc8c;
|
||||||
|
$cyan: #3498db;
|
||||||
|
|
||||||
|
$primary: $green;
|
||||||
|
$secondary: $gray-700;
|
||||||
|
$success: $green;
|
||||||
|
$dark: $gray-300;
|
||||||
|
|
||||||
|
$body-color: $gray-200;
|
||||||
|
$body-bg: $black;
|
||||||
|
$link-color: $success;
|
||||||
|
$border-color: rgba($body-color, 0.25);
|
||||||
|
$mark-bg: $gray-900;
|
||||||
|
$text-muted: $gray-600;
|
||||||
|
$yiq-contrasted-threshold: 175;
|
||||||
|
|
||||||
|
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
$font-size-base: 0.9375rem;
|
||||||
|
$h1-font-size: 3rem;
|
||||||
|
$h2-font-size: 2.5rem;
|
||||||
|
$h3-font-size: 2rem;
|
||||||
|
|
||||||
|
$card-cap-bg: $gray-900;
|
||||||
|
$card-bg: $gray-900;
|
||||||
|
$card-color: $gray-300;
|
||||||
|
|
||||||
|
$navbar-padding-y: 1rem;
|
||||||
|
$navbar-dark-color: rgba($white, 0.6);
|
||||||
|
$navbar-dark-hover-color: $white;
|
||||||
|
$navbar-light-color: rgba($white, 0.6);
|
||||||
|
$navbar-light-hover-color: $white;
|
||||||
|
$navbar-light-active-color: $white;
|
||||||
|
$navbar-light-toggler-border-color: rgba($gray-900, 0.1);
|
||||||
|
$navbar-light-brand-color: $white;
|
||||||
|
$navbar-light-brand-hover-color: $navbar-light-brand-color;
|
||||||
|
|
||||||
|
$nav-link-padding-x: 2rem;
|
||||||
|
$nav-link-disabled-color: $gray-500;
|
||||||
|
|
||||||
|
$nav-tabs-border-color: $gray-700;
|
||||||
|
$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color
|
||||||
|
transparent;
|
||||||
|
$nav-tabs-link-active-color: $white;
|
||||||
|
$nav-tabs-link-active-border-color: $nav-tabs-border-color
|
||||||
|
$nav-tabs-border-color transparent;
|
||||||
|
|
||||||
|
$input-bg: $gray-900;
|
||||||
|
$input-color: $white;
|
||||||
|
$input-disabled-bg: darken($gray-900, 20%);
|
||||||
|
$input-border-color: $gray-800;
|
||||||
|
$input-group-addon-color: $gray-800;
|
||||||
|
$input-group-addon-bg: $gray-800;
|
||||||
|
|
||||||
|
$hr-border-color: rgba($body-color, 0.25);
|
||||||
|
|
||||||
|
$table-border-color: $gray-700;
|
||||||
|
|
||||||
|
$custom-file-color: $gray-500;
|
||||||
|
$custom-file-border-color: $body-bg;
|
||||||
|
|
||||||
|
$dropdown-bg: $gray-900;
|
||||||
|
$dropdown-border-color: $gray-800;
|
||||||
|
$dropdown-divider-bg: $gray-700;
|
||||||
|
$dropdown-link-color: $white;
|
||||||
|
$dropdown-link-hover-color: $white;
|
||||||
|
$dropdown-link-hover-bg: $primary;
|
||||||
|
|
||||||
|
$pagination-color: $white;
|
||||||
|
$pagination-bg: $success;
|
||||||
|
$pagination-border-width: 0;
|
||||||
|
$pagination-border-color: transparent;
|
||||||
|
$pagination-hover-color: $white;
|
||||||
|
$pagination-hover-bg: lighten($success, 10%);
|
||||||
|
$pagination-hover-border-color: transparent;
|
||||||
|
$pagination-active-bg: $pagination-hover-bg;
|
||||||
|
$pagination-active-border-color: transparent;
|
||||||
|
$pagination-disabled-color: $white;
|
||||||
|
$pagination-disabled-bg: darken($success, 15%);
|
||||||
|
$pagination-disabled-border-color: transparent;
|
||||||
|
|
||||||
|
$jumbotron-bg: $gray-900;
|
||||||
|
$popover-bg: $gray-900;
|
||||||
|
$popover-header-bg: $gray-900;
|
||||||
|
$toast-background-color: $gray-800;
|
||||||
|
$toast-header-background-color: $gray-900;
|
||||||
|
$modal-content-bg: $gray-800;
|
||||||
|
$modal-content-border-color: $gray-700;
|
||||||
|
$modal-header-border-color: $gray-700;
|
||||||
|
$progress-bg: $gray-700;
|
||||||
|
$list-group-bg: $gray-800;
|
||||||
|
$list-group-border-color: $gray-700;
|
||||||
|
$list-group-hover-bg: $gray-700;
|
||||||
|
$breadcrumb-bg: $gray-700;
|
||||||
|
$close-color: $white;
|
||||||
|
$close-text-shadow: none;
|
||||||
|
$pre-color: inherit;
|
||||||
|
$custom-select-bg: $gray-700;
|
||||||
|
$custom-select-color: $white;
|
||||||
|
$light: $gray-900;
|
11829
src/assets/css/themes/darkly-pureblack.css
Normal file
11829
src/assets/css/themes/darkly-pureblack.css
Normal file
File diff suppressed because it is too large
Load diff
2
src/assets/css/themes/darkly-pureblack.scss
Normal file
2
src/assets/css/themes/darkly-pureblack.scss
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
@import "variables.darkly-pureblack";
|
||||||
|
@import "../../../../node_modules/bootstrap/scss/bootstrap";
|
|
@ -8,7 +8,7 @@ import RobotsHandler from "./handlers/robots-handler";
|
||||||
import ServiceWorkerHandler from "./handlers/service-worker-handler";
|
import ServiceWorkerHandler from "./handlers/service-worker-handler";
|
||||||
import ThemeHandler from "./handlers/theme-handler";
|
import ThemeHandler from "./handlers/theme-handler";
|
||||||
import ThemesListHandler from "./handlers/themes-list-handler";
|
import ThemesListHandler from "./handlers/themes-list-handler";
|
||||||
import setDefaultCsp from "./middleware/set-default-csp";
|
import { setCacheControl, setDefaultCsp } from "./middleware";
|
||||||
|
|
||||||
const server = express();
|
const server = express();
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"]
|
||||||
server.use(express.json());
|
server.use(express.json());
|
||||||
server.use(express.urlencoded({ extended: false }));
|
server.use(express.urlencoded({ extended: false }));
|
||||||
server.use("/static", express.static(path.resolve("./dist")));
|
server.use("/static", express.static(path.resolve("./dist")));
|
||||||
|
server.use(setCacheControl);
|
||||||
|
|
||||||
if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
|
if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
|
||||||
server.use(setDefaultCsp);
|
server.use(setDefaultCsp);
|
||||||
|
|
42
src/server/middleware.ts
Normal file
42
src/server/middleware.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import type { NextFunction, Response } from "express";
|
||||||
|
import { UserService } from "../shared/services";
|
||||||
|
|
||||||
|
export function setDefaultCsp({
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
}: {
|
||||||
|
res: Response;
|
||||||
|
next: NextFunction;
|
||||||
|
}) {
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
`default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *; media-src *`
|
||||||
|
);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cache-control headers. If user is logged in, set `private` to prevent storing data in
|
||||||
|
// shared caches (eg nginx) and leaking of private data. If user is not logged in, allow caching
|
||||||
|
// all responses for 60 seconds to reduce load on backend and database. The specific cache
|
||||||
|
// interval is rather arbitrary and could be set higher (less server load) or lower (fresher data).
|
||||||
|
//
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||||
|
export function setCacheControl({
|
||||||
|
res,
|
||||||
|
next,
|
||||||
|
}: {
|
||||||
|
res: Response;
|
||||||
|
next: NextFunction;
|
||||||
|
}) {
|
||||||
|
const user = UserService.Instance;
|
||||||
|
let caching;
|
||||||
|
if (user.auth()) {
|
||||||
|
caching = "private";
|
||||||
|
} else {
|
||||||
|
caching = "public, max-age=60";
|
||||||
|
}
|
||||||
|
res.setHeader("Cache-Control", caching);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
import type { NextFunction, Response } from "express";
|
|
||||||
|
|
||||||
export default function ({ res, next }: { res: Response; next: NextFunction }) {
|
|
||||||
res.setHeader(
|
|
||||||
"Content-Security-Policy",
|
|
||||||
`default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *; media-src * data:`
|
|
||||||
);
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@ const themes: ReadonlyArray<string> = [
|
||||||
"darkly",
|
"darkly",
|
||||||
"darkly-red",
|
"darkly-red",
|
||||||
"darkly-compact",
|
"darkly-compact",
|
||||||
|
"darkly-pureblack",
|
||||||
"litely",
|
"litely",
|
||||||
"litely-red",
|
"litely-red",
|
||||||
"litely-compact",
|
"litely-compact",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
import { myAuthRequired } from "@utils/app";
|
||||||
import getUserInterfaceLangId from "@utils/app/user-interface-language";
|
|
||||||
import { capitalizeFirstLetter } from "@utils/helpers";
|
import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
|
@ -41,8 +40,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
: undefined
|
: undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const userInterfaceLangId = getUserInterfaceLangId(this.props.allLanguages);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={["comment-form", "mb-3", this.props.containerClass].join(
|
className={["comment-form", "mb-3", this.props.containerClass].join(
|
||||||
|
@ -52,7 +49,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
|
||||||
{UserService.Instance.myUserInfo ? (
|
{UserService.Instance.myUserInfo ? (
|
||||||
<MarkdownTextArea
|
<MarkdownTextArea
|
||||||
initialContent={initialContent}
|
initialContent={initialContent}
|
||||||
initialLanguageId={userInterfaceLangId}
|
|
||||||
showLanguage
|
showLanguage
|
||||||
buttonTitle={this.buttonTitle}
|
buttonTitle={this.buttonTitle}
|
||||||
finished={this.props.finished}
|
finished={this.props.finished}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
|
||||||
return this.props.iconVersion ? (
|
return this.props.iconVersion ? (
|
||||||
this.selectBtn
|
this.selectBtn
|
||||||
) : (
|
) : (
|
||||||
<div className="language-select row mb-3">
|
<div className="language-select mb-3">
|
||||||
<label
|
<label
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"col-form-label",
|
"col-form-label",
|
||||||
|
|
|
@ -274,12 +274,8 @@ export class MarkdownTextArea extends Component<
|
||||||
<LanguageSelect
|
<LanguageSelect
|
||||||
iconVersion
|
iconVersion
|
||||||
allLanguages={this.props.allLanguages}
|
allLanguages={this.props.allLanguages}
|
||||||
// Only set the selected language ID if it exists as an option
|
|
||||||
// in the dropdown; otherwise, set it to 0 (Undetermined)
|
|
||||||
selectedLanguageIds={
|
selectedLanguageIds={
|
||||||
languageId && this.props.siteLanguages.includes(languageId)
|
languageId ? Array.of(languageId) : undefined
|
||||||
? [languageId]
|
|
||||||
: [0]
|
|
||||||
}
|
}
|
||||||
siteLanguages={this.props.siteLanguages}
|
siteLanguages={this.props.siteLanguages}
|
||||||
onChange={this.handleLanguageChange}
|
onChange={this.handleLanguageChange}
|
||||||
|
|
|
@ -166,7 +166,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
communityTitle() {
|
communityTitle() {
|
||||||
const community = this.props.community_view.community;
|
const community = this.props.community_view.community;
|
||||||
const subscribed = this.props.community_view.subscribed;
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h5 className="mb-0">
|
<h5 className="mb-0">
|
||||||
|
@ -176,33 +176,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
<span className="me-2">
|
<span className="me-2">
|
||||||
<CommunityLink community={community} hideAvatar />
|
<CommunityLink community={community} hideAvatar />
|
||||||
</span>
|
</span>
|
||||||
{subscribed === "Subscribed" && (
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary btn-sm me-2"
|
|
||||||
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon icon="check" classes="icon-inline text-success me-1" />
|
|
||||||
{I18NextService.i18n.t("joined")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{subscribed === "Pending" && (
|
|
||||||
<button
|
|
||||||
className="btn btn-warning me-2"
|
|
||||||
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
I18NextService.i18n.t("subscribe_pending")
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{community.removed && (
|
{community.removed && (
|
||||||
<small className="me-2 text-muted fst-italic">
|
<small className="me-2 text-muted fst-italic">
|
||||||
{I18NextService.i18n.t("removed")}
|
{I18NextService.i18n.t("removed")}
|
||||||
|
@ -259,8 +232,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
subscribe() {
|
subscribe() {
|
||||||
const community_view = this.props.community_view;
|
const community_view = this.props.community_view;
|
||||||
return (
|
|
||||||
community_view.subscribed === "NotSubscribed" && (
|
if (community_view.subscribed === "NotSubscribed") {
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary d-block mb-2 w-100"
|
className="btn btn-secondary d-block mb-2 w-100"
|
||||||
onClick={linkEvent(this, this.handleFollowCommunity)}
|
onClick={linkEvent(this, this.handleFollowCommunity)}
|
||||||
|
@ -271,8 +245,41 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
I18NextService.i18n.t("subscribe")
|
I18NextService.i18n.t("subscribe")
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (community_view.subscribed === "Subscribed") {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary d-block mb-2 w-100"
|
||||||
|
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
||||||
|
>
|
||||||
|
{this.state.followCommunityLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon icon="check" classes="icon-inline text-success me-1" />
|
||||||
|
{I18NextService.i18n.t("joined")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (community_view.subscribed === "Pending") {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="btn btn-warning d-block mb-2 w-100"
|
||||||
|
onClick={linkEvent(this, this.handleUnfollowCommunity)}
|
||||||
|
>
|
||||||
|
{this.state.followCommunityLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
I18NextService.i18n.t("subscribe_pending")
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockCommunity() {
|
blockCommunity() {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
myAuth,
|
myAuth,
|
||||||
myAuthRequired,
|
myAuthRequired,
|
||||||
} from "@utils/app";
|
} from "@utils/app";
|
||||||
import getUserInterfaceLangId from "@utils/app/user-interface-language";
|
|
||||||
import {
|
import {
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -324,9 +323,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const url = this.state.form.url;
|
const firstLang = this.state.form.language_id;
|
||||||
|
const selectedLangs = firstLang ? Array.of(firstLang) : undefined;
|
||||||
|
|
||||||
const userInterfaceLangId = getUserInterfaceLangId(this.props.allLanguages);
|
const url = this.state.form.url;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
|
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
|
||||||
|
@ -494,8 +494,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
</div>
|
</div>
|
||||||
<LanguageSelect
|
<LanguageSelect
|
||||||
allLanguages={this.props.allLanguages}
|
allLanguages={this.props.allLanguages}
|
||||||
selectedLanguageIds={[userInterfaceLangId]}
|
|
||||||
siteLanguages={this.props.siteLanguages}
|
siteLanguages={this.props.siteLanguages}
|
||||||
|
selectedLanguageIds={selectedLangs}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
onChange={this.handleLanguageChange}
|
onChange={this.handleLanguageChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -54,7 +54,6 @@ import showScores from "./show-scores";
|
||||||
import siteBannerCss from "./site-banner-css";
|
import siteBannerCss from "./site-banner-css";
|
||||||
import updateCommunityBlock from "./update-community-block";
|
import updateCommunityBlock from "./update-community-block";
|
||||||
import updatePersonBlock from "./update-person-block";
|
import updatePersonBlock from "./update-person-block";
|
||||||
import getUserInterfaceLangId from "./user-interface-language";
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
buildCommentsTree,
|
buildCommentsTree,
|
||||||
|
@ -90,7 +89,6 @@ export {
|
||||||
getRecipientIdFromProps,
|
getRecipientIdFromProps,
|
||||||
getRoleLabelPill,
|
getRoleLabelPill,
|
||||||
getUpdatedSearchId,
|
getUpdatedSearchId,
|
||||||
getUserInterfaceLangId,
|
|
||||||
initializeSite,
|
initializeSite,
|
||||||
insertCommentIntoTree,
|
insertCommentIntoTree,
|
||||||
isAuthPath,
|
isAuthPath,
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { Language } from "lemmy-js-client";
|
|
||||||
import { I18NextService } from "../../services/I18NextService";
|
|
||||||
|
|
||||||
export default function getUserInterfaceLangId(
|
|
||||||
allLanguages: Language[]
|
|
||||||
): number {
|
|
||||||
// Get the string of the browser- or user-defined language, like en-US
|
|
||||||
const i18nLang = I18NextService.i18n.language;
|
|
||||||
|
|
||||||
// Find the Language object with a code that matches the initial characters of
|
|
||||||
// this string
|
|
||||||
const userLang = allLanguages.find(lang => {
|
|
||||||
return i18nLang.indexOf(lang.code) === 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return the ID of that language object, or "0" for Undetermined
|
|
||||||
return userLang?.id || 0;
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue