feat: add initial implementation of eSodisce / GURS batch helper extension
Publish Firefox extension / publish (push) Successful in 4m21s
Publish Firefox extension / publish (push) Successful in 4m21s
This commit is contained in:
1 parent
902cf5d813
commit
f1a16ddec0
10 files changed
+769
-1
No files matched your search
@@ -0,0 +1,454 @@
|
|||||||
|
name: Publish Firefox extension
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
# The workflow's own update-manifest-only commit must not start another run.
|
||||||
|
paths-ignore:
|
||||||
|
- updates.json
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: publish-main
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
API_BASE: https://git.vahta.net/api/v1
|
||||||
|
REPOSITORY: komelt/zemljiska-knjiga-vticnik
|
||||||
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
|
TARGET_COMMIT: ${{ github.sha }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Validate and stage extension
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
command -v jq >/dev/null
|
||||||
|
command -v node >/dev/null
|
||||||
|
command -v curl >/dev/null
|
||||||
|
command -v npx >/dev/null
|
||||||
|
command -v sha256sum >/dev/null
|
||||||
|
|
||||||
|
jq -e . manifest.json >/dev/null
|
||||||
|
SOURCE_VERSION="$(jq -er '.version | strings' manifest.json)"
|
||||||
|
if [[ ! "$SOURCE_VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
|
||||||
|
echo "manifest.json version must contain one to three numeric components" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! "$RUN_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "The Actions run number is missing or is not a positive integer" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${SOURCE_VERSION}.${RUN_NUMBER}"
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
||||||
|
echo "Computed Firefox version is invalid: $VERSION" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
STAGING_DIR="$RUNNER_TEMP/zemljiska-knjiga-vticnik-$VERSION"
|
||||||
|
SIGNED_XPI="$RUNNER_TEMP/zemljiska-knjiga-vticnik-$VERSION.xpi"
|
||||||
|
rm -rf "$STAGING_DIR"
|
||||||
|
mkdir -p "$STAGING_DIR"
|
||||||
|
RUNTIME_FILES=(manifest.json background.js content-script.js popup.html popup.css popup.js)
|
||||||
|
cp -- "${RUNTIME_FILES[@]}" "$STAGING_DIR/"
|
||||||
|
|
||||||
|
jq --arg version "$VERSION" '.version = $version' \
|
||||||
|
"$STAGING_DIR/manifest.json" > "$STAGING_DIR/manifest.json.tmp"
|
||||||
|
mv -- "$STAGING_DIR/manifest.json.tmp" "$STAGING_DIR/manifest.json"
|
||||||
|
|
||||||
|
jq -e . "$STAGING_DIR/manifest.json" >/dev/null
|
||||||
|
for javascript_file in background.js content-script.js popup.js; do
|
||||||
|
node --check "$STAGING_DIR/$javascript_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf 'VERSION=%s\nSTAGING_DIR=%s\nSIGNED_XPI=%s\n' \
|
||||||
|
"$VERSION" "$STAGING_DIR" "$SIGNED_XPI" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Reuse an existing Gitea release asset when available
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
ASSET_NAME="zemljiska-knjiga-vticnik-$VERSION.xpi"
|
||||||
|
RELEASE_RESPONSE="$RUNNER_TEMP/gitea-release-$VERSION.json"
|
||||||
|
RELEASE_URL="$API_BASE/repos/$REPOSITORY/releases/tags/v$VERSION"
|
||||||
|
|
||||||
|
if ! RELEASE_STATUS="$(curl --silent --show-error \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--write-out '%{http_code}' --output "$RELEASE_RESPONSE" \
|
||||||
|
"$RELEASE_URL")"; then
|
||||||
|
echo 'Could not query the Gitea release.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$RELEASE_STATUS" in
|
||||||
|
404)
|
||||||
|
printf 'RELEASE_ID=\nRELEASE_ASSET_EXISTS=false\n' >> "$GITHUB_ENV"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
2??)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Gitea release lookup returned HTTP $RELEASE_STATUS" >&2
|
||||||
|
cat "$RELEASE_RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
RELEASE_ID="$(jq -er '.id | numbers' "$RELEASE_RESPONSE")"
|
||||||
|
matching_assets="$(jq -c --arg name "$ASSET_NAME" \
|
||||||
|
'[.assets[]? | select(.name == $name)]' "$RELEASE_RESPONSE")"
|
||||||
|
asset_count="$(jq -er 'length' <<< "$matching_assets")"
|
||||||
|
if [[ "$asset_count" -gt 1 ]]; then
|
||||||
|
echo "Gitea release contains multiple assets named $ASSET_NAME" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$asset_count" -eq 0 ]]; then
|
||||||
|
printf 'RELEASE_ID=%s\nRELEASE_ASSET_EXISTS=false\n' "$RELEASE_ID" >> "$GITHUB_ENV"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset_size="$(jq -r '.[0].size // empty' <<< "$matching_assets")"
|
||||||
|
asset_url="$(jq -r '.[0].browser_download_url // empty' <<< "$matching_assets")"
|
||||||
|
if [[ ! "$asset_url" =~ ^https://[^[:space:]]+$ ]]; then
|
||||||
|
echo 'The existing release asset has a missing or invalid HTTPS browser download URL.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f -- "$SIGNED_XPI"
|
||||||
|
curl --fail --silent --show-error --location \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--output "$SIGNED_XPI" \
|
||||||
|
"$asset_url"
|
||||||
|
[[ -s "$SIGNED_XPI" ]] || {
|
||||||
|
echo 'The existing release asset downloaded as an empty file.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if [[ -n "$asset_size" && "$(wc -c < "$SIGNED_XPI")" != "$asset_size" ]]; then
|
||||||
|
echo 'The downloaded release asset size does not match Gitea metadata.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
XPI_SHA256="$(sha256sum "$SIGNED_XPI" | awk '{print $1}')"
|
||||||
|
[[ "$XPI_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
||||||
|
echo 'Could not calculate a valid SHA-256 digest for the existing release asset.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
expected_hash="$(jq -r --arg version "$VERSION" \
|
||||||
|
'[.addons[]?.updates[]? | select(.version == $version) | .update_hash] | unique | if length == 1 then .[0] else "" end' \
|
||||||
|
updates.json 2>/dev/null || true)"
|
||||||
|
if [[ -n "$expected_hash" && "$expected_hash" != "sha256:$XPI_SHA256" ]]; then
|
||||||
|
echo 'The existing release asset does not match the update manifest hash.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'RELEASE_ID=%s\nRELEASE_ASSET_EXISTS=true\nXPI_SHA256=%s\n' \
|
||||||
|
"$RELEASE_ID" "$XPI_SHA256" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Sign unlisted build through AMO
|
||||||
|
if: env.RELEASE_ASSET_EXISTS != 'true'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
|
||||||
|
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
ARTIFACTS_DIR="$RUNNER_TEMP/web-ext-artifacts-$VERSION"
|
||||||
|
rm -rf "$ARTIFACTS_DIR" "$SIGNED_XPI"
|
||||||
|
mkdir -p "$ARTIFACTS_DIR"
|
||||||
|
|
||||||
|
npx --yes web-ext@10.7.0 sign \
|
||||||
|
--channel=unlisted \
|
||||||
|
--source-dir "$STAGING_DIR" \
|
||||||
|
--artifacts-dir "$ARTIFACTS_DIR" \
|
||||||
|
--api-key "$AMO_JWT_ISSUER" \
|
||||||
|
--api-secret "$AMO_JWT_SECRET"
|
||||||
|
|
||||||
|
mapfile -t signed_files < <(find "$ARTIFACTS_DIR" -maxdepth 1 -type f -name '*.xpi' -print)
|
||||||
|
if [[ "${#signed_files[@]}" -ne 1 ]]; then
|
||||||
|
echo "Expected exactly one signed XPI, found ${#signed_files[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp -- "${signed_files[0]}" "$SIGNED_XPI"
|
||||||
|
XPI_SHA256="$(sha256sum "$SIGNED_XPI" | awk '{print $1}')"
|
||||||
|
[[ "$XPI_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
||||||
|
echo 'Could not calculate a valid SHA-256 digest for the signed XPI.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf 'XPI_SHA256=%s\n' "$XPI_SHA256" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create public Gitea release and upload XPI
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
ASSET_NAME="zemljiska-knjiga-vticnik-$VERSION.xpi"
|
||||||
|
RELEASE_RESPONSE="$RUNNER_TEMP/gitea-release-$VERSION.json"
|
||||||
|
|
||||||
|
download_asset() {
|
||||||
|
local asset_url="$1"
|
||||||
|
local asset_size="$2"
|
||||||
|
if [[ ! "$asset_url" =~ ^https://[^[:space:]]+$ ]]; then
|
||||||
|
echo 'The existing release asset has a missing or invalid HTTPS browser download URL.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -f -- "$SIGNED_XPI"
|
||||||
|
curl --fail --silent --show-error --location \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--output "$SIGNED_XPI" \
|
||||||
|
"$asset_url"
|
||||||
|
[[ -s "$SIGNED_XPI" ]] || {
|
||||||
|
echo 'The existing release asset downloaded as an empty file.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if [[ -n "$asset_size" && "$(wc -c < "$SIGNED_XPI")" != "$asset_size" ]]; then
|
||||||
|
echo 'The downloaded release asset size does not match Gitea metadata.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
XPI_SHA256="$(sha256sum "$SIGNED_XPI" | awk '{print $1}')"
|
||||||
|
[[ "$XPI_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
||||||
|
echo 'Could not calculate a valid SHA-256 digest for the existing release asset.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
local expected_hash
|
||||||
|
expected_hash="$(jq -r --arg version "$VERSION" \
|
||||||
|
'[.addons[]?.updates[]? | select(.version == $version) | .update_hash] | unique | if length == 1 then .[0] else "" end' \
|
||||||
|
updates.json 2>/dev/null || true)"
|
||||||
|
if [[ -n "$expected_hash" && "$expected_hash" != "sha256:$XPI_SHA256" ]]; then
|
||||||
|
echo 'The existing release asset does not match the update manifest hash.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
RELEASE_ASSET_EXISTS=true
|
||||||
|
}
|
||||||
|
|
||||||
|
inspect_assets() {
|
||||||
|
local asset_count asset_url asset_size
|
||||||
|
asset_count="$(jq -er --arg name "$ASSET_NAME" \
|
||||||
|
'[.assets[]? | select(.name == $name)] | length' "$RELEASE_RESPONSE")"
|
||||||
|
if [[ "$asset_count" -gt 1 ]]; then
|
||||||
|
echo "Gitea release contains multiple assets named $ASSET_NAME" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$asset_count" -eq 1 ]]; then
|
||||||
|
asset_size="$(jq -r --arg name "$ASSET_NAME" \
|
||||||
|
'.assets[] | select(.name == $name) | .size // empty' "$RELEASE_RESPONSE")"
|
||||||
|
asset_url="$(jq -r --arg name "$ASSET_NAME" \
|
||||||
|
'.assets[] | select(.name == $name) | .browser_download_url // empty' "$RELEASE_RESPONSE")"
|
||||||
|
download_asset "$asset_url" "$asset_size"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch_release_by_tag() {
|
||||||
|
local status
|
||||||
|
if ! status="$(curl --silent --show-error \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--write-out '%{http_code}' --output "$RELEASE_RESPONSE" \
|
||||||
|
"$API_BASE/repos/$REPOSITORY/releases/tags/v$VERSION")"; then
|
||||||
|
echo 'Could not query the Gitea release after a concurrent change.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[[ "$status" == 2?? ]] || {
|
||||||
|
echo "Gitea release lookup returned HTTP $status" >&2
|
||||||
|
cat "$RELEASE_RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
RELEASE_ID="$(jq -er '.id | numbers' "$RELEASE_RESPONSE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "${RELEASE_ID:-}" ]]; then
|
||||||
|
RELEASE_PAYLOAD="$(jq -n \
|
||||||
|
--arg tag "v$VERSION" \
|
||||||
|
--arg name "zemljiska-knjiga-vticnik $VERSION" \
|
||||||
|
--arg target "$TARGET_COMMIT" \
|
||||||
|
'{tag_name: $tag, name: $name, target_commitish: $target, draft: false, prerelease: false}')"
|
||||||
|
if ! CREATE_STATUS="$(curl --silent --show-error \
|
||||||
|
--request POST \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data "$RELEASE_PAYLOAD" \
|
||||||
|
--write-out '%{http_code}' --output "$RELEASE_RESPONSE" \
|
||||||
|
"$API_BASE/repos/$REPOSITORY/releases")"; then
|
||||||
|
echo 'Could not create the Gitea release.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$CREATE_STATUS" in
|
||||||
|
2??)
|
||||||
|
RELEASE_ID="$(jq -er '.id | numbers' "$RELEASE_RESPONSE")"
|
||||||
|
;;
|
||||||
|
409|422)
|
||||||
|
fetch_release_by_tag
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Gitea release creation returned HTTP $CREATE_STATUS" >&2
|
||||||
|
cat "$RELEASE_RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${RELEASE_ASSET_EXISTS:-false}" != 'true' ]]; then
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--output "$RELEASE_RESPONSE" \
|
||||||
|
"$API_BASE/repos/$REPOSITORY/releases/$RELEASE_ID"
|
||||||
|
inspect_assets
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${RELEASE_ASSET_EXISTS:-false}" != 'true' ]]; then
|
||||||
|
UPLOAD_RESPONSE="$RUNNER_TEMP/gitea-upload-$VERSION.json"
|
||||||
|
if ! UPLOAD_STATUS="$(curl --silent --show-error \
|
||||||
|
--request POST \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--header 'Content-Type: application/x-xpinstall' \
|
||||||
|
--data-binary "@$SIGNED_XPI" \
|
||||||
|
--write-out '%{http_code}' --output "$UPLOAD_RESPONSE" \
|
||||||
|
"$API_BASE/repos/$REPOSITORY/releases/$RELEASE_ID/assets?name=$ASSET_NAME")"; then
|
||||||
|
echo 'Could not upload the signed XPI to the Gitea release.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$UPLOAD_STATUS" in
|
||||||
|
2??)
|
||||||
|
;;
|
||||||
|
409|422)
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--header "Authorization: token $GITEA_TOKEN" \
|
||||||
|
--output "$RELEASE_RESPONSE" \
|
||||||
|
"$API_BASE/repos/$REPOSITORY/releases/$RELEASE_ID"
|
||||||
|
inspect_assets
|
||||||
|
[[ "${RELEASE_ASSET_EXISTS:-false}" == 'true' ]] || {
|
||||||
|
echo 'Gitea rejected the upload but the expected asset is still absent.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Gitea asset upload returned HTTP $UPLOAD_STATUS" >&2
|
||||||
|
cat "$UPLOAD_RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$XPI_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
||||||
|
echo 'Could not calculate a valid SHA-256 digest for the signed XPI.' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
UPDATE_LINK="https://git.vahta.net/komelt/zemljiska-knjiga-vticnik/releases/download/v$VERSION/$ASSET_NAME"
|
||||||
|
UPDATE_MANIFEST="$RUNNER_TEMP/zemljiska-knjiga-vticnik-$VERSION-updates.json"
|
||||||
|
jq -n \
|
||||||
|
--arg id 'eprostor-gurs-batch@example.invalid' \
|
||||||
|
--arg version "$VERSION" \
|
||||||
|
--arg update_link "$UPDATE_LINK" \
|
||||||
|
--arg update_hash "sha256:$XPI_SHA256" \
|
||||||
|
'{addons: {($id): {updates: [{version: $version, update_link: $update_link, update_hash: $update_hash}]}}}' \
|
||||||
|
> "$UPDATE_MANIFEST"
|
||||||
|
jq -e . "$UPDATE_MANIFEST" >/dev/null
|
||||||
|
printf 'XPI_SHA256=%s\nUPDATE_MANIFEST=%s\n' "$XPI_SHA256" "$UPDATE_MANIFEST" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Commit update manifest
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
export LC_ALL=C
|
||||||
|
|
||||||
|
normalize_component() {
|
||||||
|
local component="$1"
|
||||||
|
component="${component#${component%%[!0]*}}"
|
||||||
|
printf '%s\n' "${component:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
compare_firefox_versions() {
|
||||||
|
local left="$1"
|
||||||
|
local right="$2"
|
||||||
|
local left_part right_part left_normalized right_normalized
|
||||||
|
local max_parts i
|
||||||
|
local -a left_parts right_parts
|
||||||
|
IFS='.' read -r -a left_parts <<< "$left"
|
||||||
|
IFS='.' read -r -a right_parts <<< "$right"
|
||||||
|
max_parts="${#left_parts[@]}"
|
||||||
|
[[ "${#right_parts[@]}" -gt "$max_parts" ]] && max_parts="${#right_parts[@]}"
|
||||||
|
for ((i = 0; i < max_parts; i += 1)); do
|
||||||
|
left_part="${left_parts[i]:-0}"
|
||||||
|
right_part="${right_parts[i]:-0}"
|
||||||
|
left_normalized="$(normalize_component "$left_part")"
|
||||||
|
right_normalized="$(normalize_component "$right_part")"
|
||||||
|
if [[ "${#left_normalized}" -lt "${#right_normalized}" ]]; then
|
||||||
|
printf '%s\n' -1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "${#left_normalized}" -gt "${#right_normalized}" ]]; then
|
||||||
|
printf '%s\n' 1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "$left_normalized" < "$right_normalized" ]]; then
|
||||||
|
printf '%s\n' -1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "$left_normalized" > "$right_normalized" ]]; then
|
||||||
|
printf '%s\n' 1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' 0
|
||||||
|
}
|
||||||
|
|
||||||
|
compare_current_manifest_versions() {
|
||||||
|
local current_version comparison
|
||||||
|
[[ -f updates.json ]] || return 0
|
||||||
|
jq -e . updates.json >/dev/null
|
||||||
|
while IFS= read -r current_version; do
|
||||||
|
[[ -z "$current_version" ]] && continue
|
||||||
|
if [[ ! "$current_version" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
|
||||||
|
echo "Current updates.json contains an invalid Firefox version: $current_version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
comparison="$(compare_firefox_versions "$VERSION" "$current_version")"
|
||||||
|
if [[ "$comparison" != '1' ]]; then
|
||||||
|
echo "Current update version $current_version is newer than or equal to $VERSION; skipping manifest commit."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done < <(jq -er '.addons[]?.updates[]?.version // empty' updates.json)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
git fetch --prune origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
if ! compare_current_manifest_versions; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
cp -- "$UPDATE_MANIFEST" updates.json
|
||||||
|
jq -e . updates.json >/dev/null
|
||||||
|
|
||||||
|
git config user.name 'gitea-actions[bot]'
|
||||||
|
git config user.email 'gitea-actions[bot]@users.noreply.git.vahta.net'
|
||||||
|
git add -- updates.json
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo 'updates.json is already current; nothing to commit.'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git commit --only updates.json -m "chore: publish Firefox update $VERSION"
|
||||||
|
if git push origin HEAD:main; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "The main branch advanced during manifest publication; retrying ($attempt/3)." >&2
|
||||||
|
git reset --hard origin/main
|
||||||
|
sleep "$((attempt * 2))"
|
||||||
|
done
|
||||||
|
echo 'Could not publish updates.json after three bounded retries.' >&2
|
||||||
|
exit 1
|
||||||
@@ -9,8 +9,9 @@ Neuradna Firefox WebExtension za pomoč pri ponavljajočem se izpolnjevanju obra
|
|||||||
- Izrecno dejanje uporabnika za zagon je potrebno v trenutnem zavihku.
|
- Izrecno dejanje uporabnika za zagon je potrebno v trenutnem zavihku.
|
||||||
- Razširitev je omejena na `https://esodisce.si/*`.
|
- Razširitev je omejena na `https://esodisce.si/*`.
|
||||||
- Razširitev ne uporablja Firefox Downloads API-ja in ne shranjuje PDF-datotek; samo izpolni obrazec in pritisne gumb `btn_pdf`.
|
- Razširitev ne uporablja Firefox Downloads API-ja in ne shranjuje PDF-datotek; samo izpolni obrazec in pritisne gumb `btn_pdf`.
|
||||||
|
- Vhodno datoteko hrani samo v lokalni shrambi razširitve; ne zbira in ne pošilja telemetrije ali podatkov razvijalcu.
|
||||||
|
|
||||||
## Namestitev v Firefox
|
## Razvojna/testna namestitev v Firefox
|
||||||
|
|
||||||
1. Odprite `about:debugging`.
|
1. Odprite `about:debugging`.
|
||||||
2. Izberite **This Firefox** / **Ta Firefox**.
|
2. Izberite **This Firefox** / **Ta Firefox**.
|
||||||
@@ -21,6 +22,19 @@ Neuradna Firefox WebExtension za pomoč pri ponavljajočem se izpolnjevanju obra
|
|||||||
|
|
||||||
Za spremembe datotek razširitev odstranite in jo znova naložite v `about:debugging`. Sidebar ostane odprt med uporabo strani in izbiro datoteke; zadnja uspešno prebrana datoteka se shrani v lokalno shrambo razširitve.
|
Za spremembe datotek razširitev odstranite in jo znova naložite v `about:debugging`. Sidebar ostane odprt med uporabo strani in izbiro datoteke; zadnja uspešno prebrana datoteka se shrani v lokalno shrambo razširitve.
|
||||||
|
|
||||||
|
## Produkcijska namestitev in samodejne posodobitve
|
||||||
|
|
||||||
|
Produkcijske izdaje objavlja Gitea Actions v javnem repozitoriju [komelt/zemljiska-knjiga-vticnik](https://git.vahta.net/komelt/zemljiska-knjiga-vticnik). Za produkcijsko uporabo prenesite in namestite podpisani XPI iz javnega Gitea Release-a. Ne nameščajte ga prek `about:debugging`, ker je taka namestitev začasna in nima stabilnega mehanizma za samodejne posodobitve.
|
||||||
|
|
||||||
|
Za nastavitev repozitorija mora biti repozitorij javen; javno morajo biti berljivi tudi Release asseti in surove datoteke, zlasti `updates.json` na stabilnem naslovu. V Gitea omogočite Actions in nastavite vgrajeni Actions token tega opravila na pravice `contents: write` (za potisk veje `main` in Release API). **Ne ustvarjajte skrivnosti `GITEA_TOKEN`**: `secrets.GITEA_TOKEN` je vgrajeni token opravila, ne uporabniško določena skrivnost. Ustvarite samo naslednji skrivnosti:
|
||||||
|
|
||||||
|
- `AMO_JWT_ISSUER` – Mozilla AMO JWT issuer;
|
||||||
|
- `AMO_JWT_SECRET` – Mozilla AMO JWT secret.
|
||||||
|
|
||||||
|
Vsak potisk v `main` ustvari podpisano AMO unlisted izdajo, objavi XPI v javnem Release-u in posodobi `updates.json`; samodejni potisk samo te datoteke ne sproži nove izdaje. Razširitev uporablja `browser_specific_settings.gecko.update_url` do surove datoteke na veji `main`, zato Firefox preverja nove izdaje po namestitvi podpisanega XPI. Osnovna različica je v izvornem `manifest.json`, akcija pa ji samodejno doda številko zagona, na primer `0.2.0` postane `0.2.0.17`.
|
||||||
|
|
||||||
|
Za izvajanje je potreben registriran Gitea runner z oznako `ubuntu-latest`. Na runnerju morajo biti na voljo `git`, `bash`, Node.js/npm/npx, `jq`, `curl` in `sha256sum`. Strežnik `git.vahta.net` mora imeti HTTPS-certifikat, ki mu zaupata Firefox in runnerji; sicer namestitev ter preverjanje in samodejne posodobitve ne bodo delovali.
|
||||||
|
|
||||||
## Vhodna datoteka
|
## Vhodna datoteka
|
||||||
|
|
||||||
Vsaka vrstica vsebuje dve polji: **številčno šifro KO** in **parcelno številko**. Podprta so ločila `;`, tabulator in vejica. Prvo polje mora biti pozitivno celo število; vodilne ničle se ohranijo kot del niza. Drugo polje ostane niz, zato so podprte oblike, kot je `901/29`.
|
Vsaka vrstica vsebuje dve polji: **številčno šifro KO** in **parcelno številko**. Podprta so ločila `;`, tabulator in vejica. Prvo polje mora biti pozitivno celo število; vodilne ničle se ohranijo kot del niza. Drugo polje ostane niz, zato so podprte oblike, kot je `901/29`.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
browser.action.onClicked.addListener(() => {
|
||||||
|
browser.sidebarAction.toggle().catch((error) => {
|
||||||
|
console.error('[eProstor GURS] Sidebar ni bilo mogoče preklopiti.', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
(() => {
|
||||||
|
if (window.__eprostorGursHelperLoaded) return;
|
||||||
|
window.__eprostorGursHelperLoaded = true;
|
||||||
|
|
||||||
|
const SELECTORS = {
|
||||||
|
koNumber: 'idZnakNep.katastrskaObcina.idsrcsifrant',
|
||||||
|
parcel: 'idZnakNep.parcelnaStevilka'
|
||||||
|
};
|
||||||
|
const LOG_PREFIX = '[eProstor GURS]';
|
||||||
|
let stopRequested = false;
|
||||||
|
let running = false;
|
||||||
|
|
||||||
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
const report = (text) => browser.runtime.sendMessage({ type: 'BATCH_STATUS', text }).catch(() => {});
|
||||||
|
const log = (...args) => console.log(LOG_PREFIX, ...args);
|
||||||
|
const logError = (...args) => console.error(LOG_PREFIX, ...args);
|
||||||
|
|
||||||
|
function findField(selector) {
|
||||||
|
// getElementById avoids CSS escaping issues because the literal IDs contain dots.
|
||||||
|
return document.getElementById(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNativeValue(element, value) {
|
||||||
|
element.focus();
|
||||||
|
const setter = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), 'value')?.set;
|
||||||
|
if (setter) setter.call(element, value); else element.value = value;
|
||||||
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processRecord(record, index, total, confirmMode) {
|
||||||
|
if (stopRequested) return false;
|
||||||
|
log(`Začetek zapisa ${index + 1}/${total}`, {
|
||||||
|
koNumber: record.koNumber,
|
||||||
|
parcel: record.parcel
|
||||||
|
});
|
||||||
|
const koNumber = findField(SELECTORS.koNumber);
|
||||||
|
const parcel = findField(SELECTORS.parcel);
|
||||||
|
log('Polji najdeni', {
|
||||||
|
koField: SELECTORS.koNumber,
|
||||||
|
koFieldFound: Boolean(koNumber),
|
||||||
|
parcelField: SELECTORS.parcel,
|
||||||
|
parcelFieldFound: Boolean(parcel)
|
||||||
|
});
|
||||||
|
if (!koNumber || !parcel) throw new Error('Polj z ID-jema ni mogoče najti; preverite selectorje v content-script.js.');
|
||||||
|
|
||||||
|
if (confirmMode === 'manual' && !window.confirm(`Obdelam ${index + 1}/${total}: KO ${record.koNumber} / parcela ${record.parcel}?`)) {
|
||||||
|
log(`Zapis ${index + 1}/${total} preskočen na ročno zahtevo`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log('Neposreden vnos KO', { recordNumber: index + 1, koNumber: record.koNumber });
|
||||||
|
setNativeValue(koNumber, record.koNumber);
|
||||||
|
log('Neposreden vnos parcele', { recordNumber: index + 1, parcel: record.parcel });
|
||||||
|
setNativeValue(parcel, record.parcel);
|
||||||
|
|
||||||
|
const button = document.getElementById('btn_pdf');
|
||||||
|
log('btn_pdf preverjen', { recordNumber: index + 1, found: Boolean(button) });
|
||||||
|
if (!button) throw new Error('Gumba z ID-jem »btn_pdf« ni mogoče najti.');
|
||||||
|
button.click();
|
||||||
|
log('btn_pdf kliknjen', { recordNumber: index + 1 });
|
||||||
|
report(`Zapis ${index + 1}/${total}: vrednosti vnesene in potrjene z gumbom btn_pdf.`);
|
||||||
|
log(`Zapis ${index + 1}/${total} uspešno obdelan`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeError(error) {
|
||||||
|
const isObject = error !== null && (typeof error === 'object' || typeof error === 'function');
|
||||||
|
return {
|
||||||
|
message: isObject && error.message !== undefined ? String(error.message) : String(error),
|
||||||
|
name: isObject && error.name !== undefined ? String(error.name) : typeof error,
|
||||||
|
cause: isObject ? error.cause : undefined,
|
||||||
|
stack: isObject ? error.stack : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomDiagnostics() {
|
||||||
|
return {
|
||||||
|
koFieldPresent: Boolean(findField(SELECTORS.koNumber)),
|
||||||
|
parcelFieldPresent: Boolean(findField(SELECTORS.parcel)),
|
||||||
|
btnPdfPresent: Boolean(document.getElementById('btn_pdf'))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(async (message) => {
|
||||||
|
if (message.type === 'STOP_BATCH') {
|
||||||
|
stopRequested = true;
|
||||||
|
log('Zahtev za ustavitev prejet', { state: 'stop-requested' });
|
||||||
|
return { message: 'Ustavljanje…' };
|
||||||
|
}
|
||||||
|
if (message.type !== 'START_BATCH') return undefined;
|
||||||
|
if (running) return { message: 'Obdelava že teče.' };
|
||||||
|
running = true;
|
||||||
|
stopRequested = false;
|
||||||
|
const { records, confirmMode = 'manual', delayMs = 1500 } = message;
|
||||||
|
log('Batch začet', { total: records.length, confirmMode, delayMs });
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < records.length && !stopRequested; i += 1) {
|
||||||
|
try {
|
||||||
|
await processRecord(records[i], i, records.length, confirmMode);
|
||||||
|
} catch (error) {
|
||||||
|
const errorDetails = describeError(error);
|
||||||
|
logError(`Napaka pri zapisu ${i + 1}/${records.length}`, {
|
||||||
|
recordNumber: i + 1,
|
||||||
|
total: records.length,
|
||||||
|
record: records[i],
|
||||||
|
error: errorDetails,
|
||||||
|
dom: getDomDiagnostics()
|
||||||
|
});
|
||||||
|
report(`Zapis ${i + 1}: ${errorDetails.message}`);
|
||||||
|
if (confirmMode === 'manual' && !window.confirm(`Napaka pri zapisu ${i + 1}: ${errorDetails.message}\nNadaljujem?`)) break;
|
||||||
|
}
|
||||||
|
await sleep(delayMs);
|
||||||
|
}
|
||||||
|
const messageText = stopRequested ? 'Obdelava ustavljena.' : 'Obdelava končana.';
|
||||||
|
log(messageText, {
|
||||||
|
state: stopRequested ? 'stopped' : 'completed',
|
||||||
|
total: records.length
|
||||||
|
});
|
||||||
|
report(messageText);
|
||||||
|
return { message: messageText };
|
||||||
|
} finally {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Šifra KO;Parcelna številka
|
||||||
|
2491;901/29
|
||||||
|
2491;901/30
|
||||||
|
0107;1820
|
||||||
|
0107;1861
|
||||||
|
1535;1192/1
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "eSodisce / GURS batch helper",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"description": "Assists with repeated eSodisce/GURS parcel form entries. It never handles certificates, keys, PINs or PDF downloads.",
|
||||||
|
"permissions": ["activeTab", "storage"],
|
||||||
|
"host_permissions": ["https://esodisce.si/*"],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_title": "eSodisce GURS batch helper"
|
||||||
|
},
|
||||||
|
"sidebar_action": {
|
||||||
|
"default_title": "eSodisce / GURS batch helper",
|
||||||
|
"default_panel": "popup.html",
|
||||||
|
"open_at_install": false
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["https://esodisce.si/*"],
|
||||||
|
"js": ["content-script.js"],
|
||||||
|
"run_at": "document_idle"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "eprostor-gurs-batch@example.invalid",
|
||||||
|
"strict_min_version": "109.0",
|
||||||
|
"update_url": "https://git.vahta.net/komelt/zemljiska-knjiga-vticnik/raw/branch/main/updates.json",
|
||||||
|
"data_collection_permissions": {
|
||||||
|
"required": ["none"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
:root { color-scheme: light dark; font: 14px system-ui, sans-serif; }
|
||||||
|
body { width: 100%; box-sizing: border-box; margin: 0; background: Canvas; color: CanvasText; }
|
||||||
|
main { padding: 14px; }
|
||||||
|
h1 { font-size: 18px; margin: 0 0 4px; }
|
||||||
|
.muted { color: GrayText; font-size: 12px; }
|
||||||
|
label, legend { display: block; margin-top: 10px; font-weight: 600; }
|
||||||
|
input[type=file], input[type=number] { box-sizing: border-box; width: 100%; margin-top: 4px; }
|
||||||
|
fieldset { margin: 12px 0; padding: 8px; }
|
||||||
|
fieldset label { margin-top: 5px; font-weight: 400; }
|
||||||
|
button { width: 100%; margin-top: 14px; padding: 8px; border: 0; border-radius: 5px; background: #1769aa; color: white; cursor: pointer; }
|
||||||
|
button:disabled { opacity: .5; cursor: not-allowed; }
|
||||||
|
button.secondary { background: #666; }
|
||||||
|
#status { min-height: 2.5em; margin-top: 10px; white-space: pre-wrap; }
|
||||||
|
details { margin-top: 10px; font-size: 12px; }
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="sl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>eSodisce / GURS batch helper</title>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>eSodisce / GURS</h1>
|
||||||
|
<p class="muted">Pomočnik za zaporedno izpolnjevanje obrazca po šifri KO.</p>
|
||||||
|
<label for="inputFile">TXT/CSV seznam</label>
|
||||||
|
<input id="inputFile" type="file" accept=".txt,.csv,text/plain,text/csv">
|
||||||
|
<div id="fileInfo" class="muted">Datoteka še ni izbrana.</div>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Potrjevanje</legend>
|
||||||
|
<label><input type="radio" name="confirmMode" value="manual" checked> ročno pred vsakim zapisom</label>
|
||||||
|
<label><input type="radio" name="confirmMode" value="auto"> samodejno (brez vprašanj)</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<label for="delayMs">Čakanje med zapisi (ms)</label>
|
||||||
|
<input id="delayMs" type="number" min="500" step="100" value="1500">
|
||||||
|
|
||||||
|
<button id="startButton" type="button" disabled>Začni v trenutnem zavihku</button>
|
||||||
|
<button id="stopButton" type="button" class="secondary">Ustavi</button>
|
||||||
|
<div id="status" role="status" aria-live="polite">Pripravljeno.</div>
|
||||||
|
</main>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
(() => {
|
||||||
|
const inputFile = document.querySelector('#inputFile');
|
||||||
|
const fileInfo = document.querySelector('#fileInfo');
|
||||||
|
const startButton = document.querySelector('#startButton');
|
||||||
|
const stopButton = document.querySelector('#stopButton');
|
||||||
|
const delayInput = document.querySelector('#delayMs');
|
||||||
|
const status = document.querySelector('#status');
|
||||||
|
let records = [];
|
||||||
|
|
||||||
|
function updateLoadedFile(fileName) {
|
||||||
|
fileInfo.textContent = `${fileName}: ${records.length} veljavnih zapisov`;
|
||||||
|
startButton.disabled = records.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(text) { status.textContent = text; }
|
||||||
|
|
||||||
|
function parseLine(line) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) return null;
|
||||||
|
// Prefer tab, then semicolon, then comma; exactly two fields are required.
|
||||||
|
const separator = trimmed.includes('\t') ? '\t' : (trimmed.includes(';') ? ';' : ',');
|
||||||
|
const fields = trimmed.split(separator).map((field) => field.trim().replace(/^"|"$/g, ''));
|
||||||
|
if (fields.length !== 2 || !fields[0] || !fields[1]) return null;
|
||||||
|
const header = `${fields[0]} ${fields[1]}`.toLocaleLowerCase('sl');
|
||||||
|
if (header.includes('katastr') && header.includes('parcel')) return null;
|
||||||
|
if (!/^\d+$/.test(fields[0]) || /^0+$/.test(fields[0])) return null;
|
||||||
|
return { koNumber: fields[0], parcel: fields[1] };
|
||||||
|
}
|
||||||
|
|
||||||
|
inputFile.addEventListener('change', async () => {
|
||||||
|
const file = inputFile.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
const lines = (await file.text()).split(/\r?\n/);
|
||||||
|
records = lines.map(parseLine).filter(Boolean);
|
||||||
|
await browser.storage.local.set({ batchInput: { fileName: file.name, records } });
|
||||||
|
updateLoadedFile(file.name);
|
||||||
|
setStatus(records.length ? 'Pripravljeno za trenutni zavihek.' : 'Ni veljavnih zapisov.');
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.storage.local.get('batchInput').then(({ batchInput }) => {
|
||||||
|
if (!batchInput || !Array.isArray(batchInput.records) || !batchInput.records.length) return;
|
||||||
|
records = batchInput.records;
|
||||||
|
updateLoadedFile(batchInput.fileName || 'Shranjena vhodna datoteka');
|
||||||
|
setStatus('Naložena je zadnja izbrana vhodna datoteka.');
|
||||||
|
});
|
||||||
|
|
||||||
|
startButton.addEventListener('click', async () => {
|
||||||
|
if (!records.length) return;
|
||||||
|
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab?.id) { setStatus('Ciljnega zavihka ni mogoče najti.'); return; }
|
||||||
|
const confirmMode = document.querySelector('input[name="confirmMode"]:checked').value;
|
||||||
|
const delayMs = Math.max(500, Number.parseInt(delayInput.value, 10) || 1500);
|
||||||
|
try {
|
||||||
|
const response = await browser.tabs.sendMessage(tab.id, { type: 'START_BATCH', records, confirmMode, delayMs });
|
||||||
|
setStatus(response?.message || 'Obdelava zagnana.');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus(`Napaka: odprite ciljno stran in poskusite znova. ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stopButton.addEventListener('click', async () => {
|
||||||
|
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
||||||
|
if (!tab?.id) { setStatus('Ciljnega zavihka ni mogoče najti.'); return; }
|
||||||
|
try {
|
||||||
|
const response = await browser.tabs.sendMessage(tab.id, { type: 'STOP_BATCH' });
|
||||||
|
setStatus(response?.message || 'Zahteva za ustavitev poslana.');
|
||||||
|
} catch (error) {
|
||||||
|
setStatus(`Napaka: odprite ciljno stran in poskusite znova. ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener((message) => {
|
||||||
|
if (message.type === 'BATCH_STATUS') setStatus(message.text);
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"addons": {
|
||||||
|
"eprostor-gurs-batch@example.invalid": {
|
||||||
|
"updates": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user