Compare commits
63 Commits
0d3c24da02
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 36cf092ab0 | |||
| 934b471c36 | |||
| c3858fb750 | |||
| b9507378ba | |||
| 56a417ece8 | |||
| 67e3941bcc | |||
| 4aaa1fe32d | |||
| 0f4eca7dff | |||
| 3718999740 | |||
| 2cd062bce0 | |||
| ec2c5dda98 | |||
| 7f79015199 | |||
| 8a2b8c3532 | |||
| 742c61aba2 | |||
| 83a8f9e438 | |||
| 6cc247dd5f | |||
| 1ae4c16782 | |||
| 1673133bba | |||
| f20ec2f258 | |||
| 509943577e | |||
| bf46ee0219 | |||
| c335577045 | |||
| f77e1f64ad | |||
| 9c8b914884 | |||
| 2debeb7f85 | |||
| bbe9726968 | |||
| dc51fa3ba7 | |||
| 64c79d9eb9 | |||
| 3fcf0f71b0 | |||
| ddf2c54880 | |||
| 762c309760 | |||
| 1f964e9b88 | |||
| 438c91d226 | |||
| 92c1b75480 | |||
| 3b3d93e089 | |||
| 14103bc551 | |||
| 07e612699e | |||
| ddfcde30c3 | |||
| 67cf24cf42 | |||
| 8a6ccebbaa | |||
| 828b8ccf8a | |||
| f18f8d7d24 | |||
| 02c3adb06e | |||
| a9a6589f7f | |||
| 7ada67433e | |||
| 7c56b73716 | |||
| 420a3484ca | |||
| 74abf0aac6 | |||
| cbb52d74a4 | |||
| 1b1ea501a8 | |||
| 3f249936ea | |||
| 4e4ad45acd | |||
| a9feece9b7 | |||
| db68be7338 | |||
| 512520dad4 | |||
| ac2bedac72 | |||
| e3b1d7dc89 | |||
| ebe30e375b | |||
| 94cd879449 | |||
| 189ca4c94b | |||
| 510e122a9f | |||
| b8bf5845c6 | |||
| 2d3f967a10 |
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
zola
|
||||
zbuild.sh
|
||||
static/processed_images/
|
||||
themes/yunohost/
|
||||
public/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
Клонируем репозиторий
|
||||
|
||||
```$ git clone https://gitea.zlinux.ru/gitea/zloy_linux/blog.zlinux.ru.git ~/blog```
|
||||
```$ git clone https://gitea.zlinux.ru/zloy_linux/blog.zlinux.ru.git ~/blog```
|
||||
|
||||
переходим в директорию нашего блога
|
||||
|
||||
|
||||
116
config.toml
@@ -200,6 +200,19 @@ fediverse_creator = { handle = "zloy_linux", domain = "zlinux.ru" }
|
||||
show_previous_next_article_links = true
|
||||
invert_previous_next_article_links = true
|
||||
|
||||
# For multilingual sites: show current language code on the language switcher.
|
||||
show_selected_language_code_in_language_switcher = false
|
||||
|
||||
# Enable iine like buttons on all posts: https://iine.to/
|
||||
# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy
|
||||
iine = true
|
||||
iine_icon = "thumbs_up" # See https://iine.to/#customise
|
||||
# Unify like counts across all language versions of the same page.
|
||||
# When enabled, likes on /es/blog/hello/ will count towards /blog/hello/ (default language).
|
||||
iine_unified_languages = true
|
||||
|
||||
|
||||
|
||||
enable_csp = false
|
||||
|
||||
|
||||
@@ -213,7 +226,7 @@ enable_csp = false
|
||||
#]
|
||||
|
||||
|
||||
remote_repository_url = "https://gitea.zlinux.ru/gitea/zloy_linux/blog.zlinux.ru.git"
|
||||
remote_repository_url = "https://gitea.zlinux.ru/zloy_linux/blog.zlinux.ru.git"
|
||||
remote_repository_git_platform = "auto"
|
||||
remote_repository_branch = "main"
|
||||
show_remote_changes = true
|
||||
@@ -227,6 +240,20 @@ code_block_name_links = true
|
||||
|
||||
|
||||
compact_tags = false
|
||||
|
||||
short_date_format = ""
|
||||
|
||||
# Date format used for the archive page.
|
||||
# Default is "06 July" in English and "%d %b" in other languages.
|
||||
archive_date_format = ""
|
||||
|
||||
# Per-language date format overrides.
|
||||
# Examples: Spanish uses "3 de febrero de 2024", German uses "3. Februar 2024"
|
||||
date_formats = [
|
||||
{ lang = "ru", long = "%d %B %Y", short = "%-d %b %Y", archive = "%d %b" },
|
||||
{ lang = "en", long = "%d. %B %Y", short = "%d.%m.%Y" },
|
||||
]
|
||||
|
||||
tag_sorting = "frequency"
|
||||
|
||||
menu = [
|
||||
@@ -235,6 +262,7 @@ menu = [
|
||||
{ name = "articles", url = "articles", trailing_slash = true },
|
||||
{ name = "tags", url = "tags", trailing_slash = true },
|
||||
{ name = "archive", url = "archive", trailing_slash = true },
|
||||
{ name = "git", url = "https://gitea.zlinux.ru/zloy_linux", trailing_slash = true },
|
||||
]
|
||||
|
||||
feed_icon = true
|
||||
@@ -242,24 +270,31 @@ email = "emxveV9saW51eEB6bGludXgucnU="
|
||||
encode_plaintext_email = true
|
||||
|
||||
socials = [
|
||||
{ name = "sharkey", url = "https://zlinux.ru/@zloy_linux", icon = "sharkey" },
|
||||
{ name = "microblog", url = "https://log.zlinux.ru/", icon = "microblog" },
|
||||
{ name = "telegram", url = "https://t.me/#", icon = "telegram" },
|
||||
{ name = "peertube", url = "https://video.zlinux.ru/a/zloy_linux/video-channels", icon = "peertube" },
|
||||
# { name = "peertube", url = "https://video.zlinux.ru/a/zloy_linux/video-channels", icon = "peertube" },
|
||||
# { name = "youtube", url = "https://youtube.com/#", icon = "youtube" },
|
||||
{ name = "pixelfed", url = "https://pixelfed.social/zloy_linuxoid", icon = "pixelfeed" },
|
||||
{ name = "pixelfed", url = "https://pixelfed.social/zloy_linuxoid", icon = "pixelfed" },
|
||||
]
|
||||
|
||||
footer_menu = [
|
||||
# {url = "about", name = "about", trailing_slash = true},
|
||||
# {url = "privacy", name = "privacy", trailing_slash = true},
|
||||
{ name = "projects", url = "projects", trailing_slash = true },
|
||||
{ url = "https://zloy.goatcounter.com", name = "статистика", trailing_slash = true },
|
||||
{ url = "sitemap.xml", name = "sitemap", trailing_slash = false },
|
||||
]
|
||||
|
||||
[extra.analytics]
|
||||
service = "umami"
|
||||
id = "4ee44323-925a-401b-996d-2b6a63dfc527"
|
||||
self_hosted_url = "https://stats.zlinux.ru"
|
||||
#service = "umami"
|
||||
#id = "4ee44323-925a-401b-996d-2b6a63dfc527"
|
||||
#self_hosted_url = "https://stats.zlinux.ru"
|
||||
service = "goatcounter"
|
||||
id = "zloy"
|
||||
#self_hosted_url = "https://zloy.goatcounter.com"
|
||||
|
||||
# Optional: For Umami, enable this option to respect users' Do Not Track (DNT) settings. The default is true.
|
||||
do_not_track = true
|
||||
|
||||
[extra.isso]
|
||||
enabled_for_all_posts = true # Enables Isso on all posts. It can be enabled on individual posts by setting `isso = true` in the [extra] section of a post's front matter.
|
||||
@@ -273,3 +308,70 @@ avatar = true
|
||||
voting = true
|
||||
page_author_hashes = "" # hash (or list of hashes) of the author.
|
||||
lazy_loading = true # Loads when the comments are in the viewport (using the Intersection Observer API).
|
||||
|
||||
|
||||
[extra.webmentions]
|
||||
# To disable for a specific section or page, set webmentions = false in that page/section's front matter's [extra] section.
|
||||
enable = false
|
||||
# Specify the domain registered with webmention.io.
|
||||
domain = ""
|
||||
|
||||
# The HTML ID for the object to fill in with the webmention data.
|
||||
# Defaults to "webmentions"
|
||||
# id = "webmentions"
|
||||
|
||||
# data configuration for the webmention.min.js script
|
||||
# The base URL to use for this page. Defaults to window.location
|
||||
# page_url =
|
||||
|
||||
# Additional URLs to check, separated by |s
|
||||
# add_urls
|
||||
|
||||
# The maximum number of words to render in reply mentions.
|
||||
# wordcount = 20
|
||||
|
||||
# The maximum number of mentions to retrieve. Defaults to 30.
|
||||
# max_webmentions = 30
|
||||
|
||||
# By default, Webmentions render using the mf2 'url' element, which plays
|
||||
# nicely with webmention bridges (such as brid.gy and telegraph)
|
||||
# but allows certain spoofing attacks. If you would like to prevent
|
||||
# spoofing, set this to a non-empty string (e.g. "true").
|
||||
# prevent_spoofing
|
||||
|
||||
# What to order the responses by; defaults to 'published'. See
|
||||
# https://github.com/aaronpk/webmention.io#api
|
||||
# sort_by
|
||||
|
||||
# The order to sort the responses by; defaults to 'up' (i.e. oldest
|
||||
# first). See https://github.com/aaronpk/webmention.io#api
|
||||
# sort_dir
|
||||
|
||||
# If set to a non-empty string (e.g. "true"), will display comment-type responses
|
||||
# (replies/mentions/etc.) as being part of the reactions
|
||||
# (favorites/bookmarks/etc.) instead of in a separate comment list.
|
||||
# comments_are_reactions = "true"
|
||||
|
||||
# h-card configuration
|
||||
# Will identify you on the indieweb (see https://microformats.org/wiki/h-card)
|
||||
[extra.hcard]
|
||||
# Enable home page h-card.
|
||||
enable = true
|
||||
# Add your email to the card if extra.email is set and not encoded.
|
||||
with_mail = true
|
||||
# Add your social links ('socials' config) to the card.
|
||||
with_social_links = true
|
||||
# Homepage url. Defaults to the value of 'base_url'.
|
||||
homepage = "https://zlinux.ru"
|
||||
avatar = "img/avator.webp"
|
||||
# Display name, default to the value of 'author'.
|
||||
full_name = "Zloy Linux"
|
||||
# Small bio, as shown on social media profiles.
|
||||
biography = "Linux Man"
|
||||
#
|
||||
# You can add any property from https://microformats.org/wiki/h-card#Properties
|
||||
# Make sure to replace all '-' characters by '_'
|
||||
# Examples:
|
||||
p_nickname = "zloy_linux"
|
||||
# p_locality = "Bordeaux"
|
||||
p_country_name = "Russia"
|
||||
|
||||
123
content/articles/autoconnect-bluetooth-phones/index.md
Normal file
@@ -0,0 +1,123 @@
|
||||
+++
|
||||
title = "Автоматическое переключение звука на Bluetooth-наушники в Linux с помощью udev и wpctl"
|
||||
date = 2025-09-07
|
||||
description = "Если вы используете Linux и часто подключаете Bluetooth-наушники, наверняка замечали, что звук не всегда автоматически переключается на новое устройство. В этом посте мы разберём простой и надёжный способ автоматизации этого процесса с помощью udev и wpctl (PipeWire)."
|
||||
|
||||
[taxonomies]
|
||||
tags = ["bluetooth", "udev", "wpctl", "pipewire"]
|
||||
|
||||
[extra]
|
||||
quick_navigation_buttons = true
|
||||
toc = true
|
||||
mermaid = false
|
||||
social_media_card = "social_cards/index_autoconnect-bluetooth-phones.webp"
|
||||
+++
|
||||
|
||||
## Вступление
|
||||
|
||||
В Linux есть два популярных звуковых сервиса: PulseAudio и PipeWire. PipeWire постепенно становится стандартом, и в сочетании с WirePlumber можно настроить автоматическое переключение. Но иногда WirePlumber не срабатывает, или событие обрабатывается с задержкой. Решение — использовать udev, который отслеживает события подключения устройств и запускает скрипт.
|
||||
|
||||
## Шаг 1: Проверяем PipeWire и wpctl
|
||||
|
||||
Убедитесь, что PipeWire и WirePlumber запущены:
|
||||
|
||||
```bash
|
||||
systemctl --user status pipewire
|
||||
systemctl --user status wireplumber
|
||||
```
|
||||
|
||||
Также полезно посмотреть список аудиоустройств:
|
||||
|
||||
```bash
|
||||
wpctl status
|
||||
```
|
||||
|
||||
Вы увидите свои колонки и Bluetooth-устройства:
|
||||
|
||||
```ini
|
||||
Audio
|
||||
├─ Sinks:
|
||||
│ * 136. alsa_output.usb-ARTURIA_ArturiaMsd-00.analog-stereo
|
||||
│ 55. bluez_output.XX_XX_XX_XX_XX_XX.a2dp-sink
|
||||
|
||||
```
|
||||
|
||||
## Шаг 2: Создаём скрипт для переключения звука
|
||||
|
||||
Создаём скрипт `/usr/local/bin/bt-audio-autoswitch.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
ACTION=$1
|
||||
STATEFILE="/run/user/$(id -u)/bt-audio-last-sink"
|
||||
LOGTAG="bt-audio-autoswitch"
|
||||
|
||||
# Текущее устройство вывода
|
||||
get_default_sink() {
|
||||
wpctl status | awk '/\*.*sink/ {print $2; exit}' | tr -d '[]'
|
||||
}
|
||||
|
||||
# Идентификатор первого Bluetooth sink
|
||||
get_bt_sink() {
|
||||
wpctl status | awk '/bluez_output/ {print $2; exit}' | tr -d '[]'
|
||||
}
|
||||
|
||||
# Ждём обновления PipeWire
|
||||
sleep 2
|
||||
|
||||
if [ "$ACTION" = "add" ]; then
|
||||
CURRENT=$(get_default_sink)
|
||||
if [ -n "$CURRENT" ]; then
|
||||
echo "$CURRENT" > "$STATEFILE"
|
||||
logger "$LOGTAG: сохранил текущее устройство $CURRENT"
|
||||
fi
|
||||
BT_ID=$(get_bt_sink)
|
||||
if [ -n "$BT_ID" ]; then
|
||||
wpctl set-default "$BT_ID"
|
||||
logger "$LOGTAG: переключил звук на $BT_ID"
|
||||
fi
|
||||
elif [ "$ACTION" = "remove" ]; then
|
||||
if [ -f "$STATEFILE" ]; then
|
||||
PREV=$(cat "$STATEFILE")
|
||||
if [ -n "$PREV" ]; then
|
||||
wpctl set-default "$PREV"
|
||||
logger "$LOGTAG: вернул звук на $PREV"
|
||||
fi
|
||||
rm -f "$STATEFILE"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Делаем скрипт исполняемым:
|
||||
|
||||
```bash
|
||||
sudo chmod +x /usr/local/bin/bt-audio-autoswitch.sh
|
||||
```
|
||||
|
||||
## Шаг 3: Создаём udev-правило
|
||||
|
||||
```ini
|
||||
# Подключение Bluetooth аудио
|
||||
ACTION=="add", SUBSYSTEM=="sound", KERNEL=="card*", RUN+="/usr/local/bin/bt-audio-autoswitch.sh add"
|
||||
|
||||
# Отключение Bluetooth аудио
|
||||
ACTION=="remove", SUBSYSTEM=="sound", KERNEL=="card*", RUN+="/usr/local/bin/bt-audio-autoswitch.sh remove"
|
||||
```
|
||||
|
||||
Применяем новые правила:
|
||||
|
||||
```bash
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
## Шаг 4: Проверяем работу
|
||||
|
||||
Подключаем Bluetooth-наушники. Скрипт автоматически сохранит текущее устройство и переключит звук на наушники.
|
||||
|
||||
Отключаем наушники. Скрипт вернёт звук на сохранённое устройство (например, ваши колонки).
|
||||
|
||||
|
||||
## Заключение
|
||||
|
||||
Использование udev + wpctl позволяет надёжно автоматизировать переключение аудио на Bluetooth-наушники даже в случае, если WirePlumber не успевает сработать. Этот подход легко настраивается и не требует ручного выбора устройства при каждом подключении.
|
||||
|
After Width: | Height: | Size: 109 KiB |
108
content/articles/disable-screensaver-fullscreen/index.md
Normal file
@@ -0,0 +1,108 @@
|
||||
+++
|
||||
title = "Блокируем скринсейвер в полноэкранном режиме браузера Vivaldi"
|
||||
date = 2025-10-31
|
||||
description = "Если при просмотре видео в полноэкранном режиме в Vivaldi (или любом другом браузере) скринсейвер всё равно включается, то в этой статье вы узнаете как это исправить"
|
||||
|
||||
[taxonomies]
|
||||
tags = ["xscreensaver", "wmctrl", "xprop", "X11", "vivaldi", "browser"]
|
||||
|
||||
[extra]
|
||||
quick_navigation_buttons = true
|
||||
toc = false
|
||||
mermaid = false
|
||||
social_media_card = "social_cards/disable-screensaver-fullscreen.png"
|
||||
+++
|
||||
|
||||
Иногда во время просмотра видео или презентации в браузере активируется `xscreensaver` — экран блокируется, несмотря на то, что пользователь фактически «активен».
|
||||
|
||||
Чтобы этого не происходило, можно написать небольшой bash-скрипт, который будет следить за окном браузера и отключать скринсейвер, когда он в полноэкранном режиме.
|
||||
|
||||
## 💾 Установка необходимых пакетов
|
||||
|
||||
```bash
|
||||
sudo pacman -S wmctrl xorg-xprop
|
||||
```
|
||||
|
||||
## 🔧 Скрипт
|
||||
|
||||
Создаём файл `/usr/local/bin/disable_screensaver_fullscreen.sh` и вставляем следующее содержимое:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
XSCREENSAVER_CMD="xscreensaver-command"
|
||||
CHECK_INTERVAL=30
|
||||
|
||||
# Функция для деактивации скринсейвера (вызывается постоянно, пока Vivaldi в полноэкранном режиме)
|
||||
deactivate_screensaver() {
|
||||
# echo "Vivaldi в полноэкранном режиме. Отключаем скринсейвер."
|
||||
"$XSCREENSAVER_CMD" -deactivate >/dev/null 2>&1
|
||||
}
|
||||
|
||||
echo "Мониторинг полноэкранного режима Vivaldi запущен (только деактивация скринсейвера)."
|
||||
|
||||
while true; do
|
||||
VIVALDI_WINDOW_ID=$(wmctrl -l -x | grep "vivaldi-stable.Vivaldi" | awk '{print $1}' | head -n 1)
|
||||
|
||||
if [ -n "$VIVALDI_WINDOW_ID" ]; then # Проверяем, что Vivaldi запущен
|
||||
# Vivaldi запущен, проверяем полноэкранный режим
|
||||
if xprop -id "$VIVALDI_WINDOW_ID" _NET_WM_STATE | grep -q "_NET_WM_STATE_FULLSCREEN"; then
|
||||
deactivate_screensaver # Vivaldi в полноэкранном режиме: деактивируем
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep "$CHECK_INTERVAL"
|
||||
done
|
||||
```
|
||||
|
||||
Сделаем скрипт исполняемым:
|
||||
|
||||
```bash
|
||||
chmod +x /usr/local/bin/disable_screensaver_fullscreen.sh
|
||||
```
|
||||
|
||||
## 🚀 Автозапуск при старте системы
|
||||
|
||||
Создайте файл:
|
||||
`~/.config/autostart/block-screensaver-vivaldi.desktop`
|
||||
|
||||
```ini
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Block Screensaver in Vivaldi Fullscreen
|
||||
Exec=/usr/local/bin/block-screensaver-vivaldi.sh
|
||||
Hidden=false
|
||||
NoDisplay=false
|
||||
X-GNOME-Autostart-enabled=true
|
||||
```
|
||||
|
||||
В bspwm достаточно добавить в `~/.config/bspwm/bspwm.conf` строчку
|
||||
|
||||
```bash
|
||||
/usr/local/bin/vivaldi-stop-screensaver.sh &
|
||||
```
|
||||
|
||||
## 🧠 Как это работает
|
||||
|
||||
Скрипт каждые 30 секунд:
|
||||
|
||||
- Проверяет, запущен ли браузер Vivaldi.
|
||||
- Если окно в состоянии fullscreen, выполняет `xscreensaver-command -deactivate`, тем самым сбрасывая таймер бездействия.
|
||||
|
||||
Такой подход не мешает обычной работе скринсейвера, а только предотвращает его запуск во время полноэкранного просмотра видео.
|
||||
|
||||
- Нагрузка: sleep 30 → менее 0.1% CPU.
|
||||
- Безопасность: только чтение X11, не требует root.
|
||||
- Логи: раскомментируйте echo в функции для отладки.
|
||||
|
||||
## 🧩 Совместимость
|
||||
|
||||
Скрипт можно адаптировать под:
|
||||
|
||||
- Chromium / Brave / Chrome — заменив vivaldi-stable.Vivaldi на chromium.Chromium или соответствующее имя.
|
||||
- Firefox — используйте Navigator.Firefox.
|
||||
|
||||
## ✅ Вывод
|
||||
|
||||
Простой, но эффективный способ избавить себя от внезапного включения заставки во время фильмов, YouTube и стримов.
|
||||
Пусть xscreensaver отдыхает, когда ты смотришь видео — а не мешает! 😄
|
||||
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 67 KiB |
@@ -19,22 +19,31 @@ social_media_card = "social_cards/index-post-ia.webp"
|
||||
|
||||
Этот ИИ оказался самым тупым и выдал
|
||||
|
||||
<img src="img/perplexity.png">
|
||||
|
||||
<div class="gallery">
|
||||
<a href="img/perplexity.png">
|
||||
<img src="img/perplexity.png" alt="Превью">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Grok
|
||||
|
||||
Оказался поумнее
|
||||
|
||||
<img src="img/grok.png">
|
||||
|
||||
<div class="gallery">
|
||||
<a href="img/grok.png">
|
||||
<img src="img/grok.png" alt="Grok">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## chatGPT
|
||||
|
||||
Выдал примерно тоже самое, что и Grok
|
||||
|
||||
<img src="img/chat_gpt.png">
|
||||
|
||||
<div class="gallery">
|
||||
<a href="img/chat_gpt.png">
|
||||
<img src="img/chat_gpt.png" alt="ChatGPT">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Свой вариант
|
||||
|
||||
|
||||
128
content/blog/polybar-stop-screensaver/index.md
Normal file
@@ -0,0 +1,128 @@
|
||||
+++
|
||||
title = "Переписал скрипт stop-screensaver и добавил на панель Polybar"
|
||||
date = 2025-11-22
|
||||
description = "В прошлом [посте](https://blog.zlinux.ru/articles/disable-screensaver-fullscreen/) выкладывал скрипт, который отключал скринсейвер при просмотре видео в браузере. Позже понял, что держать его запущенным постоянно - не лучшая идея."
|
||||
|
||||
[taxonomies]
|
||||
tags = ["xscreensaver", "polybar", "playerctl"]
|
||||
|
||||
[extra]
|
||||
quick_navigation_buttons = true
|
||||
toc = false
|
||||
mermaid = false
|
||||
social_media_card = "social_cards/polybar-stop-screensaver.png"
|
||||
+++
|
||||
|
||||
В прошлом [посте](https://blog.zlinux.ru/articles/disable-screensaver-fullscreen/) выкладывал скрипт, который отключал скринсейвер при просмотре видео в браузере. Позже понял, что держать его запущенным постоянно - не лучшая идея.
|
||||
|
||||
## Новый скрипт
|
||||
|
||||
Переписал скрипт `/usr/local/bin/stop-screensaver.sh` и максимально упростил:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
while true; do
|
||||
status=$(playerctl --player=vivaldi status 2>/dev/null)
|
||||
if [[ "$status" == "Playing" ]]; then
|
||||
xscreensaver-command -deactivate >/dev/null
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
```
|
||||
|
||||
Для удобства написал полноценный модуль для `Polybar`, который позволяет включать и выключать эту функцию по клику. Для работы понадобится всего два небольших скрипта.
|
||||
|
||||
|
||||
## Статусный скрипт
|
||||
|
||||
Показывает иконку "ICON_ON" когда демон запущен, и "ICON_OFF" - когда нет) `/home/$USER/.config/polybar/scripts/stop_screensaver_status.sh`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
STATEFILE="/tmp/stop-screensaver.state"
|
||||
ICON_ON=""
|
||||
ICON_OFF=""
|
||||
|
||||
if [ -f "$STATEFILE" ]; then
|
||||
state=$(cat "$STATEFILE")
|
||||
else
|
||||
state=""
|
||||
fi
|
||||
|
||||
# fallback проверка процесса, если statefile отсутствует/неактуален
|
||||
if pgrep -f "/usr/local/bin/stop-screensaver.sh" >/dev/null; then
|
||||
state="running"
|
||||
fi
|
||||
|
||||
if [ "$state" = "running" ]; then
|
||||
echo " $ICON_ON "
|
||||
else
|
||||
echo " $ICON_OFF "
|
||||
fi
|
||||
```
|
||||
|
||||
|
||||
## Кликабельный скрипт
|
||||
|
||||
Скрипт для запуска/остановки демона. `/home/$USER/.config/polybar/scripts/stop_screensaver_toggle.sh`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
SCRIPT="/usr/local/bin/stop-screensaver.sh"
|
||||
STATEFILE="/tmp/stop-screensaver.state"
|
||||
|
||||
if pgrep -f "$SCRIPT" >/dev/null; then
|
||||
pkill -f "$SCRIPT"
|
||||
else
|
||||
nohup "$SCRIPT" >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# записать текущее состояние
|
||||
if pgrep -f "$SCRIPT" >/dev/null; then
|
||||
echo "running" >"$STATEFILE"
|
||||
else
|
||||
echo "stopped" >"$STATEFILE"
|
||||
fi
|
||||
```
|
||||
|
||||
Делаем файлы исполняемыми
|
||||
|
||||
```bash
|
||||
chmod +x ~/.config/polybar/scripts/stop_screensaver_*.sh
|
||||
```
|
||||
|
||||
|
||||
## Добавляем в polybar
|
||||
|
||||
В конфиге `/home/$USER/.config/polybar/config.ini` прописываем:
|
||||
|
||||
```ini
|
||||
[module/stop_screensaver]
|
||||
type = custom/script
|
||||
exec = ~/.config/polybar/scripts/stop_screensaver_status.sh
|
||||
interval = 2
|
||||
click-left = ~/.config/polybar/scripts/stop_screensaver_toggle.sh
|
||||
label-background = ${gruvbox.green-alt}
|
||||
label-foreground = ${gruvbox.black}
|
||||
```
|
||||
|
||||
и добавляем расположение модуля `stop_screensaver` в modules-left/modules-center/modules-right
|
||||
|
||||
|
||||
<div class="gallery">
|
||||
<a href="stop-screensaver-off.png" data-caption="Значок на панеле Polybar">
|
||||
<img src="stop-screensaver-off.png" alt="stop_screensaver выключен">
|
||||
</a>
|
||||
<a href="stop-screensaver-on.png">
|
||||
<img src="stop-screensaver-on.png" alt="stop_screensaver включен">
|
||||
</a>
|
||||
...
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 72 KiB |
BIN
content/blog/polybar-stop-screensaver/stop-screensaver-off.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
content/blog/polybar-stop-screensaver/stop-screensaver-on.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
100
content/blog/refused-syncthing/index.md
Normal file
@@ -0,0 +1,100 @@
|
||||
+++
|
||||
title = "Отказался от Syncthing"
|
||||
date = 2025-11-28
|
||||
description = "За последние годы я был преданным пользователем Syncthing. Он честно отрабатывал синхронизацию дотфайлов, музыки, баз паролей и хранилища Obsidian между ПК, домашним сервером и телефоном."
|
||||
|
||||
[taxonomies]
|
||||
tags = ["syncthing", "rsync", "music", "keepass", "seafile", "webdav", "git", "obsidian", "sync", "android", "archlinux"]
|
||||
|
||||
[extra]
|
||||
quick_navigation_buttons = true
|
||||
toc = false
|
||||
mermaid = false
|
||||
social_media_card = "social_cards/refused-syncthing.png"
|
||||
+++
|
||||
|
||||
За последние годы я был преданным пользователем `Syncthing`. Он честно отрабатывал синхронизацию дотфайлов, музыки, баз паролей и хранилища `Obsidian` между ПК, домашним сервером и телефоном.
|
||||
|
||||
Но со временем накопился «технический долг»: постоянные конфликты в `KeePassXC`, лишний трафик, батарея на телефоне, а главное — ощущение, что я использую универсальный молоток там, где достаточно отвёртки. В итоге я полностью отказался от `Syncthing` и перешёл на три узкоспециализированных, но гораздо более надёжных и лёгких решения.
|
||||
|
||||
## 1. Музыка для Navidrome → rsync (односторонняя синхронизация)
|
||||
|
||||
Раньше я синхронизировал ~60 ГБ музыки в обе стороны через `Syncthing`.
|
||||
|
||||
Теперь всё просто: на ПК лежит «золотая» копия библиотеки, а на сервере я обновляю её только когда хочу.
|
||||
|
||||
```bash
|
||||
rsync -avzh --progress --stats --delete --itemize-changes --exclude='ненужное' /home/zloy_linux/Музыка/ user@server.ru:/home/user/music/
|
||||
```
|
||||
|
||||
Почему `rsync`, а не `Syncthing`:
|
||||
|
||||
- Никаких конфликтов и дублей
|
||||
- --delete убирает удалённые файлы
|
||||
- Полный контроль: я решаю, когда и что синхронизировать
|
||||
- Можно запускать по расписанию через cron/anacron или вручную после добавления альбомов
|
||||
|
||||
**Значение опций:**
|
||||
**-z** сжатие (полезно, если канал медленный).
|
||||
**-h** человекочитаемые размеры.
|
||||
**--stats** - статистика по передаче.
|
||||
**--delete** - убирает удалённые файлы
|
||||
**--exclude** исключения.
|
||||
|
||||
## 2. Пароли KeePassXC → Seafile + WebDAV (одна общая база)
|
||||
|
||||
`Syncthing` постоянно создавал .sync-conflict файлы при одновременном редактировании базы с телефона и ПК. Решение оказалось проще простого:
|
||||
|
||||
1. Поднял `Seafile`
|
||||
2. Включил `WebDAV` в библиотеке
|
||||
3. В `KeePassXC` на всех устройствах указал один и тот же URL:
|
||||
|
||||
```ini
|
||||
https://cloud.userdomain.ru:/seafdav/library/sync/paroli.kdbx
|
||||
```
|
||||
|
||||
Теперь:
|
||||
|
||||
- Одна база, никаких конфликтов
|
||||
- Автоматическая блокировка при открытии
|
||||
- История изменений в Seafile на 180 дней (если вдруг что-то удалишь)
|
||||
|
||||
## 3. Заметки Obsidian → Git
|
||||
|
||||
Раньше я синхронизировал всё хранилице через `Syncthing` → конфликты, битые ссылки, тормоза на мобильном. Сейчас:
|
||||
|
||||
- Хранилище лежит в приватном репозитории на своём Gitea (можно GitHub, GitLab, Codeberg)
|
||||
- В `Obsidian` установлен плагин `Git`
|
||||
- Настройки плагина:
|
||||
|
||||
```ini
|
||||
Commit message: "auto commit: {{date}}"
|
||||
Auto commit every: 3 минуты
|
||||
Auto pull/push on startup: включено
|
||||
Pull on mobile: включено
|
||||
```
|
||||
|
||||
На телефоне - приложение `Obsidian` + `git`. Плюсы:
|
||||
|
||||
- Полная история изменений (можно откатить удалённую заметку за секунду)
|
||||
- Никаких конфликтов - `git` сам делает merge
|
||||
- Работает даже без интернета (коммиты локально, пуш при подключении)
|
||||
|
||||
## Итог: что я получил после отказа от Syncthing
|
||||
|
||||
|
||||
|
||||
| Что синхронизирую | Было (Syncthing) | Стало (2025) | Выигрыш |
|
||||
|-------------------------|------------------------------------------|---------------------------------------|----------------------------------------------------|
|
||||
| Музыка (~60 ГБ) | Двусторонняя синхронизация, конфликты | rsync (односторонне) | -90 % трафика, нет дублей, полный контроль |
|
||||
| Пароли (KeePassXC) | Постоянные .kdbx.sync-conflict файлы | Seafile + WebDAV (одна общая база) | Ноль конфликтов, блокировка, история изменений |
|
||||
| Заметки (Obsidian) | Конфликты, битые ссылки, тормоза | Obsidian Git + личный Gitea | История коммитов, ветки, оффлайн-работа, автопуш |
|
||||
| Батарея телефона | Syncthing в фоне всё время | Только rsync по необходимости | +2–3 часа автономности |
|
||||
| Трафик и нагрузка | Постоянный скан и передача | Только когда я сам запускаю | Минимальный фоновый трафик |
|
||||
|
||||
|
||||
**Syncthing** - отличный инструмент, но он решает задачу «синхронизировать всё со всем».
|
||||
|
||||
А мне оказалось достаточно трёх точечных решений, которые делают ровно то, что нужно - и ничего лишнего.
|
||||
|
||||
2025 год — год простых и надёжных решений. `rsync` + `Seafile` `WebDAV` + `Obsidian` `Git`.
|
||||
|
After Width: | Height: | Size: 90 KiB |
@@ -17,7 +17,11 @@ social_media_card = "social_cards/index-post-predprosmotr.webp"
|
||||
|
||||
При добавлении в пост ссылки должна отображаться предварительная информация о содержимом веб-страницы, а в данном случае получалось вот это
|
||||
|
||||
<img src="not_preview.webp" />
|
||||
<div class="gallery">
|
||||
<a href="not_preview.webp">
|
||||
<img src="not_preview.webp" alt="Предварительный просмотр недоступен">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Поначалу копался в настройках инстанса, но оказалось, проблема крылась во внутреннем DNS, который возвращал для домена локальный IP-адрес
|
||||
|
||||
@@ -34,7 +38,11 @@ Address: ::1
|
||||
|
||||
Сменил DNS сервер, стало выдавать глобальный IP и всё заработало.
|
||||
|
||||
<img src="yes_preview.webp" />
|
||||
<div class="gallery">
|
||||
<a href="yes_preview.webp">
|
||||
<img src="yes_preview.webp" alt="Предварительный просмотр доступен">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Поигрался с ссылками и оказалось
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ social_media_card = "social_cards/index_zamena-ssd-server.webp"
|
||||
|
||||
Среди SATA SSD ассортимент оказался шире, и в наличии был [Samsung 870 EVO](https://www.dns-shop.ru/product/cd3ad695f76bed20/500-gb-25-sata-nakopitel-samsung-870-evo-mz-77e500bw/). Такой же, но в формате `M.2 NVMe`, уже два года работает в моём основном ПК без проблем. Выбор очевиден.
|
||||
|
||||
<img src="samsung-ssd.webp" />
|
||||
<div class="gallery">
|
||||
<a href="samsung-ssd.webp">
|
||||
<img src="samsung-ssd.webp" alt="Samsung 870 EVO и какой - то ноунейм">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Замена SSD
|
||||
|
||||
@@ -31,7 +35,11 @@ social_media_card = "social_cards/index_zamena-ssd-server.webp"
|
||||
|
||||
Привет и уважение разработчикам замечательной [Clonezilla](https://clonezilla.org) — просто, быстро и удобно (ну, если не считать великолепный `dd`).
|
||||
|
||||
<img src="clonezilla.webp" />
|
||||
<div class="gallery">
|
||||
<a href="clonezilla.webp">
|
||||
<img src="clonezilla.webp" alt="Клонировние старого ssd">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Итог
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ Looking for detailed instructions or tips on using **tabi**? The [blog](https://
|
||||
|
||||
Contributions are much appreciated! We appreciate bug reports, improvements to translations or documentation (however minor), feature requests… Check out the [Contributing Guidelines](https://github.com/welpo/tabi/blob/main/CONTRIBUTING.md) to learn how you can help. Thank you!
|
||||
|
||||
## License
|
||||
## license
|
||||
|
||||

|
||||
|
||||
The code is available under the [MIT license](https://choosealicense.com/licenses/mit/).
|
||||
|
||||
|
||||
9
content/projects/microblog.md
Normal file
@@ -0,0 +1,9 @@
|
||||
+++
|
||||
title = "Микроблог"
|
||||
description = "Zloy про Arch, терминал, велосипеды и всё, что движется (и компилируется)."
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
local_image = "projects/microblog.svg"
|
||||
link_to = "https://log.zlinux.ru"
|
||||
+++
|
||||
@@ -1,9 +0,0 @@
|
||||
+++
|
||||
title = "Peertube"
|
||||
description = "Видеохостинг"
|
||||
weight = 2
|
||||
|
||||
[extra]
|
||||
local_image = "projects/peertube.svg"
|
||||
link_to = "https://video.zlinux.ru"
|
||||
+++
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 57 KiB |
@@ -1,9 +0,0 @@
|
||||
+++
|
||||
title = "Sharkey"
|
||||
description = "Социальная сеть"
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
local_image = "projects/sharkey.svg"
|
||||
link_to = "https://zlinux.ru"
|
||||
+++
|
||||
BIN
content/soft/lossless-cut/img/lossless-cut-export.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
content/soft/lossless-cut/img/lossless-cut-main.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |
70
content/soft/lossless-cut/index.md
Normal file
@@ -0,0 +1,70 @@
|
||||
+++
|
||||
title = "LosslessCut – Лёгкий и быстрый редактор видео"
|
||||
date = 2025-03-22
|
||||
description = "LosslessCut — это бесплатный и кроссплатформенный инструмент для быстрой обрезки и монтажа видео и аудио файлов без перекодирования. Он разработан для тех, кому нужно быстро вырезать фрагменты из медиафайлов, сохранив оригинальное качество."
|
||||
|
||||
[taxonomies]
|
||||
tags = ["video", "editor", "ffmpeg"]
|
||||
|
||||
[extra]
|
||||
quick_navigation_buttons = true
|
||||
toc = true
|
||||
mermaid = true
|
||||
social_media_card = "social_cards/index-lossless-cut.webp"
|
||||
+++
|
||||
|
||||
<div class="gallery">
|
||||
<a href="img/lossless-cut-main.webp">
|
||||
<img src="img/lossless-cut-main.webp" alt="Внешний вид Lossless-Cut">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- Быстрая обрезка и нарезка – позволяет вырезать нужные фрагменты видео и аудио без потерь.
|
||||
- Поддержка множества форматов – работает с MP4, MKV, WEBM, MOV, AAC, MP3 и другими контейнерами.
|
||||
- Без перекодирования (lossless) – не ухудшает качество, поскольку не выполняет повторную компрессию.
|
||||
- Извлечение аудиодорожек – позволяет выделить аудиофайлы из видео.
|
||||
- Поддержка субтитров и метаданных – можно сохранять и удалять субтитры и другие потоки.
|
||||
- Создание скриншотов – позволяет делать кадры из видео в высоком разрешении.
|
||||
- Слияние файлов – объединяет несколько медиафайлов одинакового формата.
|
||||
- Разделение по главам – поддерживает работу с файлами, содержащими главы (например, Blu-ray рипы).
|
||||
|
||||
|
||||
|
||||
<div class="gallery">
|
||||
<a href="img/lossless-cut-export.webp">
|
||||
<img src="img/lossless-cut-export.webp" alt="Параметры экспорта">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Преимущества
|
||||
|
||||
✔️ Скорость – операции выполняются моментально, так как нет необходимости в перекодировании.
|
||||
✔️ Лёгкость использования – интуитивно понятный интерфейс, не требует сложной настройки.
|
||||
✔️ Кроссплатформенность – доступен для Windows, macOS и Linux.
|
||||
✔️ Open Source – проект с открытым исходным кодом, доступный на GitHub.
|
||||
|
||||
## Недостатки
|
||||
|
||||
❌ Не поддерживает сложный монтаж (например, переходы или наложение эффектов).
|
||||
❌ Возможны проблемы с редактированием некоторых кодеков (например, HEVC).
|
||||
❌ Нет автоматического исправления ошибок в файлах.
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
yay losslesscut-bin
|
||||
```
|
||||
|
||||
## Заключение
|
||||
|
||||
LosslessCut идеально подходит для быстрой и эффективной нарезки видео и аудио без потерь качества. Это простой инструмент для тех, кто не хочет возиться с громоздкими видеоредакторами.
|
||||
|
||||
**Подходит для:**
|
||||
✅ Быстрой нарезки записей с экрана или камеры.
|
||||
✅ Вырезания рекламы из записей трансляций.
|
||||
✅ Извлечения звука из видео.
|
||||
|
||||
|
||||
* GitHub: [https://github.com/mifi/lossless-cut](https://github.com/mifi/lossless-cut)
|
||||
BIN
content/soft/lossless-cut/social_cards/index-lossless-cut.webp
Normal file
|
After Width: | Height: | Size: 179 KiB |
@@ -13,7 +13,11 @@ mermaid = true
|
||||
social_media_card = "social_cards/index-soft-nmail.webp"
|
||||
+++
|
||||
|
||||
<img src="img/nmail.png">
|
||||
<div class="gallery">
|
||||
<a href="img/nmail.png">
|
||||
<img src="img/nmail.png" alt="Внешний вид Nmail">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Описание
|
||||
|
||||
|
||||
@@ -32,7 +32,11 @@ Yazi (что означает "утка") - это файловый менедж
|
||||
- SCP и другие протоколы 🌐 – можно настроить для удалённой работы с файлами.
|
||||
|
||||
|
||||
<img src="img/yazi_screen.png">
|
||||
<div class="gallery">
|
||||
<a href="img/yazi_screen.png">
|
||||
<img src="img/yazi_screen.png" alt="Внешний вид приложения">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Установка
|
||||
|
||||
|
||||
237
sass/baguetteBox.scss
Normal file
@@ -0,0 +1,237 @@
|
||||
/*!
|
||||
* baguetteBox.js
|
||||
* @author feimosi
|
||||
* @version 1.13.0
|
||||
* @url https://github.com/feimosi/baguetteBox.js
|
||||
*/
|
||||
#baguetteBox-overlay {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000000;
|
||||
background-color: #222;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
-webkit-transition: opacity 0.5s ease;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
#baguetteBox-overlay.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
#baguetteBox-overlay .full-image {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#baguetteBox-overlay .full-image figure {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#baguetteBox-overlay .full-image img {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
-webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
-moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
#baguetteBox-overlay .full-image figcaption {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 1.8;
|
||||
white-space: normal;
|
||||
color: #ccc;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#baguetteBox-overlay .full-image:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
width: 1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
#baguetteBox-slider {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
-webkit-transition: left 0.4s ease, -webkit-transform 0.4s ease;
|
||||
transition: left 0.4s ease, -webkit-transform 0.4s ease;
|
||||
transition: left 0.4s ease, transform 0.4s ease;
|
||||
transition: left 0.4s ease, transform 0.4s ease, -webkit-transform 0.4s ease, -moz-transform 0.4s ease;
|
||||
}
|
||||
#baguetteBox-slider.bounce-from-right {
|
||||
-webkit-animation: bounceFromRight 0.4s ease-out;
|
||||
animation: bounceFromRight 0.4s ease-out;
|
||||
}
|
||||
#baguetteBox-slider.bounce-from-left {
|
||||
-webkit-animation: bounceFromLeft 0.4s ease-out;
|
||||
animation: bounceFromLeft 0.4s ease-out;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bounceFromRight {
|
||||
0% {
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
margin-left: -30px;
|
||||
}
|
||||
100% {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounceFromRight {
|
||||
0% {
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
margin-left: -30px;
|
||||
}
|
||||
100% {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes bounceFromLeft {
|
||||
0% {
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
margin-left: 30px;
|
||||
}
|
||||
100% {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@keyframes bounceFromLeft {
|
||||
0% {
|
||||
margin-left: 0;
|
||||
}
|
||||
50% {
|
||||
margin-left: 30px;
|
||||
}
|
||||
100% {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.baguetteBox-button#previous-button, .baguetteBox-button#next-button {
|
||||
top: 50%;
|
||||
top: calc(50% - 30px);
|
||||
width: 44px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.baguetteBox-button {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
-moz-border-radius: 15%;
|
||||
border-radius: 15%;
|
||||
background-color: #323232;
|
||||
background-color: rgba(50, 50, 50, 0.5);
|
||||
color: #ddd;
|
||||
font: 1.6em sans-serif;
|
||||
-webkit-transition: background-color 0.4s ease;
|
||||
transition: background-color 0.4s ease;
|
||||
}
|
||||
.baguetteBox-button:focus, .baguetteBox-button:hover {
|
||||
background-color: rgba(50, 50, 50, 0.9);
|
||||
}
|
||||
.baguetteBox-button#next-button {
|
||||
right: 2%;
|
||||
}
|
||||
.baguetteBox-button#previous-button {
|
||||
left: 2%;
|
||||
}
|
||||
.baguetteBox-button#close-button {
|
||||
top: 20px;
|
||||
right: 2%;
|
||||
right: calc(2% + 6px);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.baguetteBox-button svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Preloader
|
||||
Borrowed from http://tobiasahlin.com/spinkit/
|
||||
*/
|
||||
.baguetteBox-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.baguetteBox-double-bounce1,
|
||||
.baguetteBox-double-bounce2 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-moz-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-animation: bounce 2s infinite ease-in-out;
|
||||
animation: bounce 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.baguetteBox-double-bounce2 {
|
||||
-webkit-animation-delay: -1s;
|
||||
animation-delay: -1s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bounce {
|
||||
0%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
-moz-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "../themes/tabi/sass/main.scss";
|
||||
@import "./baguetteBox.scss";
|
||||
|
||||
// main.scss
|
||||
|
||||
@@ -34,20 +35,12 @@ article {
|
||||
}
|
||||
|
||||
|
||||
.gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 4fr));
|
||||
grid-auto-rows: auto;
|
||||
grid-auto-flow: dense;
|
||||
gap: 18px;
|
||||
margin-top: 4vmin;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
border-radius: 1rem;
|
||||
box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
|
||||
transition: transform 200ms ease;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.gallery img:hover {
|
||||
|
||||
BIN
screenshot.png
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 216 KiB |
4788
static/projects/microblog.svg
Normal file
|
After Width: | Height: | Size: 360 KiB |
@@ -83,6 +83,9 @@ load_comments = "إظهار التعليقات"
|
||||
copied = "تم النسخ!"
|
||||
copy_code_to_clipboard = "نسخ الشِفرة إلى الحافظة"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "M'agrada aquesta publicació"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "مُشَغل بواسطة"
|
||||
and = "و"
|
||||
|
||||
@@ -73,6 +73,9 @@ powered_by = "Propulsat per"
|
||||
and = "i"
|
||||
site_source = "Codi del lloc"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "M'agrada aquesta publicació"
|
||||
|
||||
# 404 error.
|
||||
# https://welpo.github.io/tabi/404.html
|
||||
page_missing = "La pàgina que has sol·licitat sembla que no existeix"
|
||||
|
||||
@@ -72,6 +72,9 @@ load_comments = "Kommentare laden"
|
||||
copied = "Kopiert!"
|
||||
copy_code_to_clipboard = "Code in die Zwischenablage kopieren"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Dieser Beitrag gefällt mir"
|
||||
|
||||
# Footer.
|
||||
powered_by = "Angetrieben von"
|
||||
and = "und"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Load comments"
|
||||
copied = "Copied!"
|
||||
copy_code_to_clipboard = "Copy code to clipboard"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Like this post"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Powered by"
|
||||
and = "&"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Cargar comentarios"
|
||||
copied = "Copiado!"
|
||||
copy_code_to_clipboard = "Copiar código al portapapeles"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Me gusta esta publicación"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Impulsado por"
|
||||
and = "y"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Lae kommentaarid"
|
||||
copied = "Kopeeritud!"
|
||||
copy_code_to_clipboard = "Kopeeri kood lõikelauale"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Mulle meeldib see postitus"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Toetab"
|
||||
and = "ja"
|
||||
|
||||
@@ -69,6 +69,9 @@ load_comments = "بارگذاری نظرات"
|
||||
copied = "کپی شد!"
|
||||
copy_code_to_clipboard = "کپی کد به کلیپبورد"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "این مقاله را دوست دارم"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "قدرت گرفته از"
|
||||
and = "و"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Afficher les commentaires"
|
||||
copied = "Copié !"
|
||||
copy_code_to_clipboard = "Copier le code dans le presse-papier"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "J'aime cet article"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Propulsé par"
|
||||
and = "et"
|
||||
|
||||
@@ -70,6 +70,9 @@ load_comments = "कमेंट्स लोड करें"
|
||||
copied = "कॉपी किया गया!"
|
||||
copy_code_to_clipboard = "कोड क्लिपबोर्ड में कॉपी करें"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "मुझे यह पोस्ट पसंद है"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "चालित द्वारा"
|
||||
and = "और"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Carica commenti"
|
||||
copied = "Copiato!"
|
||||
copy_code_to_clipboard = "Copia codice negli appunti"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Mi piace questo post"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Alimentato da"
|
||||
and = "e"
|
||||
|
||||
@@ -72,6 +72,9 @@ load_comments = "コメントを読む"
|
||||
copied = "コピーしました!"
|
||||
copy_code_to_clipboard = "コードをクリップボードにコピー"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "いいね!"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Powered by"
|
||||
and = "と"
|
||||
|
||||
@@ -72,6 +72,9 @@ load_comments = "댓글 불러오기"
|
||||
copied = "복사됨!"
|
||||
copy_code_to_clipboard = "코드를 클립보드에 복사"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "이 글이 좋아요"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "제공됨"
|
||||
and = "&"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Laad opmerkingen"
|
||||
copied = "Gekopieerd!"
|
||||
copy_code_to_clipboard = "Kopieer code naar klembord"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Vind ik leuk"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Aangedreven door"
|
||||
and = "&"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "ମତାମତ ଲୋଡ କରନ୍ତୁ"
|
||||
copied = "କପି ହେଲା!"
|
||||
copy_code_to_clipboard = "କ୍ଲିପବୋର୍ଡକୁ କପି କରନ୍ତୁ"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "ମୋର ଏହି ପୋସ୍ଟ ଭଲ ଲାଗେ"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "ଚାଳିତ ଦ୍ୱାରା"
|
||||
and = "ଏବଂ"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "Carregar comentários"
|
||||
copied = "Copiado!"
|
||||
copy_code_to_clipboard = "Copiar código para a área de transferência"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Gosto desta publicação"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Impulsionado por"
|
||||
and = "e"
|
||||
|
||||
@@ -30,6 +30,7 @@ few_results = "$NUMBER результата" # 2, 3, 4 but not 12-14
|
||||
many_results = "$NUMBER результатов" # 5-9, 0, 11-14, and others
|
||||
|
||||
# Navigation.
|
||||
skip_to_content = "Перейти к содержанию"
|
||||
pinned = "Закреплено"
|
||||
jump_to_posts = "Перейти к записям"
|
||||
read_more = "Читать далее"
|
||||
@@ -79,6 +80,9 @@ load_comments = "Загрузить комментарии"
|
||||
copied = "Скопировано!"
|
||||
copy_code_to_clipboard = "Скопировать код в буфер обмена"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Мне нравится эта статья"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Под управлением"
|
||||
and = "&"
|
||||
|
||||
@@ -81,6 +81,9 @@ load_comments = "Завантажити коментарі"
|
||||
copied = "Скопійовано!"
|
||||
copy_code_to_clipboard = "Копіювати код у буфер обміну"
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "Мені подобається ця стаття"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "Під управлінням"
|
||||
and = "та"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "载入留言"
|
||||
copied = "已复制!" # Machine translated.
|
||||
copy_code_to_clipboard = "复制代码到剪贴板" # Machine translated.
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "喜欢这篇文章"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "网站基于"
|
||||
and = "和"
|
||||
|
||||
@@ -68,6 +68,9 @@ load_comments = "載入留言"
|
||||
copied = "已复制!" # Machine translated.
|
||||
copy_code_to_clipboard = "复制代码到剪贴板" # Machine translated.
|
||||
|
||||
# iine appreciation button.
|
||||
like_this_post = "喜歡這篇文章"
|
||||
|
||||
# Footer: Powered by Zola and tabi.
|
||||
powered_by = "網站基於"
|
||||
and = "和"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@use 'parts/_header-anchor.scss';
|
||||
@use 'parts/_header.scss';
|
||||
@use 'parts/_home-banner.scss';
|
||||
@use 'parts/_iine.scss';
|
||||
@use 'parts/_image-hover.scss';
|
||||
@use 'parts/_image-toggler.scss';
|
||||
@use 'parts/_image.scss';
|
||||
@@ -22,6 +23,7 @@
|
||||
@use 'parts/_table.scss';
|
||||
@use 'parts/_tags.scss';
|
||||
@use 'parts/_theme-switch.scss';
|
||||
@use 'parts/_webmention.scss';
|
||||
@use 'parts/_zola-error.scss';
|
||||
|
||||
@font-face {
|
||||
@@ -196,7 +198,7 @@ article {
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
margin-top: -0.15em;
|
||||
color: var(--text-color-high-contrast);
|
||||
|
||||
@@ -53,7 +53,7 @@ footer nav {
|
||||
|
||||
.social > img {
|
||||
aspect-ratio: 1/1;
|
||||
width: 1.5rem;
|
||||
width: 2.2rem;
|
||||
height: auto;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -98,11 +98,10 @@ header {
|
||||
.tag {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
.separator {
|
||||
}
|
||||
.separator {
|
||||
margin-inline-end: 0.2rem;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.language-switcher {
|
||||
@@ -125,8 +124,30 @@ header {
|
||||
background: var(--meta-color);
|
||||
}
|
||||
}
|
||||
|
||||
.language-switcher-icon-with-code {
|
||||
margin-inline-end: 0.3rem;
|
||||
width: 0.7rem;
|
||||
height: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.language-switcher-icon-code {
|
||||
position: absolute;
|
||||
top: -0.15rem;
|
||||
z-index: 10;
|
||||
inset-inline-start: 0.7rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--text-color);
|
||||
font-size: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
color: var(--meta-color);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
37
themes/tabi/sass/parts/_iine.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
.iine-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-family: var(--sans-serif-font);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.counter {
|
||||
margin-left: .2rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.iine-auto-buttons {
|
||||
margin-top: 2rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
@@ -48,6 +48,9 @@ ul {
|
||||
|
||||
.title-container {
|
||||
padding-bottom: 8px;
|
||||
.social {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-divider {
|
||||
@@ -325,3 +328,28 @@ details summary {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
#skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0;
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
z-index: 9999;
|
||||
transition: all 0.1s ease;
|
||||
border-radius: 0 0 5px 0;
|
||||
background-color: var(--primary-color);
|
||||
padding: 4px 8px;
|
||||
color: var(--hover-color);
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#skip-link:focus {
|
||||
top: 0;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
outline: 2px solid var(--text-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ $padding: 2.5rem;
|
||||
|
||||
@media only screen and (max-width: 1100px) {
|
||||
.bloglist-container {
|
||||
grid-template-columns: 1fr;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pinned-label svg {
|
||||
|
||||
149
themes/tabi/sass/parts/_webmention.scss
Normal file
@@ -0,0 +1,149 @@
|
||||
#webmentions {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
margin: 0;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.2em;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1.5em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9em;
|
||||
|
||||
svg {
|
||||
margin-inline-end: 0.2rem;
|
||||
}
|
||||
|
||||
.svg-icon,
|
||||
span {
|
||||
margin-inline-end: .3rem;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li,
|
||||
p {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
|
||||
.likes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
transition: transform 0.8s ease-out, z-index 0s linear 0.4s;
|
||||
margin-bottom: .375rem;
|
||||
margin-inline-start: -.75rem;
|
||||
|
||||
&:first-child {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.3) translateY(-4px);
|
||||
z-index: 10;
|
||||
transition: transform 0.05s ease-out, z-index 0s linear 0s;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
border: 2px solid var(--background-color, white);
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1/1;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-0);
|
||||
padding: 1rem;
|
||||
overflow: hidden;
|
||||
font-size: 80%;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.p-author {
|
||||
font-style: bold;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.u-url {
|
||||
font-style: italic;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.u-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin-inline-end: .625rem;
|
||||
width: 2rem;
|
||||
max-width: 100%;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
input {
|
||||
flex: 1;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 20px 0px 0px 20px;
|
||||
background-color: var(--input-background-color);
|
||||
padding-inline: 1rem 1rem;
|
||||
padding-block: .75rem;
|
||||
width: calc(60% - 2rem);
|
||||
color: var(--text-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0px 20px 20px 0px;
|
||||
background-color: var(--input-background-color);
|
||||
padding-inline: 0.7rem 0.7rem;
|
||||
padding-block: .75rem;
|
||||
width: 7rem;
|
||||
color: var(--text-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 12 KiB |
7
themes/tabi/static/js/baguetteBox.min.js
vendored
Normal file
2
themes/tabi/static/js/katex.min.js
vendored
2238
themes/tabi/static/js/mermaid.min.js
vendored
412
themes/tabi/static/js/webmention.js
Normal file
@@ -0,0 +1,412 @@
|
||||
/* webmention.js
|
||||
|
||||
Simple thing for embedding webmentions from webmention.io into a page, client-side.
|
||||
|
||||
(c)2018-2022 fluffy (http://beesbuzz.biz)
|
||||
2025 mmai (https://misc.rhumbs.fr)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Basic usage:
|
||||
|
||||
<script src="/path/to/webmention.js" data-param="val" ... async />
|
||||
<div id="webmentions"></div>
|
||||
|
||||
Allowed parameters:
|
||||
|
||||
page-url:
|
||||
|
||||
The base URL to use for this page. Defaults to window.location
|
||||
|
||||
add-urls:
|
||||
|
||||
Additional URLs to check, separated by |s
|
||||
|
||||
id:
|
||||
|
||||
The HTML ID for the object to fill in with the webmention data.
|
||||
Defaults to "webmentions"
|
||||
|
||||
wordcount:
|
||||
|
||||
The maximum number of words to render in reply mentions.
|
||||
|
||||
max-webmentions:
|
||||
|
||||
The maximum number of mentions to retrieve. Defaults to 30.
|
||||
|
||||
prevent-spoofing:
|
||||
|
||||
By default, Webmentions render using the mf2 'url' element, which plays
|
||||
nicely with webmention bridges (such as brid.gy and telegraph)
|
||||
but allows certain spoofing attacks. If you would like to prevent
|
||||
spoofing, set this to a non-empty string (e.g. "true").
|
||||
|
||||
sort-by:
|
||||
|
||||
What to order the responses by; defaults to 'published'. See
|
||||
https://github.com/aaronpk/webmention.io#api
|
||||
|
||||
sort-dir:
|
||||
|
||||
The order to sort the responses by; defaults to 'up' (i.e. oldest
|
||||
first). See https://github.com/aaronpk/webmention.io#api
|
||||
|
||||
comments-are-reactions:
|
||||
|
||||
If set to a non-empty string (e.g. "true"), will display comment-type responses
|
||||
(replies/mentions/etc.) as being part of the reactions
|
||||
(favorites/bookmarks/etc.) instead of in a separate comment list.
|
||||
|
||||
A more detailed example:
|
||||
|
||||
<!-- If you want to translate the UI -->
|
||||
<script src="/path/to/umd/i18next.js"></script>
|
||||
<script>
|
||||
// Setup i18next as described in https://www.i18next.com/overview/getting-started#basic-sample
|
||||
</script>
|
||||
<!-- Otherwise, only using the following is fine -->
|
||||
<script src="/path/to/webmention.min.js"
|
||||
data-id="webmentionContainer"
|
||||
data-wordcount="30"
|
||||
data-prevent-spoofing="true"
|
||||
data-comments-are-reactions="true"
|
||||
/>
|
||||
|
||||
*/
|
||||
|
||||
// Begin LibreJS code licensing
|
||||
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Shim i18next
|
||||
window.i18next = window.i18next || {
|
||||
t: function t(/** @type {string} */key) { return key; }
|
||||
}
|
||||
const t = window.i18next.t.bind(window.i18next);
|
||||
|
||||
/**
|
||||
* Read the configuration value.
|
||||
*
|
||||
* @param {string} key The configuration key.
|
||||
* @param {string} dfl The default value.
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCfg(key, dfl) {
|
||||
return document.currentScript.getAttribute("data-" + key) || dfl;
|
||||
}
|
||||
|
||||
const refurl = getCfg("page-url", window.location.href.replace(/#.*$/, ""));
|
||||
const addurls = getCfg("add-urls", undefined);
|
||||
const containerID = getCfg("id", "webmentions");
|
||||
/** @type {Number} */
|
||||
const textMaxWords = getCfg("wordcount");
|
||||
const maxWebmentions = getCfg("max-webmentions", 30);
|
||||
const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url";
|
||||
const sortBy = getCfg("sort-by", "published");
|
||||
const sortDir = getCfg("sort-dir", "up");
|
||||
|
||||
/**
|
||||
* Strip the protocol off a URL.
|
||||
*
|
||||
* @param {string} url The URL to strip protocol off.
|
||||
* @returns {string}
|
||||
*/
|
||||
function stripurl(url) {
|
||||
return url.substr(url.indexOf('//'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate multiple mentions from the same source URL.
|
||||
*
|
||||
* @param {Array<Reaction>} mentions Mentions of the source URL.
|
||||
* @return {Array<Reaction>}
|
||||
*/
|
||||
function dedupe(mentions) {
|
||||
/** @type {Array<Reaction>} */
|
||||
const filtered = [];
|
||||
/** @type {Record<string, boolean>} */
|
||||
const seen = {};
|
||||
|
||||
mentions.forEach(function (r) {
|
||||
// Strip off the protocol (i.e. treat http and https the same)
|
||||
const source = stripurl(r.url);
|
||||
if (!seen[source]) {
|
||||
filtered.push(r);
|
||||
seen[source] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format comments as HTML.
|
||||
*
|
||||
* @param {Array<Reaction>} comments The comments to format.
|
||||
* @returns string
|
||||
*/
|
||||
function formatComments(type, comments) {
|
||||
|
||||
let html = `
|
||||
<div class="webcomments">
|
||||
<h3 id="replies-header">` + getIcon('comment') + ` <span>` + comments.length + `</span> ` + type + `s </h3>
|
||||
<ol aria-labelledby="replies-header" role="list">`;
|
||||
comments.forEach(function (comment) {
|
||||
let content = '';
|
||||
if (comment.hasOwnProperty('content')) {
|
||||
if (comment.content.hasOwnProperty('html')) {
|
||||
content = comment.content.html;
|
||||
} else if (comment.content.hasOwnProperty('text')) {
|
||||
content = comment.content.text;
|
||||
}
|
||||
}
|
||||
|
||||
html += `
|
||||
<li class="comment h-entry">
|
||||
<div>
|
||||
<a class="comment_author u-author"
|
||||
href="`+ comment.author.url + `"
|
||||
target="_blank"
|
||||
title="`+ comment.author.name + `"
|
||||
rel="noreferrer">
|
||||
<img
|
||||
src="`+ comment.author.photo + `"
|
||||
alt=""
|
||||
class="u-photo"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="48"
|
||||
height="48"
|
||||
>
|
||||
<span class="p-author">`+ comment.author.name + `</span>
|
||||
</a>`;
|
||||
if (comment.published) {
|
||||
const published = new Date(comment.published);
|
||||
html += `
|
||||
<time class="dt-published" datetime="`+ comment.published + `">` + published.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) + `</time>`;
|
||||
}
|
||||
html += `
|
||||
</div>
|
||||
|
||||
<p class="e-entry">`+ content + `
|
||||
<a class="u-url"
|
||||
href="`+ comment.url + `"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
source
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
html += `
|
||||
</ol >
|
||||
</div >
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Reaction
|
||||
* @property {string} url
|
||||
* @property {Object?} author
|
||||
* @property {string?} author.name
|
||||
* @property {string?} author.photo
|
||||
* @property {Object?} content
|
||||
* @property {string?} content.text
|
||||
* @property {RSVPEmoji?} rsvp
|
||||
* @property {MentionType?} wm-property
|
||||
* @property {string?} wm-source
|
||||
*/
|
||||
|
||||
function getIcon(name) {
|
||||
|
||||
if (name == 'like') {
|
||||
return `<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M95.997 41.986l-.026-.035C85.746 28.36 68.428 21.423 51.165 24.881 30.138 29.094 15.004 47.558 15 69.003c0 24.413 14.906 47.964 39.486 70.086 8.43 7.586 17.437 14.468 26.444 20.533.728.49 1.444.967 2.148 1.43l1.39.909 1.355.872 1.317.835.645.403 1.259.78 1.194.726 1.032.619 1.38.807.418.236a6 6 0 005.864 0l1.138-.654 1.154-.684 1.118-.675.614-.376 1.26-.779a212 212 0 00.644-.403l1.317-.835 1.355-.872 1.39-.909c.704-.463 1.42-.94 2.148-1.43 9.007-6.065 18.015-12.947 26.444-20.533C162.094 116.967 177 93.416 177 69.004c-.004-21.446-15.138-39.91-36.165-44.123-17.07-3.42-34.174 3.323-44.43 16.568l-.408.537zm42.48-5.338c15.421 3.09 26.52 16.63 26.523 32.357 0 19.607-12.438 39.847-33.532 59.357l-1.316 1.205c-.22.201-.443.402-.666.603-7.977 7.18-16.548 13.727-25.118 19.498l-.745.5c-.74.494-1.466.973-2.177 1.437l-1.402.906-1.359.864-.662.416-1.292.8-.732.446-.73-.446-1.292-.8-.662-.416-1.36-.864-1.4-.906a235.406 235.406 0 01-2.923-1.937c-8.57-5.77-17.14-12.319-25.118-19.498l-.666-.603-1.316-1.205C39.438 108.852 27 88.612 27 69.004c.003-15.726 11.102-29.267 26.523-32.356 15.253-3.056 30.565 4.954 36.756 19.208l.204.478c2.084 4.878 9.009 4.85 11.053-.045 6.062-14.511 21.52-22.73 36.941-19.641z" fill="currentColor" /></svg> `;
|
||||
} else if (name == 'repost') {
|
||||
return `<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M18.472 146.335l-.075-.184a5.968 5.968 0 01-.216-.684l-.014-.056a5.643 5.643 0 01-.082-.397l-.013-.083a5.886 5.886 0 01-.072-.96V144c0-.157.006-.313.018-.467l.006-.075c.012-.132.028-.261.048-.39l.016-.095c.008-.05.017-.1.027-.149.005-.019.008-.038.012-.058.028-.133.06-.264.096-.393l.026-.088a5.86 5.86 0 01.482-1.159l.043-.077a5.642 5.642 0 01.31-.49l.015-.022.076-.104.044-.059a3.856 3.856 0 01.165-.208l.052-.061c.102-.12.21-.236.321-.348l18-18a6 6 0 018.661 8.303l-.175.183L38.484 138H120c23.196 0 42-18.804 42-42a6 6 0 0112 0c0 29.525-23.696 53.516-53.107 53.993L120 150H38.486l7.757 7.757a6 6 0 01.175 8.303l-.175.183a6 6 0 01-8.303.175l-.183-.175-18-18-.145-.151a6.036 6.036 0 01-.829-1.125l-.058-.105a4.08 4.08 0 01-.06-.114l-.04-.077a4.409 4.409 0 01-.139-.3l-.014-.036zM154.06 25.582l.183.175 18 18a6.036 6.036 0 01.974 1.276l.058.105c.02.035.038.07.056.105l.043.086a4.411 4.411 0 01.14.3l.014.036a5.965 5.965 0 01.291.868l.014.056c.032.13.059.263.082.397l.013.083a5.886 5.886 0 01.067.692v.014a6.11 6.11 0 01-.013.692l-.006.075a5.856 5.856 0 01-.048.39l-.016.095c-.008.05-.017.1-.027.149-.005.019-.008.038-.012.058-.028.133-.06.264-.096.393l-.026.088a5.86 5.86 0 01-.482 1.159l-.043.077-.052.09-.029.048a6.006 6.006 0 01-.32.478l-.044.059a3.857 3.857 0 01-.165.208l-.052.061a6.34 6.34 0 01-.176.197l-.145.15-18 18a6 6 0 01-8.661-8.302l.175-.183L153.514 54H72c-23.196 0-42 18.804-42 42a6 6 0 11-12 0c0-29.525 23.696-53.516 53.107-53.993L72 42h81.516l-7.759-7.757a6 6 0 01-.175-8.303l.175-.183a6 6 0 018.303-.175z" fill="currentColor" /></svg> `;
|
||||
} else if (name == 'comment') {
|
||||
return `<svg width = "24" height = "24" viewBox = "0 0 150 150" xmlns = "http://www.w3.org/2000/svg" > <path d="M75-.006a75 75 0 0174.997 74.31l.003.69c0 41.422-33.579 75-75 75H11.75c-6.49 0-11.75-5.26-11.75-11.75v-63.25a75 75 0 0175-75zm0 12a63 63 0 00-63 63v63h63c34.446 0 62.435-27.645 62.992-61.93l.008-1.041-.003-.633A63 63 0 0075 11.994zm21 72a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45zm0-24a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45z" fill="currentColor" /></svg> `;
|
||||
} else if (name == 'bookmark') {
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M 6.0097656 2 C 4.9143111 2 4.0097656 2.9025988 4.0097656 3.9980469 L 4 22 L 12 19 L 20 22 L 20 20.556641 L 20 4 C 20 2.9069372 19.093063 2 18 2 L 6.0097656 2 z M 6.0097656 4 L 18 4 L 18 19.113281 L 12 16.863281 L 6.0019531 19.113281 L 6.0097656 4 z" fill="currentColor"></path>
|
||||
</svg>`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of reactions as HTML.
|
||||
*
|
||||
* @param {Array<Reaction>} reacts List of reactions to format
|
||||
* @returns string
|
||||
*/
|
||||
function formatReactions(type, reacts) {
|
||||
let html = `
|
||||
<div class="color--primary" >
|
||||
<h3 id=`+ type + ` - header"> ` + getIcon(type) + ` <span>` + reacts.length + `</span> ` + type + `s </h3>
|
||||
|
||||
<ol class="likes" role = "list" aria - labelledby="`+ type + `-header"> `;
|
||||
|
||||
reacts.forEach(function (react) {
|
||||
html += `
|
||||
<li class="h-card">
|
||||
<a class="u-url"
|
||||
href="`+ react.author.url + `
|
||||
target="_blank"
|
||||
rel = "noreferrer"
|
||||
title = "`+ react.author.name + `" >
|
||||
<img
|
||||
alt=""
|
||||
class="lazy mentions__image u-photo"
|
||||
src="`+ react.author.photo + `"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="48"
|
||||
height="48"
|
||||
>
|
||||
<span class="p-author visually-hidden" aria-hidden="true">{{ author }}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</ol >
|
||||
</div >
|
||||
`;
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef WebmentionResponse
|
||||
* @type {Object}
|
||||
* @property {Array<Reaction>} children
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register event listener.
|
||||
*/
|
||||
window.addEventListener("load", async function () {
|
||||
const container = document.getElementById(containerID);
|
||||
if (!container) {
|
||||
// no container, so do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
const pages = [stripurl(refurl)];
|
||||
if (!!addurls) {
|
||||
addurls.split('|').forEach(function (url) {
|
||||
pages.push(stripurl(url));
|
||||
});
|
||||
}
|
||||
|
||||
let apiURL = `https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=${sortDir}`;
|
||||
|
||||
pages.forEach(function (path) {
|
||||
apiURL += `&target[]=${encodeURIComponent('http:' + path)}&target[]=${encodeURIComponent('https:' + path)}`;
|
||||
});
|
||||
|
||||
// apiURL = 'http://127.0.0.1:1111/test_webmentions.jf2';
|
||||
/** @type {WebmentionResponse} */
|
||||
let json = {};
|
||||
try {
|
||||
// const response = await window.fetch(apiURL);
|
||||
const response = await window.fetch(apiURL);
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
json = await response.json();
|
||||
} else {
|
||||
console.error("Could not parse response");
|
||||
new Error(response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
// Purposefully not escalate further, i.e. no UI update
|
||||
console.error("Request failed", error);
|
||||
}
|
||||
|
||||
/** @type {Array<Reaction>} */
|
||||
let comments = [];
|
||||
/** @type {Array<Reaction>} */
|
||||
let mentions = [];
|
||||
/** @type {Array<Reaction>} */
|
||||
const bookmarks = [];
|
||||
/** @type {Array<Reaction>} */
|
||||
const likes = [];
|
||||
/** @type {Array<Reaction>} */
|
||||
const reposts = [];
|
||||
/** @type {Array<Reaction>} */
|
||||
const follows = [];
|
||||
|
||||
/** @type {Record<MentionType, Array<Reaction>>} */
|
||||
const mapping = {
|
||||
"in-reply-to": comments,
|
||||
"like-of": likes,
|
||||
"repost-of": reposts,
|
||||
"bookmark-of": bookmarks,
|
||||
"follow-of": follows,
|
||||
"mention-of": mentions,
|
||||
"rsvp": comments
|
||||
};
|
||||
|
||||
json.children.forEach(function (child) {
|
||||
// Map each mention into its respective container
|
||||
const store = mapping[child['wm-property']];
|
||||
if (store) {
|
||||
store.push(child);
|
||||
}
|
||||
});
|
||||
|
||||
// format the comment-type things
|
||||
let formattedMentions = '';
|
||||
if (mentions.length > 0) {
|
||||
formattedMentions = formatComments('mention', dedupe(mentions));
|
||||
}
|
||||
|
||||
let formattedComments = '';
|
||||
if (comments.length > 0) {
|
||||
formattedComments = formatComments('comment', dedupe(comments));
|
||||
}
|
||||
|
||||
// format likes
|
||||
let likesStr = '';
|
||||
if (likes.length > 0) {
|
||||
likesStr = formatReactions('like', dedupe(likes));
|
||||
}
|
||||
|
||||
// format reposts
|
||||
let repostsStr = '';
|
||||
if (reposts.length > 0) {
|
||||
repostsStr = formatReactions('repost', dedupe(reposts));
|
||||
}
|
||||
|
||||
// format bookmarks
|
||||
let bookmarksStr = '';
|
||||
if (bookmarks.length > 0) {
|
||||
bookmarksStr = formatReactions('bookmark', dedupe(bookmarks));
|
||||
}
|
||||
|
||||
container.innerHTML = `<div id="webmentions">${repostsStr}${likesStr}${bookmarksStr}${formattedComments}${formattedMentions}</div>`;
|
||||
});
|
||||
}());
|
||||
|
||||
// End-of-file marker for LibreJS
|
||||
// @license-end
|
||||
66
themes/tabi/static/js/webmention.min.js
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
(()=>{function t(t,e){return document.currentScript.getAttribute("data-"+t)||e}window.i18next=window.i18next||{t:function(t){return t}},window.i18next.t.bind(window.i18next);let m=t("page-url",window.location.href.replace(/#.*$/,"")),f=t("add-urls",void 0),e=t("id","webmentions"),g=(t("wordcount"),t("max-webmentions",30)),v=(t("prevent-spoofing"),t("sort-by","published")),b=t("sort-dir","up");function y(t){return t.substr(t.indexOf("//"))}function x(t){let l=[],a={};return t.forEach(function(t){var e=y(t.url);a[e]||(l.push(t),a[e]=!0)}),l}function L(t,e){let a=`
|
||||
<div class="webcomments">
|
||||
<h3 id="replies-header">`+o("comment")+" <span>"+e.length+"</span> "+t+`s </h3>
|
||||
<ol aria-labelledby="replies-header" role="list">`;return e.forEach(function(t){let e="";var l;t.hasOwnProperty("content")&&(t.content.hasOwnProperty("html")?e=t.content.html:t.content.hasOwnProperty("text")&&(e=t.content.text)),a+=`
|
||||
<li class="comment h-entry">
|
||||
<div>
|
||||
<a class="comment_author u-author"
|
||||
href="`+t.author.url+`"
|
||||
target="_blank"
|
||||
title="`+t.author.name+`"
|
||||
rel="noreferrer">
|
||||
<img
|
||||
src="`+t.author.photo+`"
|
||||
alt=""
|
||||
class="u-photo"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="48"
|
||||
height="48"
|
||||
>
|
||||
<span class="p-author">`+t.author.name+`</span>
|
||||
</a>`,t.published&&(l=new Date(t.published),a+=`
|
||||
<time class="dt-published" datetime="`+t.published+'">'+l.toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"})+"</time>"),a+=`
|
||||
</div>
|
||||
|
||||
<p class="e-entry">`+e+`
|
||||
<a class="u-url"
|
||||
href="`+t.url+`"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
source
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
`}),a+=`
|
||||
</ol >
|
||||
</div >
|
||||
`}function o(t){return"like"==t?'<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M95.997 41.986l-.026-.035C85.746 28.36 68.428 21.423 51.165 24.881 30.138 29.094 15.004 47.558 15 69.003c0 24.413 14.906 47.964 39.486 70.086 8.43 7.586 17.437 14.468 26.444 20.533.728.49 1.444.967 2.148 1.43l1.39.909 1.355.872 1.317.835.645.403 1.259.78 1.194.726 1.032.619 1.38.807.418.236a6 6 0 005.864 0l1.138-.654 1.154-.684 1.118-.675.614-.376 1.26-.779a212 212 0 00.644-.403l1.317-.835 1.355-.872 1.39-.909c.704-.463 1.42-.94 2.148-1.43 9.007-6.065 18.015-12.947 26.444-20.533C162.094 116.967 177 93.416 177 69.004c-.004-21.446-15.138-39.91-36.165-44.123-17.07-3.42-34.174 3.323-44.43 16.568l-.408.537zm42.48-5.338c15.421 3.09 26.52 16.63 26.523 32.357 0 19.607-12.438 39.847-33.532 59.357l-1.316 1.205c-.22.201-.443.402-.666.603-7.977 7.18-16.548 13.727-25.118 19.498l-.745.5c-.74.494-1.466.973-2.177 1.437l-1.402.906-1.359.864-.662.416-1.292.8-.732.446-.73-.446-1.292-.8-.662-.416-1.36-.864-1.4-.906a235.406 235.406 0 01-2.923-1.937c-8.57-5.77-17.14-12.319-25.118-19.498l-.666-.603-1.316-1.205C39.438 108.852 27 88.612 27 69.004c.003-15.726 11.102-29.267 26.523-32.356 15.253-3.056 30.565 4.954 36.756 19.208l.204.478c2.084 4.878 9.009 4.85 11.053-.045 6.062-14.511 21.52-22.73 36.941-19.641z" fill="currentColor" /></svg> ':"repost"==t?'<svg focusable = "false" width = "24" height = "24" viewBox = "0 0 192 192" xmlns = "http://www.w3.org/2000/svg" > <path d="M18.472 146.335l-.075-.184a5.968 5.968 0 01-.216-.684l-.014-.056a5.643 5.643 0 01-.082-.397l-.013-.083a5.886 5.886 0 01-.072-.96V144c0-.157.006-.313.018-.467l.006-.075c.012-.132.028-.261.048-.39l.016-.095c.008-.05.017-.1.027-.149.005-.019.008-.038.012-.058.028-.133.06-.264.096-.393l.026-.088a5.86 5.86 0 01.482-1.159l.043-.077a5.642 5.642 0 01.31-.49l.015-.022.076-.104.044-.059a3.856 3.856 0 01.165-.208l.052-.061c.102-.12.21-.236.321-.348l18-18a6 6 0 018.661 8.303l-.175.183L38.484 138H120c23.196 0 42-18.804 42-42a6 6 0 0112 0c0 29.525-23.696 53.516-53.107 53.993L120 150H38.486l7.757 7.757a6 6 0 01.175 8.303l-.175.183a6 6 0 01-8.303.175l-.183-.175-18-18-.145-.151a6.036 6.036 0 01-.829-1.125l-.058-.105a4.08 4.08 0 01-.06-.114l-.04-.077a4.409 4.409 0 01-.139-.3l-.014-.036zM154.06 25.582l.183.175 18 18a6.036 6.036 0 01.974 1.276l.058.105c.02.035.038.07.056.105l.043.086a4.411 4.411 0 01.14.3l.014.036a5.965 5.965 0 01.291.868l.014.056c.032.13.059.263.082.397l.013.083a5.886 5.886 0 01.067.692v.014a6.11 6.11 0 01-.013.692l-.006.075a5.856 5.856 0 01-.048.39l-.016.095c-.008.05-.017.1-.027.149-.005.019-.008.038-.012.058-.028.133-.06.264-.096.393l-.026.088a5.86 5.86 0 01-.482 1.159l-.043.077-.052.09-.029.048a6.006 6.006 0 01-.32.478l-.044.059a3.857 3.857 0 01-.165.208l-.052.061a6.34 6.34 0 01-.176.197l-.145.15-18 18a6 6 0 01-8.661-8.302l.175-.183L153.514 54H72c-23.196 0-42 18.804-42 42a6 6 0 11-12 0c0-29.525 23.696-53.516 53.107-53.993L72 42h81.516l-7.759-7.757a6 6 0 01-.175-8.303l.175-.183a6 6 0 018.303-.175z" fill="currentColor" /></svg> ':"comment"==t?'<svg width = "24" height = "24" viewBox = "0 0 150 150" xmlns = "http://www.w3.org/2000/svg" > <path d="M75-.006a75 75 0 0174.997 74.31l.003.69c0 41.422-33.579 75-75 75H11.75c-6.49 0-11.75-5.26-11.75-11.75v-63.25a75 75 0 0175-75zm0 12a63 63 0 00-63 63v63h63c34.446 0 62.435-27.645 62.992-61.93l.008-1.041-.003-.633A63 63 0 0075 11.994zm21 72a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45zm0-24a6 6 0 01.225 11.996l-.225.004H51a6 6 0 01-.225-11.996l.225-.004h45z" fill="currentColor" /></svg> ':"bookmark"==t?`<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M 6.0097656 2 C 4.9143111 2 4.0097656 2.9025988 4.0097656 3.9980469 L 4 22 L 12 19 L 20 22 L 20 20.556641 L 20 4 C 20 2.9069372 19.093063 2 18 2 L 6.0097656 2 z M 6.0097656 4 L 18 4 L 18 19.113281 L 12 16.863281 L 6.0019531 19.113281 L 6.0097656 4 z" fill="currentColor"></path>
|
||||
</svg>`:void 0}function k(t,e){let l=`
|
||||
<div class="color--primary" >
|
||||
<h3 id=`+t+' - header"> '+o(t)+" <span>"+e.length+"</span> "+t+`s </h3>
|
||||
|
||||
<ol class="likes" role = "list" aria - labelledby="`+t+'-header"> ';return e.forEach(function(t){l+=`
|
||||
<li class="h-card">
|
||||
<a class="u-url"
|
||||
href="`+t.author.url+`
|
||||
target="_blank"
|
||||
rel = "noreferrer"
|
||||
title = "`+t.author.name+`" >
|
||||
<img
|
||||
alt=""
|
||||
class="lazy mentions__image u-photo"
|
||||
src="`+t.author.photo+`"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="48"
|
||||
height="48"
|
||||
>
|
||||
<span class="p-author visually-hidden" aria-hidden="true">{{ author }}</span>
|
||||
</a>
|
||||
</li>
|
||||
`}),l+=`
|
||||
</ol >
|
||||
</div >
|
||||
`}window.addEventListener("load",async function(){var c=document.getElementById(e);if(c){let e=[y(m)],l=(f&&f.split("|").forEach(function(t){e.push(y(t))}),`https://webmention.io/api/mentions.jf2?per-page=${g}&sort-by=${v}&sort-dir=`+b),t=(e.forEach(function(t){l+=`&target[]=${encodeURIComponent("http:"+t)}&target[]=`+encodeURIComponent("https:"+t)}),{});try{200<=(h=await window.fetch(l)).status&&h.status<300?t=await h.json():(console.error("Could not parse response"),new Error(h.statusText))}catch(t){console.error("Request failed",t)}var h,d=[],u=[],p=[],w=[];let a={"in-reply-to":h=[],"like-of":p,"repost-of":w,"bookmark-of":u,"follow-of":[],"mention-of":d,rsvp:h},o=(t.children.forEach(function(t){var e=a[t["wm-property"]];e&&e.push(t)}),""),n=(0<d.length&&(o=L("mention",x(d))),""),r=(0<h.length&&(n=L("comment",x(h))),""),i=(0<p.length&&(r=k("like",x(p))),""),s=(0<w.length&&(i=k("repost",x(w))),"");0<u.length&&(s=k("bookmark",x(u))),c.innerHTML=`<div id="webmentions">${i}${r}${s}${n}${o}</div>`}})})();
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.8 KiB |
BIN
themes/tabi/static/social_icons/email1.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
themes/tabi/static/social_icons/microblog.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
themes/tabi/static/social_icons/pixelfed.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 14 KiB |
BIN
themes/tabi/static/social_icons/rss1.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
themes/tabi/static/social_icons/telegram1.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
themes/tabi/static/social_icons/xmpp.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -7,6 +7,18 @@
|
||||
{# Set locale for date #}
|
||||
{% set date_locale = macros_translate::translate(key="date_locale", default="en_GB", language_strings=language_strings) %}
|
||||
|
||||
{#- Check for language-specific date formats -#}
|
||||
{%- set language_format = "" -%}
|
||||
{%- if config.extra.date_formats -%}
|
||||
{%- for format_config in config.extra.date_formats -%}
|
||||
{%- if format_config.lang == lang -%}
|
||||
{%- if format_config.archive -%}
|
||||
{%- set_global language_format = format_config.archive -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
<div class="archive">
|
||||
<ul class="list-with-title">
|
||||
{%- set source_paths = section.extra.section_path | default(value="blog/") -%}
|
||||
@@ -55,7 +67,13 @@
|
||||
<li class="listing-item">
|
||||
<div class="post-time">
|
||||
<span class="date">
|
||||
{%- if language_format -%}
|
||||
{{ post.date | date(format=language_format, locale=date_locale) }}
|
||||
{%- elif config.extra.archive_date_format -%}
|
||||
{{ post.date | date(format=config.extra.archive_date_format, locale=date_locale) }}
|
||||
{%- else -%}
|
||||
{{ post.date | date(format="%d %b", locale=date_locale) }}
|
||||
{%- endif -%}
|
||||
</span>
|
||||
</div>
|
||||
<a href="{{ post.permalink }}" title="{{ post.title }}">{{ post.title | markdown(inline=true) | safe }}</a>
|
||||
|
||||
@@ -66,7 +66,9 @@
|
||||
{%- endif -%}
|
||||
" rel="alternate" type="text/html"/>
|
||||
<generator uri="https://www.getzola.org/">Zola</generator>
|
||||
{%- if last_updated -%}
|
||||
<updated>{{ last_updated | date(format="%+") }}</updated>
|
||||
{%- endif -%}
|
||||
<id>{{ feed_url | safe }}</id>
|
||||
{%- for page in pages %}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="hide_from_feed", page=page, default_global_value=false) == "true" -%}
|
||||
@@ -98,10 +100,10 @@
|
||||
{% if config.extra.full_content_in_feed %}
|
||||
<content type="html">{{ page.content }}</content>
|
||||
{% endif -%}
|
||||
{% if page.summary -%}
|
||||
<summary type="html">{{ page.summary | striptags | trim_end_matches(pat=".") | safe }}…</summary>
|
||||
{% elif page.description -%}
|
||||
{% if page.description -%}
|
||||
<summary type="html">{{ page.description }}</summary>
|
||||
{% elif page.summary -%}
|
||||
<summary type="html">{{ page.summary | striptags | trim_end_matches(pat=".") | safe }}…</summary>
|
||||
{% endif -%}
|
||||
</entry>
|
||||
{%- endfor %}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
{% import "macros/feed_utils.html" as feed_utils %}
|
||||
{% import "macros/format_date.html" as macros_format_date %}
|
||||
{% import "macros/list_posts.html" as macros_list_posts %}
|
||||
{% import "macros/page_header.html" as macros_page_header %}
|
||||
{% import "macros/rel_attributes.html" as macros_rel_attributes %}
|
||||
{% import "macros/series_page.html" as macros_series_page %}
|
||||
{% import "macros/settings.html" as macros_settings %}
|
||||
{% import "macros/table_of_contents.html" as macros_toc %}
|
||||
{% import "macros/target_attribute.html" as macros_target_attribute %}
|
||||
{% import "macros/translate.html" as macros_translate %}
|
||||
{% import "macros/series_page.html" as macros_series_page %}
|
||||
|
||||
{# Load the internationalisation data for the current language from
|
||||
the .toml files in the user's '/i18n' folder, falling back to the theme's.
|
||||
@@ -32,8 +34,9 @@ This variable will hold all the text strings for the language #}
|
||||
{% include "partials/header.html" %}
|
||||
|
||||
<body{% if lang in rtl_languages %} dir="rtl"{% endif %}{% if config.extra.override_serif_with_sans %} class="use-sans-serif"{% endif %}>
|
||||
<a href="#main-content" id="skip-link">{{ macros_translate::translate(key="skip_to_content", default="Skip to content", language_strings=language_strings) }}</a>
|
||||
{% include "partials/nav.html" %}
|
||||
<div class="content">
|
||||
<div class="content" id="main-content">
|
||||
|
||||
{# Post page is the default #}
|
||||
{% block main_content %}
|
||||
@@ -41,6 +44,18 @@ This variable will hold all the text strings for the language #}
|
||||
{% endblock main_content %}
|
||||
</div>
|
||||
{% include "partials/footer.html" %}
|
||||
|
||||
{# Users can optionally provide this template to add content to the body element. #}
|
||||
{% include "tabi/extend_body.html" ignore missing %}
|
||||
|
||||
<!-- baguetteBox JS -->
|
||||
<script defer src="{{ get_url(path='js/baguetteBox.min.js', trailing_slash=false) | safe }}"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
baguetteBox.run('.gallery');
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
17
themes/tabi/templates/macros/feed_utils.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{#- Feed utility macros -#}
|
||||
|
||||
{#- Zola 0.19.0 uses `generate_feeds`. Prior versions use `generate_feed` -#}
|
||||
{%- macro get_generate_feed() -%}
|
||||
{{- config.generate_feeds | default(value=config.generate_feed) -}}
|
||||
{%- endmacro get_generate_feed -%}
|
||||
|
||||
{%- macro get_feed_url() -%}
|
||||
{{- config.feed_filenames[0] | default(value=(config.feed_filename)) -}}
|
||||
{%- endmacro get_feed_url -%}
|
||||
|
||||
{#- Check footer feed icon conditions -#}
|
||||
{%- macro should_show_footer_feed_icon() -%}
|
||||
{%- set generate_feed = feed_utils::get_generate_feed() == "true" -%}
|
||||
{%- set feed_url = feed_utils::get_feed_url() -%}
|
||||
{{- generate_feed and config.extra.feed_icon and feed_url -}}
|
||||
{%- endmacro should_show_footer_feed_icon -%}
|
||||
@@ -3,7 +3,23 @@
|
||||
{#- Set locale -#}
|
||||
{%- set date_locale = macros_translate::translate(key="date_locale", default="en_GB", language_strings=language_strings) -%}
|
||||
|
||||
{%- if config.extra.short_date_format and short -%}
|
||||
{#- Check for language-specific date formats -#}
|
||||
{%- set language_format = "" -%}
|
||||
{%- if config.extra.date_formats -%}
|
||||
{%- for format_config in config.extra.date_formats -%}
|
||||
{%- if format_config.lang == lang -%}
|
||||
{%- if short and format_config.short -%}
|
||||
{%- set_global language_format = format_config.short -%}
|
||||
{%- elif not short and format_config.long -%}
|
||||
{%- set_global language_format = format_config.long -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if language_format -%}
|
||||
{{ date | date(format=language_format, locale=date_locale) }}
|
||||
{%- elif config.extra.short_date_format and short -%}
|
||||
{{ date | date(format=config.extra.short_date_format, locale=date_locale) }}
|
||||
{%- elif config.extra.long_date_format and not short -%}
|
||||
{{ date | date(format=config.extra.long_date_format, locale=date_locale) }}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<li class="date">{{- macros_format_date::format_date(date=post.date, short=false, language_strings=language_strings) -}}</li>
|
||||
{%- endif -%}
|
||||
{%- if show_date and show_updated -%}
|
||||
<li class="mobile-only">{{- separator -}}</li>
|
||||
<li class="mobile-only separator">{{- separator -}}</li>
|
||||
{%- endif -%}
|
||||
{%- if show_updated -%}
|
||||
{%- set last_updated_str = macros_translate::translate(key="last_updated_on", default="Updated on $DATE", language_strings=language_strings) -%}
|
||||
@@ -142,9 +142,9 @@
|
||||
|
||||
<div class="description">
|
||||
{% if post.description %}
|
||||
<p>{{ post.description }}</p>
|
||||
<p>{{ post.description | markdown(inline=true) | safe }}</p>
|
||||
{% elif post.summary %}
|
||||
<p>{{ post.summary | striptags | trim_end_matches(pat=".") | safe }}…</p>
|
||||
<p>{{ post.summary | markdown(inline=true) | trim_end_matches(pat=".") | safe }}…</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="readmore" href="{{ post.permalink }}">{{ macros_translate::translate(key="read_more", default="Read more", language_strings=language_strings) }} <span class="arrow">→</span></a>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
{% macro page_header(title) %}
|
||||
{% macro page_header(title, show_feed_icon=false) %}
|
||||
|
||||
{% set rel_attributes = macros_rel_attributes::rel_attributes() | trim %}
|
||||
|
||||
|
||||
{%- set blank_target = macros_target_attribute::target_attribute(new_tab=config.markdown.external_links_target_blank) -%}
|
||||
|
||||
<h1 class="title-container section-title bottom-divider">
|
||||
{{ title }}
|
||||
{{ title -}}
|
||||
{% if show_feed_icon %}
|
||||
{%- set feed_url = feed_utils::get_feed_url() -%}
|
||||
<a class="no-hover-padding social" rel="{{ rel_attributes }}" {{ blank_target }} href="{{ get_url(path=term.path ~ feed_url, lang=lang, trailing_slash=false) | safe }}">
|
||||
<img loading="lazy" alt="feed" title="feed" src="{{ get_url(path='/social_icons/rss.svg') }}">
|
||||
</a>
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
{% endmacro page_header %}
|
||||
|
||||
11
themes/tabi/templates/macros/target_attribute.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% macro target_attribute(new_tab) %}
|
||||
|
||||
{%- set blank_target = "" -%}
|
||||
|
||||
{%- if new_tab -%}
|
||||
{%- set blank_target = "target=_blank" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{{ blank_target }}
|
||||
|
||||
{% endmacro target_attribute %}
|
||||
@@ -5,11 +5,7 @@
|
||||
|
||||
{%- set rel_attributes = macros_rel_attributes::rel_attributes() | trim -%}
|
||||
|
||||
{%- if config.markdown.external_links_target_blank -%}
|
||||
{%- set blank_target = "target=_blank" -%}
|
||||
{%- else -%}
|
||||
{%- set blank_target = "" -%}
|
||||
{%- endif -%}
|
||||
{%- set blank_target = macros_target_attribute::target_attribute(new_tab=config.markdown.external_links_target_blank) -%}
|
||||
|
||||
{# Debugging #}
|
||||
{# <div><pre>
|
||||
@@ -61,6 +57,8 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
</pre></div>
|
||||
|
||||
{% set settings_to_test = [
|
||||
"iine",
|
||||
"iine_icon",
|
||||
"enable_cards_tag_filtering",
|
||||
"footnote_backlinks",
|
||||
"add_src_to_code_block",
|
||||
@@ -102,14 +100,16 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
</tbody>
|
||||
</table>
|
||||
</div> #}
|
||||
|
||||
{# {{ __tera_context }} #}
|
||||
{# End debugging #}
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 class="article-title">
|
||||
<article class="h-entry">
|
||||
<h1 class="p-name article-title">
|
||||
{{ page.title | markdown(inline=true) | safe }}
|
||||
</h1>
|
||||
<a class="u-url u-uid" href="{{ page.permalink | safe }}"></a>
|
||||
|
||||
<ul class="meta">
|
||||
{#- Draft indicator -#}
|
||||
@@ -118,7 +118,7 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{% endif %}
|
||||
|
||||
{#- Author(s) -#}
|
||||
{% if page.authors or config.author and macros_settings::evaluate_setting_priority(setting="show_author", page=page, default_global_value=false) == "true" %}
|
||||
{%- if page.authors or config.author and macros_settings::evaluate_setting_priority(setting="show_author", page=page, default_global_value=false) == "true" -%}
|
||||
{%- if page.authors -%}
|
||||
{%- set author_list = page.authors -%}
|
||||
{%- else -%}
|
||||
@@ -126,29 +126,36 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{%- endif -%}
|
||||
|
||||
{%- if author_list | length == 1 -%}
|
||||
{%- set author_string = author_list.0 -%}
|
||||
{%- set author_string = '<span class="p-author">' ~ author_list.0 ~ '</span>' -%}
|
||||
{%- else -%}
|
||||
{%- set last_author = author_list | last -%}
|
||||
{%- set other_authors = author_list | slice(end=-1) -%}
|
||||
{%- set author_separator = macros_translate::translate(key="author_separator", default=", ", language_strings=language_strings) -%}
|
||||
{%- set author_separator = '</span>' ~ author_separator ~ '<span class="p-author">' -%}
|
||||
{%- set conjunction = macros_translate::translate(key="author_conjunction", default=" and ", language_strings=language_strings) -%}
|
||||
{%- set conjunction = '</span>' ~ conjunction ~ '<span class="p-author">' -%}
|
||||
{%- set author_string = other_authors | join(sep=author_separator) -%}
|
||||
{%- set author_string = author_string ~ conjunction ~ last_author -%}
|
||||
{%- set author_string = '<span class="p-author">' ~ author_string ~ '</span>' -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set by_author = macros_translate::translate(key="by_author", default="By $AUTHOR", language_strings=language_strings) -%}
|
||||
<li>{{ by_author | replace(from="$AUTHOR", to=author_string) }}</li>
|
||||
<li>{{ by_author | replace(from="$AUTHOR", to=author_string) | safe }}</li>
|
||||
{%- set previous_visible = true -%}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if config.extra.hcard and config.extra.hcard.enable and ( not author_list or author_list is containing(config.author)) -%}
|
||||
{% include "partials/hcard_small.html" %}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set separator_with_class = "<span class='separator' aria-hidden='true'>" ~ separator ~ "</span>"-%}
|
||||
|
||||
{#- Date -#}
|
||||
{% if page.date and macros_settings::evaluate_setting_priority(setting="show_date", page=page, default_global_value=true) == "true" %}
|
||||
<li>{%- if previous_visible -%}{{ separator_with_class | safe }}{%- endif -%}{{ macros_format_date::format_date(date=page.date, short=true, language_strings=language_strings) }}</li>
|
||||
{%- if page.date and macros_settings::evaluate_setting_priority(setting="show_date", page=page, default_global_value=true) == "true" -%}
|
||||
<li><time class="dt-published" datetime="{{ page.date }}">{%- if previous_visible -%}{{ separator_with_class | safe }}{%- endif -%}{{ macros_format_date::format_date(date=page.date, short=true, language_strings=language_strings) }}</time></li>
|
||||
{#- Variable to keep track of whether we've shown a section, to avoid separators as the first element -#}
|
||||
{%- set previous_visible = true -%}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
|
||||
{#- Reading time -#}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="show_reading_time", page=page, default_global_value=true) == "true" -%}
|
||||
@@ -160,7 +167,7 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{%- if page.taxonomies and page.taxonomies.tags -%}
|
||||
<li class="tag">{%- if previous_visible -%}{{ separator_with_class | safe }}{%- endif -%}{{- macros_translate::translate(key="tags", default="tags", language_strings=language_strings) | capitalize -}}: </li>
|
||||
{%- for tag in page.taxonomies.tags -%}
|
||||
<li class="tag"><a href="{{ get_taxonomy_url(kind='tags', name=tag, lang=lang) | safe }}">{{ tag }}</a>
|
||||
<li class="tag"><a class="p-category" href="{{ get_taxonomy_url(kind='tags', name=tag, lang=lang) | safe }}">{{ tag }}</a>
|
||||
{%- if not loop.last -%}
|
||||
,
|
||||
{%- endif -%}
|
||||
@@ -175,7 +182,7 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{%- set formatted_date = macros_format_date::format_date(date=page.updated, short=true, language_strings=language_strings) -%}
|
||||
{%- set updated_str = last_updated_str | replace(from="$DATE", to=formatted_date) -%}
|
||||
{%- set previous_visible = true -%}
|
||||
</ul><ul class="meta last-updated"><li>{{ updated_str }}</li>
|
||||
</ul><ul class="meta last-updated"><li><time class="dt-updated" datetime="{{ page.updated }}">{{ updated_str }}</time></li>
|
||||
{#- Show link to remote changes if enabled -#}
|
||||
{%- if config.extra.remote_repository_url and macros_settings::evaluate_setting_priority(setting="show_remote_changes", page=page, default_global_value=true) == "true" -%}
|
||||
<li>{%- if previous_visible -%}{{ separator_with_class | safe }}{%- endif -%}<a class="external" href="{% include "partials/history_url.html" %}" {{ blank_target }} rel="{{ rel_attributes }}">{{ macros_translate::translate(key="see_changes", default="See changes", language_strings=language_strings) }}</a></li>
|
||||
@@ -227,7 +234,12 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{{ macros_toc::toc(page=page, header=true, language_strings=language_strings) }}
|
||||
{% endif %}
|
||||
|
||||
<section class="body">
|
||||
{#- Optional Summary paragraph for readers -#}
|
||||
{% if page.description %}
|
||||
<p class="p-summary" hidden>{{ page.description }}</p>
|
||||
{%- endif -%}
|
||||
|
||||
<section class="e-content body">
|
||||
{#- Replace series_intro placeholder -#}
|
||||
{%- set content_with_intro = page.content -%}
|
||||
{%- if "<!-- series_intro -->" in page.content -%}
|
||||
@@ -263,6 +275,11 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{{ processed_content | replace(from="<!-- toc -->", to=macros_toc::toc(page=page, header=false, language_strings=language_strings)) | safe }}
|
||||
</section>
|
||||
|
||||
{#- iine button -#}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="iine", page=page, default_global_value=false) == "true" -%}
|
||||
{% include "partials/iine_button.html" %}
|
||||
{%- endif -%}
|
||||
|
||||
{% if macros_settings::evaluate_setting_priority(setting="show_previous_next_article_links", page=page, default_global_value=true) == "true" %}
|
||||
{%- if page.lower or page.higher -%}
|
||||
{% set next_label = macros_translate::translate(key="next", default="Next", language_strings=language_strings) %}
|
||||
@@ -333,6 +350,13 @@ Current section extra: {% if current_section %}{{ current_section.extra | json_e
|
||||
{% if comment_system %}
|
||||
{% include "partials/comments.html" %}
|
||||
{% endif %}
|
||||
{#- Webmentions -#}
|
||||
{%- set global_webmentions_enabled = config.extra.webmentions.enable | default(value=false) -%}
|
||||
{%- set page_webmentions_enabled = page.extra.webmentions | default(value=global_webmentions_enabled) -%}
|
||||
{%- set webmentions_enabled = global_webmentions_enabled and page_webmentions_enabled != false or page_webmentions_enabled == true -%}
|
||||
{%- if webmentions_enabled -%}
|
||||
{%- include "partials/webmentions.html" -%}
|
||||
{%- endif -%}
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
data-website-id="{{ analytics_id }}"
|
||||
src="https://cloud.umami.is/script.js"
|
||||
{% endif %}
|
||||
data-do-not-track="true">
|
||||
{% if config.extra.analytics.do_not_track %}data-do-not-track="true"{% endif %}>
|
||||
</script>
|
||||
|
||||
{% elif analytics_service == "plausible" %}
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
{% break %}
|
||||
{% endif %}
|
||||
{# Determine which URL to use, default is page.permalink #}
|
||||
{%- if page.extra.link_to and config.markdown.external_links_target_blank -%}
|
||||
{%- set blank_target = "target=_blank" -%}
|
||||
{%- else -%}
|
||||
{%- set blank_target = "" -%}
|
||||
{%- endif -%}
|
||||
{%- set blank_target = macros_target_attribute::target_attribute(new_tab=config.markdown.external_links_target_blank and page.extra.link_to) -%}
|
||||
|
||||
{% set target_url = page.extra.link_to | default(value=page.permalink) %}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ content="default-src 'self'
|
||||
{%- set giscus_enabled = config.extra.giscus.enabled_for_all_posts or page.extra.giscus -%}
|
||||
{%- set hyvortalk_enabled = config.extra.hyvortalk.enabled_for_all_posts or page.extra.hyvortalk -%}
|
||||
{%- set isso_enabled = config.extra.isso.enabled_for_all_posts or page.extra.isso -%}
|
||||
{%- if page -%}
|
||||
{%- set iine_enabled = macros_settings::evaluate_setting_priority(setting="iine", page=page, default_global_value=false) == "true" -%}
|
||||
{%- endif -%}
|
||||
{%- if page -%}
|
||||
{%- set mermaid_enabled = macros_settings::evaluate_setting_priority(setting="mermaid", page=page, default_global_value=false) == "true" -%}
|
||||
{%- endif -%}
|
||||
@@ -50,10 +53,21 @@ content="default-src 'self'
|
||||
{%- set script_src = script_src ~ " " ~ " utteranc.es" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if mermaid_enabled and not serve_local_mermaid -%}
|
||||
{%- if (mermaid_enabled and not serve_local_mermaid) or iine_enabled -%}
|
||||
{%- set script_src = script_src ~ " " ~ " cdn.jsdelivr.net" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#- Check if a webmention system is enabled to allow the necessary domains and directives -#}
|
||||
{%- if config.extra.webmentions.enable -%}
|
||||
{%- set connect_src = connect_src ~ " webmention.io" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#- Check if iine like buttons are enabled to allow the necessary domains -#}
|
||||
{%- if iine_enabled -%}
|
||||
{%- set connect_src = connect_src ~ " vhiweeypifbwacashxjz.supabase.co" -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
{#- Append WebSocket for Zola serve mode -#}
|
||||
{%- if config.mode == "serve" -%}
|
||||
{%- set connect_src = connect_src ~ " ws:" -%}
|
||||
@@ -61,19 +75,19 @@ content="default-src 'self'
|
||||
|
||||
{%- for domain in config.extra.allowed_domains -%}
|
||||
{%- if domain.directive == "connect-src" -%}
|
||||
{%- set configured_connect_src = domain.domains | join(sep=' ') -%}
|
||||
{%- set configured_connect_src = domain.domains | join(sep=' ') | safe -%}
|
||||
{%- set_global connect_src = connect_src ~ " " ~ configured_connect_src -%}
|
||||
{%- continue -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if domain.directive == "script-src" -%}
|
||||
{%- set configured_script_src = domain.domains | join(sep=' ') -%}
|
||||
{%- set configured_script_src = domain.domains | join(sep=' ') | safe -%}
|
||||
{%- set_global script_src = script_src ~ " " ~ configured_script_src -%}
|
||||
{%- continue -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#- Handle directives that are not connect-src -#}
|
||||
{{ domain.directive }} {{ domain.domains | join(sep=' ') -}}
|
||||
{{ domain.directive }} {{ domain.domains | join(sep=' ') | safe -}}
|
||||
|
||||
{%- if domain.directive == "style-src" -%}
|
||||
{%- if utterances_enabled or hyvortalk_enabled or mermaid_enabled %} 'unsafe-inline'
|
||||
|
||||
@@ -21,9 +21,16 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Button to go to the comment section #}
|
||||
{% if comment_system %}
|
||||
<a href="#comments" id="comments-button" class="no-hover-padding" title="{{ macros_translate::translate(key="go_to_comments", default="Go to comments section", language_strings=language_strings) }}">
|
||||
{# Button to go to the comment/webmentions section #}
|
||||
{% if comment_system or config.extra.webmentions.enable %}
|
||||
{%- if comment_system -%}
|
||||
{#- Comments are shown above webmentions -#}
|
||||
{%- set comments_id = "comments" -%}
|
||||
{%- else -%}
|
||||
{%- set comments_id = "webmentions" -%}
|
||||
{%- endif -%}
|
||||
|
||||
<a href="#{{- comments_id -}}" id="comments-button" class="no-hover-padding" title="{{ macros_translate::translate(key="go_to_comments", default="Go to comments section", language_strings=language_strings) }}">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z" clip-rule="evenodd" fill-rule="evenodd"/></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -74,3 +81,8 @@
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="footnote_backlinks", page=page_s, section=section_s, default_global_value=false) == "true" -%}
|
||||
<script defer src="{{ get_url(path='js/footnoteBacklinks.min.js', trailing_slash=false | safe )}}"></script>
|
||||
{%- endif -%}
|
||||
|
||||
{# Add iine.js for the like button #}
|
||||
{%- if macros_settings::evaluate_setting_priority(setting="iine", page=page_s, section=section_s, default_global_value=false) == "true" -%}
|
||||
<script defer src="https://cdn.jsdelivr.net/gh/welpo/iine@main/iine.mini.js"></script>
|
||||
{%- endif -%}
|
||||
|
||||
@@ -2,18 +2,11 @@
|
||||
|
||||
{%- set rel_attributes = macros_rel_attributes::rel_attributes() | trim -%}
|
||||
|
||||
{%- if config.markdown.external_links_target_blank -%}
|
||||
{%- set blank_target = "target=_blank" -%}
|
||||
{%- else -%}
|
||||
{%- set blank_target = "" -%}
|
||||
{%- endif -%}
|
||||
{%- set blank_target = macros_target_attribute::target_attribute(new_tab=config.markdown.external_links_target_blank) -%}
|
||||
|
||||
{#- Feed icon -#}
|
||||
{#- Zola 0.19.0 uses `generate_feeds`. Prior versions use `generate_feed` -#}
|
||||
{%- set generate_feed = config.generate_feeds | default(value=config.generate_feed) -%}
|
||||
{%- set feed_url = config.feed_filenames[0] | default(value=(config.feed_filename)) -%}
|
||||
{%- set should_show_feed = generate_feed and config.extra.feed_icon and feed_url -%}
|
||||
|
||||
{%- set feed_url = feed_utils::get_feed_url() -%}
|
||||
{%- set should_show_feed = feed_utils::should_show_footer_feed_icon() == "true" -%}
|
||||
{%- set should_show_footer_icons = should_show_feed or config.extra.socials or config.extra.email -%}
|
||||
|
||||
<footer>
|
||||
|
||||
75
themes/tabi/templates/partials/hcard.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{%- set hcard = config.extra.hcard -%}
|
||||
|
||||
{% set full_name = config.author %}
|
||||
{% if hcard.full_name %}
|
||||
{% set full_name = hcard.full_name %}
|
||||
{% endif %}
|
||||
|
||||
{%- set homepage = config.base_url -%}
|
||||
{% if hcard.homepage %}
|
||||
{%- set homepage = hcard.homepage -%}
|
||||
{% endif %}
|
||||
|
||||
{% if hcard.enable %}
|
||||
<div class="h-card hidden">
|
||||
<div>
|
||||
{%- if hcard.avatar -%}
|
||||
<img
|
||||
class="u-photo"
|
||||
src="{{ get_url(path=hcard.avatar, cachebust=true) }}"
|
||||
width="200"
|
||||
height="200"
|
||||
alt="{{ full_name }}"
|
||||
/>
|
||||
{%- endif -%}
|
||||
|
||||
<span class="p-name" rel="me">{{ full_name }}</span>
|
||||
|
||||
{% if hcard.p_nickname %}
|
||||
( <span class="p-nickname">{{ hcard.p_nickname }}</span> )
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if hcard.biography %}
|
||||
<p class="p-note">{{ hcard.biography }}</p>
|
||||
{% endif %}
|
||||
|
||||
{# links #}
|
||||
<div>
|
||||
{%- if hcard.with_mail and config.extra.email and not config.extra.encode_plaintext_email -%}
|
||||
<span>
|
||||
<a class="u-email" href="mailto:{{ config.extra.email | safe }}">email</a>
|
||||
</span> -
|
||||
{%- endif -%}
|
||||
|
||||
<span>
|
||||
<a class="u-url u-id" href="{{ homepage }}">homepage</a>
|
||||
</span> -
|
||||
|
||||
{%- if hcard.with_social_links and config.extra.socials %}
|
||||
{% for social in config.extra.socials %}
|
||||
<span>
|
||||
<a class="p-url" rel="me" href="{{ social.url | safe }}">{{ social.name }}</a>
|
||||
</span> -
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# additional properties #}
|
||||
{% set dl_started = false %}
|
||||
{% for key, value in hcard %}
|
||||
{% if key not in ['enable', 'with_mail', 'with_social_links', 'homepage', 'full_name', 'avatar', 'biography', 'p_nickname'] %}
|
||||
{% if not dl_started %}
|
||||
<dl>
|
||||
{% set_global dl_started = true %}
|
||||
{% endif %}
|
||||
<dt>{{ key | replace(from="p_", to="") | replace(from="u_", to="") | replace(from="dt_", to="") | replace(from="_", to=" ") | capitalize }}</dt>
|
||||
<dd class="{{ key | replace(from="_", to="-") }}">{{ value }}</dd>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if dl_started %}
|
||||
</dl>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
26
themes/tabi/templates/partials/hcard_small.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{%- set hcard = config.extra.hcard -%}
|
||||
|
||||
{%- set full_name = config.author -%}
|
||||
{%- if hcard.full_name -%}
|
||||
{%- set full_name = hcard.full_name -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set homepage = config.base_url -%}
|
||||
{%- if hcard.homepage -%}
|
||||
{%- set homepage = hcard.homepage -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set icon_attr = "" -%}
|
||||
{%- if hcard.avatar -%}
|
||||
{%- set icon_attr = "author-icon" -%}
|
||||
{%- endif -%}
|
||||
|
||||
<span class="hidden p-author h-card">
|
||||
<a rel="author" href="{{ homepage }}" class="u-url {{ icon_attr }}" title="{{ full_name }}">
|
||||
{%- if hcard.avatar -%}
|
||||
<img class="u-photo" src="{{ get_url(path=hcard.avatar, cachebust=true) }}" alt="{{ full_name }}" />
|
||||
{%- else -%}
|
||||
{{ full_name }}
|
||||
{%- endif -%}
|
||||
</a>
|
||||
</span>
|
||||
@@ -176,4 +176,6 @@
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
{# Users can optionally provide this template to add content to the head element. #}
|
||||
{% include "tabi/extend_head.html" ignore missing %}
|
||||
</head>
|
||||
|
||||
33
themes/tabi/templates/partials/iine_button.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% import "macros/settings.html" as macros_settings %}
|
||||
{%- set button_icon = button_icon | default(value=macros_settings::evaluate_setting_priority(setting="iine_icon", page=page | default(value=""), section=section | default(value=""), default_global_value="heart")) -%}
|
||||
{%- if config.extra.iine_unified_languages and lang != config.default_language -%}
|
||||
{%- set unified_slug = page.path | replace(from='/' ~ lang ~ '/', to='/') -%}
|
||||
{%- set slug = slug | default(value=unified_slug) -%}
|
||||
{%- else -%}
|
||||
{%- set slug = slug | default(value=page.path) -%}
|
||||
{%- endif -%}
|
||||
{%- if label -%}
|
||||
{%- set final_label = label -%}
|
||||
{%- elif language_strings -%}
|
||||
{%- set final_label = macros_translate::translate(key="like_this_post", default="Like this post", language_strings=language_strings) -%}
|
||||
{%- else -%}
|
||||
{%- set final_label = "Like this post" -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if button_icon == "heart" -%}
|
||||
{%- set icon_display = "♥️" -%}
|
||||
{%- elif button_icon == "thumbs_up" -%}
|
||||
{%- set icon_display = "👍" -%}
|
||||
{%- elif button_icon == "upvote" -%}
|
||||
{%- set icon_display = "⬆️" -%}
|
||||
{%- else -%}
|
||||
{%- set icon_display = button_icon -%}
|
||||
{%- endif -%}
|
||||
|
||||
<form method="post" action="https://vhiweeypifbwacashxjz.supabase.co/rest/v1/rpc/increment_hits?apikey=sb_publishable_EoB7MFJhCmb6PiAk-GPJ4w_PGhQ44Ru" class="iine-form">
|
||||
<input type="hidden" name="page_slug" value="{%- if slug -%}{{ slug }}{%- else -%}{{ current_url | default(value=page.path) }}{%- endif -%}">
|
||||
<button class="iine-button" type="submit"
|
||||
{%- if slug %} data-slug="{{ slug }}"{% endif %}
|
||||
{%- if button_icon %} data-icon="{{ button_icon }}"{% endif %}
|
||||
aria-label="{{ final_label }}" title="{{ final_label }}">{{ icon_display }}</button>
|
||||
</form>
|
||||
@@ -1,7 +1,12 @@
|
||||
<li class="language-switcher">
|
||||
<details class="dropdown">
|
||||
<summary role="button" aria-haspopup="true" title="{{ macros_translate::translate(key="language_selection", default="Language selection", language_strings=language_strings) }}" aria-label="{{ macros_translate::translate(key="language_selection", default="Language selection", language_strings=language_strings) }}">
|
||||
{%- if config.extra.show_selected_language_code_in_language_switcher -%}
|
||||
<div class="language-switcher-icon language-switcher-icon-with-code"></div>
|
||||
<div class="language-switcher-icon-code">{{lang}}</div>
|
||||
{%- else -%}
|
||||
<div class="language-switcher-icon"></div>
|
||||
{%- endif -%}
|
||||
</summary>
|
||||
<div class="dropdown-content" role="menu">
|
||||
{#- Display the current language first in the dropdown -#}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<header>
|
||||
<nav class="navbar">
|
||||
<div class="nav-title">
|
||||
<a class="home-title" href="{{ get_url(path='/', lang=lang) }}">{{ config.title }}</a>
|
||||
<a class="home-title" href="{{ get_url(path='/', lang=lang, trailing_slash=(lang == config.default_language)) }}">{{ config.title }}</a>
|
||||
</div>
|
||||
|
||||
{%- if config.extra.menu %}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{#- Setup -#}
|
||||
{% if not config.title %}
|
||||
{{ throw(message="ERROR: No `title` set in `config.toml`. tabi requires a title to function.") }}
|
||||
{% endif %}
|
||||
{%- set prefix = config.title | safe -%}
|
||||
{%- set custom_separator = config.extra.separator | default(value="•") -%}
|
||||
{%- set separator = " " ~ custom_separator ~ " " -%}
|
||||
|
||||
51
themes/tabi/templates/partials/webmentions.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{# Incorporate webmention.io links and script into the page head.
|
||||
1. Provide the link to the webmention data in the at webmention.io.
|
||||
2. Link to the stylesheet for styling webmentions on a page.
|
||||
3. Add and configure the javascript to fetch and display the webmentions collected at webmention.io. #}
|
||||
|
||||
<link rel="webmention" href="https://webmention.io/{{ config.extra.webmentions.domain }}/webmention" />
|
||||
|
||||
{# Calculate the configured data for the script, if any #}
|
||||
|
||||
{% set script_data = "" %}
|
||||
|
||||
{% if config.extra.webmentions.id %}
|
||||
{% set script_data = script_data ~ "data-id=" ~ config.extra.webmentions.id %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.page_url %}
|
||||
{% set script_data = script_data ~ " data-page-url=" ~ config.extra.webmentions.page_url %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.add_urls %}
|
||||
{% set script_data = script_data ~ "data-add-urls=" ~ config.extra.webmentions.add_urls %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.wordcount %}
|
||||
{% set script_data = script_data ~ " data-wordcount=" ~ config.extra.webmentions.wordcount %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.max_webmentions %}
|
||||
{% set script_data = script_data ~ "data-max-webmentions=" ~ config.extra.webmentions.max_webmentions %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.prevent_spoofing %}
|
||||
{% set script_data = script_data ~ "data-prevent-spoofing=" ~ config.extra.webmentions.prevent_spoofing %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.sort_by %}
|
||||
{% set script_data = script_data ~ "data-sort-by=" ~ config.extra.webmentions.sort_by %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.sort_dir %}
|
||||
{% set script_data = script_data ~ "data-sort-dir=" ~ config.extra.webmentions.sort_dir %}
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.webmentions.comments_are_reactions %}
|
||||
{% set script_data = script_data ~ " data-comments-are-reactions=" ~ config.extra.webmentions.comments_are_reactions %}
|
||||
{% endif %}
|
||||
|
||||
<script async src="{{ get_url(path='js/webmention.min.js', trailing_slash=false, cachebust=true) | safe }}" {{ script_data }}>
|
||||
</script>
|
||||
|
||||
<div class="webmentions-container" id="webmentions"></div>
|
||||
@@ -24,7 +24,10 @@
|
||||
{%- endif -%}
|
||||
|
||||
<main {% if more_than_one_section_shown %}class="{{ first_section }}-first"{% endif %}>
|
||||
{%- if section.extra.header %}
|
||||
{%- if config.extra.hcard %}
|
||||
{%- include "partials/hcard.html" -%}
|
||||
{% endif -%}
|
||||
{%- if section.extra.header %}
|
||||
{%- include "partials/home_banner.html" -%}
|
||||
{%- elif section.content -%}
|
||||
<section>
|
||||
@@ -51,4 +54,6 @@
|
||||
{%- endif -%}
|
||||
</main>
|
||||
|
||||
{%- include "partials/extra_features.html" -%}
|
||||
|
||||
{% endblock main_content %}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
{#- Fallback to absolute path if relative path doesn't work -#}
|
||||
{%- if not meta -%}
|
||||
{%- set meta = get_image_metadata(path=src, allow_missing=true) -%}
|
||||
{%- set image_url = get_url(path=src) -%}
|
||||
{%- set image_url = get_url(path=src, cachebust=true) -%}
|
||||
{%- else -%}
|
||||
{%- set image_url = get_url(path=relative_path) -%}
|
||||
{%- set image_url = get_url(path=relative_path, cachebust=true) -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
{%- set relative_light_path = colocated_path ~ light_src -%}
|
||||
{%- set light_meta = get_image_metadata(path=relative_light_path, allow_missing=true) -%}
|
||||
{%- if not light_meta -%}
|
||||
{%- set light_image_url = get_url(path=light_src) -%}
|
||||
{%- set light_image_url = get_url(path=light_src, cachebust=true) -%}
|
||||
{%- else -%}
|
||||
{%- set light_image_url = get_url(path=relative_light_path) -%}
|
||||
{%- set light_image_url = get_url(path=relative_light_path, cachebust=true) -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
{%- set relative_dark_path = colocated_path ~ dark_src -%}
|
||||
{%- set dark_meta = get_image_metadata(path=relative_dark_path, allow_missing=true) -%}
|
||||
{%- if not dark_meta -%}
|
||||
{%- set dark_image_url = get_url(path=dark_src) -%}
|
||||
{%- set dark_image_url = get_url(path=dark_src, cachebust=true) -%}
|
||||
{%- else -%}
|
||||
{%- set dark_image_url = get_url(path=relative_dark_path) -%}
|
||||
{%- set dark_image_url = get_url(path=relative_dark_path, cachebust=true) -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
{#- Fallback to absolute path if relative path doesn't work -#}
|
||||
{%- if not meta -%}
|
||||
{%- set meta = get_image_metadata(path=src, allow_missing=true) -%}
|
||||
{%- set image_url = get_url(path=src) -%}
|
||||
{%- set image_url = get_url(path=src, cachebust=true) -%}
|
||||
{%- else %}
|
||||
{%- set image_url = get_url(path=relative_path) -%}
|
||||
{%- set image_url = get_url(path=relative_path, cachebust=true) -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||