diff --git a/.eslintrc.json b/.eslintrc.json index 598b7f3..81257cb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,16 +21,6 @@ "@typescript-eslint/explicit-module-boundary-types": 0, "@typescript-eslint/no-empty-function": 0, "arrow-body-style": 0, - "jsx-a11y/alt-text": 1, - "jsx-a11y/anchor-is-valid": 1, - "jsx-a11y/aria-activedescendant-has-tabindex": 1, - "jsx-a11y/aria-role": 1, - "jsx-a11y/click-events-have-key-events": 1, - "jsx-a11y/iframe-has-title": 1, - "jsx-a11y/interactive-supports-focus": 1, - "jsx-a11y/no-redundant-roles": 1, - "jsx-a11y/no-static-element-interactions": 1, - "jsx-a11y/role-has-required-aria-props": 1, "curly": 0, "eol-last": 0, "eqeqeq": 0, diff --git a/src/assets/css/main.css b/src/assets/css/main.css index d61dafd..cb4a8b8 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -124,7 +124,8 @@ .emoji-picker-container { position: absolute; - top: 30px; + top: 0; + left: 50%; z-index: 1000; transform: translateX(-50%); } @@ -275,10 +276,7 @@ hr { } .mini-overlay { - position: absolute; - top: 0; - right: 0; - padding: 2px; + display: block; height: 1.5em; width: 1.5em; background: rgba(0, 0, 0, 0.4); diff --git a/src/server/handlers/theme-handler.ts b/src/server/handlers/theme-handler.ts index b107d85..456fb3b 100644 --- a/src/server/handlers/theme-handler.ts +++ b/src/server/handlers/theme-handler.ts @@ -11,22 +11,21 @@ export default async (req: Request, res: Response) => { const theme = req.params.name; if (!theme.endsWith(".css")) { - res.statusCode = 400; - res.send("Theme must be a css file"); + return res.status(400).send("Theme must be a css file"); } const customTheme = path.resolve(extraThemesFolder, theme); if (existsSync(customTheme)) { - res.sendFile(customTheme); + return res.sendFile(customTheme); } else { const internalTheme = path.resolve(`./dist/assets/css/themes/${theme}`); // If the theme doesn't exist, just send litely if (existsSync(internalTheme)) { - res.sendFile(internalTheme); + return res.sendFile(internalTheme); } else { - res.sendFile(path.resolve("./dist/assets/css/themes/litely.css")); + return res.sendFile(path.resolve("./dist/assets/css/themes/litely.css")); } } }; diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 96bf101..08ba40e 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -33,12 +33,13 @@ export class App extends Component { <>
- - ${I18NextService.i18n.t("jump_to_content", "Jump to content")} - + {I18NextService.i18n.t("jump_to_content", "Jump to content")} + {siteView && ( )} diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index 2ede00e..11cfb6c 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -347,10 +347,10 @@ export class Navbar extends Component { )} {person && ( - + )} ) : ( diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index 41f67e6..394de6e 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -3,7 +3,6 @@ import { getCommentParentId, myAuth, myAuthRequired, - newVote, showScores, } from "@utils/app"; import { futureDaysToUnixTime, numToSI } from "@utils/helpers"; @@ -56,13 +55,14 @@ import { CommentNodeI, CommentViewType, PurgeType, - VoteType, + VoteContentType, } from "../../interfaces"; import { mdToHtml, mdToHtmlNoImages } from "../../markdown"; import { I18NextService, UserService } from "../../services"; import { setupTippy } from "../../tippy"; import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { MomentTime } from "../common/moment-time"; +import { VoteButtonsCompact } from "../common/vote-buttons"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "../person/person-listing"; import { CommentForm } from "./comment-form"; @@ -283,7 +283,7 @@ export class CommentNode extends Component { node.comment_view.counts.child_count > 0; return ( -
  • +
  • { )} {/* This is an expanding spacer for mobile */}
    + {showScores() && ( <> - - {this.state.upvoteLoading ? ( - - ) : ( - - {numToSI(this.commentView.counts.score)} - - )} - + {numToSI(this.commentView.counts.score)} + )} @@ -420,7 +409,7 @@ export class CommentNode extends Component { } /> )} -
    +
    {this.props.showContext && this.linkBtn()} {this.props.markable && ( - {this.props.enableDownvotes && ( - - )} + ) : ( {this.props.uploadTitle} diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index 2d306dc..8963d5e 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -23,15 +23,28 @@ import NavigationPrompt from "./navigation-prompt"; import ProgressBar from "./progress-bar"; interface MarkdownTextAreaProps { + /** + * Initial content inside the textarea + */ initialContent?: string; + /** + * Numerical ID of the language to select in dropdown + */ initialLanguageId?: number; placeholder?: string; buttonTitle?: string; maxLength?: number; + /** + * Whether this form is for a reply to a Private Message. + * If true, a "Cancel" button is shown that will close the reply. + */ replyType?: boolean; focus?: boolean; disabled?: boolean; finished?: boolean; + /** + * Whether to show the language selector + */ showLanguage?: boolean; hideNavigationWarnings?: boolean; onContentChange?(val: string): void; @@ -154,7 +167,7 @@ export class MarkdownTextArea extends Component< onEmojiClick={e => this.handleEmoji(this, e)} disabled={this.isDisabled} > - +
    diff --git a/src/shared/components/common/moment-time.tsx b/src/shared/components/common/moment-time.tsx index 7c5693e..ec97eb4 100644 --- a/src/shared/components/common/moment-time.tsx +++ b/src/shared/components/common/moment-time.tsx @@ -39,7 +39,7 @@ export class MomentTime extends Component { return ( {formatPastDate(this.props.updated)} diff --git a/src/shared/components/common/searchable-select.tsx b/src/shared/components/common/searchable-select.tsx index a29fe16..801cd86 100644 --- a/src/shared/components/common/searchable-select.tsx +++ b/src/shared/components/common/searchable-select.tsx @@ -106,8 +106,12 @@ export class SearchableSelect extends Component< + {this.props.enableDownvotes && ( + + )} +
    + ); + } +} + +export class VoteButtons extends Component { + state: VoteButtonsState = { + upvoteLoading: false, + downvoteLoading: false, + }; + + constructor(props: any, context: any) { + super(props, context); + } + + render() { + return ( +
    + + {showScores() ? ( +
    + {numToSI(this.props.counts.score)} +
    + ) : ( +
    + )} + {this.props.enableDownvotes && ( + + )} +
    + ); + } +} diff --git a/src/shared/components/community/sidebar.tsx b/src/shared/components/community/sidebar.tsx index 81a5daf..733c19a 100644 --- a/src/shared/components/community/sidebar.tsx +++ b/src/shared/components/community/sidebar.tsx @@ -204,17 +204,17 @@ export class Sidebar extends Component { )} {community.removed && ( - + {I18NextService.i18n.t("removed")} )} {community.deleted && ( - + {I18NextService.i18n.t("deleted")} )} {community.nsfw && ( - + {I18NextService.i18n.t("nsfw")} )} @@ -309,7 +309,7 @@ export class Sidebar extends Component { const community_view = this.props.community_view; return ( <> -
      +
        {amMod(this.props.moderators) && ( <>
      • diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index 7ac69fe..76877d8 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -44,7 +44,6 @@ interface AdminSettingsState { instancesRes: RequestState; bannedRes: RequestState; leaveAdminTeamRes: RequestState; - emojiLoading: boolean; loading: boolean; themeList: string[]; isIsomorphic: boolean; @@ -59,7 +58,6 @@ export class AdminSettings extends Component { bannedRes: { state: "empty" }, instancesRes: { state: "empty" }, leaveAdminTeamRes: { state: "empty" }, - emojiLoading: false, loading: false, themeList: [], isIsomorphic: false, @@ -215,7 +213,6 @@ export class AdminSettings extends Component { onCreate={this.handleCreateEmoji} onDelete={this.handleDeleteEmoji} onEdit={this.handleEditEmoji} - loading={this.state.emojiLoading} />
  • @@ -345,35 +342,23 @@ export class AdminSettings extends Component { } async handleEditEmoji(form: EditCustomEmoji) { - this.setState({ emojiLoading: true }); - const res = await HttpService.client.editCustomEmoji(form); if (res.state === "success") { updateEmojiDataModel(res.data.custom_emoji); } - - this.setState({ emojiLoading: false }); } async handleDeleteEmoji(form: DeleteCustomEmoji) { - this.setState({ emojiLoading: true }); - const res = await HttpService.client.deleteCustomEmoji(form); if (res.state === "success") { removeFromEmojiDataModel(res.data.id); } - - this.setState({ emojiLoading: false }); } async handleCreateEmoji(form: CreateCustomEmoji) { - this.setState({ emojiLoading: true }); - const res = await HttpService.client.createCustomEmoji(form); if (res.state === "success") { updateEmojiDataModel(res.data.custom_emoji); } - - this.setState({ emojiLoading: false }); } } diff --git a/src/shared/components/home/emojis-form.tsx b/src/shared/components/home/emojis-form.tsx index 8428a54..caf8221 100644 --- a/src/shared/components/home/emojis-form.tsx +++ b/src/shared/components/home/emojis-form.tsx @@ -1,4 +1,5 @@ import { myAuthRequired, setIsoData } from "@utils/app"; +import { capitalizeFirstLetter } from "@utils/helpers"; import { Component, linkEvent } from "inferno"; import { CreateCustomEmoji, @@ -11,14 +12,13 @@ import { HttpService, I18NextService } from "../../services"; import { pictrsDeleteToast, toast } from "../../toast"; import { EmojiMart } from "../common/emoji-mart"; import { HtmlTags } from "../common/html-tags"; -import { Icon } from "../common/icon"; +import { Icon, Spinner } from "../common/icon"; import { Paginator } from "../common/paginator"; interface EmojiFormProps { onEdit(form: EditCustomEmoji): void; onCreate(form: CreateCustomEmoji): void; onDelete(form: DeleteCustomEmoji): void; - loading: boolean; } interface EmojiFormState { @@ -36,6 +36,7 @@ interface CustomEmojiViewForm { keywords: string; changed: boolean; page: number; + loading: boolean; } export class EmojiForm extends Component { @@ -52,6 +53,7 @@ export class EmojiForm extends Component { keywords: x.keywords.map(x => x.keyword).join(" "), changed: false, page: 1 + Math.floor(index / this.itemsPerPage), + loading: false, })), page: 1, }; @@ -119,33 +121,39 @@ export class EmojiForm extends Component { .map((cv, index) => ( (this.scrollRef[cv.shortcode] = e)}> - - + {cv.image_url.length > 0 && ( + {cv.alt_text} + )} + {cv.image_url.length === 0 && ( + + + + )} { - )} )} - {this.state.expanded && post.embed_video_url && ( -
    - -
    - )} ); } - - handleIframeExpand(i: MetadataCard) { - i.setState({ expanded: !i.state.expanded }); - } } diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index ebea432..081792a 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -358,7 +358,7 @@ export class PostForm extends Component { htmlFor="file-upload" className={`${ UserService.Instance.myUserInfo && "pointer" - } d-inline-block float-right text-muted font-weight-bold`} + } d-inline-block float-right text-muted fw-bold`} data-tippy-content={I18NextService.i18n.t("upload_image")} > @@ -377,7 +377,7 @@ export class PostForm extends Component {
    archive.org {I18NextService.i18n.t("archive_link")} @@ -386,7 +386,7 @@ export class PostForm extends Component { href={`${ghostArchiveUrl}/search?term=${encodeURIComponent( url )}`} - className="me-2 d-inline-block float-right text-muted small font-weight-bold" + className="me-2 d-inline-block float-right text-muted small fw-bold" rel={relTags} > ghostarchive.org {I18NextService.i18n.t("archive_link")} @@ -395,7 +395,7 @@ export class PostForm extends Component { href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent( url )}`} - className="me-2 d-inline-block float-right text-muted small font-weight-bold" + className="me-2 d-inline-block float-right text-muted small fw-bold" rel={relTags} > archive.today {I18NextService.i18n.t("archive_link")} @@ -419,7 +419,7 @@ export class PostForm extends Component { )} {this.props.crossPosts && this.props.crossPosts.length > 0 && ( <> -
    +
    {I18NextService.i18n.t("cross_posts")}
    { return ( suggestedTitle && ( -
    { > {I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "} {suggestedTitle} -
    + ) ); } @@ -613,7 +613,7 @@ export class PostForm extends Component { suggestedPosts && suggestedPosts.length > 0 && ( <> -
    - - +
    ); @@ -262,6 +262,7 @@ export class PostListing extends Component { const { post } = this.postView; const { url } = post; + // if direct video link if (url && isVideo(url)) { return (
    @@ -272,6 +273,20 @@ export class PostListing extends Component { ); } + // if embedded video link + if (url && post.embed_video_url) { + return ( +
    + +
    + ); + } + return <>; } @@ -338,7 +353,7 @@ export class PostListing extends Component { ); } else if (url) { - if (!this.props.hideImage && isVideo(url)) { + if ((!this.props.hideImage && isVideo(url)) || post.embed_video_url) { return ( { ); } - voteBar() { - return ( -
    - - {showScores() ? ( -
    - {numToSI(this.postView.counts.score)} -
    - ) : ( -
    - )} - {this.props.enableDownvotes && ( - - )} -
    - ); - } - get postLink() { const post = this.postView.post; return ( @@ -538,7 +504,7 @@ export class PostListing extends Component { )} {post.deleted && ( @@ -546,7 +512,7 @@ export class PostListing extends Component { )} {post.locked && ( @@ -554,7 +520,7 @@ export class PostListing extends Component { )} {post.featured_community && ( { )} {post.featured_local && ( @@ -591,7 +557,7 @@ export class PostListing extends Component {

    {url && !(hostname(url) === getExternalHost()) && ( { )} - {mobile && !this.props.viewOnly && this.mobileVotes} + {mobile && !this.props.viewOnly && ( + + )} {UserService.Instance.myUserInfo && !this.props.viewOnly && this.postActions()} @@ -707,13 +682,16 @@ export class PostListing extends Component { data-tippy-content={I18NextService.i18n.t("more")} data-bs-toggle="dropdown" aria-expanded="false" - aria-controls="advancedButtonsDropdown" + aria-controls={`advancedButtonsDropdown${post.id}`} aria-label={I18NextService.i18n.t("more")} > -

      +
        {!this.myPost ? ( <>
      • {this.reportButton}
      • @@ -763,9 +741,12 @@ export class PostListing extends Component { {post_view.counts.comments} {this.unreadCount && ( - - ({this.unreadCount} {I18NextService.i18n.t("new")}) - + <> + {" "} + + ({this.unreadCount} {I18NextService.i18n.t("new")}) + + )} ); @@ -778,69 +759,6 @@ export class PostListing extends Component { : pv.unread_comments; } - get mobileVotes() { - // TODO: make nicer - const tippy = showScores() - ? { "data-tippy-content": this.pointsTippy } - : {}; - return ( - <> -
        - - {this.props.enableDownvotes && ( - - )} -
        - - ); - } - get saveButton() { const saved = this.postView.saved; const label = saved @@ -939,7 +857,6 @@ export class PostListing extends Component {
    - {/* Post body prev or thumbnail */} + {/* Post thumbnail */} {!this.state.imageExpanded && this.thumbnail()}
    @@ -1489,7 +1406,16 @@ export class PostListing extends Component { {/* The larger view*/}
    - {!this.props.viewOnly && this.voteBar()} + {!this.props.viewOnly && ( + + )}
    {this.thumbnail()}
    @@ -1854,24 +1780,6 @@ export class PostListing extends Component { setupTippy(); } - handleUpvote(i: PostListing) { - i.setState({ upvoteLoading: true }); - i.props.onPostVote({ - post_id: i.postView.post.id, - score: newVote(VoteType.Upvote, i.props.post_view.my_vote), - auth: myAuthRequired(), - }); - } - - handleDownvote(i: PostListing) { - i.setState({ downvoteLoading: true }); - i.props.onPostVote({ - post_id: i.postView.post.id, - score: newVote(VoteType.Downvote, i.props.post_view.my_vote), - auth: myAuthRequired(), - }); - } - get pointsTippy(): string { const points = I18NextService.i18n.t("number_of_points", { count: Number(this.postView.counts.score), diff --git a/src/shared/components/private_message/create-private-message.tsx b/src/shared/components/private_message/create-private-message.tsx index 8afd348..840a442 100644 --- a/src/shared/components/private_message/create-private-message.tsx +++ b/src/shared/components/private_message/create-private-message.tsx @@ -115,7 +115,9 @@ export class CreatePrivateMessage extends Component< return (
    -
    {I18NextService.i18n.t("create_private_message")}
    +

    + {I18NextService.i18n.t("create_private_message")} +

    +
    {!this.props.privateMessageView && ( -
    +
    -
    +
    )} +
    + + + # + + # + + +
    { + this.handlePrivateMessageSubmit(this, event); + }} initialContent={this.state.content} onContentChange={this.handleContentChange} allLanguages={[]} siteLanguages={[]} hideNavigationWarnings + onReplyCancel={() => this.handleCancel(this)} + replyType={this.props.replyType} + buttonTitle={ + this.props.privateMessageView + ? capitalizeFirstLetter(I18NextService.i18n.t("save")) + : capitalizeFirstLetter(I18NextService.i18n.t("send_message")) + } />
    - - {this.state.showDisclaimer && ( -
    -
    -
    - - # - - # - - -
    -
    -
    - )} -
    -
    - - {this.props.privateMessageView && ( - - )} -
      -
    • -
    -
    -
    ); } @@ -200,8 +161,4 @@ export class PrivateMessageForm extends Component< event.preventDefault(); i.setState({ previewMode: !i.state.previewMode }); } - - handleShowDisclaimer(i: PrivateMessageForm) { - i.setState({ showDisclaimer: !i.state.showDisclaimer }); - } } diff --git a/src/shared/components/private_message/private-message.tsx b/src/shared/components/private_message/private-message.tsx index af8d64e..f91087a 100644 --- a/src/shared/components/private_message/private-message.tsx +++ b/src/shared/components/private_message/private-message.tsx @@ -109,17 +109,17 @@ export class PrivateMessage extends Component<
  • -
    {this.state.collapsed ? ( - + ) : ( - + )} -
    +
  • {this.state.showEdit && ( @@ -140,11 +140,12 @@ export class PrivateMessage extends Component< dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)} /> )} -
      +
        {!this.mine && ( <>