Add Linkki broadcast image
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 48s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 55s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 54s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim
context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:linkki-tiedotus oci_labels:… (push) Successful in 39s
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 48s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 55s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 54s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim
context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:linkki-tiedotus oci_labels:… (push) Successful in 39s
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ effective_ui_language }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ translations["app.title"] }}</title>
|
||||
<link rel="icon" type="image/png" href="{{ static_asset_url('favicon.png') }}">
|
||||
<link rel="stylesheet" href="{{ static_asset_url('app.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-actions">
|
||||
<a class="sidebar-link {% if current_path == '/' %}active{% endif %}" href="/">{{ translations["nav.instructions"] }}</a>
|
||||
<form method="post" action="/logout">
|
||||
<button type="submit">{{ translations["nav.sign_out"] }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/language" class="language-form">
|
||||
<input type="hidden" name="return_to" value="{{ current_path }}">
|
||||
<label>
|
||||
<span>{{ translations["nav.interface_language"] }}</span>
|
||||
<select name="ui_language" onchange="this.form.submit()">
|
||||
{% for option in language_options %}
|
||||
<option value="{{ option.code }}" {% if option.code == ui_language %}selected{% endif %}>{{ translations[option.label_key] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/announcements" class="create-form">
|
||||
<input type="hidden" name="return_to" value="{{ current_path }}">
|
||||
<input type="text" name="title" placeholder="{{ translations['create.placeholder'] }}" required class="{% if not permissions.save_announcements %}permission-disabled{% endif %}" {% if not permissions.save_announcements %}disabled title="{{ translations['create.permission_denied'] }}"{% endif %}>
|
||||
<button type="submit" class="{% if not permissions.save_announcements %}permission-disabled{% endif %}" {% if not permissions.save_announcements %}disabled title="{{ translations['create.permission_denied'] }}"{% endif %}>{{ translations["create.button"] }}</button>
|
||||
</form>
|
||||
{% if create_error %}
|
||||
<p class="error">{{ create_error }}</p>
|
||||
{% endif %}
|
||||
|
||||
<nav class="announcement-list">
|
||||
{% for item in announcements %}
|
||||
<div class="announcement-entry">
|
||||
<a class="announcement-item {% if broadcast and item.directory == broadcast.directory %}active{% endif %}" href="/announcements/{{ item.directory }}">
|
||||
<span class="announcement-title-row">
|
||||
{% if item.sidebar_has_pins %}
|
||||
<span class="status-primary-marker status-{{ item.sidebar_pin_status }} status-primary-marker-pinned" aria-hidden="true"></span>
|
||||
{% endif %}
|
||||
{% if item.status_symbol %}
|
||||
<span class="status-symbol status-{{ item.status }} {% if item.sidebar_links_incomplete %}status-symbol-incomplete{% endif %}">{{ item.status_symbol }}</span>
|
||||
{% endif %}
|
||||
<span class="announcement-title">{{ item.title }}</span>
|
||||
</span>
|
||||
<small>{{ item.target_summary }}</small>
|
||||
{% for meta_line in item.sidebar_meta_lines %}
|
||||
<small class="announcement-meta">{{ meta_line }}</small>
|
||||
{% endfor %}
|
||||
</a>
|
||||
<div class="announcement-order-controls">
|
||||
<form method="post" action="/announcements/{{ item.directory }}/move">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="return_to" value="{{ current_path }}">
|
||||
<button type="submit" class="{% if not permissions.save_announcements %}permission-disabled{% endif %}" {% if not item.can_move_up or not permissions.save_announcements %}disabled{% endif %}>↑</button>
|
||||
</form>
|
||||
<form method="post" action="/announcements/{{ item.directory }}/move">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="return_to" value="{{ current_path }}">
|
||||
<button type="submit" class="{% if not permissions.save_announcements %}permission-disabled{% endif %}" {% if not item.can_move_down or not permissions.save_announcements %}disabled{% endif %}>↓</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const key = "linkki.sidebar.scrollTop";
|
||||
const sidebar = document.querySelector(".sidebar");
|
||||
if (!sidebar) return;
|
||||
|
||||
const restore = () => {
|
||||
const raw = window.sessionStorage.getItem(key);
|
||||
if (!raw) return;
|
||||
const scrollTop = Number(raw);
|
||||
if (!Number.isFinite(scrollTop)) return;
|
||||
sidebar.scrollTop = scrollTop;
|
||||
window.requestAnimationFrame(() => {
|
||||
sidebar.scrollTop = scrollTop;
|
||||
});
|
||||
};
|
||||
|
||||
const persist = () => {
|
||||
window.sessionStorage.setItem(key, String(sidebar.scrollTop));
|
||||
};
|
||||
|
||||
restore();
|
||||
sidebar.addEventListener("scroll", persist, { passive: true });
|
||||
window.addEventListener("beforeunload", persist);
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const link = event.target.closest("a");
|
||||
if (link && link.closest(".sidebar")) {
|
||||
persist();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("submit", (event) => {
|
||||
const form = event.target;
|
||||
if (form instanceof HTMLFormElement && form.closest(".sidebar")) {
|
||||
persist();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section id="editor-root"
|
||||
data-broadcast='{{ broadcast | tojson }}'
|
||||
data-permissions='{{ permissions | tojson }}'
|
||||
data-timezone='{{ timezone }}'
|
||||
data-translations='{{ translations | tojson }}'>
|
||||
<header class="editor-header">
|
||||
<div>
|
||||
<h1>{{ broadcast.metadata.title }}</h1>
|
||||
<p>{{ broadcast.directory }}</p>
|
||||
</div>
|
||||
<div class="top-actions">
|
||||
<button type="button" id="save-button">{{ translations["editor.save"] }}</button>
|
||||
<button type="button" id="publish-button">{{ translations["editor.post_now"] }}</button>
|
||||
<button type="button" id="schedule-button">{{ translations["editor.schedule"] }}</button>
|
||||
<button type="button" id="delete-storage-button" class="danger">{{ translations["editor.delete_storage"] }}</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="editor-tabs">
|
||||
<div class="editor-tab-list" role="tablist" aria-label="{{ translations['editor.settings_tabs'] }}">
|
||||
<button type="button" class="editor-tab-button active" id="editor-tab-main" data-tab="main" role="tab" aria-selected="true">{{ translations["editor.tab_main"] }}</button>
|
||||
<button type="button" class="editor-tab-button" id="editor-tab-telegram" data-tab="telegram" role="tab" aria-selected="false">{{ translations["editor.tab_telegram"] }}</button>
|
||||
<button type="button" class="editor-tab-button" id="editor-tab-pinning" data-tab="pinning" role="tab" aria-selected="false">{{ translations["editor.tab_pinning"] }}</button>
|
||||
<button type="button" class="editor-tab-button" id="editor-tab-logs" data-tab="logs" role="tab" aria-selected="false">{{ translations["editor.tab_logs"] }}</button>
|
||||
</div>
|
||||
|
||||
<section class="editor-tab-panel active" id="editor-tab-panel-main" data-tab-panel="main" role="tabpanel" aria-labelledby="editor-tab-main">
|
||||
<section class="meta-panel">
|
||||
<label class="meta-title-field">{{ translations["editor.title"] }}
|
||||
<input type="text" id="title-input" value="{{ broadcast.metadata.title }}">
|
||||
</label>
|
||||
<label class="meta-checkbox meta-global-link-preview">
|
||||
<input type="checkbox" id="disable-link-previews-global">
|
||||
<span>{{ translations["editor.disable_link_previews_global"] }}</span>
|
||||
</label>
|
||||
<div class="telegram-target-fields">
|
||||
<label>{{ translations["editor.telegram_bot"] }}
|
||||
<select id="telegram-bot-select"></select>
|
||||
</label>
|
||||
<label>{{ translations["editor.telegram_chat"] }}
|
||||
<select id="telegram-chat-select"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="meta-stack">
|
||||
<label>{{ translations["editor.language_order"] }}
|
||||
<select id="language-order-select">
|
||||
<option value="fi,sv,en">{{ translations["editor.language_order.fi_sv_en"] }}</option>
|
||||
<option value="fi,en,sv">{{ translations["editor.language_order.fi_en_sv"] }}</option>
|
||||
<option value="sv,fi,en">{{ translations["editor.language_order.sv_fi_en"] }}</option>
|
||||
<option value="sv,en,fi">{{ translations["editor.language_order.sv_en_fi"] }}</option>
|
||||
<option value="en,fi,sv">{{ translations["editor.language_order.en_fi_sv"] }}</option>
|
||||
<option value="en,sv,fi">{{ translations["editor.language_order.en_sv_fi"] }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="meta-schedule-field">{{ translations["editor.schedule_label"] }}
|
||||
<input type="datetime-local" id="schedule-input">
|
||||
<small>{{ translations["editor.schedule_timezone"] }}: {{ timezone }}</small>
|
||||
</label>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="editor-tab-panel" id="editor-tab-panel-telegram" data-tab-panel="telegram" role="tabpanel" aria-labelledby="editor-tab-telegram" hidden>
|
||||
<section id="telegram-binding-panel" class="telegram-binding-panel"></section>
|
||||
<button type="button" id="delete-telegram-button" class="danger telegram-delete-button">{{ translations["editor.delete_telegram"] }}</button>
|
||||
</section>
|
||||
|
||||
<section class="editor-tab-panel" id="editor-tab-panel-pinning" data-tab-panel="pinning" role="tabpanel" aria-labelledby="editor-tab-pinning" hidden>
|
||||
<section class="pinning-panel">
|
||||
<div class="meta-stack pinning-controls">
|
||||
<label>{{ translations["editor.discussion_pinning"] }}
|
||||
<select id="discussion-pinning-select">
|
||||
<option value="none">{{ translations["editor.discussion_pinning.none"] }}</option>
|
||||
<option value="first">{{ translations["editor.discussion_pinning.first"] }}</option>
|
||||
<option value="last">{{ translations["editor.discussion_pinning.last"] }}</option>
|
||||
<option value="all">{{ translations["editor.discussion_pinning.all"] }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="meta-checkbox meta-delete-service-field">
|
||||
<input type="checkbox" id="delete-pin-service-messages">
|
||||
<span>{{ translations["editor.delete_pin_service_messages"] }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="unpin-scheduler">
|
||||
<label>{{ translations["editor.unpin_schedule_label"] }}
|
||||
<input type="datetime-local" id="unpin-schedule-input">
|
||||
</label>
|
||||
<button type="button" id="unpin-schedule-button">{{ translations["editor.schedule_unpin"] }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="editor-tab-panel" id="editor-tab-panel-logs" data-tab-panel="logs" role="tabpanel" aria-labelledby="editor-tab-logs" hidden>
|
||||
<section class="status-box" id="status-box">
|
||||
<div class="status-box-content" id="status-box-content"></div>
|
||||
<button type="button" id="clear-status-button">{{ translations["editor.clear_status"] }}</button>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="images-panel">
|
||||
<h2>{{ translations["editor.global_images"] }}</h2>
|
||||
<div class="image-controls">
|
||||
<span class="upload-label">{{ translations["editor.add_global_images"] }}</span>
|
||||
<input type="file" id="upload-global" accept="image/*" multiple>
|
||||
</div>
|
||||
<div class="image-paste-controls">
|
||||
<button type="button" id="open-global-image-import-button" class="secondary-button">{{ translations["editor.open_image_import"] }}</button>
|
||||
</div>
|
||||
<div class="image-list" data-scope="global"></div>
|
||||
</section>
|
||||
|
||||
<section id="languages-container"></section>
|
||||
</section>
|
||||
<div id="image-import-modal" class="image-import-modal" hidden>
|
||||
<div class="image-import-dialog" role="dialog" aria-modal="true" aria-labelledby="image-import-title">
|
||||
<div class="image-import-header">
|
||||
<h2 id="image-import-title">{{ translations["editor.image_import_title"] }}</h2>
|
||||
<button type="button" id="image-import-close-button" class="secondary-button">{{ translations["editor.close_image_import"] }}</button>
|
||||
</div>
|
||||
<p class="image-import-help">{{ translations["editor.image_import_help"] }}</p>
|
||||
<label>{{ translations["editor.image_import_format"] }}
|
||||
<select id="image-import-format-select">
|
||||
<option value="auto">{{ translations["editor.image_import_format.auto"] }}</option>
|
||||
<option value="png">{{ translations["editor.image_import_format.png"] }}</option>
|
||||
<option value="jpeg">{{ translations["editor.image_import_format.jpeg"] }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{ translations["editor.image_import_url"] }}
|
||||
<input type="url" id="image-import-url-input" placeholder="{{ translations['editor.image_import_url_placeholder'] }}">
|
||||
</label>
|
||||
<div class="image-import-actions">
|
||||
<button type="button" id="image-import-submit-url-button">{{ translations["editor.import_image_url_submit"] }}</button>
|
||||
<button type="button" id="image-import-cancel-button" class="secondary-button">{{ translations["editor.cancel_image_import"] }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ static_asset_url('app.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="instructions-panel">
|
||||
<h1>{{ translations["instructions.title"] }}</h1>
|
||||
|
||||
<section class="instruction-section">
|
||||
<h2>{{ translations["instructions.quick_start_title"] }}</h2>
|
||||
<ul>
|
||||
{% for item in translations["instructions.quick_start_items"] %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="instruction-section">
|
||||
<h2>{{ translations["instructions.states_title"] }}</h2>
|
||||
<ul>
|
||||
{% for item in translations["instructions.states_items"] %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="instruction-section">
|
||||
<h2>{{ translations["instructions.formatting_title"] }}</h2>
|
||||
<ul>
|
||||
{% for item in translations["instructions.formatting_items"] %}
|
||||
<li><strong>{{ item.label }}</strong>: <code>{{ item.example }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="instruction-section">
|
||||
<h2>{{ translations["instructions.posted_title"] }}</h2>
|
||||
<ul>
|
||||
{% for item in translations["instructions.posted_items"] %}
|
||||
<li>{{ item }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ effective_ui_language }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ translations["app.title"] }}</title>
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<main class="login-card">
|
||||
<h1>{{ translations["login.title"] }}</h1>
|
||||
<form method="post" action="/login-language" class="login-language-form">
|
||||
<label>{{ translations["nav.interface_language"] }}
|
||||
<select name="ui_language" onchange="this.form.submit()">
|
||||
{% for option in login_language_options %}
|
||||
<option value="{{ option.code }}" {% if option.code == login_ui_language %}selected{% endif %}>{{ option.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
<form method="post" action="/login">
|
||||
<label>{{ translations["login.username"] }} <input type="text" name="username" required autofocus></label>
|
||||
<label>{{ translations["login.password"] }} <input type="password" name="password" required></label>
|
||||
<button type="submit">{{ translations["login.sign_in"] }}</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user