Merge branch 'main' into vaporwave

This commit is contained in:
SleeplessOne1917 2023-06-28 20:42:22 -04:00 committed by GitHub
commit 8f47cd6780
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 23725 additions and 78 deletions

View file

@ -32,3 +32,14 @@ pipeline:
auto_tag: true
when:
event: tag
nightly_build:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy-ui
dockerfile: Dockerfile
platforms: linux/amd64
tag: dev
when:
event: cron

View file

@ -1,6 +1,6 @@
{
"name": "lemmy-ui",
"version": "0.18.1-rc.1",
"version": "0.18.1-rc.2",
"description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0",

View 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;

View file

@ -0,0 +1,58 @@
@import "./variables";
// Colors
$white: #fff;
$gray-100: #f8f9fa;
$gray-200: #ebebeb;
$gray-300: #bbb;
$gray-500: #adb5bd;
$gray-800: #303030;
$gray-900: #222;
$blue: #5555ff;
$cyan: #55ffff;
$green: #55ff55;
$indigo: #ff55ff;
$red: #ff5555;
$yellow: #fefe54;
$orange: #a85400;
$pink: #fe54fe;
$purple: #fe5454;
$primary: #fefe54;
$secondary: $gray-900;
$success: #00aa00;
$danger: #aa0000;
$info: #00aaaa;
$warning: #aa00aa;
$light: $gray-800;
$dark: black;
$body-bg: #000084;
$body-color: $gray-300;
$link-hover-color: $white;
$font-family-sans-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$font-family-monospace: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
$navbar-dark-color: $gray-300;
$navbar-light-brand-color: $gray-300;
$navbar-dark-active-color: $gray-100;
$nav-tabs-link-active-color: $gray-100;
$navbar-dark-hover-color: rgba($gray-300, 0.75);
$navbar-light-disabled-color: $gray-800;
$navbar-light-active-color: $gray-100;
$navbar-light-hover-color: $gray-200;
$navbar-light-color: $gray-300;
$enable-rounded: false;
$input-color: $white;
$input-bg: rgb(102, 102, 102);
$input-placeholder-color: $gray-500;
$input-disabled-bg: $gray-800;
$card-bg: $gray-800;
$card-border-color: $white;
$mark-bg: #463b00;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
@import "variables.darkly-pureblack";
@import "../../../../node_modules/bootstrap/scss/bootstrap";

11594
src/assets/css/themes/i386.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
@import "variables.i386";
@import "../../../../node_modules/bootstrap/scss/bootstrap";
.btn-outline-secondary {
color: $gray-500;
}
.dropdown-item.active,
.dropdown-item:hover,
option:disabled {
color: $secondary;
}
.input-group-text {
background: $gray-500;
}

View file

@ -75,7 +75,12 @@ export default async (req: Request, res: Response) => {
routeData = await activeRoute.fetchInitialData(initialFetchReq);
}
if (!activeRoute) {
res.status(404);
}
} else if (try_site.state === "failed") {
res.status(500);
errorPageData = getErrorPageData(new Error(try_site.msg), site);
}
@ -89,6 +94,7 @@ export default async (req: Request, res: Response) => {
if (error.msg === "instance_is_private") {
return res.redirect(`/signup`);
} else {
res.status(500);
errorPageData = getErrorPageData(new Error(error.msg), site);
}
}

View file

@ -8,7 +8,7 @@ import RobotsHandler from "./handlers/robots-handler";
import ServiceWorkerHandler from "./handlers/service-worker-handler";
import ThemeHandler from "./handlers/theme-handler";
import ThemesListHandler from "./handlers/themes-list-handler";
import setDefaultCsp from "./middleware/set-default-csp";
import { setCacheControl, setDefaultCsp } from "./middleware";
const server = express();
@ -19,6 +19,7 @@ const [hostname, port] = process.env["LEMMY_UI_HOST"]
server.use(express.json());
server.use(express.urlencoded({ extended: false }));
server.use("/static", express.static(path.resolve("./dist")));
server.use(setCacheControl);
if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
server.use(setDefaultCsp);

42
src/server/middleware.ts Normal file
View 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();
}

View file

@ -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();
}

View file

@ -8,11 +8,13 @@ const themes: ReadonlyArray<string> = [
"darkly",
"darkly-red",
"darkly-compact",
"darkly-pureblack",
"litely",
"litely-red",
"litely-compact",
"vaporwave-dark",
"vaporwave-light",
"i386"
];
export async function buildThemeList(): Promise<ReadonlyArray<string>> {

View file

@ -1,5 +1,4 @@
import { myAuthRequired } from "@utils/app";
import getUserInterfaceLangId from "@utils/app/user-interface-language";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
@ -41,8 +40,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
: undefined
: undefined;
const userInterfaceLangId = getUserInterfaceLangId(this.props.allLanguages);
return (
<div
className={["comment-form", "mb-3", this.props.containerClass].join(
@ -52,7 +49,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
{UserService.Instance.myUserInfo ? (
<MarkdownTextArea
initialContent={initialContent}
initialLanguageId={userInterfaceLangId}
showLanguage
buttonTitle={this.buttonTitle}
finished={this.props.finished}

View file

@ -49,7 +49,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
return this.props.iconVersion ? (
this.selectBtn
) : (
<div className="language-select row mb-3">
<div className="language-select mb-3">
<label
className={classNames(
"col-form-label",

View file

@ -274,12 +274,8 @@ export class MarkdownTextArea extends Component<
<LanguageSelect
iconVersion
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={
languageId && this.props.siteLanguages.includes(languageId)
? [languageId]
: [0]
languageId ? Array.of(languageId) : undefined
}
siteLanguages={this.props.siteLanguages}
onChange={this.handleLanguageChange}

View file

@ -166,7 +166,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
communityTitle() {
const community = this.props.community_view.community;
const subscribed = this.props.community_view.subscribed;
return (
<div>
<h5 className="mb-0">
@ -176,33 +176,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<span className="me-2">
<CommunityLink community={community} hideAvatar />
</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 && (
<small className="me-2 text-muted fst-italic">
{I18NextService.i18n.t("removed")}
@ -259,8 +232,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
subscribe() {
const community_view = this.props.community_view;
return (
community_view.subscribed === "NotSubscribed" && (
if (community_view.subscribed === "NotSubscribed") {
return (
<button
className="btn btn-secondary d-block mb-2 w-100"
onClick={linkEvent(this, this.handleFollowCommunity)}
@ -271,8 +245,41 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
I18NextService.i18n.t("subscribe")
)}
</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() {

View file

@ -4,7 +4,6 @@ import {
myAuth,
myAuthRequired,
} from "@utils/app";
import getUserInterfaceLangId from "@utils/app/user-interface-language";
import {
capitalizeFirstLetter,
debounce,
@ -324,9 +323,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
}
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 (
<form className="post-form" onSubmit={linkEvent(this, handlePostSubmit)}>
@ -494,8 +494,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div>
<LanguageSelect
allLanguages={this.props.allLanguages}
selectedLanguageIds={[userInterfaceLangId]}
siteLanguages={this.props.siteLanguages}
selectedLanguageIds={selectedLangs}
multiple={false}
onChange={this.handleLanguageChange}
/>

View file

@ -54,7 +54,6 @@ import showScores from "./show-scores";
import siteBannerCss from "./site-banner-css";
import updateCommunityBlock from "./update-community-block";
import updatePersonBlock from "./update-person-block";
import getUserInterfaceLangId from "./user-interface-language";
export {
buildCommentsTree,
@ -90,7 +89,6 @@ export {
getRecipientIdFromProps,
getRoleLabelPill,
getUpdatedSearchId,
getUserInterfaceLangId,
initializeSite,
insertCommentIntoTree,
isAuthPath,

View file

@ -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;
}