{% extends "base.twig" %}
{% block title %}{% if message and message.subject %}{{ message.subject }} | {{ t('ui.shared_message.title', {}, locale, ['common']) }}{% else %}{{ t('ui.shared_message.title', {}, locale, ['common']) }}{% endif %} - {{ parent() }}{% endblock %}
{% block meta_description_tags %}
{% if message %}
{% if ai_og_summary %}
{% set description = ai_og_summary %}
<meta name="description" content="{{ description|e }}">
<meta property="og:description" content="{{ description|e }}">
{% else %}
{# Fallback: first 160 chars of body #}
{% set description = message.message_text|striptags|slice(0, 160)|trim %}
{% if description %}
<meta name="description" content="{{ description }}{% if description|length >= 160 %}...{% endif %}">
<meta property="og:description" content="{{ description }}{% if description|length >= 160 %}...{% endif %}">
{% endif %}
{% endif %}
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
{% block meta_tags %}
{% if message %}
{% set description = ai_og_summary ? ai_og_summary : (message.message_text|striptags|slice(0, 160)|trim) %}
{# Open Graph meta tags for social media sharing #}
<meta property="og:title" content="{% if message.subject %}{{ message.subject }}{% else %}{{ t('ui.shared_message.title', {}, locale, ['common']) }}{% endif %}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{ share_url }}">
{% if message.from_name %}
<meta property="og:site_name" content="{{ system_name }}">
<meta property="article:author" content="{{ message.from_name }}">
{% endif %}
{% if message.date_written %}
<meta property="article:published_time" content="{{ message.date_written }}">
{% endif %}
{% if og_image_url %}
<meta property="og:image" content="{{ og_image_url }}">
{% if og_image_mime %}<meta property="og:image:type" content="{{ og_image_mime }}">{% endif %}
{% if og_image_width %}<meta property="og:image:width" content="{{ og_image_width }}">{% endif %}
{% if og_image_height %}<meta property="og:image:height" content="{{ og_image_height }}">{% endif %}
<meta name="twitter:card" content="summary_large_image">
{% else %}
<meta name="twitter:card" content="summary">
{% endif %}
<meta name="twitter:title" content="{% if message.subject %}{{ message.subject }}{% else %}{{ t('ui.shared_message.title', {}, locale, ['common']) }}{% endif %}">
{% if description %}
<meta name="twitter:description" content="{{ description }}{% if description|length >= 160 %}...{% endif %}">
{% endif %}
{% if og_image_url %}
<meta name="twitter:image" content="{{ og_image_url }}">
{% endif %}
{# Additional meta tags #}
{% if message.echoarea %}
<meta name="keywords" content="FidoNet, {{ message.echoarea }}, echomail, message board, BBS">
{% else %}
<meta name="keywords" content="FidoNet, netmail, BBS, message">
{% endif %}
{% endif %}
<link href="/css/ansisys.css" rel="stylesheet">
<script src="/js/ansisys.js"></script>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<!-- Shared Message Header -->
<div class="alert alert-info mb-4" id="shareInfo" style="display: none;">
<div class="d-flex align-items-center">
<i class="fas fa-share-alt me-2"></i>
<div>
<strong>{{ t('ui.shared_message.title', {}, locale, ['common']) }}</strong>
</div>
</div>
</div>
<!-- Message Card -->
<div class="card" id="messageCard" style="display: none;">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0" id="messageSubject">{{ t('ui.common.loading', {}, locale, ['common']) }}</h5>
<div class="btn-group btn-group-sm">
<a href="/login" class="btn btn-outline-primary" id="loginBtn" style="display: none;">
<i class="fas fa-sign-in-alt"></i>
{{ t('ui.shared_message.login_to_reply', {}, locale, ['common']) }}
</a>
<a href="/register" class="btn btn-outline-secondary" id="registerBtn" style="display: none;">
<i class="fas fa-user-plus"></i>
{{ t('ui.register.title', {}, locale, ['common']) }}
</a>
</div>
</div>
</div>
<div class="card-body">
<div id="messageContent">
<div class="loading-spinner text-center py-4">
<i class="fas fa-spinner fa-spin me-2"></i>
{{ t('ui.shared_message.loading', {}, locale, ['common']) }}
</div>
</div>
</div>
<div class="card-footer text-muted small">
<div class="row">
<div class="col-md-6">
<i class="fas fa-info-circle me-1"></i>
{{ t('ui.shared_message.shared_on', {}, locale, ['common']) }} <span id="shareDate"></span>
</div>
<div class="col-md-6 text-md-end">
<span id="shareStats"></span>
</div>
</div>
</div>
</div>
<!-- Error Display -->
<div class="alert alert-danger" id="errorMessage" style="display: none;">
<div class="d-flex align-items-center">
<i class="fas fa-exclamation-triangle me-2"></i>
<div>
<strong>{{ t('ui.shared_message.unable_to_load', {}, locale, ['common']) }}</strong>
<p class="mb-0 mt-1" id="errorText"></p>
</div>
</div>
<div class="mt-3">
<a href="/" class="btn btn-primary">
<i class="fas fa-home"></i>
{{ t('ui.shared_message.go_to_main_site', {}, locale, ['common']) }}
</a>
</div>
</div>
<!-- Authentication Required -->
<div class="alert alert-warning" id="authRequired" style="display: none;">
<div class="d-flex align-items-center">
<i class="fas fa-lock me-2"></i>
<div>
<strong>{{ t('ui.shared_message.login_required', {}, locale, ['common']) }}</strong>
<p class="mb-0 mt-1">{{ t('ui.shared_message.login_required_help', {}, locale, ['common']) }}</p>
</div>
</div>
<div class="mt-3">
<a href="/login" class="btn btn-primary me-2">
<i class="fas fa-sign-in-alt"></i>
{{ t('ui.base.login', {}, locale, ['common']) }}
</a>
<a href="/register" class="btn btn-outline-primary">
<i class="fas fa-user-plus"></i>
{{ t('ui.register.title', {}, locale, ['common']) }}
</a>
</div>
</div>
<!-- About This System -->
<div class="card mt-4">
<div class="card-body">
<h6 class="card-title">
<i class="fas fa-info-circle me-2"></i>
{{ t('ui.shared_message.about_this_message', {}, locale, ['common']) }}
</h6>
<p class="card-text">
{{ t('ui.shared_message.about_text', {}, locale, ['common']) }}
</p>
<div class="alert alert-primary" id="registrationPrompt" style="display: none;">
<div class="d-flex align-items-start">
<i class="fas fa-user-plus me-3 mt-1"></i>
<div class="flex-grow-1">
<h6 class="alert-heading mb-2">{{ t('ui.shared_message.join_conversation', {}, locale, ['common']) }}</h6>
<p class="mb-2">
{{ t('ui.shared_message.join_conversation_help', {}, locale, ['common']) }}
</p>
<div class="d-flex gap-2 flex-wrap">
<a href="/register" class="btn btn-primary btn-sm">
<i class="fas fa-user-plus me-1"></i>
{{ t('ui.shared_message.register_now', {}, locale, ['common']) }}
</a>
<a href="/login" class="btn btn-outline-primary btn-sm">
<i class="fas fa-sign-in-alt me-1"></i>
{{ t('ui.shared_message.already_have_account', {}, locale, ['common']) }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<small class="text-muted">
<i class="fas fa-network-wired me-1"></i>
<strong>{{ t('ui.shared_message.network', {}, locale, ['common']) }}:</strong> <span id="networkName">{{ t('ui.common.loading', {}, locale, ['common']) }}</span>
</small>
</div>
<div class="col-md-6">
<small class="text-muted">
<i class="fas fa-code me-1"></i>
<strong>{{ t('ui.shared_message.powered_by', {}, locale, ['common']) }}:</strong> BinktermPHP
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const shareKey = '{{ shareKey|default('') }}';
const shareArea = '{{ shareArea|default('')|e('js') }}';
const shareSlug = '{{ shareSlug|default('')|e('js') }}';
function apiError(payload, fallback) {
if (window.getApiErrorMessage) {
return window.getApiErrorMessage(payload, fallback);
}
return payload && payload.error ? payload.error : fallback;
}
function uiT(key, fallback, params = {}) {
if (window.t) {
return window.t(key, params, fallback);
}
return fallback;
}
$(document).ready(function() {
loadSharedMessage();
loadUserSettings();
});
function loadSharedMessage() {
const apiUrl = shareKey
? `/api/messages/shared/${shareKey}`
: `/api/messages/shared/${encodeURIComponent(shareArea)}/${encodeURIComponent(shareSlug)}`;
$.get(apiUrl)
.done(function(data) {
if (data.success) {
displaySharedMessage(data.message, data.share_info);
} else {
showError(apiError(data, uiT('ui.shared_message.load_failed', 'Failed to load shared message')));
}
})
.fail(function(xhr) {
if (xhr.status === 401) {
showAuthRequired();
} else if (xhr.status === 404) {
showError(uiT('ui.shared_message.not_available', 'This shared message is not available. It may have expired or been revoked.'));
} else {
showError(uiT('ui.shared_message.load_failed_retry', 'Failed to load the shared message. Please try again later.'));
}
});
}
function displaySharedMessage(message, shareInfo) {
// Set message subject
$('#messageSubject').text(message.subject || uiT('messages.no_subject', '(No Subject)'));
// Parse message to separate kludge lines from body
const parsedMessage = parseEchomailMessage(message.message_text || '', message.kludge_lines || '');
const bodyHtml = message.markup_html
? message.markup_html
: formatMessageBodyForDisplay(message, parsedMessage.messageBody);
// Build message content
const html = `
<div class="message-header-full mb-3">
<div class="row">
<div class="col-md-6">
<strong>${uiT('ui.shared_message.from_label', 'From:')}</strong> ${escapeHtml(message.from_name)}<br>
<small class="text-muted">${formatFidonetAddress(message.from_address, message.from_system_name)}</small>
</div>
<div class="col-md-6">
<strong>${uiT('ui.shared_message.to_label', 'To:')}</strong> ${escapeHtml(message.to_name || uiT('ui.common.all', 'All'))}<br>
${message.echoarea ? `<span class="badge" style="background-color: ${message.echoarea_color || '#28a745'}; color: white;">${message.echoarea}</span>` : ''}
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<strong>${uiT('ui.shared_message.date_label', 'Date:')}</strong> ${formatFullDate(message.date_written)}
</div>
<div class="col-md-6">
<strong>${uiT('ui.shared_message.subject_label', 'Subject:')}</strong> ${escapeHtml(message.subject || uiT('messages.no_subject', '(No Subject)'))}
</div>
</div>
</div>
<div class="message-text">
${bodyHtml}
</div>
${message.origin_line ? `<div class="message-origin mt-2"><small class="text-muted">${escapeHtml(message.origin_line)}</small></div>` : ''}
`;
const messageContent = document.getElementById('messageContent');
if (window.BinkMediaPlayer) {
BinkMediaPlayer.cleanup(messageContent);
}
$('#messageContent').html(html);
if (window.BinkMediaPlayer) {
BinkMediaPlayer.scan(messageContent);
}
$('#messageCard').show();
// Show share info
const sharedDate = formatDate(shareInfo.created_at);
const accessCount = shareInfo.access_count || 0;
$('#shareDate').text(sharedDate);
$('#shareStats').text(uiT('ui.shared_message.viewed_times', 'Viewed {count} times', { count: accessCount }));
$('#shareInfo').show();
// Show login/register buttons for anonymous users if not a public share
const isLoggedIn = checkUserStatus();
if (!isLoggedIn && !shareInfo.is_public) {
$('#loginBtn, #registerBtn').show();
}
// Show registration prompt for anonymous users
if (!isLoggedIn) {
$('#registrationPrompt').show();
}
// Set network name
const networkName = message.echoarea_domain || uiT('ui.shared_message.local_bulletin_board', 'Local Bulletin Board');
$('#networkName').text(networkName);
}
function showError(errorText) {
$('#errorText').text(errorText);
$('#errorMessage').show();
}
function showAuthRequired() {
$('#authRequired').show();
}
function checkUserStatus() {
// Check if user is logged in by looking for the session cookie
const sessionCookie = document.cookie
.split('; ')
.find(row => row.startsWith('binktermphp_session='));
return !!sessionCookie;
}
function loadUserSettings() {
// Load font settings if user is logged in
if (checkUserStatus()) {
$.get('/api/user/settings')
.done(function(data) {
const fontFamily = data.font_family || 'Courier New, Monaco, Consolas, monospace';
const fontSize = data.font_size || 16;
// Apply font settings to message text
const css = `
.message-text, .message-text pre, .message-formatted {
font-family: ${fontFamily} !important;
font-size: ${fontSize}px !important;
}
`;
$('<style id="user-font-styles">' + css + '</style>').appendTo('head');
})
.fail(function() {
console.log('Failed to load font settings, using defaults');
});
}
}
// Global settings for message parsing
window.userSettings = {
timezone: 'America/Los_Angeles' // Default timezone
};
</script>
<style>
/* Shared message specific styles */
.message-header-full {
background-color: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
border: 1px solid #dee2e6;
}
.message-text {
font-family: 'Courier New', Monaco, Consolas, monospace;
font-size: 16px;
line-height: 1.4;
white-space: pre-wrap;
word-wrap: break-word;
}
.message-formatted {
font-family: 'Courier New', Monaco, Consolas, monospace;
}
.message-preformatted {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
font-size: 0.875rem;
overflow-x: auto;
}
.message-quote {
border-left: 3px solid #6c757d;
padding-left: 1rem;
margin: 0.5rem 0;
color: #6c757d;
}
.message-signature {
color: #6c757d;
font-style: italic;
}
.message-signature-separator {
color: #adb5bd;
text-align: center;
margin: 1rem 0;
}
.quote-line {
margin: 0.25rem 0;
}
.message-paragraph {
margin: 0.5rem 0;
}
.message-origin {
border-top: 1px solid #dee2e6;
padding-top: 0.5rem;
margin-top: 1rem;
}
/* Loading spinner */
.loading-spinner {
color: #6c757d;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.message-text {
font-size: 14px;
}
.message-header-full {
padding: 0.75rem;
}
}
</style>
{% endblock %}
|