mirror of
https://github.com/armbian/imager.git
synced 2026-01-06 12:31:28 -08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4645cdece2 | ||
|
|
9cf8ca299b | ||
|
|
0431b243bb | ||
|
|
4a3aa68051 | ||
|
|
d63d047a0f | ||
|
|
4b9f5289f7 | ||
|
|
840509ec0c |
63
.github/workflows/build-artifacts.yml
vendored
63
.github/workflows/build-artifacts.yml
vendored
@@ -184,10 +184,38 @@ jobs:
|
||||
os: macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: "-"
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Import Apple Developer Certificate
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
run: |
|
||||
# Create a temporary keychain
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# Import certificate
|
||||
echo -n "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
|
||||
security import certificate.p12 -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# Verify certificate
|
||||
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||
|
||||
# Set as default keychain
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -241,6 +269,39 @@ jobs:
|
||||
ditto -c -k --sequesterRsrc --keepParent "$app" "$CARGO_TARGET_DIR/release/bundle/macos/${base}-${{ matrix.arch }}.app.zip"
|
||||
done
|
||||
|
||||
- name: Notarize and staple DMG
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_TARGET_DIR: src-tauri/target/${{ matrix.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Find the DMG file
|
||||
DMG_FILE=$(find "$CARGO_TARGET_DIR/release/bundle/dmg/" -name "*.dmg" -type f | head -n 1)
|
||||
|
||||
if [[ -z "$DMG_FILE" ]]; then
|
||||
echo "No DMG file found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Notarizing: $DMG_FILE"
|
||||
|
||||
# Submit DMG for notarization
|
||||
xcrun notarytool submit "$DMG_FILE" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_ID_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait \
|
||||
--output-format json
|
||||
|
||||
# Staple the notarization ticket to DMG
|
||||
xcrun stapler staple "$DMG_FILE"
|
||||
|
||||
# Verify stapling
|
||||
xcrun stapler validate -v "$DMG_FILE"
|
||||
|
||||
echo "Notarization completed successfully"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
245
.github/workflows/build.yml
vendored
245
.github/workflows/build.yml
vendored
@@ -6,12 +6,8 @@ on:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_linux_x64:
|
||||
description: 'Build Linux x64'
|
||||
type: boolean
|
||||
default: true
|
||||
build_linux_arm64:
|
||||
description: 'Build Linux ARM64'
|
||||
build_linux:
|
||||
description: 'Build Linux (x64 + ARM64)'
|
||||
type: boolean
|
||||
default: true
|
||||
build_macos:
|
||||
@@ -34,7 +30,7 @@ concurrency:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
NODE_VERSION: '20'
|
||||
TAURI_CLI_VERSION: '^2'
|
||||
TAURI_CLI_VERSION: '2.9.6'
|
||||
# Tauri updater signing key (set in GitHub Secrets)
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
@@ -93,11 +89,35 @@ jobs:
|
||||
omitNameDuringUpdate: true
|
||||
replacesArtifacts: false
|
||||
|
||||
build-linux-x64:
|
||||
name: build-linux (x86_64-unknown-linux-gnu)
|
||||
needs: [create-release]
|
||||
if: ${{ github.event_name == 'push' || inputs.build_linux_x64 }}
|
||||
runs-on: ubuntu-24.04
|
||||
build-linux:
|
||||
name: build-linux ${{ matrix.type.name }} (${{ matrix.arch.name }}/${{ matrix.type.distro_id }})
|
||||
needs: [ create-release ]
|
||||
if: ${{ github.event_name == 'push' || inputs.build_linux }} # can't use matrix here; if is evaluated before matrix expansion
|
||||
strategy:
|
||||
fail-fast: false # let other jobs try to complete if one fails
|
||||
matrix:
|
||||
arch:
|
||||
- { name: 'amd64', runner: 'ubuntu-24.04' }
|
||||
- { name: 'arm64', runner: "ubuntu-24.04-arm" }
|
||||
type:
|
||||
# deb: build in the oldest still-supported matching container: oldstable (bookworm)
|
||||
- name: 'deb'
|
||||
distro_id: "bookworm"
|
||||
container: { image: 'debian:bookworm' }
|
||||
bundles: 'deb'
|
||||
deps: "apt"
|
||||
artifacts: "src-tauri/target/release/bundle/deb/*.deb"
|
||||
# appimage: doesn't use a container (instead, runs directly on the runner); requires sudo to install deps
|
||||
- name: 'appimage'
|
||||
distro_id: "gharunner"
|
||||
bundles: 'appimage'
|
||||
deps: "apt"
|
||||
deps_gain_root: "sudo"
|
||||
artifacts: |
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||||
runs-on: ${{ matrix.arch.runner }}
|
||||
container: ${{ matrix.type.container }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
@@ -125,10 +145,13 @@ jobs:
|
||||
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
|
||||
src-tauri/tauri.conf.json
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Install dependencies (apt)
|
||||
if: ${{ matrix.type.deps == 'apt' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
${{ matrix.type.deps_gain_root || '' }} apt-get update
|
||||
${{ matrix.type.deps_gain_root || '' }} apt-get install -y \
|
||||
build-essential \
|
||||
curl \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
@@ -139,10 +162,17 @@ jobs:
|
||||
xdg-utils
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
package-manager-cache: false # setup-node can't add key to cache, so we do it ourselves in below step
|
||||
|
||||
# Cache npm, just like setup-node would do it with "cache: npm", but with our own key that includes arch and distro
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ matrix.arch.runner }}-${{ matrix.arch.name }}-${{ matrix.type.distro_id }}-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -151,12 +181,16 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
# ensure cache separation per host distro; this also ends up caching tauri-cli, so add that to key too.
|
||||
key: "rust-cache-${{ matrix.arch.runner }}-${{ matrix.arch.name }}-${{ matrix.type.distro_id }}-${{env.TAURI_CLI_VERSION}}"
|
||||
# @TODO: Cargo.lock is in gitignore, so it's not included here - it would via rust-cache Action magic, no need to specify it.
|
||||
|
||||
- name: Cache cargo bin (tauri-cli)
|
||||
- name: Cache cargo bin (tauri-cli) # @TODO: Swatinem/rust-cache already caches this. maybe just drop this.
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo/bin
|
||||
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
|
||||
key: "cargo-bin-${{ matrix.arch.runner }}-${{ matrix.arch.name }}-${{ matrix.type.distro_id }}-stable-${{env.TAURI_CLI_VERSION}}-${{ hashFiles('**/Cargo.lock') }}"
|
||||
# @TODO: Cargo.lock is in gitignore, so it's not included here, albeit being specified.
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
@@ -172,7 +206,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build Tauri app
|
||||
run: cargo tauri build --bundles deb,appimage
|
||||
run: cargo tauri build --bundles "${{ matrix.type.bundles }}"
|
||||
|
||||
- name: Upload artifacts to GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
@@ -186,106 +220,7 @@ jobs:
|
||||
omitNameDuringUpdate: true
|
||||
replacesArtifacts: false
|
||||
artifacts: |
|
||||
src-tauri/target/release/bundle/deb/*.deb
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||||
|
||||
build-linux-arm64:
|
||||
name: build-linux (aarch64-unknown-linux-gnu)
|
||||
needs: [create-release]
|
||||
if: ${{ github.event_name == 'push' || inputs.build_linux_arm64 }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set version from release tag
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TAG='${{ needs.create-release.outputs.release_tag }}'
|
||||
VERSION="${TAG#v}"
|
||||
|
||||
echo "Setting version to $VERSION"
|
||||
|
||||
sed -i \
|
||||
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
|
||||
package.json
|
||||
|
||||
sed -i \
|
||||
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
|
||||
src-tauri/Cargo.toml
|
||||
|
||||
sed -i \
|
||||
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
|
||||
src-tauri/tauri.conf.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
patchelf \
|
||||
libssl-dev \
|
||||
libgtk-3-dev \
|
||||
squashfs-tools \
|
||||
xdg-utils
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri
|
||||
|
||||
- name: Cache cargo bin (tauri-cli)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo/bin
|
||||
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
run: npm run build
|
||||
|
||||
- name: Install Tauri CLI (if missing)
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v cargo-tauri >/dev/null 2>&1; then
|
||||
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
|
||||
fi
|
||||
|
||||
- name: Build Tauri app
|
||||
run: cargo tauri build --bundles deb,appimage
|
||||
|
||||
- name: Upload artifacts to GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: ${{ needs.create-release.outputs.release_tag }}
|
||||
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
|
||||
draft: true
|
||||
prerelease: false
|
||||
allowUpdates: true
|
||||
omitBodyDuringUpdate: true
|
||||
omitNameDuringUpdate: true
|
||||
replacesArtifacts: false
|
||||
artifacts: |
|
||||
src-tauri/target/release/bundle/deb/*.deb
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage
|
||||
src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||||
${{ matrix.type.artifacts }}
|
||||
|
||||
build-macos:
|
||||
name: build-macos (${{ matrix.target }})
|
||||
@@ -306,12 +241,39 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
# Ad-hoc signing: allows app to run after "xattr -cr" on macOS
|
||||
APPLE_SIGNING_IDENTITY: "-"
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Import Apple Developer Certificate
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
run: |
|
||||
# Create a temporary keychain
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# Import certificate
|
||||
echo -n "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
|
||||
security import certificate.p12 -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# Verify certificate
|
||||
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
||||
|
||||
# Set as default keychain
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
|
||||
- name: Set version from release tag
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -397,6 +359,39 @@ jobs:
|
||||
mv "$sig" "$CARGO_TARGET_DIR/release/bundle/macos/${base}-${{ matrix.arch }}.tar.gz.sig"
|
||||
done
|
||||
|
||||
- name: Notarize and staple DMG
|
||||
shell: bash
|
||||
env:
|
||||
CARGO_TARGET_DIR: src-tauri/target/${{ matrix.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Find the DMG file
|
||||
DMG_FILE=$(find "$CARGO_TARGET_DIR/release/bundle/dmg/" -name "*.dmg" -type f | head -n 1)
|
||||
|
||||
if [[ -z "$DMG_FILE" ]]; then
|
||||
echo "No DMG file found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Notarizing: $DMG_FILE"
|
||||
|
||||
# Submit DMG for notarization
|
||||
xcrun notarytool submit "$DMG_FILE" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_ID_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait \
|
||||
--output-format json
|
||||
|
||||
# Staple the notarization ticket to DMG
|
||||
xcrun stapler staple "$DMG_FILE"
|
||||
|
||||
# Verify stapling
|
||||
xcrun stapler validate -v "$DMG_FILE"
|
||||
|
||||
echo "Notarization completed successfully"
|
||||
|
||||
- name: Upload macOS artifacts to GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
@@ -593,8 +588,7 @@ jobs:
|
||||
name: Generate latest.json for updater
|
||||
needs:
|
||||
- create-release
|
||||
- build-linux-x64
|
||||
- build-linux-arm64
|
||||
- build-linux
|
||||
- build-macos
|
||||
- build-windows-x64
|
||||
- build-windows-arm64
|
||||
@@ -710,8 +704,7 @@ jobs:
|
||||
name: Publish release (draft -> false) + cleanup
|
||||
needs:
|
||||
- create-release
|
||||
- build-linux-x64
|
||||
- build-linux-arm64
|
||||
- build-linux
|
||||
- build-macos
|
||||
- build-windows-x64
|
||||
- build-windows-arm64
|
||||
|
||||
@@ -40,10 +40,6 @@ Prebuilt binaries are available for all supported platforms.
|
||||
| Intel & Apple Silicon | x64 & ARM64 | x64 & ARM64 |
|
||||
| <code>.dmg</code> / <code>.app.zip</code> | <code>.exe</code> / <code>.msi</code> | <code>.deb</code> / <code>.AppImage</code> |
|
||||
|
||||
**macOS: First Launch**
|
||||
|
||||
On first launch, macOS may block the application because it is not signed. If this happens, open **System Settings → Privacy & Security** and click **Open Anyway** next to *Armbian Imager was blocked*. This only needs to be done once.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Select Manufacturer** — Choose from 70+ supported SBC manufacturers or load a custom image
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"longDescription": "Armbian Imager is a utility for flashing Armbian OS images to SD cards and USB drives for single board computers.",
|
||||
"macOS": {
|
||||
"entitlements": "./entitlements.plist",
|
||||
"signingIdentity": null,
|
||||
"minimumSystemVersion": "10.15",
|
||||
"dmg": {
|
||||
"background": "./dmg-background.png",
|
||||
|
||||
@@ -120,17 +120,21 @@ export function ChangelogModal({ isOpen, onClose, version }: ChangelogModalProps
|
||||
|
||||
const itemContent = line.replace(/^\s*[-*]\s+/, '');
|
||||
const formattedItem = itemContent
|
||||
// Remove PR/issue URLs and replace with #number
|
||||
.replace(/ https:\/\/github\.com\/[^/]+\/[^/]+\/(?:pull|issues)\/(\d+)/g, ' #PR$1 ')
|
||||
.replace(/\[[^\]]+\]\(https:\/\/github\.com\/[^/]+\/[^/]+\/(?:pull|issues)\/(\d+)\)/g, '#PR$1')
|
||||
// NOW do HTML escape (won't break the text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// Replace placeholders with actual HTML
|
||||
.replace(/#PR(\d+)/g, '<span class="pr-number">#$1</span>')
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
||||
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
// Remove markdown GitHub links (PRs, issues, commits)
|
||||
// Remove remaining markdown GitHub links (commits, etc)
|
||||
.replace(/\[([^\]]+)\]\(https:\/\/github\.com\/[^)]+\)/g, '$1')
|
||||
// Remove "in https://github.com/..." but keep "by @username"
|
||||
.replace(/(by @[a-zA-Z0-9_-]+) in https:\/\/github\.com\/[^\s]+/g, '$1')
|
||||
// Remove any remaining bare GitHub URLs
|
||||
// Remove any remaining bare GitHub URLs (commits, etc)
|
||||
.replace(/https:\/\/github\.com\/[^\s]+/g, '')
|
||||
.replace(/,\s*$/g, '') // Remove trailing comma after URL removal
|
||||
.replace(/\s+/g, ' ') // Clean up extra whitespace
|
||||
@@ -164,17 +168,21 @@ export function ChangelogModal({ isOpen, onClose, version }: ChangelogModalProps
|
||||
|
||||
// Regular paragraph line
|
||||
const formattedLine = line
|
||||
// Remove PR/issue URLs and replace with #number
|
||||
.replace(/ https:\/\/github\.com\/[^/]+\/[^/]+\/(?:pull|issues)\/(\d+)/g, ' #PR$1 ')
|
||||
.replace(/\[[^\]]+\]\(https:\/\/github\.com\/[^/]+\/[^/]+\/(?:pull|issues)\/(\d+)\)/g, '#PR$1')
|
||||
// NOW do HTML escape (won't break the text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
// Replace placeholders with actual HTML
|
||||
.replace(/#PR(\d+)/g, '<span class="pr-number">#$1</span>')
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
|
||||
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
// Remove markdown GitHub links (PRs, issues, commits)
|
||||
// Remove remaining markdown GitHub links (commits, etc)
|
||||
.replace(/\[([^\]]+)\]\(https:\/\/github\.com\/[^)]+\)/g, '$1')
|
||||
// Remove "in https://github.com/..." but keep "by @username"
|
||||
.replace(/(by @[a-zA-Z0-9_-]+) in https:\/\/github\.com\/[^\s]+/g, '$1')
|
||||
// Remove any remaining bare GitHub URLs
|
||||
// Remove any remaining bare GitHub URLs (commits, etc)
|
||||
.replace(/https:\/\/github\.com\/[^\s]+/g, '')
|
||||
.replace(/,\s*$/g, '') // Remove trailing comma after URL removal
|
||||
.replace(/\s+/g, ' ') // Clean up extra whitespace
|
||||
@@ -194,6 +202,11 @@ export function ChangelogModal({ isOpen, onClose, version }: ChangelogModalProps
|
||||
// Remove trailing <br /> tags to avoid extra space at the end
|
||||
html = html.replace(/(<br\s*\/?>)+$/g, '');
|
||||
|
||||
// Remove multiple <br /> tags and <br /> before headers to avoid extra spacing
|
||||
html = html.replace(/(<br\s*\/?>){2,}/g, '<br />');
|
||||
html = html.replace(/<br\s*\/?>\s*<h2>/g, '<h2>');
|
||||
html = html.replace(/<br\s*\/?>\s*<h3>/g, '<h3>');
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
@@ -1260,3 +1260,8 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Changelog Modal */
|
||||
.changelog-body .pr-number {
|
||||
color: var(--armbian-orange);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user