Files
ACORE/templates/pages/news.html
T
lokses 655ba8c31a i18n: перевод ленты новостей (news/news-detail/news-edit) ru/uk/en
Список публикаций, форма создания/редактирования, детальная (комментарии,
публикация в Mattermost, важное/закреплённое), JS-строки (загрузка картинок,
свернуть/развернуть) помечены {% trans %} и переведены на uk/en.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 21:59:01 +03:00

175 lines
11 KiB
HTML

{% load i18n %}
<div class="page-content">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1.5rem;">
<div>
<h1 style="font-size:1.25rem; font-weight:500; color:var(--color-foreground);">{% trans "Новости" %}</h1>
<p style="font-size:0.875rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "Объявления и важные события компании" %}</p>
</div>
</div>
{% if form_error %}
<div style="background:color-mix(in srgb, var(--color-destructive) 8%, transparent); border:1px solid color-mix(in srgb, var(--color-destructive) 30%, transparent); border-radius:0.75rem; padding:0.875rem 1.25rem; margin-bottom:1rem; font-size:0.875rem; color:var(--color-destructive);">
{{ form_error }}
</div>
{% endif %}
{% if request.user.is_staff %}
<!-- Створення поста (staff) -->
<details style="margin-bottom:1.25rem;" {% if form_error %}open{% endif %}>
<summary class="btn btn-primary" style="display:inline-flex; align-items:center; gap:0.375rem; cursor:pointer; list-style:none; width:fit-content;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" x2="12" y1="5" y2="19"/><line x1="5" x2="19" y1="12" y2="12"/>
</svg>
{% trans "Новая публикация" %}
</summary>
<form id="post-create-form" hx-post="/news/" hx-target="#main-content" hx-indicator="#loader"
style="margin-top:0.75rem; background:var(--color-card); border:1px solid var(--color-border); border-radius:0.75rem; padding:1.25rem; display:flex; flex-direction:column; gap:0.875rem;">
{% csrf_token %}
<input type="text" name="title" required maxlength="250" placeholder="{% trans 'Заголовок' %}" class="input" style="width:100%; font-weight:500;">
<textarea name="body" id="post-body-editor" placeholder="{% trans 'Текст публикации...' %}"
style="width:100%; min-height:6rem;"></textarea>
<script>
(function() {
if (!window.tinymce) return;
tinymce.remove('#post-body-editor');
tinymce.init({
selector: '#post-body-editor',
height: 320,
menubar: false,
plugins: 'lists link table autolink image',
toolbar: 'undo redo | blocks | bold italic underline strikethrough | ' +
'forecolor | alignleft aligncenter alignright | ' +
'bullist numlist | link table image | removeformat',
language: 'uk',
license_key: 'gpl',
promotion: false,
branding: false,
convert_urls: false,
// Держим textarea синхронизированной постоянно — HTMX собирает
// данные формы до события configRequest
setup: function (editor) {
editor.on('change input undo redo SetContent keyup', function () {
editor.save();
});
},
// Вставленные/загруженные картинки уходят на сервер,
// в пост попадает обычный /media/-URL (data:-URI срезает санитизатор)
paste_data_images: true,
automatic_uploads: true,
images_upload_handler: function (blobInfo) {
return new Promise(function (resolve, reject) {
var form = document.getElementById('post-create-form');
var token = form.querySelector('input[name=csrfmiddlewaretoken]').value;
var data = new FormData();
data.append('file', blobInfo.blob(), blobInfo.filename());
fetch('/news/upload-image/', {
method: 'POST',
headers: {'X-CSRFToken': token},
body: data,
}).then(function (r) {
return r.json().then(function (j) { return {ok: r.ok, j: j}; });
}).then(function (res) {
if (res.ok && res.j.location) resolve(res.j.location);
else reject(res.j.error || '{% trans "Ошибка загрузки изображения" %}');
}).catch(function () {
reject('{% trans "Ошибка загрузки изображения" %}');
});
});
},
});
})();
</script>
<div style="display:flex; align-items:center; gap:1.25rem; flex-wrap:wrap;">
<select name="category" class="input" style="width:auto; font-size:0.8125rem;">
{% for value, label in categories %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
<label style="display:flex; align-items:center; gap:0.375rem; font-size:0.8125rem; color:var(--color-foreground); cursor:pointer;">
<input type="checkbox" name="is_important"> {% trans "Важно" %}
</label>
<label style="display:flex; align-items:center; gap:0.375rem; font-size:0.8125rem; color:var(--color-foreground); cursor:pointer;">
<input type="checkbox" name="is_pinned"> {% trans "Закрепить" %}
</label>
<span style="font-size:0.75rem; color:var(--color-muted-foreground);">{% trans "Опубликовать в чат:" %}</span>
{% for code, label in mattermost_target_teams %}
<label style="display:flex; align-items:center; gap:0.375rem; font-size:0.8125rem; color:var(--color-foreground); cursor:pointer;">
<input type="checkbox" name="mattermost_targets" value="{{ code }}"> {{ label }}
</label>
{% endfor %}
<button type="submit" class="btn btn-primary" style="margin-left:auto;">{% trans "Опубликовать" %}</button>
</div>
</form>
</details>
{% endif %}
<!-- Фільтри -->
<div style="display:flex; gap:0.25rem; margin-bottom:1.25rem; background:var(--color-muted); border-radius:var(--radius); padding:0.25rem; width:fit-content;">
{% with base_style="display:inline-flex; align-items:center; gap:0.375rem; padding:0.375rem 0.875rem; border-radius:calc(var(--radius) - 2px); font-size:0.8125rem; font-weight:500; text-decoration:none; cursor:pointer;" %}
<a href="/news/" hx-get="/news/" hx-target="#main-content" hx-push-url="/news/" hx-indicator="#loader"
style="{{ base_style }} {% if not active_filter %}background:var(--color-primary); color:var(--color-primary-foreground);{% else %}background:transparent; color:var(--color-muted-foreground);{% endif %}">{% trans "Все" %}</a>
<a href="/news/?category=important" hx-get="/news/?category=important" hx-target="#main-content" hx-push-url="/news/?category=important" hx-indicator="#loader"
style="{{ base_style }} {% if active_filter == 'important' %}background:var(--color-primary); color:var(--color-primary-foreground);{% else %}background:transparent; color:var(--color-muted-foreground);{% endif %}">{% trans "Важные" %}</a>
{% for value, label in categories %}
<a href="/news/?category={{ value }}" hx-get="/news/?category={{ value }}" hx-target="#main-content" hx-push-url="/news/?category={{ value }}" hx-indicator="#loader"
style="{{ base_style }} {% if active_filter == value %}background:var(--color-primary); color:var(--color-primary-foreground);{% else %}background:transparent; color:var(--color-muted-foreground);{% endif %}">{{ label }}</a>
{% endfor %}
{% endwith %}
</div>
<style>
.post-body img { max-width: 100%; height: auto; border-radius: 0.5rem; }
.post-body table { border-collapse: collapse; }
.post-body td, .post-body th { border: 1px solid var(--color-border); padding: 0.375rem 0.625rem; }
/* Сворачивание длинных постов */
.post-collapse { position: relative; max-height: 19rem; overflow: hidden; margin-bottom: 0.75rem; }
.post-collapse.expanded { max-height: none; }
.post-collapse .post-fade { display: none; position: absolute; bottom: 0; left: 0; right: 0; height: 4.5rem; background: linear-gradient(transparent, var(--color-card)); pointer-events: none; }
.post-collapse.overflowing:not(.expanded) .post-fade { display: block; }
</style>
<script>
window.feedTogglePost = function (btn) {
var wrap = btn.previousElementSibling;
var expanded = wrap.classList.toggle('expanded');
btn.textContent = expanded ? '{% trans "Свернуть" %} ↑' : '{% trans "Показать полностью" %} ↓';
if (!expanded) wrap.scrollIntoView({block: 'nearest'});
};
(function () {
document.querySelectorAll('.post-collapse:not([data-collapse-init])').forEach(function (el) {
el.setAttribute('data-collapse-init', '1');
var btn = el.nextElementSibling;
function check() {
if (el.classList.contains('expanded')) return;
if (el.scrollHeight > el.clientHeight + 24) {
el.classList.add('overflowing');
if (btn) btn.style.display = 'inline-flex';
}
}
check();
// картинки грузятся позже — перепроверяем высоту после каждой
el.querySelectorAll('img').forEach(function (img) {
if (!img.complete) img.addEventListener('load', check);
});
});
})();
</script>
<!-- Стрічка: пости повністю -->
{% for post in pinned_posts %}
{% include 'partials/feed-post-card.html' with post=post pinned=True %}
{% endfor %}
{% for post in posts %}
{% include 'partials/feed-post-card.html' with post=post pinned=False %}
{% empty %}
{% if not pinned_posts %}
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:2.5rem; text-align:center; color:var(--color-muted-foreground); font-size:0.875rem;">
{% if active_filter %}{% trans "По этому фильтру публикаций нет" %}{% else %}{% trans "Публикаций пока нет" %}{% endif %}
</div>
{% endif %}
{% endfor %}
</div>