diff --git a/.gitea/workflows/release-scraper.yaml b/.gitea/workflows/release-scraper.yaml
deleted file mode 100644
index 69a5749..0000000
--- a/.gitea/workflows/release-scraper.yaml
+++ /dev/null
@@ -1,104 +0,0 @@
-name: Release / Scraper
-
-on:
- push:
- tags:
- - "v*"
-
-defaults:
- run:
- working-directory: scraper
-
-jobs:
- # ── lint & vet ───────────────────────────────────────────────────────────────
- lint:
- name: Lint
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-go@v5
- with:
- go-version-file: scraper/go.mod
- cache-dependency-path: scraper/go.sum
-
- - name: go vet
- run: go vet ./...
-
- - name: staticcheck
- run: go tool staticcheck ./...
-
- # ── tests ────────────────────────────────────────────────────────────────────
- test:
- name: Test
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-go@v5
- with:
- go-version-file: scraper/go.mod
- cache-dependency-path: scraper/go.sum
-
- - name: Run tests
- run: go test -race -count=1 -timeout=60s ./...
-
- # ── build & publish release ──────────────────────────────────────────────────
- release:
- name: Release
- runs-on: ubuntu-latest
- needs: [lint, test]
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-go@v5
- with:
- go-version-file: scraper/go.mod
- cache-dependency-path: scraper/go.sum
-
- - name: Build binary
- run: |
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
- go build -ldflags="-s -w" -o bin/scraper-linux-amd64 ./cmd/scraper
-
- - name: Publish release
- uses: actions/gitea-release@v1
- with:
- token: ${{ secrets.GITEA_TOKEN }}
- files: scraper/bin/scraper-linux-amd64
-
- # ── docker build & push ──────────────────────────────────────────────────────
- docker:
- name: Docker
- runs-on: ubuntu-latest
- needs: [lint, test]
- defaults:
- run:
- working-directory: .
- steps:
- - uses: actions/checkout@v4
-
- - name: Log in to Gitea registry
- uses: docker/login-action@v3
- with:
- registry: gitea.kalekber.cc
- username: ${{ secrets.REGISTRY_USER }}
- password: ${{ secrets.REGISTRY_TOKEN }}
-
- - name: Docker meta
- id: meta
- uses: docker/metadata-action@v5
- with:
- images: gitea.kalekber.cc/kamil/libnovel-scraper
- tags: |
- type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
- type=raw,value=latest
-
- - name: Build and push
- uses: docker/build-push-action@v5
- with:
- context: ./scraper
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
diff --git a/scripts/link-tooltip.user.js b/scripts/link-tooltip.user.js
new file mode 100644
index 0000000..245aaa0
--- /dev/null
+++ b/scripts/link-tooltip.user.js
@@ -0,0 +1,99 @@
+// ==UserScript==
+// @name Link URL Tooltip
+// @namespace https://github.com/kalekber/libnovel-v2
+// @version 1.0.0
+// @description Show the destination URL near the cursor when hovering over any link
+// @author kalekber
+// @match *://*/*
+// @run-at document-idle
+// @grant none
+// ==/UserScript==
+
+(function () {
+ 'use strict';
+
+ // --- Inject styles ---
+ const style = document.createElement('style');
+ style.textContent = `
+ #lnk-tooltip {
+ position: fixed;
+ display: none;
+ background-color: #333;
+ color: #fff;
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-family: monospace;
+ pointer-events: none;
+ z-index: 2147483647;
+ white-space: nowrap;
+ max-width: 600px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ box-shadow: 0 2px 6px rgba(0,0,0,0.4);
+ }
+ `;
+ document.head.appendChild(style);
+
+ // --- Inject tooltip element ---
+ const tooltip = document.createElement('div');
+ tooltip.id = 'lnk-tooltip';
+ document.body.appendChild(tooltip);
+
+ // --- Helpers ---
+ function getAnchor(target) {
+ // Walk up the DOM to find the nearest
+ // (handles clicks on nested elements like text)
+ return target.closest('a[href]');
+ }
+
+ function show(anchor, clientX, clientY) {
+ tooltip.textContent = anchor.href;
+ tooltip.style.display = 'block';
+ position(clientX, clientY);
+ }
+
+ function hide() {
+ tooltip.style.display = 'none';
+ }
+
+ function position(clientX, clientY) {
+ const offset = 12;
+ const tw = tooltip.offsetWidth;
+ const th = tooltip.offsetHeight;
+ const vw = window.innerWidth;
+ const vh = window.innerHeight;
+
+ let x = clientX + offset;
+ let y = clientY + offset;
+
+ // Flip horizontally if it would overflow the right edge
+ if (x + tw > vw - 4) {
+ x = clientX - tw - offset;
+ }
+ // Flip vertically if it would overflow the bottom edge
+ if (y + th > vh - 4) {
+ y = clientY - th - offset;
+ }
+
+ tooltip.style.left = Math.max(0, x) + 'px';
+ tooltip.style.top = Math.max(0, y) + 'px';
+ }
+
+ // --- Event delegation on document ---
+ document.addEventListener('mouseover', (e) => {
+ const anchor = getAnchor(e.target);
+ if (anchor) show(anchor, e.clientX, e.clientY);
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (tooltip.style.display === 'block') {
+ position(e.clientX, e.clientY);
+ }
+ });
+
+ document.addEventListener('mouseout', (e) => {
+ const anchor = getAnchor(e.target);
+ if (anchor) hide();
+ });
+})();