diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 76916e60..ee3d7a54 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @dessalines @SleeplessOne1917 @alectrocute +* @dessalines @SleeplessOne1917 @alectrocute @jsit diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index a43a5a55..a0b16005 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -1,32 +1,29 @@ -name: "\U0001F41E Bug Report" -description: Create a report to help us improve lemmy-ui -title: "[Bug]: " +name: "\U0001F41E Bug report" +description: Report a bug to help us improve Lemmy-UI. labels: ["bug", "triage"] body: - type: markdown attributes: value: | - Found a bug? Please fill out the sections below. 👍 - Thanks for taking the time to fill out this bug report! - For backend issues, use [lemmy](https://github.com/LemmyNet/lemmy/issues/new/choose) + Thanks for taking the time to help improve Lemmy-UI by reporting a bug! - type: checkboxes attributes: label: Requirements - description: Before you create a bug report please do the following. + description: Before you create a bug report, please carefully check the following – options: - - label: Is this a bug report? For questions or discussions use https://lemmy.ml/c/lemmy_support + - label: This is a bug report, and if not, please post to https://lemmy.ml/c/lemmy_support instead. required: true - - label: Did you check to see if this issue already exists? + - label: Please [check](https://github.com/LemmyNet/lemmy-ui/issues) to see if this issue already exists. required: true - - label: Is this only a single bug? Do not put multiple bugs in one issue. + - label: It's a single bug. Do not report multiple bugs in one issue. + required: true + - label: It's a frontend issue, not a backend issue; Otherwise please create an issue on the [backend repo](https://github.com/LemmyNet/lemmy) instead. required: true - - label: Is this a server side (not related to the UI) issue? Use the [Lemmy back end](https://github.com/LemmyNet/lemmy) repo. - required: false - type: textarea id: summary attributes: label: Summary - description: A summary of the bug. + description: Explain the bug and upload images, screenshots or videos if possible. validations: required: true - type: textarea @@ -34,12 +31,13 @@ body: attributes: label: Steps to Reproduce description: | - Describe the steps to reproduce the bug. - The better your description is _(go 'here', click 'there'...)_ the fastest you'll get an _(accurate)_ resolution. + In a numbered list, walk us through the steps needed to reproduce the bug. + The better your description is _(go 'here', click 'there'...)_, the quicker we can fix it. value: | 1. 2. 3. + 4. validations: required: true - type: textarea @@ -47,20 +45,21 @@ body: attributes: label: Technical Details description: | - - Any browser console errors? + Describe your environment (OS, browser, model of smartphone, etc) + If relevant, also share any console errors and/or screenshots here. validations: required: true - type: input id: lemmy-ui-version attributes: - label: Version - description: Which Lemmy UI version do you use? Displayed in the footer. - placeholder: ex. 0.17.4-rc.4 + label: Lemmy Instance Version + description: What's the version of the Lemmy instance where the bug can be reproduced? + placeholder: ex. 0.18-rc.6 validations: required: true - type: input id: lemmy-instance attributes: label: Lemmy Instance URL - description: Which Lemmy instance do you use? The address - placeholder: lemmy.ml, lemmy.world, etc + description: What's the URL of the Lemmy instance where the bug can be reproduced? + placeholder: https://lemmy.ml diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml index 2d656819..ac7d8dc6 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml @@ -1,54 +1,27 @@ name: "\U0001F680 Feature request" -description: Suggest an idea for improving Lemmy's UI +description: Suggest an idea for Lemmy-UI. labels: ["enhancement"] body: - type: markdown attributes: value: | - Have a suggestion about Lemmy's UI? - For backend issues, use [lemmy](https://github.com/LemmyNet/lemmy/issues/new/choose) + Thanks for taking the time to help improve Lemmy-UI by suggesting a feature! - type: checkboxes attributes: label: Requirements - description: Before you create a feature request please do the following. + description: Before you create a feature request, please carefully check the following – options: - - label: Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support + - label: This is a feature request and not a bug report. Otherwise, please create a new [bug report](https://github.com/LemmyNet/lemmy-ui/issues/new?assignees=&labels=bug%2Ctriage&projects=&template=BUG_REPORT.yml) instead. required: true - - label: Did you check to see if this issue already exists? + - label: Please [check](https://github.com/LemmyNet/lemmy-ui/issues) to see if this request (or a similar one) already exists. required: true - - label: Is this only a feature request? Do not put multiple feature requests in one issue. + - label: It's a single feature. Please don't request multiple features in one issue. required: true - - label: Is this a server side (not related to the UI) issue? Use the [Lemmy back end](https://github.com/LemmyNet/lemmy) repo. - required: false - - type: textarea - id: problem - attributes: - label: Is your proposal related to a problem? - description: | - Provide a clear and concise description of what the problem is. - For example, "I'm always frustrated when..." - validations: - required: true - type: textarea id: solution attributes: - label: Describe the solution you'd like. + label: Describe the feature you'd like description: | - Provide a clear and concise description of what you want to happen. + Provide a clear and concise description of the feature. Explain why it's needed. validations: required: true - - type: textarea - id: alternatives - attributes: - label: Describe alternatives you've considered. - description: | - Let us know about other solutions you've tried or researched. - validations: - required: true - - type: textarea - id: context - attributes: - label: Additional context - description: | - Is there anything else you can add about the proposal? - You might want to link to related issues here, if you haven't already. diff --git a/.github/ISSUE_TEMPLATE/QUESTION.yml b/.github/ISSUE_TEMPLATE/QUESTION.yml deleted file mode 100644 index 734937e9..00000000 --- a/.github/ISSUE_TEMPLATE/QUESTION.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: "? Question" -description: General questions about Lemmy -title: "Question: " -labels: ["question", "triage"] -body: - - type: markdown - attributes: - value: | - Have a question about Lemmy's UI? - Please check the docs first: https://join-lemmy.org/docs/en/index.html - - type: textarea - id: question - attributes: - label: Question - description: What's the question you have about Lemmy's UI? - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..59085700 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Question + url: https://lemmy.ml/c/lemmy_support + about: Please ask and answer general questions here. + - name: Technical Discussion + url: https://github.com/LemmyNet/lemmy-ui/discussions + about: Please discuss technical topics with other contributors here. diff --git a/.github/ISSUE_TEMPLATE/hexbear.yml b/.github/ISSUE_TEMPLATE/hexbear.yml deleted file mode 100644 index 73ef5482..00000000 --- a/.github/ISSUE_TEMPLATE/hexbear.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: "Hexbear" -description: For hexbear issues -labels: ["hexbear", "triage"] -body: - - type: textarea - id: question - attributes: - label: Question - description: What's the question you have about hexbear? - validations: - required: true diff --git a/.gitignore b/.gitignore index 34454f3f..3234d3df 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ package-lock.json src/shared/translations +stats.json + diff --git a/.prettierignore b/.prettierignore index 98eb0d9b..004c815f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ src/shared/translations lemmy-translations src/assets/css/themes/*.css +stats.json diff --git a/package.json b/package.json index 9e7a0f1b..f01c907b 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { "name": "lemmy-ui", - "version": "0.18.0-rc.6", + "version": "0.18.0", "description": "An isomorphic UI for lemmy", "repository": "https://github.com/LemmyNet/lemmy-ui", "license": "AGPL-3.0", "author": "Dessalines ", "scripts": { + "analyze": "webpack --mode=none", "prebuild:dev": "yarn clean && node generate_translations.js", "build:dev": "webpack --mode=development", "prebuild:prod": "yarn clean && node generate_translations.js", "build:prod": "webpack --mode=production", "clean": "yarn run rimraf dist", - "dev": "yarn start", + "dev": "yarn build:dev --watch", "lint": "yarn translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"", "prepare": "husky install", - "start": "yarn build:dev --watch", "themes:build": "sass src/assets/css/themes/:src/assets/css/themes", "themes:watch": "sass --watch src/assets/css/themes/:src/assets/css/themes", "translations:generate": "node generate_translations.js", @@ -51,6 +51,7 @@ "copy-webpack-plugin": "^11.0.0", "cross-fetch": "^3.1.5", "css-loader": "^6.7.3", + "date-fns": "^2.30.0", "emoji-mart": "^5.4.0", "emoji-short-name": "^2.0.0", "express": "~4.18.2", @@ -76,7 +77,6 @@ "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", "mini-css-extract-plugin": "^2.7.5", - "moment": "^2.29.4", "register-service-worker": "^1.7.2", "run-node-webpack-plugin": "^1.3.0", "sanitize-html": "^2.10.0", @@ -101,6 +101,7 @@ "@types/markdown-it": "^12.2.3", "@types/markdown-it-container": "^2.0.5", "@types/node": "^20.1.2", + "@types/path-browserify": "^1.0.0", "@types/sanitize-html": "^2.9.0", "@types/serialize-javascript": "^5.0.1", "@types/toastify-js": "^1.11.1", @@ -122,6 +123,7 @@ "style-loader": "^3.3.2", "terser": "^5.17.3", "typescript": "^5.0.4", + "webpack-bundle-analyzer": "^4.9.0", "webpack-dev-server": "4.15.0" }, "packageManager": "yarn@1.22.19", diff --git a/src/client/index.tsx b/src/client/index.tsx index eb2bb80f..36059f97 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -1,4 +1,4 @@ -import { initializeSite } from "@utils/app"; +import { initializeSite, setupDateFns } from "@utils/app"; import { hydrate } from "inferno-hydrate"; import { Router } from "inferno-router"; import { App } from "../shared/components/app/app"; @@ -7,16 +7,22 @@ import { HistoryService } from "../shared/services"; import "bootstrap/js/dist/collapse"; import "bootstrap/js/dist/dropdown"; -initializeSite(window.isoData.site_res); +async function startClient() { + initializeSite(window.isoData.site_res); -const wrapper = ( - - - -); + await setupDateFns(); -const root = document.getElementById("root"); + const wrapper = ( + + + + ); -if (root) { - hydrate(wrapper, root); + const root = document.getElementById("root"); + + if (root) { + hydrate(wrapper, root); + } } + +startClient(); diff --git a/src/server/index.tsx b/src/server/index.tsx index b65506b0..25a1be64 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -1,3 +1,4 @@ +import { setupDateFns } from "@utils/app"; import express from "express"; import path from "path"; import process from "process"; @@ -31,6 +32,7 @@ server.get("/css/themelist", ThemesListHandler); server.get("/*", CatchAllHandler); server.listen(Number(port), hostname, () => { + setupDateFns(); console.log(`http://${hostname}:${port}`); }); diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index ae766b3a..13775981 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -4,6 +4,7 @@ import serialize from "serialize-javascript"; import sharp from "sharp"; import { favIconPngUrl, favIconUrl } from "../../shared/config"; import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; +import { buildThemeList } from "./build-themes-list"; import { fetchIconPng } from "./fetch-icon-png"; const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || ""; @@ -16,6 +17,10 @@ export async function createSsrHtml( ) { const site = isoData.site_res; + const fallbackTheme = ``; + if (!appleTouchIcon) { appleTouchIcon = site?.site_view.site.icon ? `data:image/png;base64,${sharp( @@ -68,7 +73,7 @@ export async function createSsrHtml( - + - ${helmet.link.toString()} + ${helmet.link.toString() || fallbackTheme} diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index b558d142..b4d3d662 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -16,6 +16,9 @@ import { isMod, } from "@utils/roles"; import classNames from "classnames"; +import isBefore from "date-fns/isBefore"; +import parseISO from "date-fns/parseISO"; +import subMinutes from "date-fns/subMinutes"; import { Component, InfernoNode, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { @@ -46,7 +49,6 @@ import { SaveComment, TransferCommunity, } from "lemmy-js-client"; -import moment from "moment"; import { commentTreeMaxDepth } from "../../config"; import { BanType, @@ -1451,9 +1453,9 @@ export class CommentNode extends Component { } get isCommentNew(): boolean { - const now = moment.utc().subtract(10, "minutes"); - const then = moment.utc(this.commentView.comment.published); - return now.isBefore(then); + const now = subMinutes(new Date(), 10); + const then = parseISO(this.commentView.comment.published); + return isBefore(now, then); } handleCommentCollapse(i: CommentNode) { diff --git a/src/shared/components/common/moment-time.tsx b/src/shared/components/common/moment-time.tsx index 1857a007..7c5693ed 100644 --- a/src/shared/components/common/moment-time.tsx +++ b/src/shared/components/common/moment-time.tsx @@ -1,6 +1,7 @@ -import { capitalizeFirstLetter } from "@utils/helpers"; +import { capitalizeFirstLetter, formatPastDate } from "@utils/helpers"; +import format from "date-fns/format"; +import parseISO from "date-fns/parseISO"; import { Component } from "inferno"; -import moment from "moment"; import { I18NextService } from "../../services"; import { Icon } from "./icon"; @@ -11,22 +12,24 @@ interface MomentTimeProps { ignoreUpdated?: boolean; } +function formatDate(input: string) { + return format(parseISO(input), "PPPPpppp"); +} + export class MomentTime extends Component { constructor(props: any, context: any) { super(props, context); - - moment.locale([...I18NextService.i18n.languages]); } createdAndModifiedTimes() { const updated = this.props.updated; let line = `${capitalizeFirstLetter( I18NextService.i18n.t("created") - )}: ${this.format(this.props.published)}`; + )}: ${formatDate(this.props.published)}`; if (updated) { line += `\n\n\n${capitalizeFirstLetter( I18NextService.i18n.t("modified") - )} ${this.format(updated)}`; + )} ${formatDate(updated)}`; } return line; } @@ -39,7 +42,7 @@ export class MomentTime extends Component { className="moment-time font-italics pointer unselectable" > - {moment.utc(this.props.updated).fromNow(!this.props.showAgo)} + {formatPastDate(this.props.updated)} ); } else { @@ -47,15 +50,11 @@ export class MomentTime extends Component { return ( - {moment.utc(published).fromNow(!this.props.showAgo)} + {formatPastDate(published)} ); } } - - format(input: string): string { - return moment.utc(input).local().format("LLLL"); - } } diff --git a/src/shared/components/common/pictrs-image.tsx b/src/shared/components/common/pictrs-image.tsx index 74437490..31fb1229 100644 --- a/src/shared/components/common/pictrs-image.tsx +++ b/src/shared/components/common/pictrs-image.tsx @@ -22,7 +22,7 @@ export class PictrsImage extends Component { render() { return ( - + @@ -31,7 +31,7 @@ export class PictrsImage extends Component { alt={this.alt()} title={this.alt()} loading="lazy" - className={classNames({ + className={classNames("overflow-hidden pictrs-image", { "img-fluid": !this.props.icon && !this.props.iconOverlay, banner: this.props.banner, "thumbnail rounded": diff --git a/src/shared/components/common/sort-select.tsx b/src/shared/components/common/sort-select.tsx index 90515d56..4d03ab5d 100644 --- a/src/shared/components/common/sort-select.tsx +++ b/src/shared/components/common/sort-select.tsx @@ -67,6 +67,13 @@ export class SortSelect extends Component { + + +