10 Commits
Author SHA1 Message Date
Olaf 38908b9a25 Release testing 2026-08-25 09:30:56 +02:00
Olaf 08b0ee013f Release workflow 2026-08-25 09:22:28 +02:00
Olaf 5d60bd801c Versioning (0.1.0) 2026-08-25 09:07:29 +02:00
Olaf d896d3d068 Copyright and license 2026-08-25 08:39:46 +02:00
Olaf 1cba4e8649 plugin settings and eyecandy 2026-08-25 08:12:39 +02:00
Olaf f6077f0063 Settings page of pluggin inproved 2026-08-25 08:03:44 +02:00
Olaf 315a89380f Removed treafik from docker setup 2026-08-25 07:53:02 +02:00
Olaf 4dc8fcfa9c finetuneing webplugin 2026-08-24 22:54:01 +02:00
Olaf 759c284b26 Added a logo 2026-08-24 22:17:48 +02:00
Olaf 4ae184f24c Add a (simple) logo 2026-08-24 22:01:35 +02:00
69 changed files with 1090 additions and 74 deletions
+1 -11
View File
@@ -8,6 +8,7 @@ APP_HEALTHCHECK_TIMEOUT=5s
APP_HEALTHCHECK_START_PERIOD=10s APP_HEALTHCHECK_START_PERIOD=10s
APP_HEALTHCHECK_RETRIES=3 APP_HEALTHCHECK_RETRIES=3
LINKLOG_APP_NAME=LinkLog LINKLOG_APP_NAME=LinkLog
LINKLOG_VERSION=0.1.0
LINKLOG_SECRET_KEY=replace-with-a-long-random-secret LINKLOG_SECRET_KEY=replace-with-a-long-random-secret
LINKLOG_TOKEN_EXPIRY_DAYS=30 LINKLOG_TOKEN_EXPIRY_DAYS=30
# Optional comma-separated override. Leave empty to use the built-in list. # Optional comma-separated override. Leave empty to use the built-in list.
@@ -15,14 +16,3 @@ LINKLOG_TRACKING_PARAMS=
# Keep the default path when using the named linklog_data volume. # Keep the default path when using the named linklog_data volume.
LINKLOG_DATABASE_PATH=/app/backend/data/linklog.db LINKLOG_DATABASE_PATH=/app/backend/data/linklog.db
# Traefik
TRAEFIK_IMAGE=traefik:v3.1
TRAEFIK_CONTAINER_NAME=linklog-traefik
TRAEFIK_HOST=localhost
TRAEFIK_HTTP_PORT=80
TRAEFIK_HTTP_INTERNAL_PORT=80
TRAEFIK_DASHBOARD_PORT=8080
TRAEFIK_DASHBOARD_BIND_ADDRESS=127.0.0.1
TRAEFIK_API_INSECURE=true
TRAEFIK_RESTART_POLICY=unless-stopped
DOCKER_SOCKET_PATH=/var/run/docker.sock
+90
View File
@@ -0,0 +1,90 @@
name: Release LinkLog
on:
push:
tags:
- 'v*'
workflow_dispatch:
env:
IMAGE_NAME: git.kolkman.org/olaf/link-log
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Check out release tag
uses: actions/checkout@v4
- name: Validate versions and signed XPI
id: release
run: |
python3 scripts/release/validate_release.py
version=$(python3 -c "import json; print(json.load(open('frontend/version.json'))['version'])")
echo "version=$version" >> "$GITHUB_OUTPUT"
if [ "${GITHUB_REF_NAME#v}" != "$version" ]; then
echo "tag ${GITHUB_REF_NAME} does not match release version $version" >&2
exit 1
fi
- name: Log in to Gitea container registry
uses: docker/login-action@v3
with:
registry: git.kolkman.org
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and publish Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ steps.release.outputs.version }}
${{ env.IMAGE_NAME }}:latest
labels: |
org.opencontainers.image.version=${{ steps.release.outputs.version }}
org.opencontainers.image.source=https://git.kolkman.org/olaf/Link-Log
- name: Create Gitea release
id: gitea_release
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
VERSION: ${{ steps.release.outputs.version }}
run: |
response=$(curl --fail-with-body --silent --show-error \
-X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"tag_name\":\"v$VERSION\",\"name\":\"LinkLog $VERSION\",\"draft\":false,\"prerelease\":false}" \
https://git.kolkman.org/api/v1/repos/olaf/Link-Log/releases)
release_id=$(printf '%s' "$response" | jq -r '.id')
test "$release_id" != null
test "$release_id" != 0
upload_url="https://git.kolkman.org/api/v1/repos/olaf/Link-Log/releases/$release_id/assets"
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
- name: Upload signed XPI and update manifest
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
UPLOAD_URL: ${{ steps.gitea_release.outputs.upload_url }}
VERSION: ${{ steps.release.outputs.version }}
run: |
curl --fail-with-body --silent --show-error \
-X POST -H "Authorization: token $GITEA_TOKEN" \
-H 'Content-Type: application/x-xpinstall' \
--data-binary "@XPI/signed/LinkLog-$VERSION.xpi" \
"$UPLOAD_URL?name=LinkLog-$VERSION.xpi"
curl --fail-with-body --silent --show-error \
-X POST -H "Authorization: token $GITEA_TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @webextension/updates.json \
"$UPLOAD_URL?name=updates.json"
- name: Publish release links
env:
VERSION: ${{ steps.release.outputs.version }}
run: |
echo "Docker image: $IMAGE_NAME:$VERSION"
echo "Signed XPI: https://git.kolkman.org/olaf/Link-Log/releases/download/v$VERSION/LinkLog-$VERSION.xpi"
echo "Firefox update manifest: https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json"
+2
View File
@@ -15,3 +15,5 @@ __pycache__/
*.sqlite3 *.sqlite3
*.log *.log
backend/data/avatars/ backend/data/avatars/
LinkLog.afdesign~lock~
+3
View File
@@ -1,3 +1,6 @@
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
FROM python:3.11-slim FROM python:3.11-slim
WORKDIR /app WORKDIR /app
+12
View File
@@ -0,0 +1,12 @@
LinkLog is licensed under the GNU General Public License, version 3 or any later version (GPL-3.0-or-later).
Copyright (C) 2026 Olaf Kolkman
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
The complete license text is available at:
https://www.gnu.org/licenses/gpl-3.0.html
BIN
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1804 1712" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-669.006,-255.89)">
<g transform="matrix(0.972876,0,0,1.26057,-24.1588,-587.485)">
<rect x="712.491" y="669.041" width="1854.11" height="1357.72" style="fill:rgb(88,91,112);"/>
</g>
<g transform="matrix(5.14628,0,0,6.12686,41.2191,-8673.98)">
<path d="M141.95,1644.94L141.95,1633.94C157.554,1633.4 169.724,1631.04 178.462,1626.86C187.2,1622.69 193.344,1615.8 196.894,1606.21C200.444,1596.61 202.218,1583.47 202.218,1566.77C202.218,1554.44 201.146,1544.16 199,1535.93C196.855,1527.7 193.285,1521.25 188.292,1516.57C185.016,1513.6 181.154,1511.26 176.707,1509.54C172.26,1507.83 167.15,1506.62 161.376,1505.92C155.603,1505.21 149.128,1504.86 141.95,1504.86L141.95,1493.86L384.076,1493.86L384.076,1504.86C373.388,1504.86 364.221,1505.7 356.575,1507.38C348.929,1509.06 342.708,1512.12 337.909,1516.57C333.111,1521.01 329.581,1527.27 327.319,1535.35C325.056,1543.42 323.925,1553.9 323.925,1566.77L323.925,1633.36C339.06,1633.36 353.532,1631.08 367.341,1626.51C381.15,1621.95 393.399,1616 404.087,1608.66C414.776,1601.41 423.299,1593.3 429.657,1584.32C436.016,1575.35 439.546,1566.61 440.248,1558.11L452.536,1558.11C452.536,1562.4 452.419,1568.35 452.185,1575.96C451.951,1583.56 451.6,1591.85 451.132,1600.82C450.586,1609.95 449.942,1618.34 449.201,1625.99C448.46,1633.63 447.582,1639.95 446.568,1644.94L141.95,1644.94Z" style="fill:rgb(245,224,220);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.135097,0,0,0.266653,668.366,1282.68)">
<path d="M846.315,1802.1L846.315,1747.02C926.615,1744.29 989.248,1732.47 1034.22,1711.57C1079.18,1690.67 1110.8,1656.2 1129.07,1608.15C1147.34,1560.11 1156.47,1494.29 1156.47,1410.69C1156.47,1348.97 1150.95,1297.51 1139.91,1256.3C1128.87,1215.09 1110.5,1182.76 1084.81,1159.33C1067.94,1144.48 1048.07,1132.76 1025.18,1124.17C1002.3,1115.58 975.999,1109.52 946.288,1106.01C916.577,1102.49 883.253,1100.73 846.315,1100.73L846.315,1045.65L2092.36,1045.65L2092.36,1100.73C2037.36,1100.73 1990.18,1104.93 1950.83,1113.33C1911.49,1121.73 1879.47,1137.06 1854.78,1159.33C1830.08,1181.59 1811.92,1212.94 1800.27,1253.37C1788.63,1293.8 1782.81,1346.24 1782.81,1410.69L1782.81,1744.09C1860.7,1744.09 1935.18,1732.66 2006.24,1709.81C2077.31,1686.96 2140.34,1657.18 2195.35,1620.46C2250.35,1584.13 2294.21,1543.51 2326.94,1498.58C2359.66,1453.66 2377.83,1409.91 2381.44,1367.33L2444.68,1367.33C2444.68,1388.82 2444.07,1418.6 2442.87,1456.69C2441.66,1494.78 2439.86,1536.28 2437.45,1581.2C2434.64,1626.9 2431.33,1668.9 2427.51,1707.18C2423.7,1745.46 2419.18,1777.1 2413.96,1802.1L846.315,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3043.37,1093.7C3001.22,1093.7 2961.37,1089.99 2923.83,1082.57C2886.29,1075.15 2853.66,1064.99 2825.96,1052.1C2797.86,1039.21 2775.67,1024.17 2759.41,1006.98C2743.15,989.794 2735.02,971.435 2735.02,951.904C2735.02,924.951 2748.97,900.732 2776.88,879.247C2804.78,857.763 2842.12,840.673 2888.9,827.978C2935.67,815.283 2987.16,808.935 3043.37,808.935C3101.59,808.935 3153.98,815.38 3200.56,828.271C3247.13,841.161 3284.07,858.447 3311.37,880.126C3338.67,901.806 3352.32,925.732 3352.32,951.904C3352.32,977.294 3338.67,1000.83 3311.37,1022.51C3284.07,1044.19 3247.13,1061.47 3200.56,1074.37C3153.98,1087.26 3101.59,1093.7 3043.37,1093.7ZM2498.94,1802.1L2498.94,1747.02C2608.95,1743.12 2686.24,1720.16 2730.81,1678.17C2775.37,1636.18 2797.66,1576.9 2797.66,1500.34C2797.66,1422.22 2776.08,1365.97 2732.91,1331.59C2689.75,1297.22 2618.39,1280.03 2518.82,1280.03L2518.82,1224.95L3327.03,1172.22L3327.03,1500.34C3327.03,1551.12 3333.76,1594.38 3347.2,1630.13C3360.66,1665.87 3386.05,1693.6 3423.39,1713.33C3460.73,1733.06 3515.53,1744.29 3587.8,1747.02L3587.8,1802.1L2498.94,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3625.19,1802.1L3625.19,1747.02C3724.76,1743.12 3796.43,1721.83 3840.19,1683.15C3862.68,1663.62 3878.94,1639.6 3888.97,1611.08C3899.01,1582.57 3904.03,1549.17 3904.03,1510.89C3904.03,1447.22 3894.7,1399.37 3876.03,1367.33C3857.36,1335.3 3827.64,1313.72 3786.89,1302.59C3746.14,1291.45 3692.24,1285.89 3625.19,1285.89L3625.19,1230.81L4433.4,1178.08L4433.4,1274.76C4505.67,1243.51 4581.56,1216.94 4661.05,1195.07C4740.55,1173.19 4822.65,1162.26 4907.37,1162.26C4940.29,1162.26 4973.62,1164.31 5007.34,1168.41C5041.07,1172.51 5073.19,1183.93 5103.7,1202.68C5122.98,1214.79 5139.84,1231.2 5154.29,1251.9C5168.34,1273 5179.59,1299.17 5188.02,1330.42C5196.45,1361.67 5200.66,1399.37 5200.66,1443.51L5199.46,1488.62C5198.26,1504.25 5197.65,1521.24 5197.65,1539.6C5197.65,1573.97 5199.46,1604.15 5203.07,1630.13C5206.69,1656.1 5215.92,1677.68 5230.78,1694.87C5245.23,1712.06 5267.51,1725.05 5297.63,1733.84C5327.74,1742.63 5369.29,1747.02 5422.29,1747.02L5422.29,1802.1L4575.53,1802.1L4575.53,1757.57C4601.63,1741.16 4619.7,1713.62 4629.74,1674.95C4639.77,1636.28 4644.79,1583.35 4644.79,1516.16C4644.79,1457.96 4639.47,1415.09 4628.83,1387.55C4618.19,1360.01 4603.74,1342.14 4585.47,1333.93C4567.2,1325.73 4546.83,1321.63 4524.34,1321.63C4510.29,1321.63 4495.54,1323.1 4480.08,1326.03C4464.62,1328.95 4449.06,1332.96 4433.4,1338.04L4433.4,1526.71C4433.4,1595.07 4438.52,1646.53 4448.76,1681.1C4459,1715.67 4476.97,1741.16 4502.66,1757.57L4502.66,1802.1L3625.19,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M6760.3,1835.5C6693.65,1835.5 6638.55,1833.06 6594.99,1828.17C6551.42,1823.29 6518.2,1814.4 6495.32,1801.51C6481.26,1793.7 6469.62,1783.35 6460.39,1770.46C6451.55,1757.96 6444.73,1743.31 6439.91,1726.51C6435.09,1709.72 6430.67,1690.38 6426.66,1668.51C6422.64,1647.41 6416.92,1627.68 6409.49,1609.33C6402.07,1590.97 6387.21,1575.83 6364.93,1563.91C6342.65,1552 6306.81,1545.85 6257.43,1545.46C6258.23,1615.38 6265.66,1666.45 6279.71,1698.68C6293.76,1730.91 6320.86,1747.02 6361.01,1747.02L6361.01,1802.1L5449.21,1802.1L5449.21,1747.02C5485.35,1745.85 5517.27,1742.92 5544.97,1738.23C5600.78,1728.86 5642.33,1707.96 5669.64,1675.54C5683.69,1658.35 5695.13,1636.87 5703.96,1611.08C5712.4,1585.3 5718.52,1554.15 5722.33,1517.63C5726.15,1481.1 5728.05,1438.43 5728.05,1389.6C5728.05,1315.38 5722.84,1255.22 5712.4,1209.13C5701.96,1163.04 5685.49,1127.1 5663.01,1101.32C5640.53,1075.93 5611.72,1058.74 5576.59,1049.76C5541.46,1040.77 5499,1036.28 5449.21,1036.28L5449.21,981.786L6257.43,929.052L6257.43,1462.84C6286.74,1442.92 6314.84,1422.12 6341.74,1400.44C6368.64,1378.76 6392.33,1358.54 6412.81,1339.79C6457.78,1298.39 6480.26,1272.8 6480.26,1263.04C6480.26,1255.22 6471.63,1250.15 6454.36,1247.8C6437.1,1245.46 6413.61,1244.29 6383.9,1244.29L6383.9,1189.21L7053.6,1189.21L7053.6,1244.29C7005.82,1244.29 6953.42,1249.76 6896.41,1260.69C6839.4,1271.63 6778.97,1288.04 6715.14,1309.91C6650.9,1331.79 6583.85,1359.33 6513.99,1392.53C6444.12,1425.73 6372.46,1464.6 6298.98,1509.13C6373.66,1497.41 6449.04,1488.13 6525.13,1481.3C6601.21,1474.46 6668.36,1471.04 6726.58,1471.04C6810.89,1471.04 6875.33,1480.42 6919.9,1499.17C6965.67,1518.7 6994.58,1550.34 7006.62,1594.09C7013.85,1619.87 7023.18,1641.94 7034.63,1660.3C7046.07,1678.66 7058.21,1693.9 7071.06,1706.01C7083.91,1718.12 7096.86,1727.2 7109.91,1733.25C7122.96,1739.31 7134.3,1742.92 7143.93,1744.09L7143.93,1805.62C7133.49,1808.74 7116.23,1811.96 7092.14,1815.28C7068.05,1818.6 7039.14,1821.83 7005.42,1824.95C6971.29,1828.08 6933.35,1830.62 6891.59,1832.57C6849.84,1834.52 6806.07,1835.5 6760.3,1835.5Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M7746.18,1802.1L7746.18,1747.02C7826.48,1744.29 7889.11,1732.47 7934.08,1711.57C7979.05,1690.67 8010.67,1656.2 8028.93,1608.15C8047.2,1560.11 8056.34,1494.29 8056.34,1410.69C8056.34,1348.97 8050.82,1297.51 8039.77,1256.3C8028.73,1215.09 8010.36,1182.76 7984.67,1159.33C7967.81,1144.48 7947.93,1132.76 7925.05,1124.17C7902.16,1115.58 7875.86,1109.52 7846.15,1106.01C7816.44,1102.49 7783.12,1100.73 7746.18,1100.73L7746.18,1045.65L8992.23,1045.65L8992.23,1100.73C8937.22,1100.73 8890.05,1104.93 8850.7,1113.33C8811.35,1121.73 8779.33,1137.06 8754.64,1159.33C8729.95,1181.59 8711.78,1212.94 8700.14,1253.37C8688.49,1293.8 8682.67,1346.24 8682.67,1410.69L8682.67,1744.09C8760.56,1744.09 8835.04,1732.66 8906.1,1709.81C8977.17,1686.96 9040.2,1657.18 9095.21,1620.46C9150.22,1584.13 9194.08,1543.51 9226.8,1498.58C9259.52,1453.66 9277.69,1409.91 9281.3,1367.33L9344.54,1367.33C9344.54,1388.82 9343.94,1418.6 9342.73,1456.69C9341.53,1494.78 9339.72,1536.28 9337.31,1581.2C9334.5,1626.9 9331.19,1668.9 9327.38,1707.18C9323.56,1745.46 9319.05,1777.1 9313.83,1802.1L7746.18,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M10190.6,1828.47C10046.1,1828.47 9919.52,1816.36 9810.91,1792.14C9702.31,1767.92 9617.89,1731.2 9557.67,1681.98C9497.44,1632.76 9467.33,1570.65 9467.33,1495.65C9467.33,1439.01 9484.69,1389.6 9519.43,1347.41C9554.15,1305.22 9603.24,1270.46 9666.67,1243.12C9730.11,1215.38 9806.19,1194.87 9894.93,1181.59C9983.66,1168.31 10082.2,1161.67 10190.6,1161.67C10298.6,1161.67 10397.2,1168.31 10486.3,1181.59C10575.5,1194.87 10651.5,1215.38 10714.6,1243.12C10778,1270.46 10827,1305.22 10861.5,1347.41C10896.1,1389.6 10913.3,1439.01 10913.3,1495.65C10913.3,1551.9 10896.1,1600.83 10861.5,1642.43C10827,1684.03 10778,1718.7 10714.6,1746.43C10651.5,1773.78 10575.5,1794.29 10486.3,1807.96C10397.2,1821.63 10298.6,1828.47 10190.6,1828.47ZM10190.6,1766.94C10209.5,1766.94 10226.5,1759.62 10241.5,1744.97C10256.6,1730.32 10269.5,1710.5 10280.4,1685.5C10302,1634.72 10312.9,1571.43 10312.9,1495.65C10312.9,1419.09 10302,1355.22 10280.4,1304.05C10269.5,1279.05 10256.6,1259.13 10241.5,1244.29C10226.5,1229.44 10209.5,1222.02 10190.6,1222.02C10171.4,1222.02 10154.2,1229.44 10139.1,1244.29C10124.1,1259.13 10111.3,1279.05 10100.9,1304.05C10090.1,1329.05 10081.8,1358.06 10076.2,1391.06C10070.6,1424.07 10067.8,1458.93 10067.8,1495.65C10067.8,1531.98 10070.6,1566.55 10076.2,1599.37C10081.8,1632.18 10090.1,1660.89 10100.9,1685.5C10111.3,1710.5 10124.1,1730.32 10139.1,1744.97C10154.2,1759.62 10171.4,1766.94 10190.6,1766.94Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M11680.5,2098C11591.8,2098 11505.8,2096.73 11422.7,2094.19C11339.6,2091.65 11261.9,2085.5 11189.7,2075.73C11166.4,2072.61 11144.8,2069.09 11124.9,2065.18C11105,2061.28 11086.5,2056.79 11069.2,2051.71C11035.1,2041.55 11008.3,2029.35 10988.8,2015.09C10969.3,2000.83 10959.6,1983.54 10959.6,1963.23C10959.6,1947.22 10967.4,1932.86 10983.1,1920.16C10998.7,1907.47 11020,1896.43 11046.9,1887.06C11073.4,1877.68 11104.1,1869.58 11139.1,1862.74C11174,1855.91 11210.7,1850.34 11249.3,1846.04C11216.4,1832.37 11189.8,1815.87 11169.5,1796.53C11149.2,1777.2 11139.1,1755.22 11139.1,1730.62C11139.1,1713.43 11144.3,1697.51 11154.7,1682.86C11165.2,1668.21 11179.4,1654.83 11197.5,1642.72C11233.2,1618.51 11279.6,1600.34 11336.6,1588.23C11278.4,1578.86 11227.2,1565.77 11183,1548.97C11138.9,1532.18 11104.2,1511.18 11079.1,1485.99C11054.1,1460.79 11041.5,1431.01 11041.5,1396.63C11041.5,1355.62 11059.5,1319.97 11095.4,1289.7C11131.3,1259.42 11179,1235.11 11238.4,1216.75C11297.5,1198.39 11365.3,1184.72 11442,1175.73C11518.7,1166.75 11599.2,1162.26 11683.5,1162.26C11753.4,1162.26 11820.9,1165.28 11886.2,1171.34C11951.4,1177.39 12011.7,1187.06 12067.1,1200.34C12080.8,1169.48 12099,1145.46 12121.9,1128.27C12144.8,1111.08 12170.6,1098.68 12199.3,1091.06C12228,1083.45 12257.7,1078.76 12288.5,1077C12319.2,1075.24 12349.2,1074.37 12378.5,1074.37C12397.4,1074.37 12418.1,1074.56 12440.8,1074.95C12463.5,1075.34 12488.3,1076.32 12515.2,1077.88C12512.8,1088.82 12510.6,1103.86 12508.6,1123C12506.6,1142.14 12501.5,1159.13 12493.5,1173.97C12484.7,1190.38 12467.6,1201.71 12442.3,1207.96C12417,1214.21 12393.1,1217.33 12370.7,1217.33L12230.9,1217.33C12215.7,1217.33 12200.9,1217.63 12186.7,1218.21C12172.4,1218.8 12159.1,1220.46 12146.6,1223.19C12200.4,1241.55 12243.7,1265.09 12276.4,1293.8C12309.1,1322.51 12325.5,1356.79 12325.5,1396.63C12325.5,1438.04 12307.6,1472.7 12271.9,1500.63C12236.2,1528.56 12188.6,1550.54 12129.2,1566.55C12069.3,1582.57 12001.4,1594.09 11925.3,1601.12C11849.2,1608.15 11768.6,1611.67 11683.5,1611.67C11638.5,1611.67 11594.4,1610.79 11551,1609.03C11507.6,1607.28 11465.7,1604.25 11425.1,1599.95L11425.7,1602.88C11413.7,1602.88 11402.2,1606.4 11391.4,1613.43C11380.6,1620.46 11375.1,1629.25 11375.1,1639.79C11375.1,1649.17 11380.5,1657.18 11391.1,1663.82C11401.7,1670.46 11415.7,1675.73 11433,1679.64C11450.6,1683.54 11470.8,1686.38 11493.5,1688.13C11516.2,1689.89 11540,1690.77 11564.9,1690.77L11884,1690.77C11957.1,1690.77 12026.9,1691.06 12093.3,1691.65C12159.8,1692.24 12222.1,1697.61 12280.3,1707.76C12334.1,1716.75 12377.9,1733.25 12411.6,1757.28C12445.3,1781.3 12462.2,1815.58 12462.2,1860.11C12462.2,1897.61 12450.8,1929.44 12427.9,1955.62C12405,1981.79 12373.6,2003.56 12333.6,2020.95C12293.7,2038.33 12247.8,2052.29 12196,2062.84C12145,2073 12090.1,2080.62 12031.3,2085.69C11972.5,2090.77 11913.1,2094.09 11853,2095.65C11793,2097.22 11735.5,2098 11680.5,2098ZM11682.9,1547.22C11712.2,1547.22 11735.9,1539.79 11754,1524.95C11772,1510.11 11785.2,1490.58 11793.4,1466.36C11801.6,1442.14 11805.8,1415.77 11805.8,1387.26C11805.8,1341.94 11796.3,1303.76 11777.4,1272.7C11758.6,1241.65 11727.1,1226.12 11682.9,1226.12C11639.1,1226.12 11607.9,1241.65 11589.2,1272.7C11570.6,1303.76 11561.2,1341.94 11561.2,1387.26C11561.2,1415.38 11565.4,1441.65 11573.6,1466.06C11581.8,1490.48 11594.9,1510.11 11612.7,1524.95C11630.6,1539.79 11654,1547.22 11682.9,1547.22ZM11708.2,2035.3C11747.1,2035.3 11786.1,2034.62 11825,2033.25C11864,2031.88 11899.5,2028.66 11931.6,2023.58C11964.1,2018.9 11990.8,2011.87 12011.7,2002.49C12032.6,1993.12 12043,1980.62 12043,1964.99C12043,1947.02 12029.1,1933.25 12001.2,1923.68C11973.3,1914.11 11935.4,1906.98 11887.7,1902.29C11839.5,1897.61 11782.4,1894.78 11716.3,1893.8C11650.3,1892.82 11579.1,1892.33 11502.8,1892.33C11490.8,1892.33 11478.6,1891.94 11466.4,1891.16C11454.1,1890.38 11442,1889.4 11430,1888.23C11404.3,1905.81 11391.4,1929.44 11391.4,1959.13C11391.4,1974.76 11398.9,1987.55 11414,1997.51C11429,2007.47 11450.4,2015.18 11478.1,2020.65C11505.8,2026.12 11539.2,2029.93 11578.1,2032.08C11617.1,2034.23 11660.4,2035.3 11708.2,2035.3Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

+62
View File
@@ -0,0 +1,62 @@
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
SHELL := /bin/sh
SOURCE_LOGO := LinkLog.svg
MAGICK ?= magick
WEB_LOGO := frontend/static/logo.svg
EXTENSION_LOGO := webextension/logo.svg
ICON_FILES := webextension/icon-16.png webextension/icon-32.png webextension/icon-48.png webextension/icon-96.png
GENERATED_LOGOS := $(WEB_LOGO) $(EXTENSION_LOGO) $(ICON_FILES)
EXTENSION_VERSION := $(shell sed -n 's/^[[:space:]]*"version":[[:space:]]*"\([^"]*\)".*/\1/p' webextension/manifest.json | head -n 1)
XPI_FILE := LinkLog-$(EXTENSION_VERSION).xpi
XPI_UNSIGNED_DIR := XPI/unsigned
XPI_SIGNED_DIR := XPI/signed
XPI_OUTPUT := $(XPI_UNSIGNED_DIR)/$(XPI_FILE)
EXTENSION_FILES := manifest.json logo.svg icon-16.png icon-32.png icon-48.png icon-96.png options.css options.html options.js popup.css popup.html popup.js
.PHONY: all logos xpi check-tools clean-generated
all: logos
logos: check-tools $(GENERATED_LOGOS)
xpi: logos
@test -n "$(EXTENSION_VERSION)" || { echo "Error: extension version is missing from webextension/manifest.json" >&2; exit 1; }
@mkdir -p $(XPI_UNSIGNED_DIR) $(XPI_SIGNED_DIR)
@rm -f $(XPI_OUTPUT)
@cd webextension && zip -q -9 "../$(XPI_OUTPUT)" $(EXTENSION_FILES)
@python3 scripts/release/validate_xpi.py "$(XPI_OUTPUT)" webextension/manifest.json
@echo "Created $(XPI_OUTPUT)"
check-tools:
@command -v $(MAGICK) >/dev/null 2>&1 || { echo "Error: ImageMagick '$(MAGICK)' is required. Install ImageMagick and retry." >&2; exit 1; }
$(WEB_LOGO): $(SOURCE_LOGO)
@mkdir -p $(@D)
@cp $< $@
$(EXTENSION_LOGO): $(SOURCE_LOGO)
@mkdir -p $(@D)
@cp $< $@
webextension/icon-16.png: $(SOURCE_LOGO)
@mkdir -p $(@D)
@$(MAGICK) $< -resize 16x16 -gravity center -extent 16x16 $@
webextension/icon-32.png: $(SOURCE_LOGO)
@mkdir -p $(@D)
@$(MAGICK) $< -resize 32x32 -gravity center -extent 32x32 $@
webextension/icon-48.png: $(SOURCE_LOGO)
@mkdir -p $(@D)
@$(MAGICK) $< -resize 48x48 -gravity center -extent 48x48 $@
webextension/icon-96.png: $(SOURCE_LOGO)
@mkdir -p $(@D)
@$(MAGICK) $< -resize 96x96 -gravity center -extent 96x96 $@
clean-generated:
@rm -f $(GENERATED_LOGOS)
+46 -9
View File
@@ -2,14 +2,17 @@
LinkLog is a Firefox extension and Python web service for saving links with a title, comment, timestamp, and tracking parameters removed. The service stores links in SQLite and can publish them through plugins, including Mastodon. LinkLog is a Firefox extension and Python web service for saving links with a title, comment, timestamp, and tracking parameters removed. The service stores links in SQLite and can publish them through plugins, including Mastodon.
For full transparency: The author used vibe coding to create this software.
## Project Layout ## Project Layout
```text ```text
backend/ FastAPI application, services, database, and tests backend/ FastAPI application, services, database, and tests
frontend/ Jinja templates and browser-side assets frontend/ Jinja templates and browser-side assets
webextension/ Firefox Manifest V3 extension webextension/ Firefox Manifest V3 extension
Logo.svg Source logo artwork used by the web and extension interfaces
Dockerfile Backend container image Dockerfile Backend container image
docker-compose.yml App plus Traefik for local proxying docker-compose.yml App with traefik reverse proxy hooks
REQUIREMENTS.md Product requirements REQUIREMENTS.md Product requirements
VIBE/ Conversation and prompt logs VIBE/ Conversation and prompt logs
``` ```
@@ -24,6 +27,8 @@ For local development:
The backend currently uses FastAPI, uvicorn, SQLite, and Pydantic. `httpx2` is included for the Starlette-compatible test client. The backend currently uses FastAPI, uvicorn, SQLite, and Pydantic. `httpx2` is included for the Starlette-compatible test client.
Jinja2 is included for server-rendered HTML templates. Jinja2 is included for server-rendered HTML templates.
The backend version is `0.1.0` and is exposed through the FastAPI/OpenAPI metadata. It can be overridden with `LINKLOG_VERSION`.
LinkLog is licensed under the GNU General Public License, version 3 or any later version. See [LICENSE](LICENSE).
## Local Installation ## Local Installation
@@ -70,6 +75,7 @@ Open these URLs:
- User feed: <http://localhost:8000/alice> - User feed: <http://localhost:8000/alice>
- Profile settings: <http://localhost:8000/profile> - Profile settings: <http://localhost:8000/profile>
- Labels: <http://localhost:8000/labels> - Labels: <http://localhost:8000/labels>
- About: <http://localhost:8000/about>
- Admin page: <http://localhost:8000/admin> - Admin page: <http://localhost:8000/admin>
- Web login: <http://localhost:8000/login> - Web login: <http://localhost:8000/login>
- Health check: <http://localhost:8000/health> - Health check: <http://localhost:8000/health>
@@ -77,13 +83,44 @@ Open these URLs:
The browser extension defaults to `http://localhost:8000`. The browser extension defaults to `http://localhost:8000`.
The supplied `Logo.svg` is bundled as `frontend/static/logo.svg` for web pages and `webextension/logo.svg` for the Firefox popup and settings page.
The visible LinkLog brand text uses the Google Foundry `Asset` font when available, with local fallbacks in the Firefox extension.
## Regenerate Logo Assets
`LinkLog.svg` is the source logo. Install ImageMagick, then regenerate the web logo, extension logo, and Firefox toolbar icons with:
```sh
make logos
```
The generated files are `frontend/static/logo.svg`, `webextension/logo.svg`, and `webextension/icon-16.png`, `icon-32.png`, `icon-48.png`, and `icon-96.png`. Override the ImageMagick executable with `make MAGICK=magick logos` when needed.
Create an installable Firefox XPI bundle with:
```sh
make xpi
```
This creates `XPI/unsigned/LinkLog-0.1.0.xpi` from the `webextension/` package and excludes macOS metadata and minified artifacts. The version is read from `webextension/manifest.json`. The `XPI/signed/` directory is reserved for signed release bundles.
## Releases
Releases run in Gitea Actions when a `v*` tag is pushed. The Docker release version comes from `frontend/version.json`; the Firefox plugin version comes from `webextension/manifest.json`. CI also requires both to match `LINKLOG_VERSION`'s default in `backend/app/core/config.py`.
The signed XPI is produced manually and must be checked into `XPI/signed/LinkLog-<version>.xpi` before creating the tag. The workflow validates the embedded manifest, publishes the XPI and `webextension/updates.json` as Gitea release assets, and publishes Docker images to `git.kolkman.org/olaf/link-log:<version>` and `:latest`.
The extension's `update_url` points at the stable raw repository URL `https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json`. Update `webextension/updates.json` with each signed XPI version and commit it together with the XPI. The release page provides a direct install link at `https://git.kolkman.org/olaf/Link-Log/releases/download/v<version>/LinkLog-<version>.xpi`.
The workflow requires Gitea Actions secrets named `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`, and `GITEA_TOKEN`. The token needs permission to create releases and upload release assets; the registry credentials need permission to push the image.
Appending a username to the root URL, such as `/alice`, opens that user's public feed and profile information. Appending a username to the root URL, such as `/alice`, opens that user's public feed and profile information.
Configuration APIs require a bearer token returned by the login endpoint. User configuration uses the identity in that token. Plugin administration additionally requires an administrator account; the development `alice` account is seeded as an administrator, while `bob` is a standard user. Configuration APIs require a bearer token returned by the login endpoint. User configuration uses the identity in that token. Plugin administration additionally requires an administrator account; the development `alice` account is seeded as an administrator, while `bob` is a standard user.
Users can change their password from the profile page. The current password is required, new passwords must contain at least 8 characters, and the endpoint is `PUT /api/user/password`. Users can change their password from the profile page. The current password is required, new passwords must contain at least 8 characters, and the endpoint is `PUT /api/user/password`.
On the profile page, the authenticated username is displayed as read-only. Users can upload a PNG, JPEG, GIF, or WebP avatar up to 2 MB; uploaded files are stored in the persistent data volume and served by the application. Bio and email fields remain empty until the user provides values. Mastodon settings default to the `mastodon.social` instance and the `From my #LinkLog: "` post prefix. On the profile page, the authenticated username is displayed as read-only. Users can upload a PNG, JPEG, GIF, or WebP avatar up to 2 MB; uploaded files are stored in the persistent data volume and served by the application. Bio and email fields remain empty until the user provides values. Mastodon settings default to the `mastodon.social` instance and the `From my #LinkLog: ` post prefix.
## Run Tests ## Run Tests
@@ -101,6 +138,8 @@ PYTHONPATH=. PYTHONWARNINGS=error pytest backend/tests -q
## Install The Firefox Extension For Development ## Install The Firefox Extension For Development
The extension is an unpacked Firefox extension. No build step is required. The extension is an unpacked Firefox extension. No build step is required.
The toolbar uses square PNG icons generated from the bundled dark-background logo; `logo.svg` remains available for the popup and settings branding.
The manifest includes stable Firefox extension metadata and references the packaged PNG icons for toolbar and add-on installation.
1. Start the backend locally. 1. Start the backend locally.
2. Open Firefox and visit `about:debugging#/runtime/this-firefox`. 2. Open Firefox and visit `about:debugging#/runtime/this-firefox`.
@@ -113,9 +152,11 @@ The extension is an unpacked Firefox extension. No build step is required.
6. Save the settings and login. 6. Save the settings and login.
7. Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it. 7. Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it.
When the extension settings page has a valid session, it shows `<username> logged in at <backend URL>` and a **Sign out** button instead of the login form. Signing out revokes the token and returns the form.
Temporary extensions are removed when Firefox restarts. Reload the extension from `about:debugging` after changing its files. Temporary extensions are removed when Firefox restarts. Reload the extension from `about:debugging` after changing its files.
## Docker And Traefik ## Docker
Create the Docker environment file before starting the stack: Create the Docker environment file before starting the stack:
@@ -132,12 +173,8 @@ The main configurable values are:
| `LINKLOG_SECRET_KEY` | token signing/security secret | required in Docker | | `LINKLOG_SECRET_KEY` | token signing/security secret | required in Docker |
| `LINKLOG_DATABASE_PATH` | SQLite file path inside the container | `/app/backend/data/linklog.db` | | `LINKLOG_DATABASE_PATH` | SQLite file path inside the container | `/app/backend/data/linklog.db` |
| `LINKLOG_TOKEN_EXPIRY_DAYS` | access-token lifetime | `30` | | `LINKLOG_TOKEN_EXPIRY_DAYS` | access-token lifetime | `30` |
| `LINKLOG_TRACKING_PARAMS` | comma-separated tracking parameters | built-in list | | `LINKLOG_TRACKING_PARAMS` | comma-separated tracking parameters (stripped from logged URLs) | built-in list |
| `TRAEFIK_HOST` | hostname routed by Traefik | `localhost` | | `TRAEFIK_HOST` | hostname routed by Traefik | `localhost` |
| `TRAEFIK_HTTP_PORT` | host port for the application proxy | `80` |
| `TRAEFIK_DASHBOARD_PORT` | host port for the dashboard | `8080` |
| `TRAEFIK_DASHBOARD_BIND_ADDRESS` | host address for the dashboard | `127.0.0.1` |
| `TRAEFIK_API_INSECURE` | enable the local dashboard API | `true` |
| `APP_PORT` | direct host port for FastAPI | `8000` | | `APP_PORT` | direct host port for FastAPI | `8000` |
The full set of supported variables is listed in `.env.example`. Application variables are passed into the container by Compose; Docker and Traefik variables are used by Compose itself. The full set of supported variables is listed in `.env.example`. Application variables are passed into the container by Compose; Docker and Traefik variables are used by Compose itself.
@@ -177,7 +214,7 @@ After logging in, open <http://localhost:8000/profile> and save the Mastodon set
- **Instance**: hostname or URL such as `mastodon.social` or `https://mastodon.social` - **Instance**: hostname or URL such as `mastodon.social` or `https://mastodon.social`
- **Access token**: a Mastodon API token with permission to create statuses - **Access token**: a Mastodon API token with permission to create statuses
- **Post prefix**: text placed immediately before the link; defaults to `From my #LinkLog: "` - **Post prefix**: text placed immediately before the link; defaults to `From my #LinkLog: `
Enable the plugin from the admin API or the admin page. New links are saved first and then posted to the configured instance at `/api/v1/statuses`. A Mastodon network failure does not undo the saved link. Enable the plugin from the admin API or the admin page. New links are saved first and then posted to the configured instance at `/api/v1/statuses`. A Mastodon network failure does not undo the saved link.
+138
View File
@@ -373,3 +373,141 @@ Create a page where users can add, delete, or edit labels - Labels can be edited
### Assistant outcome ### Assistant outcome
Added schema migration 4 with label ownership, authenticated user label CRUD endpoints and a `/labels` management page. Label creators can edit/delete their own labels, non-owners are denied, and administrators can delete any label including another user's labels. Added administrator label listing/deletion controls and regression coverage. The strict backend suite passed with 15 tests. Added schema migration 4 with label ownership, authenticated user label CRUD endpoints and a `/labels` management page. Label creators can edit/delete their own labels, non-owners are denied, and administrators can delete any label including another user's labels. Added administrator label listing/deletion controls and regression coverage. The strict backend suite passed with 15 tests.
### User
The profile page shows in red: "can't access property \"classList\", document.querySelector(...) is null"
### Assistant outcome
Found that `profile.js` still queried the removed standalone `#admin-link`, while the current hamburger menu uses `#auth-admin-link`. Updated the selector with an optional null guard, bumped the live asset to `profile.js?v=3`, rebuilt/verified the Docker service, and confirmed the profile route and full strict backend suite with 15 passing tests.
### User
The post prefix is now 'From my #LinkLog: "' make that 'From my #LinkLog: '
### Assistant outcome
Removed the trailing double quote from the default Mastodon post prefix in the publisher, profile UI, fallback, tests, and documentation. Removed the stale minified profile artifact and verified Mastodon/profile tests plus the full strict backend suite with 15 passing tests.
### User
Use the logo.svg in both the plugin as well as on the web page
### Assistant outcome
Bundled the supplied logo into the web static assets and Firefox extension, replaced text-only headers with accessible logo images, added responsive sizing, and verified all web pages, extension references, scripts, and the strict backend suite.
### User
use the logo as the logo on the dropdown for firefox extensions but put it on a dark background
### Assistant outcome
Updated the Firefox popup/dropdown with a dark Catppuccin-style background and matching dark form controls, tags fieldset, status states, and buttons. The existing bundled logo remains the popup brand mark and is readable against the dark surface. Firefox scripts and the strict backend suite passed.
### User
Make sure that the logo is used in the toolbar
### Assistant outcome
Configured the Firefox Manifest V3 action and extension icon declarations to use the bundled `logo.svg` at 16, 32, 48, and 96 pixel entries. Manifest validation, logo asset verification, extension syntax checks, and the strict backend suite passed.
### User
For the logo in the toolbar make the background dark
### Assistant outcome
Added a Catppuccin Mocha `#1e1e2e` background rectangle directly behind the Firefox toolbar logo artwork, preserving the foreground logo and source asset. SVG, manifest, extension scripts, and strict backend validation passed.
### User
Make sure the addon conforms to Firefox guidelines
### Assistant outcome
Reviewed the Firefox WebExtension package, retained permissions required by the active-tab capture flow, added stable Firefox-specific extension identity metadata, and generated exact square 16/32/48/96 PNG icons for toolbar and add-on metadata from the dark logo. Popup and settings branding continue to use `logo.svg`. Manifest paths, icon dimensions, extension scripts, and backend tests were validated successfully.
### User
Create a make file that will regenerate the logo and icon files from LinkLog.svg
### Assistant outcome
Added a root `Makefile` using `LinkLog.svg` as the source. `make logos` regenerates the web SVG, extension SVG, and square 16/32/48/96 PNG icons with ImageMagick. Added tool validation, generated-file cleanup, README instructions, and VIBE logging.
### User
Make sure the addon conforms to Firefox guidelines
### Assistant outcome
Validated the Firefox package and corrected its manifest structure. The toolbar and add-on metadata use exact square PNG icons, popup/settings use the SVG logo, stable Firefox-specific identity metadata is present, all referenced files exist, and extension scripts pass syntax validation. The backend suite remains green.
### User
How come when te container starts after docker compose down -v the database is still populated with old links
### Assistant outcome
Inspected the active container and confirmed it uses `/app/backend/data/linklog.db` from the Compose volume `linklog_linklog_data`, containing 230 links. The current Compose project maps logical `linklog_data` to that prefixed volume. `docker compose down` preserves it; only `docker compose down -v` from the same project removes it. A different directory/project, an external volume, or stopping without `-v` can therefore preserve the data.
### User
Refine the Plugins Settings page. When the user is logged in it shows all "<user> logged in at <url>" and a sign-out button. If the user is not logged in it shows the form as is now.
### Assistant outcome
Updated the Firefox extension settings page to restore valid stored sessions, show `<username> logged in at <backend URL>` with a sign-out button, and show the original login form when signed out or when token validation fails. Sign-out revokes the token when possible, clears stored credentials, and returns to the form. Extension and backend validation passed.
### User
On the plugin, next to the logo, create a link of the form: [LinkLog](https://<host>/<user>)
### Assistant outcome
Added a `LinkLog` link next to the Firefox popup logo. It uses the configured backend origin and authenticated username to target `/<user>/`, preserving HTTP or HTTPS from the configured backend. Popup HTML, scripts, manifest, and backend regression validation passed.
### User
Use the Asset Font from google foundry as font for the LinkLog text
### Assistant outcome
Applied the Google Foundry `Asset` font to visible LinkLog branding on the web and Firefox popup, retaining graceful local fallbacks for restricted or offline extension contexts. Font declarations, branding references, extension scripts, whitespace checks, and the strict backend suite passed.
### User
Create an about page and add an entry in the hamburger menu. The about page explains what the linklog does
### Assistant outcome
Added an `/about` page explaining LinkLog's link capture, cleanup, tagging, filtering, ownership, and Mastodon publishing workflow. Added an always-visible About item to every shared hamburger menu, documented the route, and verified all web routes, scripts, and the strict backend suite with 15 passing tests.
### User
On the bottom of all files add a copyright statement (Olaf Kolkman) and a link to the git.kolkman.org/LinkLog repository in small print
### Assistant outcome
Added small-print attribution footers to all seven web templates and both Firefox extension pages. Each footer contains `Copyright Olaf Kolkman` and a link to `https://git.kolkman.org/olaf/Link-Log`, with matching web and extension styling. All web routes, extension footer references, strict backend tests, and whitespace checks passed.
### User
the link in the site footer must be underlined and in the same color as the other text
### Assistant outcome
Updated the web footer repository link to remain the same muted color as the surrounding footer text and always display an underline. Added a footer rendering assertion and validated the site.
### User
Add copyright sign and 2026 to the copyright notices
### Assistant outcome
Updated all web and Firefox extension footer notices to `© 2026 Olaf Kolkman`, preserving the repository links and small-print styling. Verified all eight user-facing pages, whitespace, and the strict backend suite with 15 passing tests.
### User
in the footer make the link the same color as the mother text, when clicked use a tint from the Catpuccin theme
### Assistant outcome
Updated web and extension footer links to inherit the surrounding footer text color, remain underlined, and use Catppuccin lavender/mauve interaction tints for hover, focus, and active click states. Preserved the current repository URL edits and verified the strict backend suite with 15 passing tests.
### User
Add a GNU License file
### Assistant outcome
Added a root `LICENSE` file declaring GNU General Public License version 3 or any later version (GPL-3.0-or-later), with Olaf Kolkman copyright attribution and the official GNU license reference. Documented the license in `README.md`.
### User
Add a copyright statement to source files refer to GNU license
### Assistant outcome
Added `Copyright © 2026 Olaf Kolkman` and `SPDX-License-Identifier: GPL-3.0-or-later` headers to all 47 non-minified application, test, frontend, extension, build, deployment, and source-logo files. Generated/runtime assets were left untouched. Python and JavaScript compilation, Compose validation, whitespace checks, and the strict backend suite passed with 15 tests.
### User
Add the version to the name of the resulting xpi
### Assistant outcome
Updated the Makefile to read the extension version from `webextension/manifest.json` and produce a versioned archive such as `LinkLog-0.1.0.xpi`. Updated `.gitignore` and README documentation. The generated XPI contains 12 expected extension files and passes `unzip -tq` integrity validation.
### User
Set the version number of the backend to 0.1.0
### Assistant outcome
Added backend setting `version = 0.1.0`, wired it into FastAPI and OpenAPI metadata, and exposed `LINKLOG_VERSION` as an environment override. Added regression coverage and documented the setting. Compose validation and the strict backend suite passed with 15 tests.
### User
Add the creation of an xpi bundle for the webextention to the Makefile
### Assistant outcome
Added a portable `xpi` Makefile target that regenerates logo assets and packages the Firefox extension into `LinkLog.xpi`, excluding metadata and minified artifacts. Added README instructions, ignored the generated XPI, and verified the archive contents and integrity with `unzip -tq`.
+23
View File
@@ -65,6 +65,29 @@
61. Make the tags filtering case incensitive (but maintain case of tags) 61. Make the tags filtering case incensitive (but maintain case of tags)
62. Display the date as "2026 June 18 - 20:22" 62. Display the date as "2026 June 18 - 20:22"
63. Create a page where users can add, delete, or edit labels - Labels can be edited and deleted by the same user that created them. The administrator can delete any label. 63. Create a page where users can add, delete, or edit labels - Labels can be edited and deleted by the same user that created them. The administrator can delete any label.
64. The profile page shows in red: "can't access property \"classList\", document.querySelector(...) is null"
65. The post prefix is now 'From my #LinkLog: "' make that 'From my #LinkLog: '
66. Use the logo.svg in both the plugin as well as on the web page
67. use the logo as the logo on the dropdown for firefox extensions but put it on a dark background
68. Make sure that the logo is used in the toolbar
69. For the logo in the toolbar make the background dark
70. Make sure the addon conforms to Firefox guidelines
71. Create a make file that will regenerate the logo and icon files from LinkLog.svg
72. Make sure the addon conforms to Firefox guidelines
73. How come when te container starts after docker compose down -v the database is still populated with old links
74. Refine the Plugins Settings page. When the user is logged in it shows all "<user> logged in at <url>" and a sign-out button. If the user is not logged in it shows the form as is now.
75. On the plugin, next to the logo, create a link of the form: [LinkLog](https://<host>/<user>)
76. Use the Asset Font from google foundry as font for the LinkLog text
77. Create an about page and add an entry in the hamburger menu. The about page explains what the linklog does
78. On the bottom of all files add a copyright statement (Olaf Kolkman) and a link to the git.kolkman.org/LinkLog repository in small print
79. the link in the site footer must be underlined and in the same color as the other text
80. Add copyright sign and 2026 to the copyright notices
81. in the footer make the link the same color as the mother text, when clicked use a tint from the Catpuccin theme
82. Add a GNU License file
83. Add a copyright statement to source files refer to GNU license
84. Add the version to the name of the resulting xpi
85. Set the version number of the backend to 0.1.0
84. Add the creation of an xpi bundle for the webextention to the Makefile
## Future entries ## Future entries
+4 -2
View File
@@ -1,9 +1,11 @@
# VIBE # VIBE
This code has mostly been vibe coded. For full transparency we maintain a log of prompts and results.
This folder contains an append-only record of the visible Link Log development conversation. This folder contains an append-only record of the visible Link Log development conversation.
- `CHAT_LOG.md` records user requests and assistant responses or outcomes in chronological order. - [`CHAT_LOG.md`](./CHAT_LOG.md) records user requests and assistant responses or outcomes in chronological order.
- `PROMPTS.md` records user prompts separately for quick reference. - [`PROMPTS.md`](PROMPTS.md) records user prompts separately for quick reference.
Only the user-visible conversation is recorded. System, developer, environment, and private tool instructions are intentionally excluded. Only the user-visible conversation is recorded. System, developer, environment, and private tool instructions are intentionally excluded.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -1 +1,4 @@
"""LinkLog backend package.""" """LinkLog backend package."""
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
+5
View File
@@ -1,3 +1,8 @@
"""Legacy admin page wiring."""
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import json import json
from uuid import uuid4 from uuid import uuid4
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from uuid import uuid4 from uuid import uuid4
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import APIRouter, Header, HTTPException, status from fastapi import APIRouter, Header, HTTPException, status
from pydantic import BaseModel from pydantic import BaseModel
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import json import json
from uuid import uuid4 from uuid import uuid4
+4
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from dataclasses import dataclass from dataclasses import dataclass
import os import os
from pathlib import Path from pathlib import Path
@@ -10,6 +13,7 @@ DB_PATH = BASE_DIR / 'data' / 'linklog.db'
@dataclass @dataclass
class Settings: class Settings:
app_name: str = os.getenv('LINKLOG_APP_NAME', 'LinkLog') app_name: str = os.getenv('LINKLOG_APP_NAME', 'LinkLog')
version: str = os.getenv('LINKLOG_VERSION', '0.1.0')
database_url: str = os.getenv('LINKLOG_DATABASE_URL', f'sqlite:///{DB_PATH}') database_url: str = os.getenv('LINKLOG_DATABASE_URL', f'sqlite:///{DB_PATH}')
secret_key: str = os.getenv('LINKLOG_SECRET_KEY', 'dev-secret-key-change-me') secret_key: str = os.getenv('LINKLOG_SECRET_KEY', 'dev-secret-key-change-me')
token_expiry_days: int = int(os.getenv('LINKLOG_TOKEN_EXPIRY_DAYS', '30')) token_expiry_days: int = int(os.getenv('LINKLOG_TOKEN_EXPIRY_DAYS', '30'))
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from hashlib import sha256 from hashlib import sha256
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import sqlite3 import sqlite3
import os import os
from hashlib import sha256 from hashlib import sha256
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
+10 -1
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@@ -9,10 +12,11 @@ from backend.app.api.auth import router as auth_router
from backend.app.api.links import router as links_router from backend.app.api.links import router as links_router
from backend.app.api.public import router as public_router from backend.app.api.public import router as public_router
from backend.app.api.user_config import router as user_config_router from backend.app.api.user_config import router as user_config_router
from backend.app.core.config import settings
from backend.app.database import AVATARS_DIR from backend.app.database import AVATARS_DIR
from backend.app.services.link_service import list_public_links from backend.app.services.link_service import list_public_links
app = FastAPI(title='LinkLog API') app = FastAPI(title='LinkLog API', version=settings.version)
app.mount('/static', StaticFiles(directory='frontend/static'), name='static') app.mount('/static', StaticFiles(directory='frontend/static'), name='static')
app.mount('/media', StaticFiles(directory=AVATARS_DIR), name='media') app.mount('/media', StaticFiles(directory=AVATARS_DIR), name='media')
app.include_router(auth_router, prefix='/api/auth') app.include_router(auth_router, prefix='/api/auth')
@@ -45,6 +49,11 @@ async def labels_page(request: Request):
return templates.TemplateResponse(request, 'labels.html', {}) return templates.TemplateResponse(request, 'labels.html', {})
@app.get('/about', response_class=HTMLResponse)
async def about_page(request: Request):
return templates.TemplateResponse(request, 'about.html', {})
@app.get('/login', response_class=HTMLResponse) @app.get('/login', response_class=HTMLResponse)
async def login_page(request: Request): async def login_page(request: Request):
return templates.TemplateResponse(request, 'login.html', {}) return templates.TemplateResponse(request, 'login.html', {})
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
class BasePlugin: class BasePlugin:
name = 'base' name = 'base'
version = '1.0.0' version = '1.0.0'
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone from datetime import datetime, timezone
from hashlib import sha256 from hashlib import sha256
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone from datetime import datetime, timezone
from uuid import uuid4 from uuid import uuid4
+4 -1
View File
@@ -1,10 +1,13 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import json import json
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from backend.app.plugins.base import BasePlugin from backend.app.plugins.base import BasePlugin
DEFAULT_POST_PREFIX = 'From my #LinkLog: "' DEFAULT_POST_PREFIX = 'From my #LinkLog: '
class DefaultFrontendPlugin(BasePlugin): class DefaultFrontendPlugin(BasePlugin):
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone from datetime import datetime, timezone
from hashlib import sha256 from hashlib import sha256
from uuid import uuid4 from uuid import uuid4
+16 -4
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import json import json
import threading import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -19,6 +22,7 @@ def login_headers(username='alice'):
def test_login_returns_token(): def test_login_returns_token():
assert app.version == '0.1.0'
response = client.post('/api/auth/login', json={ response = client.post('/api/auth/login', json={
'username': 'alice', 'username': 'alice',
'password': 'secret123', 'password': 'secret123',
@@ -234,6 +238,9 @@ def test_logout_revokes_token_and_admin_can_list_plugins():
def test_public_and_admin_pages_render_html(): def test_public_and_admin_pages_render_html():
root_page = client.get('/') root_page = client.get('/')
assert 'LinkLog' in root_page.text assert 'LinkLog' in root_page.text
assert 'src="/static/logo.svg"' in root_page.text
assert 'class="site-footer"' in root_page.text
assert 'https://git.kolkman.org/' in root_page.text
assert 'class="menu-toggle"' in root_page.text assert 'class="menu-toggle"' in root_page.text
assert 'id="auth-menu" class="auth-menu hidden"' in root_page.text assert 'id="auth-menu" class="auth-menu hidden"' in root_page.text
assert 'id="auth-home-link" href="/">Home</a>' in root_page.text assert 'id="auth-home-link" href="/">Home</a>' in root_page.text
@@ -255,8 +262,13 @@ def test_public_and_admin_pages_render_html():
assert client.get('/login').status_code == 200 assert client.get('/login').status_code == 200
login_page = client.get('/login').text login_page = client.get('/login').text
assert 'Sign in' in login_page assert 'Sign in' in login_page
assert 'src="/static/logo.svg"' in login_page
assert 'id="auth-session" class="auth-session hidden"' in login_page assert 'id="auth-session" class="auth-session hidden"' in login_page
assert 'logout.js?v=3' in login_page assert 'logout.js?v=3' in login_page
about_page = client.get('/about')
assert about_page.status_code == 200
assert 'Save the good stuff' in about_page.text
assert 'id="auth-about-link" href="/about"' in about_page.text
assert client.get('/admin').status_code == 200 assert client.get('/admin').status_code == 200
admin_page = client.get('/admin').text admin_page = client.get('/admin').text
assert 'Admin' in admin_page assert 'Admin' in admin_page
@@ -300,7 +312,7 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
assert client.put('/api/user/plugins/mastodon', headers=headers, json={ assert client.put('/api/user/plugins/mastodon', headers=headers, json={
'instance': base_url, 'instance': base_url,
'access_token': 'test-token', 'access_token': 'test-token',
'post_prefix': 'From my #LinkLog: "', 'post_prefix': 'From my #LinkLog: ',
}).status_code == 200 }).status_code == 200
assert client.put('/api/admin/plugins/mastodon', headers=headers, json={'enabled': True}).status_code == 200 assert client.put('/api/admin/plugins/mastodon', headers=headers, json={'enabled': True}).status_code == 200
@@ -315,7 +327,7 @@ def test_link_submission_posts_to_enabled_mastodon_plugin():
assert received['path'] == '/api/v1/statuses' assert received['path'] == '/api/v1/statuses'
assert received['authorization'] == 'Bearer test-token' assert received['authorization'] == 'Bearer test-token'
assert received['body'] == { assert received['body'] == {
'status': 'A useful page\nWorth sharing\nFrom my #LinkLog: "https://example.com/useful #python #web', 'status': 'A useful page\nWorth sharing\nFrom my #LinkLog: https://example.com/useful #python #web',
} }
finally: finally:
server.shutdown() server.shutdown()
@@ -328,12 +340,12 @@ def test_plugin_config_can_be_saved_for_mastodon():
assert client.put('/api/user/plugins/mastodon', headers=headers, json={ assert client.put('/api/user/plugins/mastodon', headers=headers, json={
'instance': 'mastodon.social', 'instance': 'mastodon.social',
'access_token': 'demo-token', 'access_token': 'demo-token',
'post_prefix': 'From my #LinkLog: "', 'post_prefix': 'From my #LinkLog: ',
}).status_code == 200 }).status_code == 200
payload = client.get('/api/user/plugins/mastodon', headers=headers).json() payload = client.get('/api/user/plugins/mastodon', headers=headers).json()
assert payload['instance'] == 'mastodon.social' assert payload['instance'] == 'mastodon.social'
assert payload['post_prefix'] == 'From my #LinkLog: "' assert payload['post_prefix'] == 'From my #LinkLog: '
admin_update = client.put('/api/admin/plugins/mastodon', headers=headers, json={ admin_update = client.put('/api/admin/plugins/mastodon', headers=headers, json={
'enabled': True, 'enabled': True,
+3
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
import sqlite3 import sqlite3
from backend.app.database import DEFAULT_TAGS, apply_migrations, get_schema_version, seed_default_tags from backend.app.database import DEFAULT_TAGS, apply_migrations, get_schema_version, seed_default_tags
+4 -1
View File
@@ -1,3 +1,6 @@
## Copyright © 2026 Olaf Kolkman
## SPDX-License-Identifier: GPL-3.0-or-later
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from backend.app.main import app from backend.app.main import app
@@ -34,7 +37,7 @@ def test_user_config_api_and_profile_page():
assert 'name="avatar" type="file"' in page_response.text assert 'name="avatar" type="file"' in page_response.text
assert 'name="avatar_url"' not in page_response.text assert 'name="avatar_url"' not in page_response.text
assert 'value="mastodon.social"' in page_response.text assert 'value="mastodon.social"' in page_response.text
assert 'From my #LinkLog: &quot;' in page_response.text assert 'value="From my #LinkLog: "' in page_response.text
assert 'id="auth-menu" class="auth-menu hidden"' in page_response.text assert 'id="auth-menu" class="auth-menu hidden"' in page_response.text
assert 'id="auth-home-link" href="/">Home</a>' in page_response.text assert 'id="auth-home-link" href="/">Home</a>' in page_response.text
assert 'id="auth-profile-link" class="hidden"' in page_response.text assert 'id="auth-profile-link" class="hidden"' in page_response.text
+3 -16
View File
@@ -1,3 +1,6 @@
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
services: services:
app: app:
build: build:
@@ -28,22 +31,6 @@ services:
- "traefik.http.routers.linklog.entrypoints=web" - "traefik.http.routers.linklog.entrypoints=web"
- "traefik.http.services.linklog.loadbalancer.server.port=8000" - "traefik.http.services.linklog.loadbalancer.server.port=8000"
traefik:
image: ${TRAEFIK_IMAGE:-traefik:v3.1}
container_name: ${TRAEFIK_CONTAINER_NAME:-linklog-traefik}
command:
- --providers.docker=true
- --entrypoints.web.address=:${TRAEFIK_HTTP_INTERNAL_PORT:-80}
- --api.insecure=${TRAEFIK_API_INSECURE:-true}
ports:
- "${TRAEFIK_HTTP_PORT:-80}:${TRAEFIK_HTTP_INTERNAL_PORT:-80}"
- "${TRAEFIK_DASHBOARD_BIND_ADDRESS:-127.0.0.1}:${TRAEFIK_DASHBOARD_PORT:-8080}:8080"
restart: ${TRAEFIK_RESTART_POLICY:-unless-stopped}
depends_on:
app:
condition: service_healthy
volumes:
- "${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock:ro"
volumes: volumes:
linklog_data: linklog_data:
+3
View File
@@ -1,4 +1,7 @@
#!/bin/sh #!/bin/sh
# Copyright © 2026 Olaf Kolkman
# SPDX-License-Identifier: GPL-3.0-or-later
set -eu set -eu
chown -R linklog:linklog /app/backend/data chown -R linklog:linklog /app/backend/data
+3
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
(() => { (() => {
const pluginList = document.querySelector('#plugin-list'); const pluginList = document.querySelector('#plugin-list');
const adminLabelList = document.querySelector('#admin-label-list'); const adminLabelList = document.querySelector('#admin-label-list');
+3
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
(() => { (() => {
const loginButton = document.querySelector('#auth-login-button'); const loginButton = document.querySelector('#auth-login-button');
const profileLink = document.querySelector('#auth-profile-link'); const profileLink = document.querySelector('#auth-profile-link');
+3
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
const feedEl = document.getElementById('feed'); const feedEl = document.getElementById('feed');
const sortSelect = document.getElementById('sort-select'); const sortSelect = document.getElementById('sort-select');
const userFilter = document.getElementById('user-filter'); const userFilter = document.getElementById('user-filter');
+3
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
const form = document.querySelector('#login-form'); const form = document.querySelector('#login-form');
const status = document.querySelector('#login-status'); const status = document.querySelector('#login-status');
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1804 1712" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-669.006,-255.89)">
<g transform="matrix(0.972876,0,0,1.26057,-24.1588,-587.485)">
<rect x="712.491" y="669.041" width="1854.11" height="1357.72" style="fill:rgb(88,91,112);"/>
</g>
<g transform="matrix(5.14628,0,0,6.12686,41.2191,-8673.98)">
<path d="M141.95,1644.94L141.95,1633.94C157.554,1633.4 169.724,1631.04 178.462,1626.86C187.2,1622.69 193.344,1615.8 196.894,1606.21C200.444,1596.61 202.218,1583.47 202.218,1566.77C202.218,1554.44 201.146,1544.16 199,1535.93C196.855,1527.7 193.285,1521.25 188.292,1516.57C185.016,1513.6 181.154,1511.26 176.707,1509.54C172.26,1507.83 167.15,1506.62 161.376,1505.92C155.603,1505.21 149.128,1504.86 141.95,1504.86L141.95,1493.86L384.076,1493.86L384.076,1504.86C373.388,1504.86 364.221,1505.7 356.575,1507.38C348.929,1509.06 342.708,1512.12 337.909,1516.57C333.111,1521.01 329.581,1527.27 327.319,1535.35C325.056,1543.42 323.925,1553.9 323.925,1566.77L323.925,1633.36C339.06,1633.36 353.532,1631.08 367.341,1626.51C381.15,1621.95 393.399,1616 404.087,1608.66C414.776,1601.41 423.299,1593.3 429.657,1584.32C436.016,1575.35 439.546,1566.61 440.248,1558.11L452.536,1558.11C452.536,1562.4 452.419,1568.35 452.185,1575.96C451.951,1583.56 451.6,1591.85 451.132,1600.82C450.586,1609.95 449.942,1618.34 449.201,1625.99C448.46,1633.63 447.582,1639.95 446.568,1644.94L141.95,1644.94Z" style="fill:rgb(245,224,220);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.135097,0,0,0.266653,668.366,1282.68)">
<path d="M846.315,1802.1L846.315,1747.02C926.615,1744.29 989.248,1732.47 1034.22,1711.57C1079.18,1690.67 1110.8,1656.2 1129.07,1608.15C1147.34,1560.11 1156.47,1494.29 1156.47,1410.69C1156.47,1348.97 1150.95,1297.51 1139.91,1256.3C1128.87,1215.09 1110.5,1182.76 1084.81,1159.33C1067.94,1144.48 1048.07,1132.76 1025.18,1124.17C1002.3,1115.58 975.999,1109.52 946.288,1106.01C916.577,1102.49 883.253,1100.73 846.315,1100.73L846.315,1045.65L2092.36,1045.65L2092.36,1100.73C2037.36,1100.73 1990.18,1104.93 1950.83,1113.33C1911.49,1121.73 1879.47,1137.06 1854.78,1159.33C1830.08,1181.59 1811.92,1212.94 1800.27,1253.37C1788.63,1293.8 1782.81,1346.24 1782.81,1410.69L1782.81,1744.09C1860.7,1744.09 1935.18,1732.66 2006.24,1709.81C2077.31,1686.96 2140.34,1657.18 2195.35,1620.46C2250.35,1584.13 2294.21,1543.51 2326.94,1498.58C2359.66,1453.66 2377.83,1409.91 2381.44,1367.33L2444.68,1367.33C2444.68,1388.82 2444.07,1418.6 2442.87,1456.69C2441.66,1494.78 2439.86,1536.28 2437.45,1581.2C2434.64,1626.9 2431.33,1668.9 2427.51,1707.18C2423.7,1745.46 2419.18,1777.1 2413.96,1802.1L846.315,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3043.37,1093.7C3001.22,1093.7 2961.37,1089.99 2923.83,1082.57C2886.29,1075.15 2853.66,1064.99 2825.96,1052.1C2797.86,1039.21 2775.67,1024.17 2759.41,1006.98C2743.15,989.794 2735.02,971.435 2735.02,951.904C2735.02,924.951 2748.97,900.732 2776.88,879.247C2804.78,857.763 2842.12,840.673 2888.9,827.978C2935.67,815.283 2987.16,808.935 3043.37,808.935C3101.59,808.935 3153.98,815.38 3200.56,828.271C3247.13,841.161 3284.07,858.447 3311.37,880.126C3338.67,901.806 3352.32,925.732 3352.32,951.904C3352.32,977.294 3338.67,1000.83 3311.37,1022.51C3284.07,1044.19 3247.13,1061.47 3200.56,1074.37C3153.98,1087.26 3101.59,1093.7 3043.37,1093.7ZM2498.94,1802.1L2498.94,1747.02C2608.95,1743.12 2686.24,1720.16 2730.81,1678.17C2775.37,1636.18 2797.66,1576.9 2797.66,1500.34C2797.66,1422.22 2776.08,1365.97 2732.91,1331.59C2689.75,1297.22 2618.39,1280.03 2518.82,1280.03L2518.82,1224.95L3327.03,1172.22L3327.03,1500.34C3327.03,1551.12 3333.76,1594.38 3347.2,1630.13C3360.66,1665.87 3386.05,1693.6 3423.39,1713.33C3460.73,1733.06 3515.53,1744.29 3587.8,1747.02L3587.8,1802.1L2498.94,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3625.19,1802.1L3625.19,1747.02C3724.76,1743.12 3796.43,1721.83 3840.19,1683.15C3862.68,1663.62 3878.94,1639.6 3888.97,1611.08C3899.01,1582.57 3904.03,1549.17 3904.03,1510.89C3904.03,1447.22 3894.7,1399.37 3876.03,1367.33C3857.36,1335.3 3827.64,1313.72 3786.89,1302.59C3746.14,1291.45 3692.24,1285.89 3625.19,1285.89L3625.19,1230.81L4433.4,1178.08L4433.4,1274.76C4505.67,1243.51 4581.56,1216.94 4661.05,1195.07C4740.55,1173.19 4822.65,1162.26 4907.37,1162.26C4940.29,1162.26 4973.62,1164.31 5007.34,1168.41C5041.07,1172.51 5073.19,1183.93 5103.7,1202.68C5122.98,1214.79 5139.84,1231.2 5154.29,1251.9C5168.34,1273 5179.59,1299.17 5188.02,1330.42C5196.45,1361.67 5200.66,1399.37 5200.66,1443.51L5199.46,1488.62C5198.26,1504.25 5197.65,1521.24 5197.65,1539.6C5197.65,1573.97 5199.46,1604.15 5203.07,1630.13C5206.69,1656.1 5215.92,1677.68 5230.78,1694.87C5245.23,1712.06 5267.51,1725.05 5297.63,1733.84C5327.74,1742.63 5369.29,1747.02 5422.29,1747.02L5422.29,1802.1L4575.53,1802.1L4575.53,1757.57C4601.63,1741.16 4619.7,1713.62 4629.74,1674.95C4639.77,1636.28 4644.79,1583.35 4644.79,1516.16C4644.79,1457.96 4639.47,1415.09 4628.83,1387.55C4618.19,1360.01 4603.74,1342.14 4585.47,1333.93C4567.2,1325.73 4546.83,1321.63 4524.34,1321.63C4510.29,1321.63 4495.54,1323.1 4480.08,1326.03C4464.62,1328.95 4449.06,1332.96 4433.4,1338.04L4433.4,1526.71C4433.4,1595.07 4438.52,1646.53 4448.76,1681.1C4459,1715.67 4476.97,1741.16 4502.66,1757.57L4502.66,1802.1L3625.19,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M6760.3,1835.5C6693.65,1835.5 6638.55,1833.06 6594.99,1828.17C6551.42,1823.29 6518.2,1814.4 6495.32,1801.51C6481.26,1793.7 6469.62,1783.35 6460.39,1770.46C6451.55,1757.96 6444.73,1743.31 6439.91,1726.51C6435.09,1709.72 6430.67,1690.38 6426.66,1668.51C6422.64,1647.41 6416.92,1627.68 6409.49,1609.33C6402.07,1590.97 6387.21,1575.83 6364.93,1563.91C6342.65,1552 6306.81,1545.85 6257.43,1545.46C6258.23,1615.38 6265.66,1666.45 6279.71,1698.68C6293.76,1730.91 6320.86,1747.02 6361.01,1747.02L6361.01,1802.1L5449.21,1802.1L5449.21,1747.02C5485.35,1745.85 5517.27,1742.92 5544.97,1738.23C5600.78,1728.86 5642.33,1707.96 5669.64,1675.54C5683.69,1658.35 5695.13,1636.87 5703.96,1611.08C5712.4,1585.3 5718.52,1554.15 5722.33,1517.63C5726.15,1481.1 5728.05,1438.43 5728.05,1389.6C5728.05,1315.38 5722.84,1255.22 5712.4,1209.13C5701.96,1163.04 5685.49,1127.1 5663.01,1101.32C5640.53,1075.93 5611.72,1058.74 5576.59,1049.76C5541.46,1040.77 5499,1036.28 5449.21,1036.28L5449.21,981.786L6257.43,929.052L6257.43,1462.84C6286.74,1442.92 6314.84,1422.12 6341.74,1400.44C6368.64,1378.76 6392.33,1358.54 6412.81,1339.79C6457.78,1298.39 6480.26,1272.8 6480.26,1263.04C6480.26,1255.22 6471.63,1250.15 6454.36,1247.8C6437.1,1245.46 6413.61,1244.29 6383.9,1244.29L6383.9,1189.21L7053.6,1189.21L7053.6,1244.29C7005.82,1244.29 6953.42,1249.76 6896.41,1260.69C6839.4,1271.63 6778.97,1288.04 6715.14,1309.91C6650.9,1331.79 6583.85,1359.33 6513.99,1392.53C6444.12,1425.73 6372.46,1464.6 6298.98,1509.13C6373.66,1497.41 6449.04,1488.13 6525.13,1481.3C6601.21,1474.46 6668.36,1471.04 6726.58,1471.04C6810.89,1471.04 6875.33,1480.42 6919.9,1499.17C6965.67,1518.7 6994.58,1550.34 7006.62,1594.09C7013.85,1619.87 7023.18,1641.94 7034.63,1660.3C7046.07,1678.66 7058.21,1693.9 7071.06,1706.01C7083.91,1718.12 7096.86,1727.2 7109.91,1733.25C7122.96,1739.31 7134.3,1742.92 7143.93,1744.09L7143.93,1805.62C7133.49,1808.74 7116.23,1811.96 7092.14,1815.28C7068.05,1818.6 7039.14,1821.83 7005.42,1824.95C6971.29,1828.08 6933.35,1830.62 6891.59,1832.57C6849.84,1834.52 6806.07,1835.5 6760.3,1835.5Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M7746.18,1802.1L7746.18,1747.02C7826.48,1744.29 7889.11,1732.47 7934.08,1711.57C7979.05,1690.67 8010.67,1656.2 8028.93,1608.15C8047.2,1560.11 8056.34,1494.29 8056.34,1410.69C8056.34,1348.97 8050.82,1297.51 8039.77,1256.3C8028.73,1215.09 8010.36,1182.76 7984.67,1159.33C7967.81,1144.48 7947.93,1132.76 7925.05,1124.17C7902.16,1115.58 7875.86,1109.52 7846.15,1106.01C7816.44,1102.49 7783.12,1100.73 7746.18,1100.73L7746.18,1045.65L8992.23,1045.65L8992.23,1100.73C8937.22,1100.73 8890.05,1104.93 8850.7,1113.33C8811.35,1121.73 8779.33,1137.06 8754.64,1159.33C8729.95,1181.59 8711.78,1212.94 8700.14,1253.37C8688.49,1293.8 8682.67,1346.24 8682.67,1410.69L8682.67,1744.09C8760.56,1744.09 8835.04,1732.66 8906.1,1709.81C8977.17,1686.96 9040.2,1657.18 9095.21,1620.46C9150.22,1584.13 9194.08,1543.51 9226.8,1498.58C9259.52,1453.66 9277.69,1409.91 9281.3,1367.33L9344.54,1367.33C9344.54,1388.82 9343.94,1418.6 9342.73,1456.69C9341.53,1494.78 9339.72,1536.28 9337.31,1581.2C9334.5,1626.9 9331.19,1668.9 9327.38,1707.18C9323.56,1745.46 9319.05,1777.1 9313.83,1802.1L7746.18,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M10190.6,1828.47C10046.1,1828.47 9919.52,1816.36 9810.91,1792.14C9702.31,1767.92 9617.89,1731.2 9557.67,1681.98C9497.44,1632.76 9467.33,1570.65 9467.33,1495.65C9467.33,1439.01 9484.69,1389.6 9519.43,1347.41C9554.15,1305.22 9603.24,1270.46 9666.67,1243.12C9730.11,1215.38 9806.19,1194.87 9894.93,1181.59C9983.66,1168.31 10082.2,1161.67 10190.6,1161.67C10298.6,1161.67 10397.2,1168.31 10486.3,1181.59C10575.5,1194.87 10651.5,1215.38 10714.6,1243.12C10778,1270.46 10827,1305.22 10861.5,1347.41C10896.1,1389.6 10913.3,1439.01 10913.3,1495.65C10913.3,1551.9 10896.1,1600.83 10861.5,1642.43C10827,1684.03 10778,1718.7 10714.6,1746.43C10651.5,1773.78 10575.5,1794.29 10486.3,1807.96C10397.2,1821.63 10298.6,1828.47 10190.6,1828.47ZM10190.6,1766.94C10209.5,1766.94 10226.5,1759.62 10241.5,1744.97C10256.6,1730.32 10269.5,1710.5 10280.4,1685.5C10302,1634.72 10312.9,1571.43 10312.9,1495.65C10312.9,1419.09 10302,1355.22 10280.4,1304.05C10269.5,1279.05 10256.6,1259.13 10241.5,1244.29C10226.5,1229.44 10209.5,1222.02 10190.6,1222.02C10171.4,1222.02 10154.2,1229.44 10139.1,1244.29C10124.1,1259.13 10111.3,1279.05 10100.9,1304.05C10090.1,1329.05 10081.8,1358.06 10076.2,1391.06C10070.6,1424.07 10067.8,1458.93 10067.8,1495.65C10067.8,1531.98 10070.6,1566.55 10076.2,1599.37C10081.8,1632.18 10090.1,1660.89 10100.9,1685.5C10111.3,1710.5 10124.1,1730.32 10139.1,1744.97C10154.2,1759.62 10171.4,1766.94 10190.6,1766.94Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M11680.5,2098C11591.8,2098 11505.8,2096.73 11422.7,2094.19C11339.6,2091.65 11261.9,2085.5 11189.7,2075.73C11166.4,2072.61 11144.8,2069.09 11124.9,2065.18C11105,2061.28 11086.5,2056.79 11069.2,2051.71C11035.1,2041.55 11008.3,2029.35 10988.8,2015.09C10969.3,2000.83 10959.6,1983.54 10959.6,1963.23C10959.6,1947.22 10967.4,1932.86 10983.1,1920.16C10998.7,1907.47 11020,1896.43 11046.9,1887.06C11073.4,1877.68 11104.1,1869.58 11139.1,1862.74C11174,1855.91 11210.7,1850.34 11249.3,1846.04C11216.4,1832.37 11189.8,1815.87 11169.5,1796.53C11149.2,1777.2 11139.1,1755.22 11139.1,1730.62C11139.1,1713.43 11144.3,1697.51 11154.7,1682.86C11165.2,1668.21 11179.4,1654.83 11197.5,1642.72C11233.2,1618.51 11279.6,1600.34 11336.6,1588.23C11278.4,1578.86 11227.2,1565.77 11183,1548.97C11138.9,1532.18 11104.2,1511.18 11079.1,1485.99C11054.1,1460.79 11041.5,1431.01 11041.5,1396.63C11041.5,1355.62 11059.5,1319.97 11095.4,1289.7C11131.3,1259.42 11179,1235.11 11238.4,1216.75C11297.5,1198.39 11365.3,1184.72 11442,1175.73C11518.7,1166.75 11599.2,1162.26 11683.5,1162.26C11753.4,1162.26 11820.9,1165.28 11886.2,1171.34C11951.4,1177.39 12011.7,1187.06 12067.1,1200.34C12080.8,1169.48 12099,1145.46 12121.9,1128.27C12144.8,1111.08 12170.6,1098.68 12199.3,1091.06C12228,1083.45 12257.7,1078.76 12288.5,1077C12319.2,1075.24 12349.2,1074.37 12378.5,1074.37C12397.4,1074.37 12418.1,1074.56 12440.8,1074.95C12463.5,1075.34 12488.3,1076.32 12515.2,1077.88C12512.8,1088.82 12510.6,1103.86 12508.6,1123C12506.6,1142.14 12501.5,1159.13 12493.5,1173.97C12484.7,1190.38 12467.6,1201.71 12442.3,1207.96C12417,1214.21 12393.1,1217.33 12370.7,1217.33L12230.9,1217.33C12215.7,1217.33 12200.9,1217.63 12186.7,1218.21C12172.4,1218.8 12159.1,1220.46 12146.6,1223.19C12200.4,1241.55 12243.7,1265.09 12276.4,1293.8C12309.1,1322.51 12325.5,1356.79 12325.5,1396.63C12325.5,1438.04 12307.6,1472.7 12271.9,1500.63C12236.2,1528.56 12188.6,1550.54 12129.2,1566.55C12069.3,1582.57 12001.4,1594.09 11925.3,1601.12C11849.2,1608.15 11768.6,1611.67 11683.5,1611.67C11638.5,1611.67 11594.4,1610.79 11551,1609.03C11507.6,1607.28 11465.7,1604.25 11425.1,1599.95L11425.7,1602.88C11413.7,1602.88 11402.2,1606.4 11391.4,1613.43C11380.6,1620.46 11375.1,1629.25 11375.1,1639.79C11375.1,1649.17 11380.5,1657.18 11391.1,1663.82C11401.7,1670.46 11415.7,1675.73 11433,1679.64C11450.6,1683.54 11470.8,1686.38 11493.5,1688.13C11516.2,1689.89 11540,1690.77 11564.9,1690.77L11884,1690.77C11957.1,1690.77 12026.9,1691.06 12093.3,1691.65C12159.8,1692.24 12222.1,1697.61 12280.3,1707.76C12334.1,1716.75 12377.9,1733.25 12411.6,1757.28C12445.3,1781.3 12462.2,1815.58 12462.2,1860.11C12462.2,1897.61 12450.8,1929.44 12427.9,1955.62C12405,1981.79 12373.6,2003.56 12333.6,2020.95C12293.7,2038.33 12247.8,2052.29 12196,2062.84C12145,2073 12090.1,2080.62 12031.3,2085.69C11972.5,2090.77 11913.1,2094.09 11853,2095.65C11793,2097.22 11735.5,2098 11680.5,2098ZM11682.9,1547.22C11712.2,1547.22 11735.9,1539.79 11754,1524.95C11772,1510.11 11785.2,1490.58 11793.4,1466.36C11801.6,1442.14 11805.8,1415.77 11805.8,1387.26C11805.8,1341.94 11796.3,1303.76 11777.4,1272.7C11758.6,1241.65 11727.1,1226.12 11682.9,1226.12C11639.1,1226.12 11607.9,1241.65 11589.2,1272.7C11570.6,1303.76 11561.2,1341.94 11561.2,1387.26C11561.2,1415.38 11565.4,1441.65 11573.6,1466.06C11581.8,1490.48 11594.9,1510.11 11612.7,1524.95C11630.6,1539.79 11654,1547.22 11682.9,1547.22ZM11708.2,2035.3C11747.1,2035.3 11786.1,2034.62 11825,2033.25C11864,2031.88 11899.5,2028.66 11931.6,2023.58C11964.1,2018.9 11990.8,2011.87 12011.7,2002.49C12032.6,1993.12 12043,1980.62 12043,1964.99C12043,1947.02 12029.1,1933.25 12001.2,1923.68C11973.3,1914.11 11935.4,1906.98 11887.7,1902.29C11839.5,1897.61 11782.4,1894.78 11716.3,1893.8C11650.3,1892.82 11579.1,1892.33 11502.8,1892.33C11490.8,1892.33 11478.6,1891.94 11466.4,1891.16C11454.1,1890.38 11442,1889.4 11430,1888.23C11404.3,1905.81 11391.4,1929.44 11391.4,1959.13C11391.4,1974.76 11398.9,1987.55 11414,1997.51C11429,2007.47 11450.4,2015.18 11478.1,2020.65C11505.8,2026.12 11539.2,2029.93 11578.1,2032.08C11617.1,2034.23 11660.4,2035.3 11708.2,2035.3Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

+3
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
(() => { (() => {
const logoutButton = document.querySelector('#logout-button'); const logoutButton = document.querySelector('#logout-button');
+5 -2
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
(() => { (() => {
const profileForm = document.querySelector('#profile-form'); const profileForm = document.querySelector('#profile-form');
const passwordForm = document.querySelector('#password-form'); const passwordForm = document.querySelector('#password-form');
@@ -5,7 +8,7 @@ const mastodonForm = document.querySelector('#mastodon-form');
const profileLogoutButton = document.querySelector('#logout-button'); const profileLogoutButton = document.querySelector('#logout-button');
const accessToken = localStorage.getItem('linklogAccessToken'); const accessToken = localStorage.getItem('linklogAccessToken');
const defaultMastodonInstance = 'mastodon.social'; const defaultMastodonInstance = 'mastodon.social';
const defaultPostPrefix = 'From my #LinkLog: "'; const defaultPostPrefix = 'From my #LinkLog: ';
function authHeaders(includeJson = false) { function authHeaders(includeJson = false) {
return { return {
@@ -33,7 +36,7 @@ async function loadProfile() {
avatarPreview.classList.remove('hidden'); avatarPreview.classList.remove('hidden');
} }
if (profile.is_admin) { if (profile.is_admin) {
document.querySelector('#admin-link').classList.remove('hidden'); document.querySelector('#auth-admin-link')?.classList.remove('hidden');
} }
profileLogoutButton.classList.remove('hidden'); profileLogoutButton.classList.remove('hidden');
} }
+52 -1
View File
@@ -1,4 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap'); /* Copyright © 2026 Olaf Kolkman */
/* SPDX-License-Identifier: GPL-3.0-or-later */
@import url('https://fonts.googleapis.com/css2?family=Asset&family=DM+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap');
:root { :root {
--base: #1e1e2e; --base: #1e1e2e;
@@ -84,6 +87,20 @@ body::selection {
line-height: 1; line-height: 1;
} }
.site-logo {
display: block;
width: min(260px, 70vw);
height: 150px;
margin-bottom: 12px;
object-fit: contain;
object-position: left center;
}
.site-header .site-logo + h1,
.feed-link {
font-family: 'Asset', 'Space Grotesk', sans-serif;
}
.site-header p { .site-header p {
max-width: 34rem; max-width: 34rem;
margin: 14px 0 0; margin: 14px 0 0;
@@ -193,6 +210,26 @@ main.container {
padding-bottom: 64px; padding-bottom: 64px;
} }
.site-footer {
max-width: 960px;
margin: 0 auto;
padding: 0 24px 24px;
color: var(--muted);
font-size: 0.72rem;
text-align: center;
}
.site-footer a {
color: inherit;
text-decoration: underline;
}
.site-footer a:hover,
.site-footer a:focus-visible,
.site-footer a:active {
color: var(--lavender);
}
.toolbar { .toolbar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -445,6 +482,11 @@ button:disabled {
box-shadow: var(--shadow); box-shadow: var(--shadow);
} }
.about-content {
display: grid;
gap: 14px;
}
.link-item h2 { .link-item h2 {
margin: 0 0 8px; margin: 0 0 8px;
color: var(--text); color: var(--text);
@@ -529,6 +571,11 @@ button:disabled {
font-size: 2.35rem; font-size: 2.35rem;
} }
.site-logo {
width: min(220px, 68vw);
height: 48px;
}
.header-row { .header-row {
align-items: center; align-items: center;
gap: 12px; gap: 12px;
@@ -555,6 +602,10 @@ button:disabled {
padding-top: 18px; padding-top: 18px;
} }
.site-footer {
padding: 0 14px 18px;
}
.toolbar { .toolbar {
gap: 12px; gap: 12px;
padding: 14px; padding: 14px;
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en">
<head>
<meta charset="utf-8" />
<title>About LinkLog</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<header class="site-header">
<div class="container">
<div class="header-row">
<div>
<img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<h1>About</h1>
<p>A quiet place for the links worth keeping.</p>
</div>
<div class="header-actions">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
<a id="auth-admin-link" class="hidden" href="/admin">Admin</a>
<div id="auth-session" class="auth-session hidden"><a id="auth-username" class="user-name" href="/"></a></div>
<button id="logout-button" class="logout-button hidden" type="button">Sign out</button>
</nav>
</div>
</div>
</div>
</header>
<main class="container about-content">
<section class="link-item">
<h2>Save the good stuff</h2>
<p>LinkLog captures the page title, cleaned URL, comment, and timestamp when you save a page from the Firefox extension.</p>
</section>
<section class="link-item">
<h2>Make it yours</h2>
<p>Organize links with up to ten tags, browse the public feed, filter by user or tag, and edit the links you own.</p>
</section>
<section class="link-item">
<h2>Share selectively</h2>
<p>LinkLog stores the feed in SQLite and can publish new links to Mastodon when that plugin is configured.</p>
</section>
<section class="link-item">
<h2>Open source</h2>
<p>LinkLog is open source software. You can run your own instance, or contribute to the project on
<a href="https://git.kolkman.org/olaf/Link-Log">my repository</a>.</p>
</section>
</main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script>
</body>
</html>
+6 -1
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,13 +13,15 @@
<div class="container"> <div class="container">
<div class="header-row"> <div class="header-row">
<div> <div>
<h1>LinkLog Admin</h1> <img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<h1>Admin</h1>
<p>Manage users and plugin configuration</p> <p>Manage users and plugin configuration</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button> <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu"> <nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a> <a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a> <a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a> <a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a> <a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
@@ -70,6 +74,7 @@
</section> </section>
</div> </div>
</main> </main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=2"></script> <script src="/static/logout.js?v=2"></script>
<script src="/static/admin.js?v=3"></script> <script src="/static/admin.js?v=3"></script>
+5 -1
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,13 +13,14 @@
<div class="container"> <div class="container">
<div class="header-row"> <div class="header-row">
<div> <div>
<h1>LinkLog</h1> <img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<p>Public link feed</p> <p>Public link feed</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button> <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu"> <nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a> <a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a> <a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a> <a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a> <a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
@@ -72,6 +75,7 @@
<section id="feed" class="feed" aria-live="polite"></section> <section id="feed" class="feed" aria-live="polite"></section>
</main> </main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script> <script src="/static/logout.js?v=3"></script>
+5
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,6 +13,7 @@
<div class="container"> <div class="container">
<div class="header-row"> <div class="header-row">
<div> <div>
<img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<h1>Labels</h1> <h1>Labels</h1>
<p>Manage your link labels</p> <p>Manage your link labels</p>
</div> </div>
@@ -18,6 +21,7 @@
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button> <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu"> <nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a> <a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a> <a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a> <a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a> <a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
@@ -40,6 +44,7 @@
<div id="label-list" class="plugin-list"></div> <div id="label-list" class="plugin-list"></div>
</section> </section>
</main> </main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script> <script src="/static/logout.js?v=3"></script>
<script src="/static/labels.js?v=1"></script> <script src="/static/labels.js?v=1"></script>
+5 -1
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,13 +13,14 @@
<div class="container"> <div class="container">
<div class="header-row"> <div class="header-row">
<div> <div>
<h1>Sign in</h1> <img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<p>Access your LinkLog settings</p> <p>Access your LinkLog settings</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button> <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu"> <nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a> <a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a> <a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a> <a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a> <a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
@@ -47,6 +50,7 @@
</form> </form>
</section> </section>
</main> </main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=3"></script> <script src="/static/logout.js?v=3"></script>
<script src="/static/login.js"></script> <script src="/static/login.js"></script>
+9 -2
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -10,11 +12,15 @@
<header class="site-header"> <header class="site-header">
<div class="container"> <div class="container">
<div class="header-row"> <div class="header-row">
<div>
<img class="site-logo" src="/static/logo.svg" alt="LinkLog" />
<h1>Profile</h1> <h1>Profile</h1>
</div>
<div class="header-actions"> <div class="header-actions">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button> <button class="menu-toggle" type="button" aria-expanded="false" aria-controls="auth-menu">Menu</button>
<nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu"> <nav id="auth-menu" class="auth-menu hidden" aria-label="Account menu">
<a id="auth-home-link" href="/">Home</a> <a id="auth-home-link" href="/">Home</a>
<a id="auth-about-link" href="/about">About</a>
<a id="auth-login-button" href="/login">Sign in</a> <a id="auth-login-button" href="/login">Sign in</a>
<a id="auth-profile-link" class="hidden" href="/profile">Profile</a> <a id="auth-profile-link" class="hidden" href="/profile">Profile</a>
<a id="auth-labels-link" class="hidden" href="/labels">Labels</a> <a id="auth-labels-link" class="hidden" href="/labels">Labels</a>
@@ -84,15 +90,16 @@
</label> </label>
<label> <label>
Post prefix Post prefix
<input id="mastodon-post-prefix" name="post_prefix" type="text" value="From my #LinkLog: &quot;" /> <input id="mastodon-post-prefix" name="post_prefix" type="text" value="From my #LinkLog: " />
</label> </label>
<button type="submit">Save Mastodon settings</button> <button type="submit">Save Mastodon settings</button>
<p id="mastodon-status" class="status" role="status"></p> <p id="mastodon-status" class="status" role="status"></p>
</form> </form>
</section> </section>
</main> </main>
<footer class="site-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">git.kolkman.org/LinkLog</a></footer>
<script src="/static/auth-header.js?v=3"></script> <script src="/static/auth-header.js?v=3"></script>
<script src="/static/logout.js?v=2"></script> <script src="/static/logout.js?v=2"></script>
<script src="/static/profile.js?v=2"></script> <script src="/static/profile.js?v=3"></script>
</body> </body>
</html> </html>
+3
View File
@@ -0,0 +1,3 @@
{
"version": "0.1.0"
}
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Validate the version and checked-in artifacts for a LinkLog release."""
import json
import re
import sys
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MANIFEST_PATH = ROOT / 'webextension' / 'manifest.json'
FRONTEND_VERSION_PATH = ROOT / 'frontend' / 'version.json'
SETTINGS_PATH = ROOT / 'backend' / 'app' / 'core' / 'config.py'
UPDATES_PATH = ROOT / 'webextension' / 'updates.json'
SIGNED_DIR = ROOT / 'XPI' / 'signed'
def fail(message: str) -> None:
raise SystemExit(f'release validation failed: {message}')
def main() -> None:
manifest = json.loads(MANIFEST_PATH.read_text())
extension_version = manifest.get('version')
if not isinstance(extension_version, str) or not re.fullmatch(r'\d+\.\d+\.\d+', extension_version):
fail('webextension/manifest.json has no valid three-part version')
frontend_version = json.loads(FRONTEND_VERSION_PATH.read_text()).get('version')
if frontend_version != extension_version:
fail(f'frontend version {frontend_version} does not match extension version {extension_version}')
gecko_settings = manifest.get('browser_specific_settings', {}).get('gecko', {})
data_permissions = gecko_settings.get('data_collection_permissions')
if data_permissions != {'required': ['websiteActivity'], 'optional': []}:
fail('Firefox data_collection_permissions must require websiteActivity and have no optional categories')
settings = SETTINGS_PATH.read_text()
match = re.search(r"version: str = os\.getenv\('LINKLOG_VERSION', '([^']+)'\)", settings)
if not match:
fail('backend version default could not be found')
backend_version = match.group(1)
if backend_version != extension_version:
fail(f'backend version {backend_version} does not match extension version {extension_version}')
xpi_path = SIGNED_DIR / f'LinkLog-{extension_version}.xpi'
if not xpi_path.is_file():
fail(f'missing manually signed artifact: {xpi_path.relative_to(ROOT)}')
with zipfile.ZipFile(xpi_path) as archive:
try:
packaged_manifest = json.loads(archive.read('manifest.json'))
except KeyError:
fail('signed XPI does not contain manifest.json')
if packaged_manifest.get('version') != extension_version:
fail('signed XPI manifest version does not match webextension/manifest.json')
if archive.testzip() is not None:
fail('signed XPI contains a corrupt member')
updates = json.loads(UPDATES_PATH.read_text())
addon_id = gecko_settings['id']
update_entries = updates.get('addons', {}).get(addon_id, {}).get('updates', [])
if not any(entry.get('version') == extension_version for entry in update_entries):
fail(f'webextension/updates.json has no update entry for {extension_version}')
print(f'validated LinkLog release {frontend_version}')
print(f'xpi={xpi_path.relative_to(ROOT)}')
if __name__ == '__main__':
main()
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Validate an unsigned LinkLog XPI produced by the Makefile."""
import json
import sys
import zipfile
from pathlib import Path
REQUIRED_FILES = {
'manifest.json',
'logo.svg',
'icon-16.png',
'icon-32.png',
'icon-48.png',
'icon-96.png',
'options.css',
'options.html',
'options.js',
'popup.css',
'popup.html',
'popup.js',
}
def fail(message: str) -> None:
raise SystemExit(f'XPI validation failed: {message}')
def main() -> None:
if len(sys.argv) != 3:
fail('usage: validate_xpi.py <xpi> <source-manifest>')
xpi_path = Path(sys.argv[1])
source_manifest_path = Path(sys.argv[2])
try:
source_manifest = json.loads(source_manifest_path.read_text())
with zipfile.ZipFile(xpi_path) as archive:
names = set(archive.namelist())
corrupt_member = archive.testzip()
if corrupt_member is not None:
fail(f'corrupt archive member: {corrupt_member}')
if 'manifest.json' not in names:
fail('manifest.json is missing')
packaged_manifest = json.loads(archive.read('manifest.json'))
except (OSError, zipfile.BadZipFile, json.JSONDecodeError) as error:
fail(str(error))
missing_files = REQUIRED_FILES - names
if missing_files:
fail(f'missing required files: {", ".join(sorted(missing_files))}')
unexpected_files = names - REQUIRED_FILES
if any(name.startswith('__MACOSX/') or name == '.DS_Store' for name in unexpected_files):
fail('archive contains macOS metadata')
if packaged_manifest != source_manifest:
fail('packaged manifest does not match webextension/manifest.json')
print(f'validated unsigned XPI {xpi_path}')
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1804 1712" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-669.006,-255.89)">
<g transform="matrix(0.972876,0,0,1.26057,-24.1588,-587.485)">
<rect x="712.491" y="669.041" width="1854.11" height="1357.72" style="fill:rgb(88,91,112);"/>
</g>
<g transform="matrix(5.14628,0,0,6.12686,41.2191,-8673.98)">
<path d="M141.95,1644.94L141.95,1633.94C157.554,1633.4 169.724,1631.04 178.462,1626.86C187.2,1622.69 193.344,1615.8 196.894,1606.21C200.444,1596.61 202.218,1583.47 202.218,1566.77C202.218,1554.44 201.146,1544.16 199,1535.93C196.855,1527.7 193.285,1521.25 188.292,1516.57C185.016,1513.6 181.154,1511.26 176.707,1509.54C172.26,1507.83 167.15,1506.62 161.376,1505.92C155.603,1505.21 149.128,1504.86 141.95,1504.86L141.95,1493.86L384.076,1493.86L384.076,1504.86C373.388,1504.86 364.221,1505.7 356.575,1507.38C348.929,1509.06 342.708,1512.12 337.909,1516.57C333.111,1521.01 329.581,1527.27 327.319,1535.35C325.056,1543.42 323.925,1553.9 323.925,1566.77L323.925,1633.36C339.06,1633.36 353.532,1631.08 367.341,1626.51C381.15,1621.95 393.399,1616 404.087,1608.66C414.776,1601.41 423.299,1593.3 429.657,1584.32C436.016,1575.35 439.546,1566.61 440.248,1558.11L452.536,1558.11C452.536,1562.4 452.419,1568.35 452.185,1575.96C451.951,1583.56 451.6,1591.85 451.132,1600.82C450.586,1609.95 449.942,1618.34 449.201,1625.99C448.46,1633.63 447.582,1639.95 446.568,1644.94L141.95,1644.94Z" style="fill:rgb(245,224,220);fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.135097,0,0,0.266653,668.366,1282.68)">
<path d="M846.315,1802.1L846.315,1747.02C926.615,1744.29 989.248,1732.47 1034.22,1711.57C1079.18,1690.67 1110.8,1656.2 1129.07,1608.15C1147.34,1560.11 1156.47,1494.29 1156.47,1410.69C1156.47,1348.97 1150.95,1297.51 1139.91,1256.3C1128.87,1215.09 1110.5,1182.76 1084.81,1159.33C1067.94,1144.48 1048.07,1132.76 1025.18,1124.17C1002.3,1115.58 975.999,1109.52 946.288,1106.01C916.577,1102.49 883.253,1100.73 846.315,1100.73L846.315,1045.65L2092.36,1045.65L2092.36,1100.73C2037.36,1100.73 1990.18,1104.93 1950.83,1113.33C1911.49,1121.73 1879.47,1137.06 1854.78,1159.33C1830.08,1181.59 1811.92,1212.94 1800.27,1253.37C1788.63,1293.8 1782.81,1346.24 1782.81,1410.69L1782.81,1744.09C1860.7,1744.09 1935.18,1732.66 2006.24,1709.81C2077.31,1686.96 2140.34,1657.18 2195.35,1620.46C2250.35,1584.13 2294.21,1543.51 2326.94,1498.58C2359.66,1453.66 2377.83,1409.91 2381.44,1367.33L2444.68,1367.33C2444.68,1388.82 2444.07,1418.6 2442.87,1456.69C2441.66,1494.78 2439.86,1536.28 2437.45,1581.2C2434.64,1626.9 2431.33,1668.9 2427.51,1707.18C2423.7,1745.46 2419.18,1777.1 2413.96,1802.1L846.315,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3043.37,1093.7C3001.22,1093.7 2961.37,1089.99 2923.83,1082.57C2886.29,1075.15 2853.66,1064.99 2825.96,1052.1C2797.86,1039.21 2775.67,1024.17 2759.41,1006.98C2743.15,989.794 2735.02,971.435 2735.02,951.904C2735.02,924.951 2748.97,900.732 2776.88,879.247C2804.78,857.763 2842.12,840.673 2888.9,827.978C2935.67,815.283 2987.16,808.935 3043.37,808.935C3101.59,808.935 3153.98,815.38 3200.56,828.271C3247.13,841.161 3284.07,858.447 3311.37,880.126C3338.67,901.806 3352.32,925.732 3352.32,951.904C3352.32,977.294 3338.67,1000.83 3311.37,1022.51C3284.07,1044.19 3247.13,1061.47 3200.56,1074.37C3153.98,1087.26 3101.59,1093.7 3043.37,1093.7ZM2498.94,1802.1L2498.94,1747.02C2608.95,1743.12 2686.24,1720.16 2730.81,1678.17C2775.37,1636.18 2797.66,1576.9 2797.66,1500.34C2797.66,1422.22 2776.08,1365.97 2732.91,1331.59C2689.75,1297.22 2618.39,1280.03 2518.82,1280.03L2518.82,1224.95L3327.03,1172.22L3327.03,1500.34C3327.03,1551.12 3333.76,1594.38 3347.2,1630.13C3360.66,1665.87 3386.05,1693.6 3423.39,1713.33C3460.73,1733.06 3515.53,1744.29 3587.8,1747.02L3587.8,1802.1L2498.94,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M3625.19,1802.1L3625.19,1747.02C3724.76,1743.12 3796.43,1721.83 3840.19,1683.15C3862.68,1663.62 3878.94,1639.6 3888.97,1611.08C3899.01,1582.57 3904.03,1549.17 3904.03,1510.89C3904.03,1447.22 3894.7,1399.37 3876.03,1367.33C3857.36,1335.3 3827.64,1313.72 3786.89,1302.59C3746.14,1291.45 3692.24,1285.89 3625.19,1285.89L3625.19,1230.81L4433.4,1178.08L4433.4,1274.76C4505.67,1243.51 4581.56,1216.94 4661.05,1195.07C4740.55,1173.19 4822.65,1162.26 4907.37,1162.26C4940.29,1162.26 4973.62,1164.31 5007.34,1168.41C5041.07,1172.51 5073.19,1183.93 5103.7,1202.68C5122.98,1214.79 5139.84,1231.2 5154.29,1251.9C5168.34,1273 5179.59,1299.17 5188.02,1330.42C5196.45,1361.67 5200.66,1399.37 5200.66,1443.51L5199.46,1488.62C5198.26,1504.25 5197.65,1521.24 5197.65,1539.6C5197.65,1573.97 5199.46,1604.15 5203.07,1630.13C5206.69,1656.1 5215.92,1677.68 5230.78,1694.87C5245.23,1712.06 5267.51,1725.05 5297.63,1733.84C5327.74,1742.63 5369.29,1747.02 5422.29,1747.02L5422.29,1802.1L4575.53,1802.1L4575.53,1757.57C4601.63,1741.16 4619.7,1713.62 4629.74,1674.95C4639.77,1636.28 4644.79,1583.35 4644.79,1516.16C4644.79,1457.96 4639.47,1415.09 4628.83,1387.55C4618.19,1360.01 4603.74,1342.14 4585.47,1333.93C4567.2,1325.73 4546.83,1321.63 4524.34,1321.63C4510.29,1321.63 4495.54,1323.1 4480.08,1326.03C4464.62,1328.95 4449.06,1332.96 4433.4,1338.04L4433.4,1526.71C4433.4,1595.07 4438.52,1646.53 4448.76,1681.1C4459,1715.67 4476.97,1741.16 4502.66,1757.57L4502.66,1802.1L3625.19,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M6760.3,1835.5C6693.65,1835.5 6638.55,1833.06 6594.99,1828.17C6551.42,1823.29 6518.2,1814.4 6495.32,1801.51C6481.26,1793.7 6469.62,1783.35 6460.39,1770.46C6451.55,1757.96 6444.73,1743.31 6439.91,1726.51C6435.09,1709.72 6430.67,1690.38 6426.66,1668.51C6422.64,1647.41 6416.92,1627.68 6409.49,1609.33C6402.07,1590.97 6387.21,1575.83 6364.93,1563.91C6342.65,1552 6306.81,1545.85 6257.43,1545.46C6258.23,1615.38 6265.66,1666.45 6279.71,1698.68C6293.76,1730.91 6320.86,1747.02 6361.01,1747.02L6361.01,1802.1L5449.21,1802.1L5449.21,1747.02C5485.35,1745.85 5517.27,1742.92 5544.97,1738.23C5600.78,1728.86 5642.33,1707.96 5669.64,1675.54C5683.69,1658.35 5695.13,1636.87 5703.96,1611.08C5712.4,1585.3 5718.52,1554.15 5722.33,1517.63C5726.15,1481.1 5728.05,1438.43 5728.05,1389.6C5728.05,1315.38 5722.84,1255.22 5712.4,1209.13C5701.96,1163.04 5685.49,1127.1 5663.01,1101.32C5640.53,1075.93 5611.72,1058.74 5576.59,1049.76C5541.46,1040.77 5499,1036.28 5449.21,1036.28L5449.21,981.786L6257.43,929.052L6257.43,1462.84C6286.74,1442.92 6314.84,1422.12 6341.74,1400.44C6368.64,1378.76 6392.33,1358.54 6412.81,1339.79C6457.78,1298.39 6480.26,1272.8 6480.26,1263.04C6480.26,1255.22 6471.63,1250.15 6454.36,1247.8C6437.1,1245.46 6413.61,1244.29 6383.9,1244.29L6383.9,1189.21L7053.6,1189.21L7053.6,1244.29C7005.82,1244.29 6953.42,1249.76 6896.41,1260.69C6839.4,1271.63 6778.97,1288.04 6715.14,1309.91C6650.9,1331.79 6583.85,1359.33 6513.99,1392.53C6444.12,1425.73 6372.46,1464.6 6298.98,1509.13C6373.66,1497.41 6449.04,1488.13 6525.13,1481.3C6601.21,1474.46 6668.36,1471.04 6726.58,1471.04C6810.89,1471.04 6875.33,1480.42 6919.9,1499.17C6965.67,1518.7 6994.58,1550.34 7006.62,1594.09C7013.85,1619.87 7023.18,1641.94 7034.63,1660.3C7046.07,1678.66 7058.21,1693.9 7071.06,1706.01C7083.91,1718.12 7096.86,1727.2 7109.91,1733.25C7122.96,1739.31 7134.3,1742.92 7143.93,1744.09L7143.93,1805.62C7133.49,1808.74 7116.23,1811.96 7092.14,1815.28C7068.05,1818.6 7039.14,1821.83 7005.42,1824.95C6971.29,1828.08 6933.35,1830.62 6891.59,1832.57C6849.84,1834.52 6806.07,1835.5 6760.3,1835.5Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M7746.18,1802.1L7746.18,1747.02C7826.48,1744.29 7889.11,1732.47 7934.08,1711.57C7979.05,1690.67 8010.67,1656.2 8028.93,1608.15C8047.2,1560.11 8056.34,1494.29 8056.34,1410.69C8056.34,1348.97 8050.82,1297.51 8039.77,1256.3C8028.73,1215.09 8010.36,1182.76 7984.67,1159.33C7967.81,1144.48 7947.93,1132.76 7925.05,1124.17C7902.16,1115.58 7875.86,1109.52 7846.15,1106.01C7816.44,1102.49 7783.12,1100.73 7746.18,1100.73L7746.18,1045.65L8992.23,1045.65L8992.23,1100.73C8937.22,1100.73 8890.05,1104.93 8850.7,1113.33C8811.35,1121.73 8779.33,1137.06 8754.64,1159.33C8729.95,1181.59 8711.78,1212.94 8700.14,1253.37C8688.49,1293.8 8682.67,1346.24 8682.67,1410.69L8682.67,1744.09C8760.56,1744.09 8835.04,1732.66 8906.1,1709.81C8977.17,1686.96 9040.2,1657.18 9095.21,1620.46C9150.22,1584.13 9194.08,1543.51 9226.8,1498.58C9259.52,1453.66 9277.69,1409.91 9281.3,1367.33L9344.54,1367.33C9344.54,1388.82 9343.94,1418.6 9342.73,1456.69C9341.53,1494.78 9339.72,1536.28 9337.31,1581.2C9334.5,1626.9 9331.19,1668.9 9327.38,1707.18C9323.56,1745.46 9319.05,1777.1 9313.83,1802.1L7746.18,1802.1Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M10190.6,1828.47C10046.1,1828.47 9919.52,1816.36 9810.91,1792.14C9702.31,1767.92 9617.89,1731.2 9557.67,1681.98C9497.44,1632.76 9467.33,1570.65 9467.33,1495.65C9467.33,1439.01 9484.69,1389.6 9519.43,1347.41C9554.15,1305.22 9603.24,1270.46 9666.67,1243.12C9730.11,1215.38 9806.19,1194.87 9894.93,1181.59C9983.66,1168.31 10082.2,1161.67 10190.6,1161.67C10298.6,1161.67 10397.2,1168.31 10486.3,1181.59C10575.5,1194.87 10651.5,1215.38 10714.6,1243.12C10778,1270.46 10827,1305.22 10861.5,1347.41C10896.1,1389.6 10913.3,1439.01 10913.3,1495.65C10913.3,1551.9 10896.1,1600.83 10861.5,1642.43C10827,1684.03 10778,1718.7 10714.6,1746.43C10651.5,1773.78 10575.5,1794.29 10486.3,1807.96C10397.2,1821.63 10298.6,1828.47 10190.6,1828.47ZM10190.6,1766.94C10209.5,1766.94 10226.5,1759.62 10241.5,1744.97C10256.6,1730.32 10269.5,1710.5 10280.4,1685.5C10302,1634.72 10312.9,1571.43 10312.9,1495.65C10312.9,1419.09 10302,1355.22 10280.4,1304.05C10269.5,1279.05 10256.6,1259.13 10241.5,1244.29C10226.5,1229.44 10209.5,1222.02 10190.6,1222.02C10171.4,1222.02 10154.2,1229.44 10139.1,1244.29C10124.1,1259.13 10111.3,1279.05 10100.9,1304.05C10090.1,1329.05 10081.8,1358.06 10076.2,1391.06C10070.6,1424.07 10067.8,1458.93 10067.8,1495.65C10067.8,1531.98 10070.6,1566.55 10076.2,1599.37C10081.8,1632.18 10090.1,1660.89 10100.9,1685.5C10111.3,1710.5 10124.1,1730.32 10139.1,1744.97C10154.2,1759.62 10171.4,1766.94 10190.6,1766.94Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
<path d="M11680.5,2098C11591.8,2098 11505.8,2096.73 11422.7,2094.19C11339.6,2091.65 11261.9,2085.5 11189.7,2075.73C11166.4,2072.61 11144.8,2069.09 11124.9,2065.18C11105,2061.28 11086.5,2056.79 11069.2,2051.71C11035.1,2041.55 11008.3,2029.35 10988.8,2015.09C10969.3,2000.83 10959.6,1983.54 10959.6,1963.23C10959.6,1947.22 10967.4,1932.86 10983.1,1920.16C10998.7,1907.47 11020,1896.43 11046.9,1887.06C11073.4,1877.68 11104.1,1869.58 11139.1,1862.74C11174,1855.91 11210.7,1850.34 11249.3,1846.04C11216.4,1832.37 11189.8,1815.87 11169.5,1796.53C11149.2,1777.2 11139.1,1755.22 11139.1,1730.62C11139.1,1713.43 11144.3,1697.51 11154.7,1682.86C11165.2,1668.21 11179.4,1654.83 11197.5,1642.72C11233.2,1618.51 11279.6,1600.34 11336.6,1588.23C11278.4,1578.86 11227.2,1565.77 11183,1548.97C11138.9,1532.18 11104.2,1511.18 11079.1,1485.99C11054.1,1460.79 11041.5,1431.01 11041.5,1396.63C11041.5,1355.62 11059.5,1319.97 11095.4,1289.7C11131.3,1259.42 11179,1235.11 11238.4,1216.75C11297.5,1198.39 11365.3,1184.72 11442,1175.73C11518.7,1166.75 11599.2,1162.26 11683.5,1162.26C11753.4,1162.26 11820.9,1165.28 11886.2,1171.34C11951.4,1177.39 12011.7,1187.06 12067.1,1200.34C12080.8,1169.48 12099,1145.46 12121.9,1128.27C12144.8,1111.08 12170.6,1098.68 12199.3,1091.06C12228,1083.45 12257.7,1078.76 12288.5,1077C12319.2,1075.24 12349.2,1074.37 12378.5,1074.37C12397.4,1074.37 12418.1,1074.56 12440.8,1074.95C12463.5,1075.34 12488.3,1076.32 12515.2,1077.88C12512.8,1088.82 12510.6,1103.86 12508.6,1123C12506.6,1142.14 12501.5,1159.13 12493.5,1173.97C12484.7,1190.38 12467.6,1201.71 12442.3,1207.96C12417,1214.21 12393.1,1217.33 12370.7,1217.33L12230.9,1217.33C12215.7,1217.33 12200.9,1217.63 12186.7,1218.21C12172.4,1218.8 12159.1,1220.46 12146.6,1223.19C12200.4,1241.55 12243.7,1265.09 12276.4,1293.8C12309.1,1322.51 12325.5,1356.79 12325.5,1396.63C12325.5,1438.04 12307.6,1472.7 12271.9,1500.63C12236.2,1528.56 12188.6,1550.54 12129.2,1566.55C12069.3,1582.57 12001.4,1594.09 11925.3,1601.12C11849.2,1608.15 11768.6,1611.67 11683.5,1611.67C11638.5,1611.67 11594.4,1610.79 11551,1609.03C11507.6,1607.28 11465.7,1604.25 11425.1,1599.95L11425.7,1602.88C11413.7,1602.88 11402.2,1606.4 11391.4,1613.43C11380.6,1620.46 11375.1,1629.25 11375.1,1639.79C11375.1,1649.17 11380.5,1657.18 11391.1,1663.82C11401.7,1670.46 11415.7,1675.73 11433,1679.64C11450.6,1683.54 11470.8,1686.38 11493.5,1688.13C11516.2,1689.89 11540,1690.77 11564.9,1690.77L11884,1690.77C11957.1,1690.77 12026.9,1691.06 12093.3,1691.65C12159.8,1692.24 12222.1,1697.61 12280.3,1707.76C12334.1,1716.75 12377.9,1733.25 12411.6,1757.28C12445.3,1781.3 12462.2,1815.58 12462.2,1860.11C12462.2,1897.61 12450.8,1929.44 12427.9,1955.62C12405,1981.79 12373.6,2003.56 12333.6,2020.95C12293.7,2038.33 12247.8,2052.29 12196,2062.84C12145,2073 12090.1,2080.62 12031.3,2085.69C11972.5,2090.77 11913.1,2094.09 11853,2095.65C11793,2097.22 11735.5,2098 11680.5,2098ZM11682.9,1547.22C11712.2,1547.22 11735.9,1539.79 11754,1524.95C11772,1510.11 11785.2,1490.58 11793.4,1466.36C11801.6,1442.14 11805.8,1415.77 11805.8,1387.26C11805.8,1341.94 11796.3,1303.76 11777.4,1272.7C11758.6,1241.65 11727.1,1226.12 11682.9,1226.12C11639.1,1226.12 11607.9,1241.65 11589.2,1272.7C11570.6,1303.76 11561.2,1341.94 11561.2,1387.26C11561.2,1415.38 11565.4,1441.65 11573.6,1466.06C11581.8,1490.48 11594.9,1510.11 11612.7,1524.95C11630.6,1539.79 11654,1547.22 11682.9,1547.22ZM11708.2,2035.3C11747.1,2035.3 11786.1,2034.62 11825,2033.25C11864,2031.88 11899.5,2028.66 11931.6,2023.58C11964.1,2018.9 11990.8,2011.87 12011.7,2002.49C12032.6,1993.12 12043,1980.62 12043,1964.99C12043,1947.02 12029.1,1933.25 12001.2,1923.68C11973.3,1914.11 11935.4,1906.98 11887.7,1902.29C11839.5,1897.61 11782.4,1894.78 11716.3,1893.8C11650.3,1892.82 11579.1,1892.33 11502.8,1892.33C11490.8,1892.33 11478.6,1891.94 11466.4,1891.16C11454.1,1890.38 11442,1889.4 11430,1888.23C11404.3,1905.81 11391.4,1929.44 11391.4,1959.13C11391.4,1974.76 11398.9,1987.55 11414,1997.51C11429,2007.47 11450.4,2015.18 11478.1,2020.65C11505.8,2026.12 11539.2,2029.93 11578.1,2032.08C11617.1,2034.23 11660.4,2035.3 11708.2,2035.3Z" style="fill:rgb(203,166,247);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

+20 -1
View File
@@ -13,7 +13,26 @@
], ],
"action": { "action": {
"default_title": "LinkLog", "default_title": "LinkLog",
"default_popup": "popup.html" "default_popup": "popup.html",
"default_icon": {
"16": "icon-16.png",
"32": "icon-32.png"
}
},
"icons": {
"48": "icon-48.png",
"96": "icon-96.png"
},
"browser_specific_settings": {
"gecko": {
"id": "linklog@kolkman.org",
"strict_min_version": "109.0",
"data_collection_permissions": {
"required": ["websiteActivity"],
"optional": []
},
"update_url": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json"
}
}, },
"options_ui": { "options_ui": {
"page": "options.html", "page": "options.html",
+49
View File
@@ -1,3 +1,6 @@
/* Copyright © 2026 Olaf Kolkman */
/* SPDX-License-Identifier: GPL-3.0-or-later */
body { body {
margin: 0; margin: 0;
font-family: sans-serif; font-family: sans-serif;
@@ -18,6 +21,15 @@ h1 {
margin-top: 0; margin-top: 0;
} }
.extension-logo {
display: block;
width: 190px;
height: 52px;
margin-bottom: 8px;
object-fit: contain;
object-position: left center;
}
label { label {
display: block; display: block;
margin-bottom: 16px; margin-bottom: 16px;
@@ -66,6 +78,43 @@ button {
color: #991b1b; color: #991b1b;
} }
.logged-in {
margin-bottom: 16px;
padding: 14px;
border: 1px solid #45475a;
border-radius: 8px;
background: #313244;
color: #cdd6f4;
}
.logged-in p {
margin: 0;
overflow-wrap: anywhere;
}
.logged-in button {
background: #f38ba8;
color: #11111b;
}
.hidden { .hidden {
display: none; display: none;
} }
.extension-footer {
margin: 16px auto;
color: #7f849c;
font-size: 0.68rem;
text-align: center;
}
.extension-footer a {
color: inherit;
text-decoration: underline;
}
.extension-footer a:hover,
.extension-footer a:focus-visible,
.extension-footer a:active {
color: #b4befe;
}
+10 -1
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -7,10 +9,16 @@
</head> </head>
<body> <body>
<main class="options-shell"> <main class="options-shell">
<h1>LinkLog Settings</h1> <img class="extension-logo" src="logo.svg" alt="LinkLog" />
<h1>Settings</h1>
<div id="status" class="status hidden" aria-live="polite"></div> <div id="status" class="status hidden" aria-live="polite"></div>
<section id="logged-in" class="logged-in hidden" aria-live="polite">
<p id="session-summary"></p>
<button type="button" id="sign-out">Sign out</button>
</section>
<form id="settings-form"> <form id="settings-form">
<label> <label>
Backend URL Backend URL
@@ -30,6 +38,7 @@
<button type="submit">Save and log in</button> <button type="submit">Save and log in</button>
</form> </form>
</main> </main>
<footer class="extension-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">Repository</a></footer>
<script src="options.js"></script> <script src="options.js"></script>
</body> </body>
+56 -1
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
const DEFAULT_BACKEND = 'http://localhost:8000'; const DEFAULT_BACKEND = 'http://localhost:8000';
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
@@ -5,6 +8,9 @@ const form = document.getElementById('settings-form');
const backendUrlInput = document.getElementById('backend-url'); const backendUrlInput = document.getElementById('backend-url');
const usernameInput = document.getElementById('username'); const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
const session = document.getElementById('logged-in');
const sessionSummary = document.getElementById('session-summary');
const signOutButton = document.getElementById('sign-out');
function setStatus(message, isError = false) { function setStatus(message, isError = false) {
statusEl.textContent = message; statusEl.textContent = message;
@@ -14,9 +20,41 @@ function setStatus(message, isError = false) {
} }
async function loadSettings() { async function loadSettings() {
const settings = await browser.storage.local.get(['backendUrl', 'username']); const settings = await browser.storage.local.get(['backendUrl', 'username', 'accessToken']);
backendUrlInput.value = settings.backendUrl || DEFAULT_BACKEND; backendUrlInput.value = settings.backendUrl || DEFAULT_BACKEND;
usernameInput.value = settings.username || ''; usernameInput.value = settings.username || '';
if (settings.accessToken && settings.backendUrl) {
try {
const response = await fetch(
`${settings.backendUrl}/api/auth/me?token=${encodeURIComponent(settings.accessToken)}`
);
if (response.ok) {
const user = await response.json();
showLoggedIn(user.username || settings.username, settings.backendUrl);
return;
}
} catch (error) {
// Show the login form when the backend cannot validate the stored token.
}
await clearSession();
}
showLoggedOut();
}
function showLoggedIn(username, backendUrl) {
sessionSummary.textContent = `${username} logged in at ${backendUrl}`;
session.classList.remove('hidden');
form.classList.add('hidden');
}
function showLoggedOut() {
session.classList.add('hidden');
form.classList.remove('hidden');
}
async function clearSession() {
await browser.storage.local.remove(['accessToken', 'tokenType', 'tokenExpiresAt', 'refreshToken']);
} }
async function saveSettingsAndLogin(event) { async function saveSettingsAndLogin(event) {
@@ -51,11 +89,28 @@ async function saveSettingsAndLogin(event) {
refreshToken: data.refresh_token, refreshToken: data.refresh_token,
}); });
showLoggedIn(data.user?.username || username, backendUrl);
passwordInput.value = '';
setStatus('Logged in successfully'); setStatus('Logged in successfully');
} catch (error) { } catch (error) {
setStatus('Unable to log in. Check backend URL and credentials.', true); setStatus('Unable to log in. Check backend URL and credentials.', true);
} }
} }
async function signOut() {
const settings = await browser.storage.local.get(['accessToken']);
if (settings.accessToken) {
await fetch(`${backendUrlInput.value.trim()}/api/auth/logout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: settings.accessToken }),
}).catch(() => undefined);
}
await clearSession();
showLoggedOut();
setStatus('Signed out');
}
form.addEventListener('submit', saveSettingsAndLogin); form.addEventListener('submit', saveSettingsAndLogin);
signOutButton.addEventListener('click', signOut);
loadSettings(); loadSettings();
+72 -15
View File
@@ -1,13 +1,17 @@
/* Copyright © 2026 Olaf Kolkman */
/* SPDX-License-Identifier: GPL-3.0-or-later */
body { body {
width: 360px; width: 360px;
margin: 0; margin: 0;
font-family: sans-serif; font-family: 'Avenir Next', sans-serif;
background: #f5f7fb; background: #1e1e2e;
color: #1f2937; color: #cdd6f4;
} }
.popup-shell { .popup-shell {
padding: 16px; padding: 16px;
background: linear-gradient(145deg, #313244, #1e1e2e 42%);
} }
header h1 { header h1 {
@@ -15,11 +19,41 @@ header h1 {
font-size: 1.2rem; font-size: 1.2rem;
} }
header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.extension-logo {
display: block;
width: 150px;
height: 42px;
margin-bottom: 0;
object-fit: contain;
object-position: left center;
}
.feed-link {
color: #b4befe;
font-family: 'Asset', 'Avenir Next', sans-serif;
font-size: 0.95rem;
font-weight: 700;
text-decoration: none;
}
.feed-link:hover {
color: #cba6f7;
text-decoration: underline;
}
label { label {
display: block; display: block;
margin-bottom: 12px; margin-bottom: 12px;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 600; font-weight: 600;
color: #a6adc8;
} }
input, input,
@@ -33,10 +67,11 @@ button {
input, input,
textarea { textarea {
margin-top: 6px; margin-top: 6px;
border: 1px solid #cbd5e1; border: 1px solid #585b70;
border-radius: 8px; border-radius: 8px;
padding: 8px 10px; padding: 8px 10px;
background: white; background: #313244;
color: #cdd6f4;
} }
textarea { textarea {
@@ -48,12 +83,13 @@ fieldset {
gap: 8px; gap: 8px;
margin: 0; margin: 0;
padding: 10px; padding: 10px;
border: 1px solid #cbd5e1; border: 1px solid #585b70;
border-radius: 8px; border-radius: 8px;
} }
legend { legend {
padding: 0 4px; padding: 0 4px;
color: #b4befe;
font-weight: 600; font-weight: 600;
} }
@@ -76,21 +112,24 @@ legend {
} }
button { button {
border: none; border: 1px solid #cba6f7;
border-radius: 8px; border-radius: 8px;
padding: 10px 12px; padding: 10px 12px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
background: #cba6f7;
color: #11111b;
} }
#submit-link { #submit-link {
background: #2563eb; background: #cba6f7;
color: white; color: #11111b;
} }
.secondary { .secondary {
background: #e2e8f0; background: #45475a;
color: #0f172a; border-color: #585b70;
color: #cdd6f4;
} }
.status { .status {
@@ -101,15 +140,33 @@ button {
} }
.status.success { .status.success {
background: #dcfce7; background: #1e3a38;
color: #166534; color: #94e2d5;
} }
.status.error { .status.error {
background: #fee2e2; background: #45243a;
color: #991b1b; color: #f38ba8;
} }
.hidden { .hidden {
display: none; display: none;
} }
.extension-footer {
margin-top: 16px;
color: #7f849c;
font-size: 0.68rem;
text-align: center;
}
.extension-footer a {
color: inherit;
text-decoration: underline;
}
.extension-footer a:hover,
.extension-footer a:focus-visible,
.extension-footer a:active {
color: #cba6f7;
}
+5 -1
View File
@@ -1,4 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- Copyright © 2026 Olaf Kolkman -->
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -8,7 +10,8 @@
<body> <body>
<main class="popup-shell"> <main class="popup-shell">
<header> <header>
<h1>LinkLog</h1> <img class="extension-logo" src="logo.svg" alt="LinkLog" />
<a id="feed-link" class="feed-link hidden" href="/" target="_blank" rel="noopener noreferrer">LinkLog</a>
</header> </header>
<div id="status" class="status hidden" aria-live="polite"></div> <div id="status" class="status hidden" aria-live="polite"></div>
@@ -41,6 +44,7 @@
</div> </div>
</form> </form>
</main> </main>
<footer class="extension-footer">Copyright © 2026 Olaf Kolkman · <a href="https://git.kolkman.org/olaf/Link-Log">Repository</a></footer>
<script src="popup.js"></script> <script src="popup.js"></script>
</body> </body>
+17
View File
@@ -1,3 +1,6 @@
// Copyright © 2026 Olaf Kolkman
// SPDX-License-Identifier: GPL-3.0-or-later
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
const form = document.getElementById('link-form'); const form = document.getElementById('link-form');
const titleInput = document.getElementById('title'); const titleInput = document.getElementById('title');
@@ -6,6 +9,7 @@ const commentInput = document.getElementById('comment');
const openSettingsButton = document.getElementById('open-settings'); const openSettingsButton = document.getElementById('open-settings');
const existingTags = document.getElementById('existing-tags'); const existingTags = document.getElementById('existing-tags');
const newTagsInput = document.getElementById('new-tags'); const newTagsInput = document.getElementById('new-tags');
const feedLink = document.getElementById('feed-link');
function setStatus(message, isError = false) { function setStatus(message, isError = false) {
statusEl.textContent = message; statusEl.textContent = message;
@@ -23,6 +27,18 @@ async function getSettings() {
return result; return result;
} }
async function updateFeedLink() {
const settings = await browser.storage.local.get(['backendUrl', 'username', 'accessToken']);
if (!settings.backendUrl || !settings.username || !settings.accessToken) return;
try {
const backend = new URL(settings.backendUrl);
feedLink.href = `${backend.origin}/${encodeURIComponent(settings.username)}/`;
feedLink.classList.remove('hidden');
} catch (error) {
feedLink.classList.add('hidden');
}
}
async function loadExistingTags() { async function loadExistingTags() {
const settings = await getSettings(); const settings = await getSettings();
if (!settings.backendUrl || !settings.accessToken) { if (!settings.backendUrl || !settings.accessToken) {
@@ -128,3 +144,4 @@ openSettingsButton.addEventListener('click', () => browser.runtime.openOptionsPa
form.addEventListener('submit', handleSubmit); form.addEventListener('submit', handleSubmit);
populateCurrentTab(); populateCurrentTab();
loadExistingTags(); loadExistingTags();
updateFeedLink();
+12
View File
@@ -0,0 +1,12 @@
{
"addons": {
"linklog@kolkman.org": {
"updates": [
{
"version": "0.1.0",
"update_link": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/XPI/signed/LinkLog-0.1.0.xpi"
}
]
}
}
}