nordabiz/templates/messages/conversations.html
Maciej Pienczyn 43c9ba6c77
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
fix: restore text paste in message editor (Quill)
The clipboard.onPaste override broke text pasting in Quill 2.x
where this internal API is no longer public. Replaced with a DOM
paste event listener on quill.root that only intercepts image
pastes and lets Quill handle text paste natively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:50:56 +02:00

333 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}Wiadomości - Norda Biznes Partner{% endblock %}
{% block container_class %}conversations-page{% endblock %}
{% block head_extra %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/quill.snow.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=13">
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
<style>
footer { display: none !important; }
main { padding-bottom: 0 !important; margin-bottom: 0 !important; }
/* Images in messages — responsive with max size */
.message-content img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
cursor: pointer;
object-fit: contain;
}
.message-content img:hover { opacity: 0.9; }
/* Images in Quill editor — click to select, drag corner to resize */
.ql-editor img {
max-width: 100%;
max-height: 200px;
border-radius: 4px;
cursor: pointer;
}
.ql-editor img:hover {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.ql-editor img.selected {
outline: 2px solid #3b82f6;
outline-offset: 2px;
resize: both;
overflow: hidden;
display: inline-block;
}
/* Hide floating buttons that overlap with chat input area */
.pwa-smart-banner, .staging-panel-toggle { display: none !important; }
</style>
{% endblock %}
{% block content %}
<div class="conversations-container" id="conversationsApp">
<!-- Left panel: Conversation list -->
<div class="conversations-panel" id="conversationsPanel">
<div class="conversations-header">
<h2>Wiadomości</h2>
<div class="conversations-search-row">
<div class="conversations-search">
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="M21 21l-4.35-4.35"></path>
</svg>
<input type="text" id="searchInput" placeholder="Szukaj rozmów i wiadomości...">
</div>
<button class="btn-new-conversation" id="newMessageBtn" title="Nowa wiadomość">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
</svg>
<span class="btn-new-text">Nowa wiadomość</span>
</button>
<button class="btn-new-conversation btn-new-group" id="newGroupBtn" title="Nowa grupa">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
<span class="btn-new-text">Nowa grupa</span>
</button>
</div>
</div>
<div class="conversation-list" id="conversationList">
<!-- Rendered by JS from __CONVERSATIONS__ data -->
</div>
<div class="archive-link" id="archiveLink" style="display:none">
<a href="#">Archiwum</a>
</div>
</div>
<!-- Right panel: Chat view -->
<div class="chat-panel" id="chatPanel">
<!-- Empty state (shown by default) -->
<div class="chat-empty" id="chatEmpty">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<p>Wybierz rozmowę lub rozpocznij nową</p>
</div>
<!-- Chat header (hidden until conversation selected) -->
<div class="chat-header" id="chatHeader" style="display:none">
<div class="chat-header-left">
<button class="back-btn" id="backBtn" title="Wróć">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5"></path>
<path d="M12 19l-7-7 7-7"></path>
</svg>
</button>
<div class="chat-header-avatar" id="headerAvatar"></div>
<div class="chat-header-info">
<h3 id="headerName"></h3>
<div class="subtitle" id="headerSubtitle"></div>
</div>
</div>
<div class="chat-header-actions">
<button class="btn-group-settings" id="groupSettingsBtn" style="display:none" title="Zarządzaj grupą">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
</button>
</div>
</div>
<!-- Pinned bar -->
<div class="pinned-bar" id="pinnedBar" style="display:none">
<span>📌 <span id="pinnedCount">0</span> przypiętych</span>
<button id="togglePins">Pokaż</button>
</div>
<!-- Group management panel -->
<div class="group-panel" id="groupPanel" style="display:none">
<div class="group-panel-header">
<h3>Zarządzanie grupą</h3>
<button class="modal-close" id="closeGroupPanel">&times;</button>
</div>
<div class="group-panel-body">
<div class="group-panel-section">
<label>Nazwa grupy:</label>
<div class="group-name-row">
<input type="text" id="groupEditName" placeholder="Nazwa grupy...">
<button class="btn-primary btn-sm" id="saveGroupName">Zapisz</button>
</div>
</div>
<div class="group-panel-section">
<label>Członkowie (<span id="groupMemberCount">0</span>):</label>
<div id="groupMembersList" class="group-members-list"></div>
</div>
<div class="group-panel-section" id="groupAddMemberSection">
<label>Dodaj członka:</label>
<input type="text" id="groupAddMemberSearch" placeholder="Wpisz imię lub nazwisko...">
<div class="recipient-suggestions" id="groupAddMemberSuggestions"></div>
</div>
</div>
</div>
<!-- Messages area -->
<div class="chat-messages" id="chatMessages" style="display:none">
<!-- Rendered by JS -->
</div>
<!-- Typing indicator -->
<div class="typing-indicator" id="typingIndicator" style="display:none">
<span id="typingName"></span> pisze
<span class="typing-dots"><span>.</span><span>.</span><span>.</span></span>
</div>
<!-- Input area -->
<div class="chat-input-area" id="chatInputArea" style="display:none">
<!-- Reply quote (shown when replying to a message) -->
<div class="reply-preview" id="replyPreview" style="display:none">
<div class="reply-preview-content">
<span class="reply-preview-name" id="replyPreviewName"></span>
<span class="reply-preview-text" id="replyPreviewText"></span>
</div>
<button class="reply-preview-close" id="replyPreviewClose">&times;</button>
</div>
<div class="chat-input-wrapper">
<div id="quillEditor"></div>
<div class="input-actions">
<button id="attachBtn" title="Dołącz plik">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
</button>
<button class="send-btn" id="sendBtn" title="Wyślij">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 2L11 13"></path>
<path d="M22 2l-7 20-4-9-9-4 20-7z"></path>
</svg>
</button>
</div>
</div>
<input type="file" id="fileInput" multiple style="display:none" accept=".jpg,.jpeg,.png,.gif,.pdf,.docx,.xlsx">
<div class="attachments-preview" id="attachmentsPreview" style="display:none"></div>
<div class="input-hint"><kbd>Enter</kbd> wyślij &nbsp;·&nbsp; <kbd>Shift</kbd>+<kbd>Enter</kbd> nowa linia</div>
</div>
</div>
<!-- Context menu (floating, positioned by JS) -->
<div class="message-context-menu" id="contextMenu" style="display:none">
<button data-action="reply" title="Odpowiedz">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 17l-5-5 5-5"></path>
<path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>
</svg>
</button>
<button data-action="react" title="Reaguj">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
</button>
<button data-action="forward" title="Przekaż">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 17l5-5-5-5"></path>
<path d="M4 18v-2a4 4 0 0 1 4-4h12"></path>
</svg>
</button>
<button data-action="pin" title="Przypnij">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l1.09 3.26L16 6l-2 2 .73 3.27L12 9.5l-2.73 1.77L10 8 8 6l2.91-.74L12 2z"></path>
<path d="M12 22v-8"></path>
</svg>
</button>
<button data-action="edit" title="Edytuj" class="owner-only">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button data-action="delete" title="Usuń" class="owner-only">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
<!-- Emoji picker -->
<div class="emoji-picker" id="emojiPicker" style="display:none">
<button data-emoji="👍">👍</button>
<button data-emoji="❤️">❤️</button>
<button data-emoji="😂">😂</button>
<button data-emoji="😮">😮</button>
<button data-emoji="😢">😢</button>
<button data-emoji="✅"></button>
</div>
<!-- New group modal -->
<div class="modal-overlay" id="newGroupModal" style="display:none">
<div class="modal-content">
<div class="modal-header">
<h3>Nowa grupa</h3>
<button class="modal-close" id="closeNewGroup">&times;</button>
</div>
<div class="modal-body">
<div class="recipient-input">
<label>Nazwa grupy:</label>
<input type="text" id="groupNameInput" placeholder="np. Projekt XYZ, Zarząd..." style="margin-bottom:12px">
</div>
<div class="recipient-input">
<label>Członkowie:</label>
<input type="text" id="groupRecipientSearch" placeholder="Wpisz imię lub nazwisko...">
<div class="recipient-suggestions" id="groupRecipientSuggestions"></div>
<div class="selected-recipients" id="groupSelectedRecipients"></div>
</div>
<div id="groupMessageEditor"></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="cancelNewGroup">Anuluj</button>
<button class="btn-primary" id="sendNewGroup">Utwórz grupę</button>
</div>
</div>
</div>
<!-- New message modal -->
<div class="modal-overlay" id="newMessageModal" style="display:none">
<div class="modal-content">
<div class="modal-header">
<h3>Nowa wiadomość</h3>
<button class="modal-close" id="closeNewMessage">&times;</button>
</div>
<div class="modal-body">
<div class="recipient-input">
<label>Do:</label>
<input type="text" id="recipientSearch" placeholder="Wpisz imię lub nazwisko...">
<div class="recipient-suggestions" id="recipientSuggestions"></div>
<div class="selected-recipients" id="selectedRecipients"></div>
</div>
<div id="newMessageEditor"></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="cancelNewMessage">Anuluj</button>
<button class="btn-primary" id="sendNewMessage">Wyślij</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
window.__CONVERSATIONS__ = {{ conversations_json|safe }};
window.__CURRENT_USER__ = {{ current_user_json|safe }};
window.__USERS__ = {{ users_json|default('[]')|safe }};
window.__CSRF_TOKEN__ = '{{ csrf_token() }}';
// Dynamically set container height based on actual position
(function() {
var container = document.getElementById('conversationsApp');
if (container) {
var top = container.getBoundingClientRect().top;
container.style.height = (window.innerHeight - top) + 'px';
window.addEventListener('resize', function() {
var t = container.getBoundingClientRect().top;
container.style.height = (window.innerHeight - t) + 'px';
});
}
})();
// Load conversations.js after data is set
(function() {
var s = document.createElement('script');
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=28';
document.body.appendChild(s);
})();
{% endblock %}