diff --git a/.github/actions/build-extension/action.yml b/.github/actions/build-extension/action.yml index c5368033..6cd68c9e 100644 --- a/.github/actions/build-extension/action.yml +++ b/.github/actions/build-extension/action.yml @@ -69,7 +69,7 @@ runs: else BASE="betterseqtaplus-${VERSION}" fi - (cd dist/chrome && zip -r "../${BASE}-chrome.zip" .) - (cd dist/firefox && zip -r "../${BASE}-firefox.zip" .) + tar -a -cf "dist/${BASE}-chrome.zip" -C dist/chrome . + tar -a -cf "dist/${BASE}-firefox.zip" -C dist/firefox . echo "chrome_zip=dist/${BASE}-chrome.zip" >> "$GITHUB_OUTPUT" echo "firefox_zip=dist/${BASE}-firefox.zip" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5974de2c..31e7769c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,11 +1,18 @@ # Nightly release workflow — updates the same "nightly" release with fresh builds from main. # Runs only on BetterSEQTA/BetterSEQTA-Plus. Uses the default GITHUB_TOKEN. +# +# Scheduled at midnight Australia/Adelaide (ACST/ACDT). GitHub cron is UTC-only, so we +# trigger at 13:30 and 14:30 UTC and only proceed when Adelaide local time is 00:00. +# Builds on windows-latest (matches local dev; Linux nightly builds were failing on Vite/Svelte). name: Nightly Release on: schedule: - - cron: "0 3 * * *" + # 13:30 UTC = 00:00 Adelaide during daylight saving (ACDT, UTC+10:30) + - cron: "30 13 * * *" + # 14:30 UTC = 00:00 Adelaide during standard time (ACST, UTC+9:30) + - cron: "30 14 * * *" workflow_dispatch: permissions: @@ -16,16 +23,37 @@ env: jobs: nightly: - runs-on: ubuntu-latest + runs-on: windows-latest + defaults: + run: + shell: bash steps: + - name: Check Adelaide midnight + id: time_check + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "proceed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + TZ=Australia/Adelaide + if [ "$(date +%H%M)" = "0000" ]; then + echo "proceed=true" >> "$GITHUB_OUTPUT" + else + echo "proceed=false" >> "$GITHUB_OUTPUT" + echo "Skipping: not midnight Australia/Adelaide ($(date +%Y-%m-%d %H:%M %Z))" + fi + - uses: actions/checkout@v4 + if: steps.time_check.outputs.proceed == 'true' - name: Set build date id: build_date - run: echo "date=$(date -u +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + if: steps.time_check.outputs.proceed == 'true' + run: echo "date=$(TZ=Australia/Adelaide date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" - name: Build extension id: build + if: steps.time_check.outputs.proceed == 'true' uses: ./.github/actions/build-extension with: gh_release_update_check: "true" @@ -34,6 +62,7 @@ jobs: release_repo: ${{ github.repository }} - name: Ensure nightly release exists + if: steps.time_check.outputs.proceed == 'true' run: | TITLE="Nightly (${{ steps.build_date.outputs.date }})" if ! gh release view "${{ env.NIGHTLY_TAG }}" 2>/dev/null; then @@ -46,6 +75,7 @@ jobs: fi - name: Upload nightly assets + if: steps.time_check.outputs.proceed == 'true' run: | gh release upload "${{ env.NIGHTLY_TAG }}" \ --clobber \ diff --git a/src/plugins/built-in/notificationCollector/index.ts b/src/plugins/built-in/notificationCollector/index.ts index 8af0127c..67d49910 100644 --- a/src/plugins/built-in/notificationCollector/index.ts +++ b/src/plugins/built-in/notificationCollector/index.ts @@ -12,6 +12,7 @@ import { injectArchivedForUser, mountArchivedNotificationInjection, } from "./injectArchivedNotifications"; +import styles from "./styles.css?inline"; const notificationCollectorSettings = { saveLocally: booleanSetting({ @@ -46,6 +47,10 @@ const notificationCollectorPlugin: Plugin< return () => {}; } + const styleEl = document.createElement("style"); + styleEl.textContent = styles; + document.head.appendChild(styleEl); + await api.storage.loaded; await api.settings.loaded; @@ -208,6 +213,7 @@ const notificationCollectorPlugin: Plugin< teardownInjection(); document.removeEventListener("visibilitychange", handleVisibilityChange); pageChangeUnregister.unregister(); + styleEl.remove(); }; }, }; diff --git a/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts b/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts index c83b4507..57785e15 100644 --- a/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts +++ b/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts @@ -10,6 +10,12 @@ import { const LIST_SELECTOR = '[class*="notifications__list___"]'; const ITEMS_SELECTOR = '[class*="notifications__items___"]'; +const ITEM_SELECTOR = '[class*="notifications__item___"]'; +const BACKED_UP_CLASS = "bsplus-notification-backed-up"; +const BACKUP_BADGE_CLASS = "bsplus-notification-backup-badge"; + +const BACKUP_CHECK_SVG = + ''; function notificationTimestamp(item: Record): number { const ms = new Date(String(item.timestamp ?? 0)).getTime(); @@ -61,9 +67,33 @@ async function tryInjectArchived(archive: ArchiveMap): Promise { async function injectWithRetries(archive: ArchiveMap, attempts = 10) { for (let i = 0; i < attempts; i++) { const done = await tryInjectArchived(archive); - if (done) return; + if (done) break; await delay(120); } + applyBackupBadges(archive); +} + +export function applyBackupBadges(archive: ArchiveMap) { + const backedUpIds = new Set(Object.keys(archive)); + + for (const itemEl of document.querySelectorAll(ITEM_SELECTOR)) { + const id = itemEl.getAttribute("data-id"); + if (!id) continue; + + if (backedUpIds.has(id)) { + itemEl.classList.add(BACKED_UP_CLASS); + if (!itemEl.querySelector(`.${BACKUP_BADGE_CLASS}`)) { + const badge = document.createElement("span"); + badge.className = BACKUP_BADGE_CLASS; + badge.title = "Saved locally"; + badge.innerHTML = BACKUP_CHECK_SVG; + itemEl.appendChild(badge); + } + } else { + itemEl.classList.remove(BACKED_UP_CLASS); + itemEl.querySelector(`.${BACKUP_BADGE_CLASS}`)?.remove(); + } + } } export function mountArchivedNotificationInjection( diff --git a/src/plugins/built-in/notificationCollector/styles.css b/src/plugins/built-in/notificationCollector/styles.css new file mode 100644 index 00000000..109605b3 --- /dev/null +++ b/src/plugins/built-in/notificationCollector/styles.css @@ -0,0 +1,25 @@ +[class*="notifications__item___"].bsplus-notification-backed-up { + position: relative; +} + +.bsplus-notification-backup-badge { + position: absolute; + top: 6px; + right: 6px; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--better-main, #22c55e); + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 2; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} + +.bsplus-notification-backup-badge svg { + width: 9px; + height: 9px; + color: #fff; +}