From 5f85a0ac1548b18566425ec76569647fe8ae4805 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 13:39:41 +0800 Subject: [PATCH 01/10] fix(ci): update macos runner image --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c46ac585..f0f94345 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,7 +146,7 @@ jobs: test-mac-arm: name: Test Mac (Apple Silicon) - runs-on: macOS-14 + runs-on: macos-latest steps: - uses: actions/checkout@v4 with: From 2f0fae846a49d8d5e949bf58e33381e2f08ebf81 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 13:53:49 +0800 Subject: [PATCH 02/10] fix: HTML zip archive is empty in releases --- electron/after-all-artifact-build.cjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/electron/after-all-artifact-build.cjs b/electron/after-all-artifact-build.cjs index 2473ee6e..cea36f49 100644 --- a/electron/after-all-artifact-build.cjs +++ b/electron/after-all-artifact-build.cjs @@ -23,10 +23,13 @@ function createHtmlArchive() { const zipBinPath = path.resolve(rootPath, 'node_modules', '7zip-bin', 'linux', 'x64', '7za'); const fivechanHtmlFolderName = `5chan-html-${packageJson.version}`; const outputFile = path.resolve(distFolderPath, `${fivechanHtmlFolderName}.zip`); - const inputFolder = path.resolve(rootPath, 'build'); + // Vite outputs to 'dist', not 'build' (CRA default) + const inputFolder = path.resolve(rootPath, 'dist'); try { - execSync(`${zipBinPath} a ${outputFile} ${inputFolder}`); - execSync(`${zipBinPath} rn -r ${outputFile} build ${fivechanHtmlFolderName}`); + // Exclude Electron builder artifacts from HTML archive + const excludes = '-xr!*.AppImage -xr!*.exe -xr!*.dmg -xr!*.blockmap -xr!*.yml -xr!*.yaml -xr!win-unpacked -xr!mac -xr!mac-arm64 -xr!linux-unpacked -xr!builder-*'; + execSync(`${zipBinPath} a ${outputFile} ${inputFolder} ${excludes}`); + execSync(`${zipBinPath} rn -r ${outputFile} dist ${fivechanHtmlFolderName}`); } catch (e) { console.error('electron build createHtmlArchive error:', e); } From 3cac7810ae28f836f29ec27e0d2c4dc8ad4cd7cf Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 14:00:22 +0800 Subject: [PATCH 03/10] Delete notifications.yml using https://github.com/mhkafadar/notifine instead --- .github/workflows/notifications.yml | 93 ----------------------------- 1 file changed, 93 deletions(-) delete mode 100644 .github/workflows/notifications.yml diff --git a/.github/workflows/notifications.yml b/.github/workflows/notifications.yml deleted file mode 100644 index d390b403..00000000 --- a/.github/workflows/notifications.yml +++ /dev/null @@ -1,93 +0,0 @@ -# send notifications to telegram group on commits, releases, issues -name: notifications - -on: - pull_request: - branches: - - master - # push: - # branches: - # - master - release: - types: [published] - issues: - types: [opened] - -jobs: -# push: -# if: ${{ github.event_name == 'push' }} -# runs-on: ubuntu-latest -# strategy: -# matrix: -# commit: ${{ github.event.commits }} -# steps: -# # - name: debug -# # env: -# # DEBUG: ${{ toJSON(matrix.commit) }} -# # run: echo "$DEBUG" - -# # write message to file -# - run: echo "${{ matrix.commit.message }}" >> message.txt -# - run: echo "" >> message.txt -# - run: echo "by ${{ matrix.commit.author.username }}" >> message.txt -# - run: echo "${{ matrix.commit.url }}" >> message.txt -# - run: date -d "${{ matrix.commit.timestamp }}" +"%d/%m/%y at %H:%M" >> message.txt - -# # send message, @plebbit telegram chat id is -1001665335693 -# - name: "telegram notification" -# uses: appleboy/telegram-action@master -# with: -# to: -1001665335693 -# token: ${{ secrets.TELEGRAM_TOKEN }} -# # commit links don't show anything useful in preview -# disable_web_page_preview: true -# format: html -# message_file: message.txt - - release: - if: ${{ github.event_name == 'release' }} - runs-on: ubuntu-latest - steps: - # - name: debug - # env: - # DEBUG: ${{ toJSON(github) }} - # run: echo "$DEBUG" - - # write message to file - - run: echo "${{ github.event.repository.name }} ${{ github.event.release.name }}" >> message.txt - - run: echo "" >> message.txt - - run: echo "${{ github.event.release.body }}" >> message.txt - - run: echo "${{ github.event.release.html_url }}" >> message.txt - - # send message, @plebbit telegram chat id is -1001665335693 - - name: "telegram notification" - uses: appleboy/telegram-action@master - with: - to: -1001665335693 - token: ${{ secrets.TELEGRAM_TOKEN }} - format: html - message_file: message.txt - - issue: - if: ${{ github.event_name == 'issues' }} - runs-on: ubuntu-latest - steps: - # - name: debug - # env: - # DEBUG: ${{ toJSON(github) }} - # run: echo "$DEBUG" - - # write message to file - - run: echo "${{ github.event.issue.title }}" >> message.txt - - run: echo "" >> message.txt - - run: echo "by ${{ github.event.issue.user.login }}" >> message.txt - - run: echo "${{ github.event.issue.html_url }}" >> message.txt - - # send message, @plebbit telegram chat id is -1001665335693 - - name: "telegram notification" - uses: appleboy/telegram-action@master - with: - to: -1001665335693 - token: ${{ secrets.TELEGRAM_TOKEN }} - format: html - message_file: message.txt From 14e8c92fa0fd2cc6eb9d5bad7646020207389373 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 16:29:15 +0800 Subject: [PATCH 04/10] feat(mod-queue): add view mode selection and integrate with post components --- src/app.tsx | 5 +- .../board-buttons/board-buttons.module.css | 24 ++ .../board-buttons/board-buttons.tsx | 59 +++ src/components/post-desktop/post-desktop.tsx | 104 ++++- src/components/post-form/post-form.module.css | 12 + src/components/post-form/post-form.tsx | 11 +- src/components/post-mobile/post-mobile.tsx | 75 +++- src/stores/use-mod-queue-store.ts | 12 +- src/views/mod-queue/mod-queue.module.css | 46 ++ src/views/mod-queue/mod-queue.tsx | 392 ++++++++++++------ src/views/post/post.module.css | 61 +++ src/views/post/post.tsx | 47 ++- 12 files changed, 703 insertions(+), 145 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index d5e60359..81f5eba1 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -83,14 +83,13 @@ const BoardLayout = () => { {isMobile - ? (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && - !isOnModQueueRoute && ( + ? (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress || isOnModQueueRoute) && ( <> ) - : (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && ( + : (subplebbitAddress || isInAllView || isInModView || isInSubscriptionsView || pendingPost?.subplebbitAddress || isOnModQueueRoute) && ( <> {!(isInAllView || isInSubscriptionsView || isInModView) && !isOnModQueueRoute && } diff --git a/src/components/board-buttons/board-buttons.module.css b/src/components/board-buttons/board-buttons.module.css index f46b0f28..9ae4944e 100644 --- a/src/components/board-buttons/board-buttons.module.css +++ b/src/components/board-buttons/board-buttons.module.css @@ -66,6 +66,30 @@ align-items: center; } +.modQueueControls { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 8px; + font-size: 12px; +} + +.modQueueControls label { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.alertThresholdInput { + width: 50px; +} + +.mobileBoardButtons .modQueueControls { + display: flex; + justify-content: center; + margin: 6px 0; +} + .desktopBoardButtons .rightSideButtons select { margin-right: 5px; } diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index 3afd631b..c7ad11a7 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -11,6 +11,7 @@ import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useFeedResetStore from '../../stores/use-feed-reset-store'; import useSortingStore from '../../stores/use-sorting-store'; import useAllFeedFilterStore from '../../stores/use-all-feed-filter-store'; +import useModQueueStore from '../../stores/use-mod-queue-store'; import useCountLinksInReplies from '../../hooks/use-count-links-in-replies'; import useIsMobile from '../../hooks/use-is-mobile'; import useTimeFilter from '../../hooks/use-time-filter'; @@ -229,6 +230,58 @@ const ImageSizeOptions = () => { ); }; +const ModQueueAlertThreshold = () => { + const { t } = useTranslation(); + const { alertThresholdValue, alertThresholdUnit, setAlertThreshold } = useModQueueStore(); + + return ( +
+ +
+ ); +}; + +const ModQueueViewSelector = () => { + const { viewMode, setViewMode } = useModQueueStore(); + return ( +
+ +
+ ); +}; + const ShowOPCommentOption = () => { const { t } = useTranslation(); const { showOPComment, setShowOPComment } = useCatalogStyleStore(); @@ -353,6 +406,8 @@ export const MobileBoardButtons = () => { isInModQueueView={isInModQueueView} /> + + ) : ( <> @@ -476,6 +531,10 @@ export const DesktopBoardButtons = () => { /> ] [ ] + + + + ) : ( <> diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 1f780380..53aade6f 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -9,7 +9,8 @@ import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDim import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; import { isValidURL } from '../../lib/utils/url-utils'; -import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import useModQueueStore from '../../stores/use-mod-queue-store'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { getBoardPath } from '../../lib/utils/route-utils'; import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store'; @@ -54,7 +55,19 @@ const useShowOmittedReplies = create((set) => ({ })), })); -const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, threadNumber }: PostProps) => { +const PostInfo = ({ + post, + postReplyCount = 0, + roles, + isHidden, + threadNumber, + isModQueue, + modQueueStatus, + modQueueError, + isPublishing, + onApprove, + onReject, +}: PostProps) => { const { t } = useTranslation(); const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, state, subplebbitAddress, timestamp } = post || {}; const title = post?.title?.trim(); @@ -74,6 +87,17 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, threadNumber }: P const params = useParams(); const location = useLocation(); const isInPostPageView = isPostPageView(location.pathname, params); + const isInModQueueView = isModQueueView(location.pathname); + const { getAlertThresholdSeconds } = useModQueueStore(); + + // Check if post is awaiting approval and over threshold (for mod queue view) + const approved = post?.approved; + const alreadyApproved = approved === true; + const alreadyRejected = removed === true; + const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected; + const timeWaiting = timestamp ? Date.now() / 1000 - timestamp : 0; + const alertThresholdSeconds = getAlertThresholdSeconds(); + const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds; const userID = address && Plebbit.getShortAddress({ address }); // should not be shortened to less than 12 characters, because users can create unlimited addresses/IDs before authenticating or passing challenges, so if the ID is short enough they can spoof it to troll users with the same ID const userIDBackgroundColor = hashStringToColor(userID); @@ -169,7 +193,14 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, threadNumber }: P ){' '} - {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} />{' '} + {isInModQueueView && isOverThreshold ? ( + <> + {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} /> ( + {getFormattedTimeAgo(timestamp)}) + + ) : ( + {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} /> + )}{' '} {cid ? ( @@ -204,7 +235,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, threadNumber }: P )} - {!isInPostPageView && !isReply && !isHidden && ( + {!isInPostPageView && !isReply && !isHidden && !isModQueue && ( [ !cid && e.preventDefault()}> @@ -213,8 +244,41 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, threadNumber }: P ] )} + {isModQueue && ( + + {modQueueStatus === 'approved' ? ( + {t('approved')} + ) : modQueueStatus === 'rejected' ? ( + {t('rejected')} + ) : modQueueStatus === 'failed' ? ( + + {t('failed')} + {modQueueError ? `: ${modQueueError}` : ''} + + ) : isPublishing ? ( + + ) : ( + <> + + [ + + ] + + + [ + + ] + + + )} + + )} - {!(removed || deleted) && } + {!(removed || deleted) && !isModQueue && } {cid && parentCid && replies && @@ -376,7 +440,19 @@ const Reply = ({ postReplyCount, reply, roles, threadNumber }: PostProps) => { ); }; -const PostDesktop = ({ post, roles, showAllReplies, showReplies = true, targetReplyCid }: PostProps) => { +const PostDesktop = ({ + post, + roles, + showAllReplies, + showReplies = true, + targetReplyCid, + isModQueue, + modQueueStatus, + modQueueError, + isPublishing, + onApprove, + onReject, +}: PostProps) => { const { t } = useTranslation(); const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, subplebbitAddress, thumbnailUrl, parentCid } = post || {}; const params = useParams(); @@ -450,7 +526,7 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true, targetRe return (
- {showReplies ? ( + {showReplies || isModQueue ? (

@@ -485,7 +561,19 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true, targetRe isInSubscriptionsView={isInSubscriptionsView} /> )} - + {!isHidden && !content && !(deleted || removed) &&
} {!isHidden && }
diff --git a/src/components/post-form/post-form.module.css b/src/components/post-form/post-form.module.css index a16a7855..8e839b96 100644 --- a/src/components/post-form/post-form.module.css +++ b/src/components/post-form/post-form.module.css @@ -18,6 +18,18 @@ margin-top: -3px; } +.modQueueTitle { + font-size: 1.5em; + font-weight: bold; + padding: 10px 0; +} + +@media (min-width: 640px) { + .modQueueTitle { + font-size: 1em; + } +} + .closed { font-size: x-large; text-align: center; diff --git a/src/components/post-form/post-form.tsx b/src/components/post-form/post-form.tsx index fb71d823..d920e83b 100644 --- a/src/components/post-form/post-form.tsx +++ b/src/components/post-form/post-form.tsx @@ -8,7 +8,7 @@ import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/s import { getHasThumbnail, getLinkMediaInfo } from '../../lib/utils/media-utils'; import { formatMarkdown } from '../../lib/utils/post-utils'; import { isValidURL } from '../../lib/utils/url-utils'; -import { isAllView, isCatalogView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import { isAllView, isCatalogView, isModQueueView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; @@ -373,6 +373,7 @@ const PostForm = () => { const isInPostView = isPostPageView(location.pathname, params); const isInAllView = isAllView(location.pathname); const isInModView = isModView(location.pathname); + const isInModQueueView = isModQueueView(location.pathname); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); const isInCatalogView = isCatalogView(location.pathname, params); @@ -398,7 +399,9 @@ const PostForm = () => { <>
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && } - {isThreadClosed ? ( + {isInModQueueView ? ( +
{t('moderation_queue')}
+ ) : isThreadClosed ? (
{t('thread_closed')}
@@ -418,7 +421,9 @@ const PostForm = () => {
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && } - {isThreadClosed ? ( + {isInModQueueView ? ( +
{t('moderation_queue')}
+ ) : isThreadClosed ? (
{t('thread_closed')}
diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index c1664f1a..c67741a5 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -9,7 +9,8 @@ import { shouldShowSnow } from '../../lib/snow'; import { getHasThumbnail } from '../../lib/utils/media-utils'; import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils'; import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils'; -import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; +import useModQueueStore from '../../stores/use-mod-queue-store'; import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits'; import { getBoardPath } from '../../lib/utils/route-utils'; import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store'; @@ -52,10 +53,21 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos const isInAllView = isAllView(location.pathname); const isInPostPageView = isPostPageView(location.pathname, params); const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); + const isInModQueueView = isModQueueView(location.pathname); + const { getAlertThresholdSeconds } = useModQueueStore(); const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); const hasThumbnail = getHasThumbnail(commentMediaInfo, link); + // Check if post is awaiting approval and over threshold (for mod queue view) + const approved = post?.approved; + const alreadyApproved = approved === true; + const alreadyRejected = removed === true; + const isAwaitingApproval = isInModQueueView && !alreadyApproved && !alreadyRejected; + const timeWaiting = timestamp ? Date.now() / 1000 - timestamp : 0; + const alertThresholdSeconds = getAlertThresholdSeconds(); + const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds; + const stateString = useStateString(post); const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]); @@ -176,7 +188,14 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos Board: {boardPath}
)} - {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} />{' '} + {isInModQueueView && isOverThreshold ? ( + <> + {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} /> ( + {getFormattedTimeAgo(timestamp)}) + + ) : ( + {getFormattedDate(timestamp)}} content={getFormattedTimeAgo(timestamp)} /> + )}{' '} {cid ? ( { ); }; -const PostMobile = ({ post, roles, showAllReplies, showReplies = true, targetReplyCid }: PostProps) => { +const PostMobile = ({ + post, + roles, + showAllReplies, + showReplies = true, + targetReplyCid, + isModQueue, + modQueueStatus, + modQueueError, + isPublishing, + onApprove, + onReject, +}: PostProps) => { const { t } = useTranslation(); const { author, cid, pinned, postCid, replyCount, state, subplebbitAddress } = post || {}; const params = useParams(); @@ -356,12 +387,12 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true, targetRep ) : (
- {showReplies && ( + {(showReplies || isModQueue) && (

)} -
+
- {!isInPostView && !isInPendingPostView && showReplies && ( + {!isInPostView && !isInPendingPostView && (showReplies || isModQueue) && (
{replyCount > 0 && `${replyCount} Replies`} {linksCount > 0 && ` / ${linksCount} Links`} - - {t('view_thread')} - + {isModQueue ? ( +
+ {modQueueStatus === 'approved' ? ( + {t('approved')} + ) : modQueueStatus === 'rejected' ? ( + {t('rejected')} + ) : modQueueStatus === 'failed' ? ( + + {t('failed')} + {modQueueError ? `: ${modQueueError}` : ''} + + ) : isPublishing ? ( + + ) : ( + <> + + + + )} +
+ ) : ( + + {t('view_thread')} + + )}
)}
diff --git a/src/stores/use-mod-queue-store.ts b/src/stores/use-mod-queue-store.ts index 25f2d7e1..5c588646 100644 --- a/src/stores/use-mod-queue-store.ts +++ b/src/stores/use-mod-queue-store.ts @@ -2,13 +2,16 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; export type AlertThresholdUnit = 'hours' | 'minutes'; +export type ModQueueViewMode = 'compact' | 'feed'; interface ModQueueState { alertThresholdValue: number; alertThresholdUnit: AlertThresholdUnit; selectedBoardFilter: string | null; + viewMode: ModQueueViewMode; setAlertThreshold: (value: number, unit: AlertThresholdUnit) => void; setSelectedBoardFilter: (boardAddress: string | null) => void; + setViewMode: (viewMode: ModQueueViewMode) => void; // Helper to get threshold in seconds for calculations getAlertThresholdSeconds: () => number; } @@ -19,10 +22,11 @@ interface OldPersistedState { alertThresholdValue?: number; alertThresholdUnit?: AlertThresholdUnit; selectedBoardFilter?: string | null; + viewMode?: ModQueueViewMode; } // Type for persisted data (without methods) -type PersistedModQueueData = Pick; +type PersistedModQueueData = Pick; const useModQueueStore = create()( persist( @@ -30,8 +34,10 @@ const useModQueueStore = create()( alertThresholdValue: 6, alertThresholdUnit: 'hours' as AlertThresholdUnit, selectedBoardFilter: null, + viewMode: 'compact', setAlertThreshold: (value, unit) => set({ alertThresholdValue: value, alertThresholdUnit: unit }), setSelectedBoardFilter: (boardAddress) => set({ selectedBoardFilter: boardAddress }), + setViewMode: (viewMode) => set({ viewMode }), getAlertThresholdSeconds: () => { const { alertThresholdValue, alertThresholdUnit } = get(); return alertThresholdUnit === 'hours' ? alertThresholdValue * 3600 : alertThresholdValue * 60; @@ -39,7 +45,7 @@ const useModQueueStore = create()( }), { name: 'mod-queue-storage', - version: 1, + version: 2, // Migrate old alertThresholdHours format to new alertThresholdValue/alertThresholdUnit format migrate: (persistedState, version): ModQueueState => { const state = persistedState as OldPersistedState; @@ -48,6 +54,7 @@ const useModQueueStore = create()( alertThresholdValue: state.alertThresholdHours, alertThresholdUnit: 'hours' as AlertThresholdUnit, selectedBoardFilter: state.selectedBoardFilter ?? null, + viewMode: state.viewMode ?? 'compact', }; // Zustand will merge this with the store definition (which includes methods) return migrated as ModQueueState; @@ -57,6 +64,7 @@ const useModQueueStore = create()( alertThresholdValue: state.alertThresholdValue ?? 6, alertThresholdUnit: state.alertThresholdUnit ?? 'hours', selectedBoardFilter: state.selectedBoardFilter ?? null, + viewMode: state.viewMode ?? 'compact', }; return current as ModQueueState; }, diff --git a/src/views/mod-queue/mod-queue.module.css b/src/views/mod-queue/mod-queue.module.css index fef3d539..2fa9bb2c 100644 --- a/src/views/mod-queue/mod-queue.module.css +++ b/src/views/mod-queue/mod-queue.module.css @@ -233,6 +233,52 @@ display: inline; } +.mobileCard { + border: var(--mod-queue-table-border); + background: var(--post-mobile-background-color, transparent); + padding: 8px; + margin: 6px 0; + display: flex; + flex-direction: column; + gap: 6px; +} + +.cardHeader { + display: flex; + justify-content: space-between; + font-size: 9pt; + font-weight: bold; +} + +.cardNumber { + color: var(--body-font-color); +} + +.cardTime { + white-space: nowrap; +} + +.cardContent { + font-size: 9pt; + color: var(--body-font-color); +} + +.cardContent a { + text-decoration: var(--post-link-text-decoration); + color: var(--post-link-text-color); +} + +.cardContent a:hover { + text-decoration: var(--post-link-text-decoration-hover); + color: var(--post-link-text-color-hover); +} + +.cardActions { + display: flex; + justify-content: flex-end; + gap: 8px; +} + /* Desktop: buttons styled as text links (same as board buttons) */ @media (min-width: 640px) { .actions { diff --git a/src/views/mod-queue/mod-queue.tsx b/src/views/mod-queue/mod-queue.tsx index bdee9f98..bc520b1d 100644 --- a/src/views/mod-queue/mod-queue.tsx +++ b/src/views/mod-queue/mod-queue.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useEffect } from 'react'; +import React, { useMemo, useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, Link } from 'react-router-dom'; import { useFeed, Comment, usePublishCommentModeration, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks'; @@ -21,6 +21,7 @@ import useChallengesStore from '../../stores/use-challenges-store'; import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils'; import Tooltip from '../../components/tooltip'; import useIsMobile from '../../hooks/use-is-mobile'; +import { Post } from '../post/post'; const { addChallenge } = useChallengesStore.getState(); @@ -55,30 +56,22 @@ interface ModQueueRowProps { // Track which action was initiated to show appropriate completion message type ModerationAction = 'approve' | 'reject' | null; -const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { +interface ModQueueActionState { + status: 'approved' | 'rejected' | 'failed' | null; + errorMessage?: string; + isPublishing: boolean; + handleApprove: () => Promise; + handleReject: () => Promise; +} + +const useModQueueActions = (comment: Comment): ModQueueActionState => { const { t } = useTranslation(); - const { getAlertThresholdSeconds } = useModQueueStore(); + const { cid, subplebbitAddress, approved, removed } = comment || {}; const [initiatedAction, setInitiatedAction] = useState(null); - const isMobile = useIsMobile(); - const { editedComment } = useEditedComment({ comment }); - const displayComment = editedComment || comment; - - const { content, title, timestamp, subplebbitAddress, cid, shortCid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number } = displayComment; - - // Check if already moderated (from previous session or API update) - // Note: `approved` and `removed` are direct fields on the comment from CommentUpdate, - // not nested under commentModeration (which is the options object for publishing moderation actions) const alreadyApproved = approved === true; const alreadyRejected = removed === true; - const timeWaiting = Date.now() / 1000 - timestamp; - const alertThresholdSeconds = getAlertThresholdSeconds(); - const isOverThreshold = timeWaiting > alertThresholdSeconds; - - // Only show alert animation for comments awaiting approval (not approved or rejected) - const isAwaitingApproval = !alreadyApproved && !alreadyRejected; - const { publishCommentModeration: approve, state: approveState, @@ -117,8 +110,7 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { }, }); - const handleApprove = async () => { - // Double confirmation for approve action + const handleApprove = useCallback(async () => { const confirm = window.confirm(t('double_confirm')); if (!confirm) { return; @@ -130,10 +122,9 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { } catch (e) { console.error(e); } - }; + }, [approve, t]); - const handleReject = async () => { - // Double confirmation for reject action + const handleReject = useCallback(async () => { const confirm = window.confirm(t('double_confirm')); if (!confirm) { return; @@ -145,9 +136,8 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { } catch (e) { console.error(e); } - }; + }, [reject, t]); - // Determine the current moderation state based on which action was initiated const isApproving = initiatedAction === 'approve' && approveState !== 'initializing' && approveState !== 'succeeded' && approveState !== 'failed'; const isRejecting = initiatedAction === 'reject' && rejectState !== 'initializing' && rejectState !== 'succeeded' && rejectState !== 'failed'; const isPublishing = isApproving || isRejecting; @@ -158,6 +148,37 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { const approveFailed = initiatedAction === 'approve' && approveState === 'failed'; const rejectFailed = initiatedAction === 'reject' && rejectState === 'failed'; + const status = alreadyApproved || approveSucceeded ? 'approved' : alreadyRejected || rejectSucceeded ? 'rejected' : approveFailed || rejectFailed ? 'failed' : null; + const errorMessage = approveFailed ? approveError?.message : rejectFailed ? rejectError?.message : undefined; + + return { status, errorMessage, isPublishing, handleApprove, handleReject }; +}; + +const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { + const { t } = useTranslation(); + const { getAlertThresholdSeconds } = useModQueueStore(); + const isMobile = useIsMobile(); + + const { editedComment } = useEditedComment({ comment }); + const displayComment = editedComment || comment; + + const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number } = displayComment; + + // Check if already moderated (from previous session or API update) + // Note: `approved` and `removed` are direct fields on the comment from CommentUpdate, + // not nested under commentModeration (which is the options object for publishing moderation actions) + const alreadyApproved = approved === true; + const alreadyRejected = removed === true; + + const timeWaiting = Date.now() / 1000 - timestamp; + const alertThresholdSeconds = getAlertThresholdSeconds(); + const isOverThreshold = timeWaiting > alertThresholdSeconds; + + // Only show alert animation for comments awaiting approval (not approved or rejected) + const isAwaitingApproval = !alreadyApproved && !alreadyRejected; + + const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment); + const boardPath = useBoardPath(subplebbitAddress); const hasTitle = title && title.trim().length > 0; const hasContent = content && content.trim().length > 0; @@ -176,23 +197,17 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { // Render the status or action buttons const renderActions = () => { // Check existing moderation state first (from API/previous sessions) - if (alreadyApproved || approveSucceeded) { + if (status === 'approved') { return {t('approved')}; } - if (alreadyRejected || rejectSucceeded) { + if (status === 'rejected') { return {t('rejected')}; } - if (approveFailed) { + if (status === 'failed') { return ( - {t('failed')}: {approveError?.message} - - ); - } - if (rejectFailed) { - return ( - - {t('failed')}: {rejectError?.message} + {t('failed')} + {errorMessage ? `: ${errorMessage}` : ''} ); } @@ -258,6 +273,137 @@ const ModQueueRow = ({ comment, isOdd = false }: ModQueueRowProps) => { ); }; +interface ModQueueCardProps { + comment: Comment; +} + +const ModQueueCard = ({ comment }: ModQueueCardProps) => { + const { t } = useTranslation(); + const { getAlertThresholdSeconds } = useModQueueStore(); + + const { editedComment } = useEditedComment({ comment }); + const displayComment = editedComment || comment; + + const { content, title, timestamp, subplebbitAddress, cid, threadCid, link, thumbnailUrl, linkWidth, linkHeight, removed, approved, number } = displayComment; + + const alreadyApproved = approved === true; + const alreadyRejected = removed === true; + + const timeWaiting = Date.now() / 1000 - timestamp; + const alertThresholdSeconds = getAlertThresholdSeconds(); + const isOverThreshold = timeWaiting > alertThresholdSeconds; + const isAwaitingApproval = !alreadyApproved && !alreadyRejected; + + const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment); + + const boardPath = useBoardPath(subplebbitAddress); + const hasTitle = title && title.trim().length > 0; + const hasContent = content && content.trim().length > 0; + const hasLink = link && link.length > 0; + const rawExcerpt = + (hasTitle ? title : null) || + (hasContent ? content : null) || + (hasLink ? link : null) || + (getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link) ? t('image') : null) || + t('no_content'); + const excerpt = rawExcerpt.length > 140 ? rawExcerpt.slice(0, 137) + '...' : rawExcerpt; + const threadTargetCid = threadCid || cid; + const postUrl = boardPath && threadTargetCid ? `/${boardPath}/thread/${threadTargetCid}` : undefined; + + const renderActions = () => { + if (status === 'approved') { + return ( +
+ {t('approved')} +
+ ); + } + if (status === 'rejected') { + return ( +
+ {t('rejected')} +
+ ); + } + if (status === 'failed') { + return ( +
+ + {t('failed')} + {errorMessage ? `: ${errorMessage}` : ''} + +
+ ); + } + if (isPublishing) { + return ( +
+ +
+ ); + } + + return ( +
+ + +
+ ); + }; + + return ( +
+
+ No. {number ?? 'N/A'} + + {isAwaitingApproval && isOverThreshold ? ( + <> + {getFormattedDate(timestamp)} ({getFormattedTimeAgo(timestamp)}) + + ) : ( + getFormattedDate(timestamp) + )} + +
+
+ {t('excerpt')}:{' '} + {postUrl ? ( + + {excerpt} + + ) : ( + {excerpt} + )} +
+ {renderActions()} +
+ ); +}; + +const ModQueueFeedPost = ({ comment }: { comment: Comment }) => { + const { editedComment } = useEditedComment({ comment }); + const displayComment = editedComment || comment; + const { status, errorMessage, isPublishing, handleApprove, handleReject } = useModQueueActions(displayComment); + + return ( + + ); +}; + interface ModQueueBoardFilterProps { subplebbits: MultisubSubplebbit[]; } @@ -474,7 +620,8 @@ export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProp export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueViewProps) => { const { t } = useTranslation(); const params = useParams(); - const { selectedBoardFilter, alertThresholdValue, alertThresholdUnit, setAlertThreshold } = useModQueueStore(); + const { selectedBoardFilter, viewMode } = useModQueueStore(); + const isMobile = useIsMobile(); const accountSubplebbitAddresses = useAccountsStore( (state) => { @@ -563,97 +710,108 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV [hasMore, subplebbitAddresses, subplebbitError, feed.length], ); - const alertThresholdControl = ( -
- -
- ); - return (
{!resolvedAddress && ( -
-
{t('moderation_queue')}
+
+
+ +
)} -
- {!resolvedAddress ? ( - <> -
- -
-
{alertThresholdControl}
- - ) : ( - <> -
-
{t('moderation_queue')}
-
-
{alertThresholdControl}
- - )} -
- {feed.length === 0 && !hasMore ? (
{t('queue_is_empty')}
) : ( <> -
-
No.
-
{t('excerpt')}
-
{t('submitted')}
-
{t('actions')}
-
- - {/* Use Virtuoso for infinite scroll only when there's more content to paginate */} - {hasMore ? ( - } - components={footerComponents} - /> - ) : ( + {viewMode === 'compact' && !isMobile && ( <> - {feed.map((comment, index) => ( - - ))} - {subplebbitError?.message && feed.length === 0 && ( -
- -
+
+
No.
+
{t('excerpt')}
+
{t('submitted')}
+
{t('actions')}
+
+ + {hasMore ? ( + } + components={footerComponents} + /> + ) : ( + <> + {feed.map((comment, index) => ( + + ))} + {subplebbitError?.message && feed.length === 0 && ( +
+ +
+ )} + + + )} + + )} + + {viewMode === 'compact' && isMobile && ( + <> + {hasMore ? ( + } + components={footerComponents} + /> + ) : ( + <> + {feed.map((comment) => ( + + ))} + {subplebbitError?.message && feed.length === 0 && ( +
+ +
+ )} + + + )} + + )} + + {viewMode === 'feed' && ( + <> + {hasMore ? ( + } + components={footerComponents} + /> + ) : ( + <> + {feed.map((comment) => ( + + ))} + {subplebbitError?.message && feed.length === 0 && ( +
+ +
+ )} + + )} - )} diff --git a/src/views/post/post.module.css b/src/views/post/post.module.css index 10482541..fdd0d19d 100644 --- a/src/views/post/post.module.css +++ b/src/views/post/post.module.css @@ -189,6 +189,55 @@ color: var(--button-desktop-text-color-hover); } +.modQueueActions { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 8px; +} + +.modQueueButtonWrapper { + display: inline-flex; +} + +.modQueueActionButton { + all: unset; + cursor: pointer; + text-transform: capitalize; + color: var(--button-desktop-text-color); + text-decoration: var(--button-text-decoration); + font-family: inherit; +} + +.modQueueActionButton:hover { + color: var(--button-desktop-text-color-hover); +} + +.modQueueActionButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.modQueueStatusApproved { + color: green; + font-weight: 700; +} + +.modQueueStatusRejected { + color: red; + font-weight: 700; +} + +.approveButton { + background-color: #3fa96b; + color: #fff; +} + +.rejectButton { + background-color: #d9534f; + color: #fff; +} + .postDesktop .spacer { width: 100%; padding: 5px; @@ -668,4 +717,16 @@ color: red; font-weight: 700; text-transform: uppercase; +} + +.alert { + color: var(--mod-queue-alert-color, red); + font-weight: bold; + animation: blink 2s infinite; +} + +@keyframes blink { + 0% { opacity: 1; } + 50% { opacity: 0.5; } + 100% { opacity: 1; } } \ No newline at end of file diff --git a/src/views/post/post.tsx b/src/views/post/post.tsx index 71333b19..8f58a58a 100644 --- a/src/views/post/post.tsx +++ b/src/views/post/post.tsx @@ -25,9 +25,26 @@ export interface PostProps { showReplies?: boolean; targetReplyCid?: string; threadNumber?: number; + isModQueue?: boolean; + modQueueStatus?: 'approved' | 'rejected' | 'failed' | null; + modQueueError?: string; + isPublishing?: boolean; + onApprove?: () => void; + onReject?: () => void; } -export const Post = ({ post, showAllReplies = false, showReplies = true, targetReplyCid }: PostProps) => { +export const Post = ({ + post, + showAllReplies = false, + showReplies = true, + targetReplyCid, + isModQueue, + modQueueStatus, + modQueueError, + isPublishing, + onApprove, + onReject, +}: PostProps) => { // Only subscribe to roles field to avoid rerenders from updatingState changes const roles = useSubplebbitField(post?.subplebbitAddress, (subplebbit) => subplebbit?.roles); const isMobile = useIsMobile(); @@ -44,9 +61,33 @@ export const Post = ({ post, showAllReplies = false, showReplies = true, targetR
{isMobile ? ( - + ) : ( - + )}
From f5c3ccd1f91e425ae9273de54e7fd0a7e18580b6 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 16:55:19 +0800 Subject: [PATCH 05/10] Update AGENTS.md --- AGENTS.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 331f4ff0..c993a9e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,6 +105,49 @@ Each MCP server injects its tool definitions into the context window, consuming If you notice many MCP tools in your context, or if the user reports degraded responses, warn them that they may have too many MCP servers enabled and suggest disabling unused ones to free up context space. +## Translations + +This project uses i18next with translation files in `public/translations/{lang}/default.json`. + +### Adding/Updating Translations + +Use `scripts/update-translations.js` to update translations across all languages. **Do not manually edit each language file.** + +**Workflow:** + +1. Create a temporary dictionary file (e.g., `translations-temp.json`) with translations for each language: + ```json + { + "en": "English text", + "es": "Spanish text", + "fr": "French text", + "de": "German text", + ... + } + ``` + +2. Run the script with the `--map` flag: + ```bash + node scripts/update-translations.js --key my_new_key --map translations-temp.json --include-en --write + ``` + +3. Delete the temporary dictionary file after the script completes. + +**Other useful commands:** + +```bash +# Copy a key's value from English to all languages (dry run first) +node scripts/update-translations.js --key some_key --from en --dry +node scripts/update-translations.js --key some_key --from en --write + +# Delete a key from all languages +node scripts/update-translations.js --key obsolete_key --delete --write + +# Audit for unused translation keys +node scripts/update-translations.js --audit --dry +node scripts/update-translations.js --audit --write +``` + ## Boundaries - Never commit secrets or API keys From d86637200bc74f41e095023bfe58a5fb4e58ec35 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 16:56:48 +0800 Subject: [PATCH 06/10] fix(mod-queue): improve alert threshold input validation and add i18n for view selector --- public/translations/ar/default.json | 5 +- public/translations/bn/default.json | 5 +- public/translations/cs/default.json | 5 +- public/translations/da/default.json | 5 +- public/translations/de/default.json | 5 +- public/translations/el/default.json | 5 +- public/translations/en/default.json | 3 ++ public/translations/es/default.json | 5 +- public/translations/fa/default.json | 5 +- public/translations/fi/default.json | 5 +- public/translations/fil/default.json | 5 +- public/translations/fr/default.json | 5 +- public/translations/he/default.json | 5 +- public/translations/hi/default.json | 5 +- public/translations/hu/default.json | 5 +- public/translations/id/default.json | 5 +- public/translations/it/default.json | 5 +- public/translations/ja/default.json | 5 +- public/translations/ko/default.json | 5 +- public/translations/mr/default.json | 5 +- public/translations/nl/default.json | 5 +- public/translations/no/default.json | 5 +- public/translations/pl/default.json | 5 +- public/translations/pt/default.json | 5 +- public/translations/ro/default.json | 5 +- public/translations/ru/default.json | 5 +- public/translations/sq/default.json | 5 +- public/translations/sv/default.json | 5 +- public/translations/te/default.json | 5 +- public/translations/th/default.json | 5 +- public/translations/tr/default.json | 5 +- public/translations/uk/default.json | 5 +- public/translations/ur/default.json | 5 +- public/translations/vi/default.json | 5 +- public/translations/zh/default.json | 5 +- .../board-buttons/board-buttons.tsx | 47 +++++++++++++++++-- 36 files changed, 182 insertions(+), 38 deletions(-) diff --git a/public/translations/ar/default.json b/public/translations/ar/default.json index b64a2b5d..71cd71a6 100644 --- a/public/translations/ar/default.json +++ b/public/translations/ar/default.json @@ -269,5 +269,8 @@ "submitted": "تم الإرسال", "hours": "ساعات", "minutes": "دقائق", - "alert_threshold": "عتبة التنبيه" + "alert_threshold": "عتبة التنبيه", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/bn/default.json b/public/translations/bn/default.json index 03badc9a..dcca3f86 100644 --- a/public/translations/bn/default.json +++ b/public/translations/bn/default.json @@ -269,5 +269,8 @@ "submitted": "জমা দেওয়া হয়েছে", "hours": "ঘণ্টা", "minutes": "মিনিট", - "alert_threshold": "সতর্কতা থ্রেশহোল্ড" + "alert_threshold": "সতর্কতা থ্রেশহোল্ড", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/cs/default.json b/public/translations/cs/default.json index 94305994..88b282ab 100644 --- a/public/translations/cs/default.json +++ b/public/translations/cs/default.json @@ -269,5 +269,8 @@ "submitted": "Odesláno", "hours": "hodin", "minutes": "minut", - "alert_threshold": "Práh upozornění" + "alert_threshold": "Práh upozornění", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/da/default.json b/public/translations/da/default.json index 41443d51..3f858add 100644 --- a/public/translations/da/default.json +++ b/public/translations/da/default.json @@ -269,5 +269,8 @@ "submitted": "Indsendt", "hours": "timer", "minutes": "minutter", - "alert_threshold": "Alarmtærskel" + "alert_threshold": "Alarmtærskel", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/de/default.json b/public/translations/de/default.json index 61e17e6d..5dc5e378 100644 --- a/public/translations/de/default.json +++ b/public/translations/de/default.json @@ -269,5 +269,8 @@ "submitted": "Eingereicht", "hours": "Stunden", "minutes": "Minuten", - "alert_threshold": "Alarmschwellenwert" + "alert_threshold": "Alarmschwellenwert", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/el/default.json b/public/translations/el/default.json index c839d059..f5658b56 100644 --- a/public/translations/el/default.json +++ b/public/translations/el/default.json @@ -269,5 +269,8 @@ "submitted": "Υποβλήθηκε", "hours": "ώρες", "minutes": "λεπτά", - "alert_threshold": "Όριο ειδοποίησης" + "alert_threshold": "Όριο ειδοποίησης", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/en/default.json b/public/translations/en/default.json index ddc363c3..78f5c28e 100644 --- a/public/translations/en/default.json +++ b/public/translations/en/default.json @@ -263,6 +263,9 @@ "alert_threshold": "Alert threshold", "hours": "hours", "minutes": "minutes", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed", "no_content": "No content", "excerpt": "Excerpt", "publishing": "Publishing", diff --git a/public/translations/es/default.json b/public/translations/es/default.json index c571db18..619021df 100644 --- a/public/translations/es/default.json +++ b/public/translations/es/default.json @@ -269,5 +269,8 @@ "submitted": "Enviado", "hours": "horas", "minutes": "minutos", - "alert_threshold": "Umbral de alerta" + "alert_threshold": "Umbral de alerta", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/fa/default.json b/public/translations/fa/default.json index 29078e1c..90c0836e 100644 --- a/public/translations/fa/default.json +++ b/public/translations/fa/default.json @@ -269,5 +269,8 @@ "submitted": "ارسال شده", "hours": "ساعت", "minutes": "دقیقه", - "alert_threshold": "آستانه هشدار" + "alert_threshold": "آستانه هشدار", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/fi/default.json b/public/translations/fi/default.json index ec303dfe..39a05080 100644 --- a/public/translations/fi/default.json +++ b/public/translations/fi/default.json @@ -269,5 +269,8 @@ "submitted": "Lähetetty", "hours": "tuntia", "minutes": "minuuttia", - "alert_threshold": "Hälytyskynnys" + "alert_threshold": "Hälytyskynnys", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/fil/default.json b/public/translations/fil/default.json index cc85e549..ab6c0d5f 100644 --- a/public/translations/fil/default.json +++ b/public/translations/fil/default.json @@ -269,5 +269,8 @@ "submitted": "Isinumite", "hours": "oras", "minutes": "minuto", - "alert_threshold": "Alert threshold" + "alert_threshold": "Alert threshold", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/fr/default.json b/public/translations/fr/default.json index 38b03726..183d1af6 100644 --- a/public/translations/fr/default.json +++ b/public/translations/fr/default.json @@ -269,5 +269,8 @@ "submitted": "Soumis", "hours": "heures", "minutes": "minutes", - "alert_threshold": "Seuil d'alerte" + "alert_threshold": "Seuil d'alerte", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/he/default.json b/public/translations/he/default.json index 5ff6ae28..fc3d917c 100644 --- a/public/translations/he/default.json +++ b/public/translations/he/default.json @@ -269,5 +269,8 @@ "submitted": "נשלח", "hours": "שעות", "minutes": "דקות", - "alert_threshold": "סף התרעה" + "alert_threshold": "סף התרעה", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/hi/default.json b/public/translations/hi/default.json index 8bb60a73..03395dae 100644 --- a/public/translations/hi/default.json +++ b/public/translations/hi/default.json @@ -269,5 +269,8 @@ "submitted": "सबमिट किया गया", "hours": "घंटे", "minutes": "मिनट", - "alert_threshold": "सतर्कता सीमा" + "alert_threshold": "सतर्कता सीमा", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/hu/default.json b/public/translations/hu/default.json index 165a7c6a..7a3ec449 100644 --- a/public/translations/hu/default.json +++ b/public/translations/hu/default.json @@ -269,5 +269,8 @@ "submitted": "Beküldve", "hours": "óra", "minutes": "perc", - "alert_threshold": "Riasztási küszöb" + "alert_threshold": "Riasztási küszöb", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/id/default.json b/public/translations/id/default.json index 1e02ffed..e8697747 100644 --- a/public/translations/id/default.json +++ b/public/translations/id/default.json @@ -269,5 +269,8 @@ "submitted": "Dikirim", "hours": "jam", "minutes": "menit", - "alert_threshold": "Ambang peringatan" + "alert_threshold": "Ambang peringatan", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/it/default.json b/public/translations/it/default.json index ce93e22b..3ee2f207 100644 --- a/public/translations/it/default.json +++ b/public/translations/it/default.json @@ -269,5 +269,8 @@ "submitted": "Inviato", "hours": "ore", "minutes": "minuti", - "alert_threshold": "Soglia di allerta" + "alert_threshold": "Soglia di allerta", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/ja/default.json b/public/translations/ja/default.json index 99472635..917bb4b8 100644 --- a/public/translations/ja/default.json +++ b/public/translations/ja/default.json @@ -269,5 +269,8 @@ "submitted": "送信済み", "hours": "時間", "minutes": "分", - "alert_threshold": "アラート閾値" + "alert_threshold": "アラート閾値", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/ko/default.json b/public/translations/ko/default.json index ad83f0ca..e3157c3b 100644 --- a/public/translations/ko/default.json +++ b/public/translations/ko/default.json @@ -269,5 +269,8 @@ "submitted": "제출됨", "hours": "시간", "minutes": "분", - "alert_threshold": "알림 임계값" + "alert_threshold": "알림 임계값", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/mr/default.json b/public/translations/mr/default.json index 37a22713..02beacf5 100644 --- a/public/translations/mr/default.json +++ b/public/translations/mr/default.json @@ -269,5 +269,8 @@ "submitted": "सबमिट केले", "hours": "तास", "minutes": "मिनिटे", - "alert_threshold": "सतर्कता मर्यादा" + "alert_threshold": "सतर्कता मर्यादा", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/nl/default.json b/public/translations/nl/default.json index 3cb6ea8d..8cf7e2ad 100644 --- a/public/translations/nl/default.json +++ b/public/translations/nl/default.json @@ -269,5 +269,8 @@ "submitted": "Ingediend", "hours": "uur", "minutes": "minuten", - "alert_threshold": "Drempelwaarde voor waarschuwing" + "alert_threshold": "Drempelwaarde voor waarschuwing", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/no/default.json b/public/translations/no/default.json index 2f036153..c4171f0b 100644 --- a/public/translations/no/default.json +++ b/public/translations/no/default.json @@ -269,5 +269,8 @@ "submitted": "Sendt inn", "hours": "timer", "minutes": "minutter", - "alert_threshold": "Terskel for varsel" + "alert_threshold": "Terskel for varsel", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/pl/default.json b/public/translations/pl/default.json index 7f92276c..780f71f5 100644 --- a/public/translations/pl/default.json +++ b/public/translations/pl/default.json @@ -269,5 +269,8 @@ "submitted": "Przesłane", "hours": "godzin", "minutes": "minut", - "alert_threshold": "Próg alertu" + "alert_threshold": "Próg alertu", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/pt/default.json b/public/translations/pt/default.json index 7b314590..16dcbde8 100644 --- a/public/translations/pt/default.json +++ b/public/translations/pt/default.json @@ -269,5 +269,8 @@ "submitted": "Enviado", "hours": "horas", "minutes": "minutos", - "alert_threshold": "Limite de alerta" + "alert_threshold": "Limite de alerta", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/ro/default.json b/public/translations/ro/default.json index e358c6ad..4525740a 100644 --- a/public/translations/ro/default.json +++ b/public/translations/ro/default.json @@ -269,5 +269,8 @@ "submitted": "Trimis", "hours": "ore", "minutes": "minute", - "alert_threshold": "Prag de alertă" + "alert_threshold": "Prag de alertă", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/ru/default.json b/public/translations/ru/default.json index e212d9d2..5eb28ec0 100644 --- a/public/translations/ru/default.json +++ b/public/translations/ru/default.json @@ -269,5 +269,8 @@ "submitted": "Отправлено", "hours": "часов", "minutes": "минут", - "alert_threshold": "Порог предупреждения" + "alert_threshold": "Порог предупреждения", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/sq/default.json b/public/translations/sq/default.json index 5898b13c..02ecec76 100644 --- a/public/translations/sq/default.json +++ b/public/translations/sq/default.json @@ -269,5 +269,8 @@ "submitted": "Dërguar", "hours": "orë", "minutes": "minuta", - "alert_threshold": "Pragu i alarmit" + "alert_threshold": "Pragu i alarmit", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/sv/default.json b/public/translations/sv/default.json index 60bf7f75..95fef456 100644 --- a/public/translations/sv/default.json +++ b/public/translations/sv/default.json @@ -269,5 +269,8 @@ "submitted": "Inskickat", "hours": "timmar", "minutes": "minuter", - "alert_threshold": "Tröskelvärde för varning" + "alert_threshold": "Tröskelvärde för varning", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/te/default.json b/public/translations/te/default.json index 4a2de249..6e43a067 100644 --- a/public/translations/te/default.json +++ b/public/translations/te/default.json @@ -269,5 +269,8 @@ "submitted": "సమర్పించబడింది", "hours": "గంటలు", "minutes": "నిమిషాలు", - "alert_threshold": "హెచ్చరిక థ్రెషోల్డ్" + "alert_threshold": "హెచ్చరిక థ్రెషోల్డ్", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/th/default.json b/public/translations/th/default.json index 386dbb82..ca3e3dff 100644 --- a/public/translations/th/default.json +++ b/public/translations/th/default.json @@ -269,5 +269,8 @@ "submitted": "ส่งแล้ว", "hours": "ชั่วโมง", "minutes": "นาที", - "alert_threshold": "เกณฑ์การแจ้งเตือน" + "alert_threshold": "เกณฑ์การแจ้งเตือน", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/tr/default.json b/public/translations/tr/default.json index b79dfb57..b3f675b4 100644 --- a/public/translations/tr/default.json +++ b/public/translations/tr/default.json @@ -269,5 +269,8 @@ "submitted": "Gönderildi", "hours": "saat", "minutes": "dakika", - "alert_threshold": "Uyarı eşiği" + "alert_threshold": "Uyarı eşiği", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/uk/default.json b/public/translations/uk/default.json index 3909a6b7..7089e305 100644 --- a/public/translations/uk/default.json +++ b/public/translations/uk/default.json @@ -269,5 +269,8 @@ "submitted": "Відправлено", "hours": "годин", "minutes": "хвилин", - "alert_threshold": "Поріг попередження" + "alert_threshold": "Поріг попередження", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/ur/default.json b/public/translations/ur/default.json index 9cd30fea..bd63e860 100644 --- a/public/translations/ur/default.json +++ b/public/translations/ur/default.json @@ -269,5 +269,8 @@ "submitted": "جمع کرایا گیا", "hours": "گھنٹے", "minutes": "منٹ", - "alert_threshold": "انتباہ کی حد" + "alert_threshold": "انتباہ کی حد", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/vi/default.json b/public/translations/vi/default.json index 2f632514..b9ea62d3 100644 --- a/public/translations/vi/default.json +++ b/public/translations/vi/default.json @@ -269,5 +269,8 @@ "submitted": "Đã gửi", "hours": "giờ", "minutes": "phút", - "alert_threshold": "Ngưỡng cảnh báo" + "alert_threshold": "Ngưỡng cảnh báo", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/public/translations/zh/default.json b/public/translations/zh/default.json index f4bdf9c3..64825a5e 100644 --- a/public/translations/zh/default.json +++ b/public/translations/zh/default.json @@ -269,5 +269,8 @@ "submitted": "已提交", "hours": "小时", "minutes": "分钟", - "alert_threshold": "警报阈值" + "alert_threshold": "警报阈值", + "modQueue.viewLabel": "View", + "modQueue.compact": "Compact", + "modQueue.feed": "Feed" } diff --git a/src/components/board-buttons/board-buttons.tsx b/src/components/board-buttons/board-buttons.tsx index c7ad11a7..381c965f 100644 --- a/src/components/board-buttons/board-buttons.tsx +++ b/src/components/board-buttons/board-buttons.tsx @@ -230,10 +230,47 @@ const ImageSizeOptions = () => { ); }; +const MAX_ALERT_THRESHOLD = 10000; // Maximum threshold value in minutes (~166 hours) + const ModQueueAlertThreshold = () => { const { t } = useTranslation(); const { alertThresholdValue, alertThresholdUnit, setAlertThreshold } = useModQueueStore(); + const handleThresholdChange = (e: React.ChangeEvent) => { + const inputValue = e.target.value.trim(); + + // Handle empty input - allow it temporarily for better UX + if (inputValue === '') { + return; + } + + // Parse safely + const parsedValue = parseInt(inputValue, 10); + + // Default to 1 if invalid or NaN + if (isNaN(parsedValue) || parsedValue < 1) { + setAlertThreshold(1, alertThresholdUnit); + return; + } + + // Convert to minutes for clamping, then convert back to current unit + const valueInMinutes = alertThresholdUnit === 'hours' ? parsedValue * 60 : parsedValue; + const clampedMinutes = Math.min(valueInMinutes, MAX_ALERT_THRESHOLD); + const finalValue = alertThresholdUnit === 'hours' ? Math.round(clampedMinutes / 60) : clampedMinutes; + + setAlertThreshold(finalValue, alertThresholdUnit); + }; + + const handleThresholdBlur = (e: React.FocusEvent) => { + const inputValue = e.target.value.trim(); + + // If empty or invalid, restore to current value or default to 1 + if (inputValue === '' || isNaN(parseInt(inputValue, 10))) { + const safeValue = alertThresholdValue >= 1 ? alertThresholdValue : 1; + setAlertThreshold(safeValue, alertThresholdUnit); + } + }; + return (
From eda8b5ed86d5376c3fac92ca90901d8c83d1edf2 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 17:02:07 +0800 Subject: [PATCH 07/10] fix(android): release build fails due to tar v7 incompatibility with Capacitor CLI --- .github/workflows/release.yml | 3 +-- package.json | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f406640..50da9561 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -164,8 +164,7 @@ jobs: - run: sed -i "s/versionName \"1.0\"/versionName \"$(node -e "console.log(require('./package.json').version)")\"/" ./android/app/build.gradle - run: cat ./android/app/build.gradle # build apk - - run: npx cap update - - run: npx cap copy + - run: npx cap sync android - run: cd android && gradle bundle - run: cd android && ./gradlew assembleRelease # optimize apk diff --git a/package.json b/package.json index 0b82517d..b29fabc5 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,9 @@ ] }, "devDependencies": { - "@capacitor/android": "7.4.4", - "@capacitor/cli": "7.4.4", - "@capacitor/core": "7.4.4", + "@capacitor/android": "7.4.5", + "@capacitor/cli": "7.4.5", + "@capacitor/core": "7.4.5", "@electron/rebuild": "4.0.0", "@react-scan/vite-plugin-react-scan": "0.1.8", "@types/memoizee": "0.4.9", @@ -142,7 +142,6 @@ "cacache": "19.0.1", "preact": "10.27.3", "@remix-run/router": "1.23.2", - "tar": "^7.5.3", "undici": "^7.18.2", "diff": "^8.0.3" }, From 6abe08787564cf2fc1af08784618c7138e055c03 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 17:06:49 +0800 Subject: [PATCH 08/10] Update yarn.lock --- yarn.lock | 72 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/yarn.lock b/yarn.lock index 54a37f42..dd342a58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1037,20 +1037,20 @@ ipaddr.js "^2.2.0" punycode "^2.3.1" -"@capacitor/android@7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-7.4.4.tgz#a14a1e844bd5079982427e247fdd17555b5fbedd" - integrity sha512-y8knfV1JXNrd6XZZLZireGT+EBCN0lvOo+HZ/s7L8LkrPBu4nY5UZn0Wxz4yOezItEII9rqYJSHsS5fMJG9gdw== +"@capacitor/android@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-7.4.5.tgz#3832b457e0a3a7079a645bf5be3cc23d406c2971" + integrity sha512-G76C+PGIFkG+hxj6JLlY003txRaGZwsXBPo4b4xiZMZaD6DG++sDCoumYl/5xL40dL2b8P5tb3CKf/khWyL2Gw== "@capacitor/app@7.0.1": version "7.0.1" resolved "https://registry.yarnpkg.com/@capacitor/app/-/app-7.0.1.tgz#0d0709fb4dde5046c24853f2d6b77a7ea411f748" integrity sha512-ArlVZAAla4MwQoKh26x2AaTDOBh5Vhp1VhMKR3RwqZSsZnazKTFGNrPbr9Ez5r1knnEDfApyjwp1uZnXK1WTYQ== -"@capacitor/cli@7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-7.4.4.tgz#bfce7db8b15d6f44c9d58dc338a5e3d662fecfbb" - integrity sha512-J7ciBE7GlJ70sr2s8oz1+H4ZdNk4MGG41fsakUlDHWva5UWgFIZYMiEdDvGbYazAYTaxN3lVZpH9zil9FfZj+Q== +"@capacitor/cli@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-7.4.5.tgz#056d8acc0d27ec3f8f79a5588e6cd6539db2895d" + integrity sha512-ApKxpzcWTqmyTD+WxBvL7UJt+x0xr9Vqa+S4Iqbl/R2kC0tf1ay22wE9pOrdWXbTqFbNhBuUuRvTvZ6Edp7QzQ== dependencies: "@ionic/cli-framework-output" "^2.2.8" "@ionic/utils-subprocess" "^3.0.1" @@ -1060,7 +1060,7 @@ env-paths "^2.2.0" fs-extra "^11.2.0" kleur "^4.1.5" - native-run "^2.0.1" + native-run "^2.0.3" open "^8.4.0" plist "^3.1.0" prompts "^2.4.2" @@ -1070,10 +1070,10 @@ tslib "^2.8.1" xml2js "^0.6.2" -"@capacitor/core@7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-7.4.4.tgz#b752dd7a12141e40d1c8ce2e37a79571d9f83518" - integrity sha512-xzjxpr+d2zwTpCaN0k+C6wKSZzWFAb9OVEUtmO72ihjr/NEDoLvsGl4WLfjWPcCO2zOy0b2X52tfRWjECFUjtw== +"@capacitor/core@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-7.4.5.tgz#334d77356fa473e95d51cf6375e9b36471faf2d6" + integrity sha512-JnUywi0WRbqm4QYoplNsMQ7KAVuyrQFha0dOH9YDVCyrVHUvPcvrnK+9j5elUMgy+pOh3ve+pk6BVoySr91c+A== dependencies: tslib "^2.1.0" @@ -6263,6 +6263,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chownr@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" @@ -8179,6 +8184,13 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-minipass@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" @@ -11271,12 +11283,17 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3. dependencies: yallist "^4.0.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minizlib@^2.0.0, minizlib@^2.1.2: +minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -11296,6 +11313,11 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + modify-filename@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1" @@ -11424,10 +11446,10 @@ napi-build-utils@^2.0.0: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== -native-run@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/native-run/-/native-run-2.0.1.tgz#a9b213c32824b007cbdd0279e0edd3c24bcc2f7a" - integrity sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w== +native-run@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/native-run/-/native-run-2.0.3.tgz#b1ceb39c8d24abf4916c07ae9aad5a9fbcd31a02" + integrity sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q== dependencies: "@ionic/utils-fs" "^3.1.7" "@ionic/utils-terminal" "^2.3.4" @@ -13958,7 +13980,19 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.1.2, tar@^6.2.1, tar@^7.4.3, tar@^7.5.3: +tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.1.2, tar@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^7.4.3: version "7.5.3" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.3.tgz#e1a41236e32446f75e63b720222112c4ffe5b3a1" integrity sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ== From 4e732d870065cdbead188b6ceb27b132be679c03 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 17:07:25 +0800 Subject: [PATCH 09/10] chore(package.json): v0.6.1 --- CHANGELOG.md | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e79fde..0652e384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ -# [0.6.0](https://github.com/plebbit/plebchan/compare/v0.5.3...v0.6.0) (2026-01-22) +## [0.6.1](https://github.com/plebbit/plebchan/compare/v0.6.0...v0.6.1) (2026-01-25) ### Bug Fixes * align asset-manifest generator output with prettier config ([4fd9a9c](https://github.com/plebbit/plebchan/commit/4fd9a9c1a98838dd9bbd05240905564fb6b6c85e)) * **android:** capacitor config was pointing to build instead of dist (legacy from CRA) ([436988e](https://github.com/plebbit/plebchan/commit/436988ee26f83c3c32c82a1a436006facec302de)) +* **android:** release build fails due to tar v7 incompatibility with Capacitor CLI ([eda8b5e](https://github.com/plebbit/plebchan/commit/eda8b5ed86d5376c3fac92ca90901d8c83d1edf2)) * **board buttons:** guard against undefined address ([2a521ad](https://github.com/plebbit/plebchan/commit/2a521ad8f82160b83f2d400c5a75f41bcb61f6d1)) * **board header:** limit clickable area of subscriptions counter to text only ([4f49ec6](https://github.com/plebbit/plebchan/commit/4f49ec666626b7711d9166fdb9933b83b2bec186)) * **boards-list:** hide Multiboards section when filtering for worksafe boards ([594be88](https://github.com/plebbit/plebchan/commit/594be882101cf849e172b90c0944b98a3629bad2)) @@ -17,6 +18,7 @@ * **ci:** macOS test builds fail with hdiutil "Resource busy" error ([46f0f49](https://github.com/plebbit/plebchan/commit/46f0f494e99a5fe113f0e27f46e97dcc0be6a2eb)) * **ci:** remove redundant electron-rebuild from build scripts ([96a3068](https://github.com/plebbit/plebchan/commit/96a306892868993f000af9c1f8928acbd928e3b3)) * **ci:** update deprecated intel mac runner ([2d23f44](https://github.com/plebbit/plebchan/commit/2d23f44bd80df5db75677b829c3484878d7f8d7c)) +* **ci:** update macos runner image ([5f85a0a](https://github.com/plebbit/plebchan/commit/5f85a0ac1548b18566425ec76569647fe8ae4805)) * **comment-content:** ensure parent comment number is populated for nested replies ([c2bbe2d](https://github.com/plebbit/plebchan/commit/c2bbe2dd0b95d7e4b1fb1e3b988df0039d5e1684)) * **comment-content:** use correct banExpiresAt path for ban visibility ([476681c](https://github.com/plebbit/plebchan/commit/476681c689edba6fe0dbc052c9908649188cca60)) * correct horizontal centering of create board modal ([e863ddb](https://github.com/plebbit/plebchan/commit/e863ddb8b901191e7b5bb4f9527b0af994895a42)) @@ -35,6 +37,7 @@ * **hooks:** fix usePopularPosts loading state and change detection ([a7614db](https://github.com/plebbit/plebchan/commit/a7614db743c3eff052b48a1401f427b38dbd03d2)) * **hooks:** remove postCid requirement from isAccountCommentAuthor check ([6175744](https://github.com/plebbit/plebchan/commit/6175744e7b4f9f56e562265751ac3bf5bcad6f2b)) * **hooks:** use structural equality for roles comparison in useStableSubplebbit ([647139c](https://github.com/plebbit/plebchan/commit/647139c0ec4a23f21fb4d7830285cc5123f9202a)) +* HTML zip archive is empty in releases ([2f0fae8](https://github.com/plebbit/plebchan/commit/2f0fae846a49d8d5e949bf58e33381e2f08ebf81)) * limit vite-plugin-eslint to src/ files only ([f65635b](https://github.com/plebbit/plebchan/commit/f65635b549a7c73f31b52c7f5656fc20b6f02a1c)) * **lint:** use correct oxlint rule names and enable react plugin in config ([9fd41f2](https://github.com/plebbit/plebchan/commit/9fd41f2f9f8099fd23d2341371d6168b204c97fe)) * **markdown:** embedded media in comment.content shouldn't be expandable and should be already expanded ([fcd8838](https://github.com/plebbit/plebchan/commit/fcd8838045a8ba66852b2f69904f939d3a8c6153)) @@ -50,6 +53,7 @@ * **mod-queue:** alert animation and counter only for pending items ([a23f5bc](https://github.com/plebbit/plebchan/commit/a23f5bc2388f3ceae6d9bdbe9b7f24247c094d4b)) * **mod-queue:** button counter includes already approved/rejected items ([02fc6a3](https://github.com/plebbit/plebchan/commit/02fc6a3171517e06012138ad9f3ac6aa620d69dc)) * **mod-queue:** close CSS breakpoint gap and remove duplicate time-ago rendering ([ab98bd0](https://github.com/plebbit/plebchan/commit/ab98bd0a87f92af5ba70b88a360fd1132e0668bb)) +* **mod-queue:** improve alert threshold input validation and add i18n for view selector ([d866372](https://github.com/plebbit/plebchan/commit/d86637200bc74f41e095023bfe58a5fb4e58ec35)) * **mod-queue:** style action buttons to match board buttons and make status indicators bold/colored ([fbdfadc](https://github.com/plebbit/plebchan/commit/fbdfadc65cd0ad24eff28552570ef91fa89fcddf)) * **mod-queue:** use link as excerpt when post has no title/content and prevent empty strings ([410fd54](https://github.com/plebbit/plebchan/commit/410fd541b33655d2668f85c25d52a1392703e3d5)) * **p2p-options:** correct Solana chainId from 1 to 101 ([52fea38](https://github.com/plebbit/plebchan/commit/52fea3851e0fe3bccadd8cae8b02e2204c45e397)) @@ -123,6 +127,7 @@ * implement post numbers ([7684541](https://github.com/plebbit/plebchan/commit/76845412e8b34c11fb70bf993a0aeaeda54a3ee4)) * **mod-queue:** add minutes support to alert threshold ([ab353b5](https://github.com/plebbit/plebchan/commit/ab353b59f62e94edd76e97e5c18cab1f3cfb2115)) * **mod-queue:** add number column, improve time display, and use edited comments ([a33b2e5](https://github.com/plebbit/plebchan/commit/a33b2e5a9aa04d3ae5daf418b51b13ce6d23d579)) +* **mod-queue:** add view mode selection and integrate with post components ([14e8c92](https://github.com/plebbit/plebchan/commit/14e8c92fa0fd2cc6eb9d5bad7646020207389373)) * **modals:** use icon-close-red.png for close buttons on home view ([21d9c0c](https://github.com/plebbit/plebchan/commit/21d9c0c6163dcd3947d26bcf3bdc5e5d0aae52dc)) * **moderation:** add purge action to edit menu ([1abc841](https://github.com/plebbit/plebchan/commit/1abc8410cd09e8d1b79df6809a9fea0241a3ae4c)) * **p2p options:** add http routers field ([3d67606](https://github.com/plebbit/plebchan/commit/3d676062af3ed54942a5f92b0958ce3501616c8b)) diff --git a/package.json b/package.json index b29fabc5..85c00ca2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "5chan", - "version": "0.6.0", + "version": "0.6.1", "description": "A bitsocial client with a 4chan UI", "type": "module", "author": "Bitsocial Labs", From 1818435b171dae2f8c73b442ba0e8f473a568ab8 Mon Sep 17 00:00:00 2001 From: plebeius Date: Sun, 25 Jan 2026 17:14:52 +0800 Subject: [PATCH 10/10] fix macos runner image --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50da9561..f10c08f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: allowUpdates: true mac: - runs-on: macOS-13 + runs-on: macos-15 permissions: contents: write steps: