feat: Introduce the Shaarli Professional theme with new templates, styles, scripts, and updated documentation.
This commit is contained in:
@@ -2387,9 +2387,6 @@ select:focus {
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.picwall-pictureframe {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@@ -2450,7 +2447,6 @@ select:focus {
|
||||
}
|
||||
|
||||
.picwall-title {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
@@ -3668,4 +3664,83 @@ select:focus {
|
||||
.media-player-time {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Footer ===== */
|
||||
.footer-main {
|
||||
margin-top: 3rem;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer-main a {
|
||||
color: var(--text-main);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.footer-main a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.footer-feeds {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.footer-separator {
|
||||
margin: 0 0.5rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.plugin-footer-text {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* ===== Tools Page ===== */
|
||||
.page-tools .list-sortable-handle {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-tools .list-sortable-handle i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.page-tools .list-group-item>i {
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bookmarklet-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.third-party-links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.third-party-links .btn i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Utility */
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -77,12 +77,12 @@
|
||||
{if="!$hide_timestamps || $is_logged_in"}
|
||||
<a href="{$base_path}/shaare/{$value.shorturl}" class="daily-item-footer-subtitle" title="{'Permalink'|t}">
|
||||
{if="$type === 'week'"}
|
||||
{function="strftime('%a %e %b, %H:%M', $link.timestamp)"}
|
||||
{function="date('D j M, H:i', $link.timestamp)"}
|
||||
{else}
|
||||
{if="$type === 'month'"}
|
||||
{function="strftime('%a %e %b %Y, %H:%M', $link.timestamp)"}
|
||||
{function="date('D j M Y, H:i', $link.timestamp)"}
|
||||
{else}
|
||||
{function="strftime('%H:%M', $link.timestamp)"}
|
||||
{function="date('H:i', $link.timestamp)"}
|
||||
{/if}
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
@@ -40,13 +40,13 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="lf_title{$index}">{'Title'|t}</label>
|
||||
<div class="{$asyncLoadClass}">
|
||||
<input type="text" class="form-control" name="lf_title" id="lf_title{$index}" {if="empty($batch_mode) && $link.title==''"}class="autofocus form-control"{else}class="form-control"{/if} value="{$link.title}" placeholder="Page title">
|
||||
<input type="text" class="form-control{if="empty($batch_mode) && $link.title==''"} autofocus{/if}" name="lf_title" id="lf_title{$index}" value="{$link.title}" placeholder="Page title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="lf_description{$index}">{'Description'|t}</label>
|
||||
<div class="{$asyncLoadClass}">
|
||||
<textarea class="form-control" name="lf_description" id="lf_description{$index}" {if="empty($batch_mode) && $link.description==''"}class="autofocus form-control"{/if} placeholder="Add a description..." rows="5">{$link.description}</textarea>
|
||||
<textarea class="form-control{if="empty($batch_mode) && $link.description==''"} autofocus{/if}" name="lf_description" id="lf_description{$index}" placeholder="Add a description..." rows="5">{$link.description}</textarea>
|
||||
</div>
|
||||
{if="$formatter==='markdown'"}
|
||||
<div class="sublabel">
|
||||
|
||||
@@ -1140,30 +1140,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
'.mp3', '.mp4', '.ogg', '.webm', '.m3u8', '.flac', '.wav', '.aac',
|
||||
'.m4a', '.opus', '.wma', '.oga', '.m3u', '.pls'
|
||||
];
|
||||
const STREAM_PATTERNS = [
|
||||
'icecast', 'shoutcast', 'stream', 'radio', 'listen', 'audio',
|
||||
'/live', '.stream'
|
||||
];
|
||||
|
||||
function isMediaUrl(url) {
|
||||
if (!url) return false;
|
||||
const lower = url.toLowerCase();
|
||||
// Check file extensions
|
||||
// Strip query params and fragment for cleaner extension matching
|
||||
const pathname = lower.split('?')[0].split('#')[0];
|
||||
// Check file extensions at end of path
|
||||
for (const ext of MEDIA_EXTENSIONS) {
|
||||
if (lower.includes(ext)) return true;
|
||||
if (pathname.endsWith(ext)) return true;
|
||||
}
|
||||
// Check streaming patterns
|
||||
for (const pattern of STREAM_PATTERNS) {
|
||||
if (lower.includes(pattern) && (lower.includes('http') || lower.includes('//'))) {
|
||||
// Must also look like an audio/stream URL (not just any page about audio)
|
||||
if (lower.endsWith('.mp3') || lower.endsWith('.ogg') || lower.endsWith('.m3u8') ||
|
||||
lower.endsWith('.aac') || lower.endsWith('.flac') || lower.endsWith('.m4a') ||
|
||||
lower.endsWith('.wav') || lower.endsWith('.opus') || lower.endsWith('.pls') ||
|
||||
lower.endsWith('.m3u') || lower.includes('.mp3') || lower.includes('.ogg') ||
|
||||
lower.includes('stream') || lower.includes('icecast') || lower.includes('listen')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Check streaming URL patterns (path-segment matching)
|
||||
if (/\/(stream|listen|live|icecast|shoutcast)(\/|$|\?)/i.test(url)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</a>
|
||||
<span class="link-url">{$value.url}</span>
|
||||
<div class="link-meta">
|
||||
<span class="link-date">{function="strftime('%c', $value.timestamp)"}</span>
|
||||
<span class="link-date">{$value.created|format_date}</span>
|
||||
<a href="{$base_path}/shaare/{$value.shorturl}" class="link-permalink" title="Permalink"><i class="mdi mdi-link-variant"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
|
||||
|
||||
<head>
|
||||
{$pageName="linklist"}
|
||||
{include="includes"}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{include="page.header"}
|
||||
|
||||
<div id="linklist">
|
||||
{loop="$plugin_start_zone"}
|
||||
{$value}
|
||||
{/loop}
|
||||
|
||||
<!-- Toolbar with View Toggle -->
|
||||
<div class="content-toolbar">
|
||||
<div class="toolbar-left">
|
||||
{include="linklist.paging"}
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="view-toggle">
|
||||
<button class="view-toggle-btn active" id="view-grid-btn" title="Grid View">
|
||||
<i class="mdi mdi-view-grid"></i>
|
||||
</button>
|
||||
<button class="view-toggle-btn" id="view-list-btn" title="List View">
|
||||
<i class="mdi mdi-view-list"></i>
|
||||
</button>
|
||||
<button class="view-toggle-btn" id="view-compact-btn" title="Compact View">
|
||||
<i class="mdi mdi-view-headline"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if="count($links)==0"}
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">
|
||||
<i class="mdi mdi-bookmark-off-outline"></i>
|
||||
</div>
|
||||
<h2 class="empty-state-title">No bookmarks found</h2>
|
||||
<p class="empty-state-text">
|
||||
{if="!empty($search_term)"}
|
||||
No results for: <strong>{$search_term}</strong>
|
||||
{else}
|
||||
Start adding bookmarks to see them here.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{else}
|
||||
|
||||
<div class="links-list view-grid" id="links-list">
|
||||
{loop="$links"}
|
||||
<div id="{$value.id}" class="link-outer{if="isset($value.sticky) && $value.sticky"} is-sticky{/if}{if="$value.class"} {$value.class}{/if}{if="$value.private"} private{else} public{/if}" data-id="{$value.id}">
|
||||
@@ -1,15 +1,15 @@
|
||||
<footer class="footer-main" style="margin-top: 3rem; padding: 2rem 0; border-top: 1px solid var(--border); text-align: center; color: var(--text-secondary); font-size: 0.9rem;">
|
||||
<footer class="footer-main">
|
||||
<p>
|
||||
Powered by <a href="https://github.com/shaarli/Shaarli" target="_blank" rel="noopener" style="color:var(--text-main);">Shaarli</a>.
|
||||
Powered by <a href="https://github.com/shaarli/Shaarli" target="_blank" rel="noopener">Shaarli</a>.
|
||||
{if="$is_logged_in"}
|
||||
<span class="footer-divider">|</span> <a href="{$base_path}/admin/tools" style="color:var(--text-main);">Administration</a>
|
||||
<span class="footer-separator">|</span> <a href="{$base_path}/admin/tools">Administration</a>
|
||||
{/if}
|
||||
</p>
|
||||
<div class="footer-feeds" style="margin-top: 0.5rem;">
|
||||
<div class="footer-feeds">
|
||||
<a href="{$base_path}/feed/rss" title="RSS Feed"><i class="mdi mdi-rss"></i> RSS</a>
|
||||
<span style="margin: 0 0.5rem;">•</span>
|
||||
<span class="footer-separator">•</span>
|
||||
<a href="{$base_path}/feed/atom" title="Atom Feed">Atom</a>
|
||||
<span style="margin: 0 0.5rem;">•</span>
|
||||
<span class="footer-separator">•</span>
|
||||
<a href="{$base_path}/daily" title="Daily">Daily</a>
|
||||
</div>
|
||||
|
||||
|
||||
+34
-38
@@ -15,76 +15,76 @@
|
||||
<div class="card-header">{'Settings'|t}</div>
|
||||
<div class="list-group">
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/configure">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-cog" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-cog"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Configure your Shaarli'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Change Title, timezone...'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
{if="!$openshaarli"}
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/password">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-lock" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-lock"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Change password'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Change your password'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
{/if}
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/plugins">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-puzzle" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-puzzle"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Plugin administration'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Enable, disable and configure plugins'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/server">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-server" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-server"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Server administration'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Check instance\'s server configuration'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/tags">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-tag-multiple" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-tag-multiple"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Manage Tags'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Rename or delete a tag in all links'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/import">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-file-import" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-file-import"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Import'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Import Netscape html bookmarks'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action ripple" href="{$base_path}/admin/export">
|
||||
<div class="list-sortable-handle" style="cursor: pointer; padding: 0; margin-right: 1rem;">
|
||||
<i class="mdi mdi-file-export" style="font-size: 1.5rem; color: var(--primary);"></i>
|
||||
<div class="list-sortable-handle">
|
||||
<i class="mdi mdi-file-export"></i>
|
||||
</div>
|
||||
<div class="list-group-item-content">
|
||||
<div class="list-group-item-label">{'Export'|t}</div>
|
||||
<div class="list-group-item-sublabel">{'Export Netscape html bookmarks'|t}</div>
|
||||
</div>
|
||||
<i class="mdi mdi-chevron-right" style="color: var(--text-muted);"></i>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,17 +136,13 @@
|
||||
<p class="text-muted" style="margin-bottom: 1.5rem;">
|
||||
Drag these buttons to your bookmarks bar to quickly add links from any page.
|
||||
</p>
|
||||
<div class="row" style="display: flex; gap: 1rem; justify-content: center;">
|
||||
<div class="col-xs-6 text-center">
|
||||
<a class="btn btn-primary" href="javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;var%20desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+'&title='%20+%20encodeURIComponent(title)+'&description='%20+%20encodeURIComponent(desc)+'&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
|
||||
<i class="mdi mdi-bookmark-plus"></i> {'Shaare link'|t}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-6 text-center">
|
||||
<a class="btn btn-secondary" href="javascript:(function(){var desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}?private=1&post='+'&description='%20+%20encodeURIComponent(desc)+'&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
|
||||
<i class="mdi mdi-note-plus"></i> {'Add Note'|t}
|
||||
</a>
|
||||
</div>
|
||||
<div class="bookmarklet-actions">
|
||||
<a class="btn btn-primary" href="javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;var%20desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+'&title='%20+%20encodeURIComponent(title)+'&description='%20+%20encodeURIComponent(desc)+'&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
|
||||
<i class="mdi mdi-bookmark-plus"></i> {'Shaare link'|t}
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="javascript:(function(){var desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}?private=1&post='+'&description='%20+%20encodeURIComponent(desc)+'&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
|
||||
<i class="mdi mdi-note-plus"></i> {'Add Note'|t}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,21 +154,21 @@
|
||||
<div class="card">
|
||||
<div class="card-header">{'3rd party'|t}</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group" style="flex-direction: row; flex-wrap: wrap; gap: 1rem; justify-content: center;">
|
||||
<div class="third-party-links">
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/shaarli/" target="_blank" class="btn btn-secondary">
|
||||
<i class="mdi mdi-firefox" style="font-size: 1.2rem;"></i> Firefox
|
||||
<i class="mdi mdi-firefox"></i> Firefox
|
||||
</a>
|
||||
<a href="https://chromewebstore.google.com/detail/add-to-shaarli/jhfblapoehcfajokolimghdfmeeakbee" target="_blank" class="btn btn-secondary">
|
||||
<i class="mdi mdi-google-chrome" style="font-size: 1.2rem;"></i> Chrome
|
||||
<i class="mdi mdi-google-chrome"></i> Chrome
|
||||
</a>
|
||||
<a href="https://f-droid.org/fr/packages/com.dimtion.shaarlier/" target="_blank" class="btn btn-secondary">
|
||||
<i class="mdi mdi-android" style="font-size: 1.2rem;"></i> Shaarlier
|
||||
<i class="mdi mdi-android"></i> Shaarlier
|
||||
</a>
|
||||
<a href="https://stakali.toneiv.eu/" target="_blank" class="btn btn-secondary">
|
||||
<i class="mdi mdi-android" style="font-size: 1.2rem;"></i> Stakali
|
||||
<i class="mdi mdi-android"></i> Stakali
|
||||
</a>
|
||||
<a href="https://github.com/lockcp/ShaarliOS" target="_blank" class="btn btn-secondary">
|
||||
<i class="mdi mdi-apple-ios" style="font-size: 1.2rem;"></i> iOS
|
||||
<i class="mdi mdi-apple-ios"></i> iOS
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-center text-muted" style="margin-top: 1.5rem;">
|
||||
|
||||
Reference in New Issue
Block a user