first commit

This commit is contained in:
2026-01-13 15:35:11 -05:00
commit b00950d5e8
186 changed files with 25169 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="404"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card" style="text-align: center; padding: 2rem;">
<div style="font-size: 4rem; color: var(--text-muted); margin-bottom: 1rem;">404</div>
<h2 style="margin-bottom: 1rem; color: var(--text-main);">{'Sorry, nothing to see here.'|t}</h2>
{if="!empty($error_message)"}
<p class="text-muted">{$error_message}</p>
{/if}
<a href="{$base_path}/" class="btn btn-primary" style="margin-top: 1rem;">{'Home'|t}</a>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+63
View File
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="addlink"}
{include="includes"}
</head>
<body onload="document.addform.post.focus();">
{include="page.header"}
<div id="headerform" class="page-add container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="GET" action="{$base_path}/admin/shaare" name="addform" class="form-add">
<div class="card">
<div class="card-header">{"Shaare a new link"|t}</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="post">{'URL or leave empty to post a note'|t}</label>
<input type="text" name="post" id="post" class="form-control autofocus" placeholder="https://...">
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">{'Add link'|t}</button>
</div>
</div>
</form>
</div>
<div class="col-md-6 col-md-offset-3">
<div style="text-align: right; margin-bottom: 1rem;">
<button type="button" class="btn btn-secondary button-batch-addform"><i class="mdi mdi-playlist-plus" aria-hidden="true"></i>&nbsp;{'BULK CREATION'|t}</button>
</div>
<form class="card batch-addform hidden" method="POST" action="{$base_path}/admin/shaare-batch" name="batch-addform">
<div class="card-header">{"Shaare multiple new links"|t}</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="urls">{'Add one URL per line to create multiple bookmarks.'|t}</label>
<textarea name="urls" id="urls" rows="5" class="form-control" placeholder="https://site1.com&#10;https://site2.com"></textarea>
</div>
<div class="form-group">
<label class="form-label" for="tags">{'Tags'|t}</label>
<input type="text" name="tags" id="tags" class="form-control lf_input" data-list="{loop="$tags"}{$key}, {/loop}" data-multiple data-autofirst autocomplete="off" placeholder="tags, separated, by, commas">
</div>
<input type="hidden" name="private" value="0">
<div class="form-group checkbox-wrapper">
<input type="checkbox" name="private" {if="$default_private_links"} checked="checked"{/if} id="lf_private" style="width: auto;"/>
<label for="lf_private" style="margin: 0;">{'Private'|t}</label>
</div>
</div>
<div class="card-footer">
<input type="hidden" name="token" value="{$token}">
<button type="submit" class="btn btn-primary">{'Add links'|t}</button>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="changepassword"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-changepassword">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="#" name="changepasswordform" id="changepasswordform">
<div class="card">
<div class="card-header">{'Change password'|t}</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="oldpassword">{'Current password'|t}</label>
<input type="password" name="oldpassword" id="oldpassword" class="form-control autofocus">
</div>
<div class="form-group">
<label class="form-label" for="setpassword">{'New password'|t}</label>
<input type="password" name="setpassword" id="setpassword" class="form-control">
</div>
<input type="hidden" name="token" value="{$token}">
</div>
<div class="card-footer">
<button type="submit" name="Save" class="btn btn-primary">{'Change'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="changetag"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-changetag">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="" name="changetag" id="changetag">
<input type="hidden" name="token" value="{$token}">
<div class="card">
<div class="card-header">{"Manage tags"|t}</div>
<div class="card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">All modifications are case sensitive.</p>
<div class="form-group">
<label class="form-label" for="fromtag">Modify this tag&#8230;</label>
<input type="text" name="fromtag" id="fromtag" value="{$fromtag}" class="form-control autofocus"
autocomplete="off" data-multiple data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}" placeholder="Old tag"/>
</div>
<div class="form-group">
<label class="form-label" for="totag">&#8230;into this tag</label>
<input type="text" name="totag" id="totag" class="form-control" placeholder="New tag">
</div>
<p class="text-muted" style="font-size: 0.9rem;">
{'You can also edit tags in the'|t} <a href="{$base_path}/tags/list?sort=usage">{'tag list'|t}</a>.
</p>
</div>
<div class="card-footer">
<button type="submit" id="button-delete" name="deletetag" class="btn btn-danger" value="Delete tag">{'Delete tag'|t}</button>
<button type="submit" name="renametag" class="btn btn-primary" value="Rename tag">{'Rename tag'|t}</button>
</div>
</div>
</form>
</div>
<div class="col-md-6 col-md-offset-3">
<form method="POST" action="{$base_path}/admin/tags/change-separator" name="changeseparator" id="changeseparator">
<div class="card">
<div class="card-header">{"Change tags separator"|t}</div>
<div class="card-body">
<p class="text-muted" style="margin-bottom: 1rem;">
{'Your current tag separator is'|t} <code>{$tags_separator}</code>{if="!empty($tags_separator_desc)"} ({$tags_separator_desc}){/if}.
</p>
<div class="form-group">
<label class="form-label" for="separator">{'New separator'|t}</label>
<input type="text" name="separator" id="separator" class="form-control" autocomplete="off"/>
<input type="hidden" name="token" value="{$token}">
</div>
<p class="text-warning" style="font-size: 0.9rem; margin-top: 1rem;">
<i class="mdi mdi-alert"></i> {'Note that hashtags won\'t fully work with a non-whitespace separator.'|t}
</p>
</div>
<div class="card-footer">
<button type="submit" name="saveseparator" class="btn btn-primary">{'Save'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+216
View File
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="configure"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container page-configure">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form method="POST" action="#" name="configform" id="configform">
<input type="hidden" name="token" value="{$token}"/>
<div class="card">
<div class="card-header">{'Configuration'|t}</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="title">Shaarli {'title'|t}</label>
<input type="text" name="title" id="title" class="form-control" value="{$title}" />
</div>
<div class="form-group">
<label class="form-label" for="titleLink">{'Home link'|t}</label>
<input type="text" name="titleLink" id="titleLink" class="form-control" value="{$titleLink}">
<div class="sublabel">{'Default value'|t}: {$base_path}/</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="form-label" for="theme">{'Theme'|t}</label>
<select name="theme" id="theme" class="form-control">
{loop="$theme_available"}
<option value="{$value}" {if="$value === $theme"}selected{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="form-label" for="formatter">{'Description formatter'|t}</label>
<select name="formatter" id="formatter" class="form-control">
{loop="$formatter_available"}
<option value="{$value}" {if="$value === $formatter"}selected="selected"{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="language">{'Language'|t}</label>
<select name="language" id="language" class="form-control">
{loop="$languages"}
<option value="{$key}" {if="$key === $language"}selected{/if}>
{$value|ucfirst}
</option>
{/loop}
</select>
</div>
<div class="form-group">
<label class="form-label">{'Timezone'|t}</label>
<div class="row">
<div class="col-sm-6" id="timezone-continent">
<select name="continent" id="continent" class="form-control">
{loop="$continents"}
{if="$key !== 'selected'"}
<option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
{$value}
</option>
{/if}
{/loop}
</select>
</div>
<div class="col-sm-6" id="timezone-city">
<select name="city" id="city" class="form-control">
{loop="$cities"}
{if="$key !== 'selected'"}
<option value="{$value.city}" {if="$cities.selected === $value.city"}selected{/if} data-continent="{$value.continent}">
{$value.city}
</option>
{/if}
{/loop}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">{'Parameters'|t}</div>
<div class="card-body">
<!-- Session Protection -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Disable session cookie hijacking protection'|t}</div>
<div class="sublabel">{'Check this if you get disconnected or if your IP address changes often'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="disablesessionprotection" id="disablesessionprotection" {if="$session_protection_disabled"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- Private Links -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Private links by default'|t}</div>
<div class="sublabel">{'All new links are private by default'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="$private_links_default"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- RSS Permalinks -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'RSS direct links'|t}</div>
<div class="sublabel">Enabling it will show a permalink in the description.</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="$enable_rss_permalinks"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- Hide Public Links -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Hide public links'|t}</div>
<div class="sublabel">{'Do not show any links if the user is not logged in'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="hidePublicLinks" id="hidePublicLinks" {if="$hide_public_links"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- Retrieve Description -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Automatically retrieve description'|t}</div>
<div class="sublabel">{'Shaarli will try to retrieve the description from meta HTML headers'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="retrieveDescription" id="retrieveDescription" {if="$retrieve_description"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- Update Check -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Check updates'|t}</div>
<div class="sublabel">{'Notify me when a new release is ready'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="updateCheck" id="updateCheck" {if="$enable_update_check"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<!-- API -->
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Enable REST API'|t}</div>
<div class="sublabel">{'Allow third party software to use Shaarli'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="enableApi" id="enableApi" {if="$api_enabled"}checked{/if}/>
<span class="toggle-slider"></span>
</label>
</div>
<hr>
<div class="form-group">
<label class="form-label" for="apiSecret">{'API secret'|t}</label>
<input type="text" name="apiSecret" id="apiSecret" class="form-control" value="{$api_secret}" placeholder="Type a random string..." />
</div>
<hr>
<div class="form-group">
<label class="form-label" for="enableThumbnails">{'Enable thumbnails'|t}</label>
<select name="enableThumbnails" id="enableThumbnails" class="form-control">
<option value="all" {if="$thumbnails_mode=='all'"}selected{/if}>{'All'|t}</option>
<option value="common" {if="$thumbnails_mode=='common'"}selected{/if}>{'Only common media hosts'|t}</option>
<option value="none" {if="$thumbnails_mode=='none'"}selected{/if}>{'None'|t}</option>
</select>
<div class="sublabel">
{if="! $gd_enabled"}
{'You need to enable the extension <code>php-gd</code> to use thumbnails.'|t}
{elseif="$thumbnails_enabled"}
<a href="{$base_path}/admin/thumbnails">{'Synchronize thumbnails'|t}</a>
{/if}
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" name="Save" class="btn btn-primary">{'Save'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="daily"}
{include="includes"}
</head>
<body class="dark-toolbar">
{include="page.header"}
<div class="subheader is-dark" style="margin-bottom: 1.5rem;">
<div class="container text-center">
<div class="btn-group" role="group">
<a class="btn {if="array_key_exists('day', $_GET) || (!array_key_exists('week', $_GET) && !array_key_exists('month', $_GET))"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/daily?day" title="{'Daily'|t}">{'Daily'|t}</a>
<a class="btn {if="array_key_exists('week', $_GET)"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/daily?week" title="{'Weekly'|t}">{'Weekly'|t}</a>
<a class="btn {if="array_key_exists('month', $_GET)"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/daily?month" title="{'Monthly'|t}">{'Monthly'|t}</a>
</div>
</div>
</div>
<div class="daily">
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="daily-header">
<h1>{$localizedType} Shaarli</h1>
<p class="daily-header-subtitle">{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</p>
<div class="container">
<div class="row">
<div class="col-xs-6">
<a {if="$previousday"}href="{$base_path}/daily?{$type}={$previousday}"{else}href="#"{/if} class="btn btn-secondary" {if="!$previousday"}disabled{/if}>
<i class="mdi mdi-arrow-left"></i> {function="t('Previous :type', '', 1, 'shaarli', [':type' => t($type)], true)"}
</a>
</div>
<div class="col-xs-6 text-right">
<a {if="$nextday"}href="{$base_path}/daily?{$type}day={$nextday}"{else}href="#"{/if} class="btn btn-secondary" {if="!$nextday"}disabled{/if}>
{function="t('Next :type', '', 1, 'shaarli', [':type' => t($type)], true)"} <i class="mdi mdi-arrow-right"></i>
</a>
</div>
</div>
<hr class="darker">
</div>
</div>
<h2 class="daily-title">{$dayDesc}</h2>
<div id="plugin_zone_about_daily" class="plugin_zone">
{loop="$daily_about_plugin"}
{$value}
{/loop}
</div>
{if="$linksToDisplay"}
<div class="daily-grid clearfix" data-columns>
{loop="$linksToDisplay"}
{$link=$value}
<div class="daily-item">
<div class="daily-card">
<a href="{$link.real_url}" class="daily-item-header ripple ripple-primary">
{$link.title}
</a>
{if="$thumbnails_enabled && !empty($link.thumbnail)"}
<div class="daily-item-image" style="background-image: url({$root_path}/{$link.thumbnail}#)"></div>
{/if}
{if="$link.formatedDescription"}
<div class="daily-item-body">
{$link.formatedDescription}
</div>
{/if}
<div class="daily-item-footer clearfix">
{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)"}
{else}
{if="$type === 'month'"}
{function="strftime('%a %e %b %Y, %H:%M', $link.timestamp)"}
{else}
{function="strftime('%H:%M', $link.timestamp)"}
{/if}
{/if}
</a>
{/if}
{if="$link.tags"}
<div class="daily-item-tags">
{$tags=implode(', ', $link['taglist'])}
{$tags}
</div>
{/if}
<div>
{loop="$link.link_plugin"}
{$value}
{/loop}
</div>
</div>
</div>
</div>
{/loop}
</div>
{else}
<div>No articles on this day.</div>
{/if}
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
{include="page.footer"}
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>{$localizedType} - {$title}</title>
<link>{$index_url}</link>
<description>{function="t('All links of one :type in a single page.', '', 1, 'shaarli', [':type' => t($type)])"}</description>
<language>{$language}</language>
<copyright>{$index_url}</copyright>
<generator>Shaarli</generator>
{loop="$days"}
<item>
<title>{$value.date_human} - {$title}</title>
<guid>{$value.absolute_url}</guid>
<link>{$value.absolute_url}</link>
<pubDate>{$value.date_rss}</pubDate>
<description><![CDATA[
{loop="$value.links"}
<h3><a href="{$value.url}">{$value.title}</a></h3>
<small>
{if="!$hide_timestamps"}{$value.created|format_date} &#8212; {/if}
<a href="{$index_url}shaare/{$value.shorturl}">{'Permalink'|t}</a>
{if="$value.tags"} &#8212; {$value.tags}{/if}
<br>
{$value.url}
</small><br>
{if="$value.thumbnail"}<img src="{$index_url}{$value.thumbnail}#" alt="thumbnail" />{/if}<br>
{if="$value.description"}{$value.description}{/if}
<br><hr>
{/loop}
]]></description>
</item>
{/loop}
</channel>
</rss><!-- Cached version of {$page_url} -->
+44
View File
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="editlinkbatch"}
{include="includes"}
</head>
<body class="page-edit-link-batch">
{include="page.header"}
<div id="progress-overlay" class="fullscreen hidden">
<div class="content-fullscreen">
<div class="container">
<div class="mbl row">
<div class="col-md-8 col-md-offset-2">
<div class="is-inverted">
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total"></span>
</div>
<div class="progress-bar">
<div class="progress-actual"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-center" style="margin: 1.5rem 0;">
<button type="submit" name="save_edit_batch" class="btn btn-primary">{'Save all'|t}</button>
</div>
{loop="$links"}
{$index=$key}
{include="editlink"}
{/loop}
<div class="text-center" style="margin: 1.5rem 0;">
<button type="submit" name="save_edit_batch" class="btn btn-primary">{'Save all'|t}</button>
</div>
{include="page.footer"}
</body>
</html>
+97
View File
@@ -0,0 +1,97 @@
{if="empty($batch_mode)"}
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="editlink"}
{include="includes"}
</head>
<body {if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"}class="from-bookmarklet"{/if}>
{include="page.header"}
{else}
{ignore}Lil hack: when included in a loop in batch mode, `$value` is assigned by RainTPL with template vars.{/ignore}
{function="extract($value) ? '' : ''"}
{/if}
{$asyncLoadClass=$link_is_new && $async_metadata && empty($link.title) ? 'loading-wrapper' : ''}
{if="!isset($index)"}
{$index=""}
{/if}
<div id="editlinkform{$index}" class="editlinkform container page-edit">
<div class="row editlinkform-row">
<div class="col-md-6 col-md-offset-3 editlinkform-col">
<form method="post" name="linkform" class="card" action="{$base_path}/admin/shaare">
{if="isset($link.id)"}
<input type="hidden" name="lf_id" value="{$link.id}">
{/if}
<input type="hidden" name="token" value="{$token}">
<input type="hidden" name="source" value="{$source}">
{if="$http_referer"}
<input type="hidden" name="returnurl" value="{$http_referer}">
{/if}
<div class="card-header">
{if="!$link_is_new"}Edit Bookmark{else}New Bookmark{/if}
{if="!$link_is_new"}<span class="card-subheader" style="font-size:0.85rem; color:var(--text-muted); font-weight:normal;"> - {'created on'|t} {$link.created|format_date}</span>{/if}
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="lf_url{$index}">{'URL'|t}</label>
<input type="text" class="form-control" name="lf_url" id="lf_url{$index}" value="{$link.url}" placeholder="https://...">
</div>
<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">
</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>
</div>
{if="$formatter==='markdown'"}
<div class="sublabel">
{'Description will be rendered with'|t} <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank" rel="noopener">{'Markdown syntax'|t}</a>.
</div>
{/if}
</div>
<div class="form-group">
<label class="form-label" for="lf_tags{$index}">{'Tags'|t}</label>
<div class="{$asyncLoadClass}">
<input type="text" class="form-control" id="lf_tags{$index}" name="lf_tags" {if="empty($batch_mode)"}class="autofocus form-control"{/if} value="{$link.tags}"
data-list="{loop="$tags"}{$key}, {/loop}" data-multiple autocomplete="off" placeholder="tags, separated, by, commas" />
</div>
</div>
{if="isset($edit_link_plugin)"}
{loop="$edit_link_plugin"}
<div class="form-group">
{$value}
</div>
{/loop}
{/if}
<div class="form-group checkbox-wrapper">
<input type="checkbox" {if="$link.private === true"}checked="checked"{/if} name="lf_private" id="lf_private{$index}" style="width:auto;"/>
<label for="lf_private{$index}" style="margin:0;">{'Private bookmark'|t}</label>
</div>
</div>
<div class="card-footer">
{if="!empty($batch_mode)"}
<button type="button" name="cancel-batch-link" class="btn btn-secondary">{'Cancel'|t}</button>
{/if}
{if="!$link_is_new"}
<a href="{$base_path}/admin/shaare/delete?id={$link.id}&amp;token={$token}"
onclick="return confirm('Delete this bookmark?');"
class="btn btn-danger">{'Delete'|t}</a>
{/if}
<button type="submit" name="save_edit" class="btn btn-primary">{'Save'|t}</button>
</div>
</form>
</div>
</div>
</div>
{if="empty($batch_mode)"}
{include="page.footer"}
{/if}
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="error"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="pageError" class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header" style="background-color: var(--danger); color: white;">
<i class="mdi mdi-alert-circle"></i> {'Error'|t}
</div>
<div class="card-body">
<h2 style="color: var(--text-main); margin-bottom: 1rem;">{$message}</h2>
{if="!empty($text)"}
<p class="text-muted">{$text}</p>
{/if}
{if="!empty($stacktrace)"}
<div style="margin-top: 1.5rem;">
<strong>Stack Trace:</strong>
<pre style="background: var(--bg-body); padding: 1rem; border-radius: 0.375rem; border: 1px solid var(--border); overflow-x: auto; margin-top: 0.5rem;">{$stacktrace}</pre>
</div>
{/if}
</div>
<div class="card-footer">
<a href="{$base_path}/" class="btn btn-primary">{'Home'|t}</a>
</div>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<!-- This is an automatically generated file.
It will be read and overwritten.
Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore}
<TITLE>{$pagetitle}</TITLE>
<H1>Shaarli export of {$selection} bookmarks on {$date}</H1>
<DL><p>{loop="links"}
<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop}
</DL><p>
+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="export"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="container page-export">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form method="post" action="{$base_path}/admin/export">
<input type="hidden" name="token" value="{$token}">
<div class="card">
<div class="card-header">{"Export Database"|t}</div>
<div class="card-body">
<div class="filter-toggle-row">
<div>
<div class="filter-toggle-label">{'Prepend note permalinks with this Shaarli instance\'s URL'|t}</div>
<div class="sublabel">{'Useful to import bookmarks in a web browser'|t}</div>
</div>
<label class="toggle-switch">
<input type="checkbox" name="prepend_note_url" id="prepend_note_url"/>
<span class="toggle-slider"></span>
</label>
</div>
<hr style="margin: 1.5rem 0; border-top: 1px solid var(--border);">
<h3 style="font-size: 1.1rem; font-weight: 600; color: var(--text-main); margin-bottom: 1rem;">Selection</h3>
<div class="form-group">
<div class="radio-group">
<div class="form-group checkbox-wrapper" style="margin-bottom: 0.5rem;">
<input name="selection" type="radio" value="all" id="selection-all" checked style="width: auto;" />
<label for="selection-all" style="margin: 0;">
<strong>Export all</strong> <span class="text-muted">- Export all links</span>
</label>
</div>
<div class="form-group checkbox-wrapper" style="margin-bottom: 0.5rem;">
<input name="selection" type="radio" value="public" id="selection-public" style="width: auto;" />
<label for="selection-public" style="margin: 0;">
<strong>Export public</strong> <span class="text-muted">- Export public links only</span>
</label>
</div>
<div class="form-group checkbox-wrapper">
<input name="selection" type="radio" value="private" id="selection-private" style="width: auto;" />
<label for="selection-private" style="margin: 0;">
<strong>Export private</strong> <span class="text-muted">- Export private links only</span>
</label>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">{'Export'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{$pagetitle}</title>
<subtitle>Shaared links</subtitle>
{if="$show_dates"}
<updated>{$last_update}</updated>
{/if}
<link rel="self" href="{$self_link}#" />
<link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#"
title="Shaarli search - {$shaarlititle}" />
{loop="$plugins_feed_header"}
{$value}
{/loop}
<author>
<name>{$pagetitle}</name>
<uri>{$index_url}</uri>
</author>
<id>{$index_url}</id>
<generator>Shaarli</generator>
{loop="$links"}
<entry>
<title>{$value.title}</title>
{if="$usepermalinks"}
<link href="{$value.guid}#" />
{else}
<link href="{$value.url}#" />
{/if}
<id>{$value.guid}</id>
{if="$show_dates"}
<published>{$value.pub_iso_date}</published>
<updated>{$value.up_iso_date}</updated>
{/if}
<content type="html" xml:lang="{$language}"><![CDATA[{$value.description}]]></content>
{loop="$value.taglist"}
<category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
{/loop}
{loop="$value.feed_plugins"}
{$value}
{/loop}
</entry>
{/loop}
</feed>
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{$pagetitle}</title>
<link>{$index_url}</link>
<description>Shaared links</description>
<language>{$language}</language>
<copyright>{$index_url}</copyright>
<generator>Shaarli</generator>
<atom:link rel="self" href="{$self_link}" />
<atom:link rel="search" type="application/opensearchdescription+xml" href="{$index_url}open-search#"
title="Shaarli search - {$shaarlititle}" />
{loop="$plugins_feed_header"}
{$value}
{/loop}
{loop="$links"}
<item>
<title>{$value.title}</title>
<guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid>
{if="$usepermalinks"}
<link>{$value.guid}</link>
{else}
<link>{$value.url}</link>
{/if}
{if="$show_dates"}
<pubDate>{$value.pub_iso_date}</pubDate>
<atom:modified>{$value.up_iso_date}</atom:modified>
{/if}
<description><![CDATA[{$value.description}]]></description>
{loop="$value.taglist"}
<category domain="{$index_url}?searchtags=">{$value}</category>
{/loop}
{loop="$value.feed_plugins"}
{$value}
{/loop}
</item>
{/loop}
</channel>
</rss>
+77
View File
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="import"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="uploaddiv" class="container page-import">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form method="post" action="{$base_path}/admin/import" enctype="multipart/form-data" name="uploadform" id="uploadform">
<input type="hidden" name="token" value="{$token}">
<input type="hidden" name="MAX_FILE_SIZE" value="{$maxfilesize}">
<div class="card">
<div class="card-header">{"Import Database"|t}</div>
<div class="card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">
Import Netscape HTML bookmarks (as exported from Firefox/Chrome/Opera/Delicious/Diigo...).
</p>
<div class="form-group">
<label class="form-label" for="filetoupload">File to upload</label>
<input type="file" id="filetoupload" name="filetoupload" class="form-control" style="height: auto; padding: 0.5rem;"/>
<div class="sublabel">{'Maximum size allowed:'|t} <strong>{$maxfilesizeHuman}</strong></div>
</div>
<div class="form-group checkbox-wrapper">
<input type="checkbox" name="overwrite" id="overwrite" style="width: auto;">
<label for="overwrite" style="margin: 0;">{'Overwrite existing bookmarks'|t}</label>
</div>
<hr style="margin: 1.5rem 0; border-top: 1px solid var(--border);">
<h3 style="font-size: 1.1rem; font-weight: 600; color: var(--text-main); margin-bottom: 1rem;">Privacy Settings</h3>
<div class="form-group">
<div class="radio-group">
<div class="form-group checkbox-wrapper" style="margin-bottom: 0.5rem;">
<input name="privacy" type="radio" value="default" id="privacy-default" checked style="width: auto;" />
<label for="privacy-default" style="margin: 0;">
<strong>Default</strong> <span class="text-muted">- {'Use values from the imported file, default to public'|t}</span>
</label>
</div>
<div class="form-group checkbox-wrapper" style="margin-bottom: 0.5rem;">
<input name="privacy" type="radio" value="private" id="privacy-private" style="width: auto;" />
<label for="privacy-private" style="margin: 0;">
<strong>Private</strong> <span class="text-muted">- {'Import all bookmarks as private'|t}</span>
</label>
</div>
<div class="form-group checkbox-wrapper">
<input name="privacy" type="radio" value="public" id="privacy-public" style="width: auto;" />
<label for="privacy-public" style="margin: 0;">
<strong>Public</strong> <span class="text-muted">- {'Import all bookmarks as public'|t}</span>
</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="default-tags">{'Add default tags'|t}</label>
<input type="text" name="default_tags" id="default-tags" class="form-control" placeholder="Separate tags with comma...">
</div>
</div>
<div class="card-footer">
<button type="submit" name="import_file" class="btn btn-primary">{'Import'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<title>{$pagetitle}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="referrer" content="same-origin">
<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
<link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#"
title="Shaarli search - {$shaarlititle}" />
<!-- Professional Theme CSS -->
<link type="text/css" rel="stylesheet" href="{$asset_path}/css/style.css#" />
<!-- Icons (Material Design Icons) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/MaterialDesign-Webfont/7.2.96/css/materialdesignicons.min.css">
<!-- Theme Color -->
<meta name="theme-color" content="#2563eb">
<!-- Plugins CSS -->
{loop="$plugins_includes.css_files"}
<link type="text/css" rel="stylesheet" href="{$root_path}/{$value}#" />
{/loop}
{if="is_file('data/user.css')"}
<link type="text/css" rel="stylesheet" href="{$root_path}/data/user.css#" />
{/if}
<script>
var shaarli = {
basePath: '{$base_path}',
rootPath: '{$root_path}',
assetPath: '{$asset_path}',
isAuth: {if="$is_logged_in"}true{else}false{/if},
pageName: '{$pageName}'
};
</script>
<script src="{$asset_path}/js/script.js#" defer></script>
{if="file_exists('tpl/shaarli-pro/extra.html')"}
{include="extra"}
{/if}
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="install"}
{include="includes"}
</head>
<body>
<div id="install" class="page-install">
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="post" action="{$base_path}/install" name="installform" id="installform">
<div class="card">
<div class="card-header">Welcome to your Shaarli</div>
<div class="card-body">
<p class="text-muted" style="margin-bottom: 1.5rem;">{'It looks like it\'s the first time you run Shaarli. Please configure it.'|t}</p>
<div class="form-group">
<label class="form-label" for="setlogin">{'Username'|t}</label>
<input type="text" name="setlogin" id="setlogin" class="form-control autofocus"/>
</div>
<div class="form-group">
<label class="form-label" for="setpassword">{'Password'|t}</label>
<input type="password" id="setpassword" name="setpassword" class="form-control"/>
</div>
<div class="form-group">
<label class="form-label" for="continent">{'Timezone'|t}</label>
<div class="row">
<div class="col-sm-6" id="timezone-continent">
<select name="continent" id="continent" class="form-control">
{loop="$continents"}
{if="$key !== 'selected'"}
<option value="{$value}" {if="$continents.selected === $value"}selected{/if}>
{$value}
</option>
{/if}
{/loop}
</select>
</div>
<div class="col-sm-6" id="timezone-city">
<select name="city" id="city" class="form-control">
{loop="$cities"}
{if="$key !== 'selected'"}
<option value="{$value.city}" {if="$cities.selected === $value.city"}selected{/if} data-continent="{$value.continent}">
{$value.city}
</option>
{/if}
{/loop}
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="title">{'Shaarli title'|t}</label>
<input type="text" name="title" id="title" class="form-control" placeholder="{'My links'|t}"/>
</div>
<div class="form-group checkbox-wrapper">
<input type="checkbox" name="updateCheck" id="updateCheck" checked="checked" style="width: auto;">
<label for="updateCheck" style="margin: 0;">{'Notify me when a new release is ready'|t}</label>
</div>
<div class="form-group">
<div class="checkbox-wrapper">
<input type="checkbox" name="enableApi" id="enableApi" style="width: auto;">
<label for="enableApi" style="margin: 0;">{'Enable REST API'|t}</label>
</div>
<div class="sublabel" style="margin-left: 1.5rem;">{'Allow third party software to use Shaarli such as mobile application'|t}</div>
</div>
</div>
<div class="card-footer">
<button type="submit" name="Save" class="btn btn-primary">{'Install'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="server.requirements"}
</div>
{include="page.footer"}
</body>
</html>
+456
View File
@@ -0,0 +1,456 @@
document.addEventListener('DOMContentLoaded', () => {
// ===== Theme Toggle =====
const themeCheckbox = document.getElementById('theme-toggle-checkbox');
const themeIconLight = document.getElementById('theme-icon-light');
const themeLabelSpan = document.querySelector('.theme-toggle-label span');
function updateTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
if (themeCheckbox) {
themeCheckbox.checked = theme === 'dark';
}
if (themeIconLight) {
themeIconLight.className = theme === 'dark' ? 'mdi mdi-weather-night' : 'mdi mdi-weather-sunny';
}
if (themeLabelSpan) {
themeLabelSpan.textContent = theme === 'dark' ? 'Dark Mode' : 'Light Mode';
}
}
// Init Theme
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
updateTheme(savedTheme);
if (themeCheckbox) {
themeCheckbox.addEventListener('change', () => {
const next = themeCheckbox.checked ? 'dark' : 'light';
updateTheme(next);
});
}
// ===== Mobile Sidebar Toggle =====
const sidebar = document.getElementById('sidebar');
const sidebarOverlay = document.getElementById('sidebar-overlay');
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
function toggleSidebar() {
sidebar?.classList.toggle('show');
sidebarOverlay?.classList.toggle('show');
}
mobileMenuBtn?.addEventListener('click', toggleSidebar);
sidebarOverlay?.addEventListener('click', toggleSidebar);
// ===== Search Overlay =====
const searchOverlay = document.getElementById('search-overlay');
const searchToggleBtn = document.getElementById('search-toggle-btn');
const searchModalInput = document.getElementById('search-modal-input');
function openSearch() {
searchOverlay?.classList.add('show');
setTimeout(() => searchModalInput?.focus(), 100);
}
function closeSearch() {
searchOverlay?.classList.remove('show');
}
searchToggleBtn?.addEventListener('click', openSearch);
// Close search on overlay click
searchOverlay?.addEventListener('click', (e) => {
if (e.target === searchOverlay) {
closeSearch();
}
});
// Keyboard shortcut: S to open search, ESC to close
document.addEventListener('keydown', (e) => {
// Don't trigger if typing in an input
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
if (e.key === 'Escape') {
closeSearch();
}
return;
}
if (e.key === 's' || e.key === 'S') {
e.preventDefault();
openSearch();
}
if (e.key === 'Escape') {
closeSearch();
closeFilterPanel();
}
});
// ===== Filter Panel =====
const filterToggleBtn = document.getElementById('filter-toggle-btn');
const filterPanel = document.getElementById('filter-panel');
const filterCloseBtn = document.getElementById('filter-close-btn');
const filterPrivate = document.getElementById('filter-private');
const filterPublic = document.getElementById('filter-public');
const filterUntagged = document.getElementById('filter-untagged');
function toggleFilterPanel() {
filterPanel?.classList.toggle('show');
}
function closeFilterPanel() {
filterPanel?.classList.remove('show');
}
filterToggleBtn?.addEventListener('click', (e) => {
e.stopPropagation();
toggleFilterPanel();
});
filterCloseBtn?.addEventListener('click', closeFilterPanel);
// Close filter when clicking outside
document.addEventListener('click', (e) => {
if (filterPanel?.classList.contains('show')) {
if (!filterPanel.contains(e.target) && e.target !== filterToggleBtn) {
closeFilterPanel();
}
}
});
// Handle filter toggle switches - using data-url like the example
document.querySelectorAll('.toggle-switch input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const label = e.target.closest('label');
if (label && label.dataset.url) {
window.location.href = label.dataset.url;
}
});
});
// Handle links per page options
// Handle custom value form submission
const filterInput = document.querySelector('.filter-input');
if (filterInput) {
filterInput.closest('form')?.addEventListener('submit', (e) => {
e.preventDefault();
const value = filterInput.value;
if (value && value > 0) {
window.location.href = shaarli.basePath + '/?nb=' + value;
}
});
}
// ===== View Toggle (Grid/List/Compact) =====
const linksList = document.getElementById('links-list');
const viewGridBtn = document.getElementById('view-grid-btn');
const viewListBtn = document.getElementById('view-list-btn');
const viewCompactBtn = document.getElementById('view-compact-btn');
function setView(view) {
if (!linksList) return;
// Remove all view classes
linksList.classList.remove('view-grid', 'view-list', 'view-compact');
// Remove active state from all buttons
viewGridBtn?.classList.remove('active');
viewListBtn?.classList.remove('active');
viewCompactBtn?.classList.remove('active');
// Apply selected view
if (view === 'list') {
linksList.classList.add('view-list');
viewListBtn?.classList.add('active');
} else if (view === 'compact') {
linksList.classList.add('view-compact');
viewCompactBtn?.classList.add('active');
} else {
// Default to grid
linksList.classList.add('view-grid');
viewGridBtn?.classList.add('active');
}
localStorage.setItem('linksView', view);
}
// Init view from localStorage
const savedView = localStorage.getItem('linksView') || 'grid';
setView(savedView);
viewGridBtn?.addEventListener('click', () => setView('grid'));
viewListBtn?.addEventListener('click', () => setView('list'));
viewCompactBtn?.addEventListener('click', () => setView('compact'));
// ===== Multi-Select Mode =====
const selectModeBtn = document.getElementById('select-mode-btn');
const bulkActionsBar = document.getElementById('bulk-actions-bar');
const bulkCount = document.getElementById('bulk-count');
const bulkSelectAll = document.getElementById('bulk-select-all');
const bulkCancel = document.getElementById('bulk-cancel');
const bulkDelete = document.getElementById('bulk-delete');
const bulkPublic = document.getElementById('bulk-public');
const bulkPrivate = document.getElementById('bulk-private');
let selectionMode = false;
let selectedIds = new Set();
function updateBulkUI() {
if (bulkCount) {
bulkCount.textContent = selectedIds.size;
}
// Update link cards visual state
document.querySelectorAll('.link-outer').forEach(card => {
const id = card.dataset.id;
if (selectedIds.has(id)) {
card.classList.add('selected');
} else {
card.classList.remove('selected');
}
});
// Update checkboxes
document.querySelectorAll('.link-checkbox').forEach(cb => {
cb.checked = selectedIds.has(cb.dataset.id);
});
}
function enterSelectionMode() {
selectionMode = true;
document.body.classList.add('selection-mode');
bulkActionsBar?.classList.add('show');
selectModeBtn?.classList.add('active');
}
function exitSelectionMode() {
selectionMode = false;
selectedIds.clear();
document.body.classList.remove('selection-mode');
bulkActionsBar?.classList.remove('show');
selectModeBtn?.classList.remove('active');
updateBulkUI();
}
function toggleSelection(id) {
if (selectedIds.has(id)) {
selectedIds.delete(id);
} else {
selectedIds.add(id);
}
if (selectedIds.size > 0 && !selectionMode) {
enterSelectionMode();
} else if (selectedIds.size === 0 && selectionMode) {
exitSelectionMode();
}
updateBulkUI();
}
selectModeBtn?.addEventListener('click', () => {
if (selectionMode) {
exitSelectionMode();
} else {
enterSelectionMode();
}
});
bulkCancel?.addEventListener('click', exitSelectionMode);
bulkSelectAll?.addEventListener('click', () => {
document.querySelectorAll('.link-outer').forEach(card => {
if (card.dataset.id) {
selectedIds.add(card.dataset.id);
}
});
updateBulkUI();
});
// Handle checkbox clicks
document.addEventListener('change', (e) => {
if (e.target.classList.contains('link-checkbox')) {
toggleSelection(e.target.dataset.id);
}
});
// Handle card clicks in selection mode
document.addEventListener('click', (e) => {
if (!selectionMode) return;
const card = e.target.closest('.link-outer');
if (card && card.dataset.id) {
// Don't toggle if clicking on actions or links
if (e.target.closest('.link-actions') ||
e.target.closest('.link-hover-actions') ||
e.target.tagName === 'A') {
return;
}
toggleSelection(card.dataset.id);
}
});
// Bulk actions
bulkDelete?.addEventListener('click', () => {
if (selectedIds.size === 0) return;
if (!confirm(`Delete ${selectedIds.size} bookmark(s)?`)) return;
// Submit form with selected IDs
const form = document.createElement('form');
form.method = 'POST';
form.action = shaarli.basePath + '/admin/shaare/delete';
selectedIds.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'id[]';
input.value = id;
form.appendChild(input);
});
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'token';
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
form.appendChild(tokenInput);
document.body.appendChild(form);
form.submit();
});
bulkPublic?.addEventListener('click', () => {
if (selectedIds.size === 0) return;
bulkVisibilityChange('public');
});
bulkPrivate?.addEventListener('click', () => {
if (selectedIds.size === 0) return;
bulkVisibilityChange('private');
});
function bulkVisibilityChange(visibility) {
const form = document.createElement('form');
form.method = 'POST';
form.action = shaarli.basePath + '/admin/shaare/visibility';
selectedIds.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'id[]';
input.value = id;
form.appendChild(input);
});
const visInput = document.createElement('input');
visInput.type = 'hidden';
visInput.name = 'visibility';
visInput.value = visibility;
form.appendChild(visInput);
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'token';
tokenInput.value = document.querySelector('input[name="token"]')?.value || '';
form.appendChild(tokenInput);
document.body.appendChild(form);
form.submit();
}
// ===== Search Type Toggle =====
const searchTagsBtn = document.getElementById('search-tags-btn');
const searchAllBtn = document.getElementById('search-all-btn');
const searchForm = document.getElementById('search-form');
searchTagsBtn?.addEventListener('click', (e) => {
e.preventDefault();
searchModalInput.name = 'searchtags';
searchAllBtn?.classList.remove('active');
searchTagsBtn?.classList.add('active');
searchForm?.submit();
});
searchAllBtn?.addEventListener('click', (e) => {
e.preventDefault();
searchModalInput.name = 'searchterm';
searchTagsBtn?.classList.remove('active');
searchAllBtn?.classList.add('active');
searchForm?.submit();
});
// ===== Thumbnail Update =====
const thumbnailsPage = document.querySelector('.page-thumbnails');
if (thumbnailsPage) {
const thumbnailPlaceholder = document.querySelector('.thumbnail-placeholder');
const thumbnailTitle = document.querySelector('.thumbnail-link-title');
const progressCurrent = document.querySelector('.progress-current');
const progressBarActual = document.querySelector('.progress-actual');
const idsInput = document.querySelector('input[name="ids"]');
if (idsInput && idsInput.value) {
const thumbnailsIdList = idsInput.value.split(',');
const total = thumbnailsIdList.length;
let i = 0;
const updateThumbnail = function (id) {
console.log('Updating thumbnail #' + i + ' with id ' + id);
fetch(shaarli.basePath + '/admin/shaare/' + id + '/update-thumbnail', {
method: 'PATCH',
headers: {
'Accept': 'application/json',
}
})
.then(response => response.json())
.then(data => {
i++;
if (thumbnailTitle) {
thumbnailTitle.textContent = data.title || 'Processing...';
}
if (thumbnailPlaceholder) {
if (data.thumbnail) {
thumbnailPlaceholder.innerHTML = '<img title="Current thumbnail" src="' + data.thumbnail + '" style="max-width: 100%; height: auto;"/>';
} else {
thumbnailPlaceholder.innerHTML = '';
}
}
if (progressCurrent) {
progressCurrent.textContent = i;
}
if (progressBarActual) {
progressBarActual.style.width = ((i * 100) / total) + '%';
}
if (i < total) {
updateThumbnail(thumbnailsIdList[i]);
} else {
if (thumbnailTitle) {
thumbnailTitle.textContent = 'Thumbnail update done!';
}
}
})
.catch(error => {
console.error('Failed to update thumbnail:', error);
if (thumbnailTitle) {
thumbnailTitle.textContent = 'Error updating thumbnail #' + i;
}
// Continue with next thumbnail even if one fails
i++;
if (i < total) {
updateThumbnail(thumbnailsIdList[i]);
}
});
};
// Start the update process
updateThumbnail(thumbnailsIdList[0]);
}
}
});
+76
View File
@@ -0,0 +1,76 @@
<!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}
<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}">
{if="$is_logged_in"}<div class="link-select-checkbox"><input type="checkbox" class="link-checkbox" data-id="{$value.id}"></div>{/if}
{if="$value.thumbnail !== false"}<div class="link-thumbnail"><img src="{$root_path}/{$value.thumbnail}#" loading="lazy" alt="" style="width:100%; height:100%; object-fit:cover;"></div>{/if}
<div class="link-visibility-badge">{if="$value.private"}<i class="mdi mdi-lock" title="Private"></i>{else}<i class="mdi mdi-lock-open-variant" title="Public"></i>{/if}</div>
{if="$is_logged_in"}
<div class="link-hover-actions">
<a href="{$base_path}/admin/shaare/{$value.id}" class="link-hover-btn" title="Edit"><i class="mdi mdi-pencil"></i></a>
<a href="{$value.real_url}" target="_blank" rel="noopener" class="link-hover-btn" title="Open"><i class="mdi mdi-open-in-new"></i></a>
<a href="{$base_path}/admin/shaare/delete?id={$value.id}&amp;token={$token}" class="link-hover-btn" title="Delete" onclick="return confirm('Delete this bookmark?');"><i class="mdi mdi-delete"></i></a>
</div>
{/if}
<div class="link-content">
<div class="link-header">
<a href="{$value.real_url}" class="link-title"{if="strpos($value.url, $value.shorturl) !== false"} title="Note"{/if}>{if="isset($value.sticky) && $value.sticky"}<i class="mdi mdi-pin" style="color:var(--primary);"></i>{/if}{$value.title_html}</a>
<span class="link-url">{$value.url}</span>
<div class="link-meta">
<span class="link-date">{function="strftime('%c', $value.timestamp)"}</span>
<a href="{$base_path}/shaare/{$value.shorturl}" class="link-permalink" title="Permalink"><i class="mdi mdi-link-variant"></i></a>
</div>
</div>
{if="$value.description"}<div class="link-description">{$value.description}</div>{/if}
<div class="link-footer">
<div class="link-tag-list">{loop="$value.taglist"}<span class="link-tag"><a href="{$base_path}/add-tag/{$key|urlencode}">{$value}</a></span>{/loop}</div>
<div class="link-actions">
{if="$is_logged_in"}
<a href="{$base_path}/admin/shaare/{$value.id}" title="Edit"><i class="mdi mdi-pencil"></i></a>
<a href="{$base_path}/admin/shaare/{$value.id}/pin?token={$token}" title="Pin/Unpin"><i class="mdi mdi-pin"></i></a>
<a href="{$base_path}/admin/shaare/delete?id={$value.id}&amp;token={$token}" title="Delete" onclick="return confirm('Delete this bookmark?');"><i class="mdi mdi-delete"></i></a>
{/if}
<a href="{$value.real_url}" target="_blank" rel="noopener" title="Open Link"><i class="mdi mdi-open-in-new"></i></a>
</div>
</div>
</div>
</div>
{/loop}
</div>
{include="linklist.paging"}
{/if}
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
{include="page.footer"}
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
{if="$page_max > 1"}
{if="$is_logged_in"}
{$total="$linkcount"}
{else}
{if="empty($privateLinkcount)"}
{$total="$linkcount"}
{else}
{$total=$linkcount - $privateLinkcount}
{/if}
{/if}
{$from=($page_current - 1) * $links_per_page + 1}
{$to=min($total, ($page_current - 1) * $links_per_page + $links_per_page)}
<div class="paging">
<div class="paging-links">
{if="$next_page_url"}<a href="{$next_page_url}" class="paging-newer" title="Newer"><i class="mdi mdi-chevron-left"></i></a>{/if}
<div class="paging-current">page <strong>{$page_current}</strong> / {$page_max}</div>
{if="$previous_page_url"}<a href="{$previous_page_url}" class="paging-older" title="Older"><i class="mdi mdi-chevron-right"></i></a>{/if}
</div>
{loop="$action_plugin"}
<div class="paging-plugin">
<a {loop="$value.attr"}{$key}="{$value}" {/loop}>{$value.html}</a>
</div>
{/loop}
</div>
{/if}
+55
View File
@@ -0,0 +1,55 @@
<!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}">
+44
View File
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="loginform"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="headerform" class="page-login container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form method="post" name="loginform">
<div class="card">
<div class="card-header">{'Login'|t}</div>
<div class="card-body">
<div class="form-group">
<label class="form-label" for="login">{'Username'|t}</label>
<input type="text" name="login" id="login" class="form-control {if="empty($username)"}autofocus{/if}" tabindex="1" {if="!empty($username)"}value="{$username}"{/if}>
</div>
<div class="form-group">
<label class="form-label" for="password">{'Password'|t}</label>
<input type="password" name="password" id="password" class="form-control {if="!empty($username)"}autofocus{/if}" tabindex="2" >
</div>
<div class="form-group checkbox-wrapper">
<input type="checkbox" name="longlastingsession" id="longlastingsession" tabindex="3" style="width: auto;"
{if="$remember_user_default"}checked="checked"{/if}>
<label for="longlastingsession" style="margin: 0;">{'Remember me'|t}</label>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary" tabindex="4">{'Login'|t}</button>
<input type="hidden" name="token" value="{$token}">
{if="$returnurl"}<input type="hidden" name="returnurl" value="{$returnurl}">{/if}
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Shaarli search - {$pagetitle}</ShortName>
<Description>Shaarli search - {$pagetitle}</Description>
<Url type="text/html" template="{$serverurl}?searchterm={searchTerms}" />
<Url type="application/atom+xml" template="{$serverurl}feed/atom?searchterm={searchTerms}"/>
<Url type="application/rss+xml" template="{$serverurl}feed/rss?searchterm={searchTerms}"/>
<InputEncoding>UTF-8</InputEncoding>
<Developer>Shaarli Community - https://github.com/shaarli/Shaarli/</Developer>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAHRklE
QVRIx5WWaWxU5xWG3++7986dfYYZb+MN2xiMDRiDFePUiQsNoiwpUNpAmhInJVEqpa0oQUlbJVKq
olaiqpLKUtOKhAJRm1BKRVWctuykpFjAgPcFx/uMl5mxPTOeuXPv3O3rjyiV0lIpfX+dc36c55xf
70vwP9TZ2fFpSQCwT5u6unX4f0QeNLx27RoMQwfRveTd11T23M+8S9w+Z3NRma1W4Hk6/nEimFpM
Xnun9Xpmz1MPY+feOhBi/fwAAOjq7iJEqmQqCZf5i7NvyNZ/bJPYgAjCiJc2Zmhyw68SM/T1+NlK
uf61BPoH+tHU1PT5ACMjI8RXvACvpZ5NT0+fmrG+2TKqtDLV0BgA2AUfXS3+UtfDX2ixCf73E+oA
rat92CTkv9fRBwEkSaLDt/JZR/v0Q7qjb8dQ5hjSqmYSOCkzBbogL+ij2RN8bik9wK88Al9tH4tG
ow88lvb19yEyPwfGGLq6OungYD9fUlosrqwoQVVVUeOU8qE/mU0ZTq6KNvreNort+5hugkayQUgY
qQld/u6qnVRhkciscOdOkNy5E0RnZ+e/AbwsZxAaHyORZA+prW01CTlnGppOqAcwUnCmlDAkAyin
Dapb2t7lNeRijpwvTGlJROXugoKS+upz/S19Kj9lJjxXGY1VU49tGevt7WOCSMHTeAXclePsQts9
Jq9oLR7rPVkHxpUYkK2c07ZDiieRNcAx3ZlNphcnsxbiMuEsXFSTZpabp+VVS17UNSV/8n7+gN75
+C1DM6VEjkgatiz/5IOCAheiUdeyr+198keKZXLzTKYjMDk/ZzGJhkV9AiPSdWaYIAY4U7TYNJMR
pugMqgHcXTiJqDK8ycMv2+TPWyWtKFw3KEdtJxNz8u8+/PNYIqeUgY/Oz+Z7q5X3gtqvG7qip8yM
HqdZg5kGgwGACoQQQkEMQ2DMIFnGE04xCRQTZFaexUT6jEEImJ2njjxx9fr13hfqfULTQ4apHept
lxf4mrqS3Tek0w1toTc1K6WcXfAwnyWH8kSkIueEhdhNrzUAv16fSCUzNwR3vr/G2lKWojMqbxF4
FWlLPDvBR+RBNpTsNqfkV7htuUe/UVq456qdzzvFh2KjdSH0I6ODs1ps9NHcw2jMfRYcEQyOCiox
aWR0fOQWVLFNUuOnrXJxfIPnpXLRKzocLpsJanhUU/bfjJ4gfwm/ys3JGX1cuS3UBvauLa/MBe9z
5c/xGRdSKpiVmhhN98JK/w4DGgg4uLh8u1NfPhWbmzszeH3G1rxv3dL7+qXyGaXHJi46s4QQophp
TKSDkHWGlAqmGCbAGAUA3sY5LlRZv3w44Dhnm0iFzbnsGfJR5E+EEsppTBVXuBptL5b8YQMlwnO+
Z2wtea4cl8Rc3KXJ4zShRm0CFaCaGhSDwGSMlbtLhSr7FnVhKhscC4+AO3L0x5PSFBdfE9i40SE6
xLSWRCIbJ6phIqEysqvk+2aJ0vhB1NK353zyaL3GS76NgRbVYykkt2OXuaQqMwuxkk0FT+OJpS+z
piXPRP1KzRvz4dRvO68PaWCMEQAIDcbqFiLJtyaSA/Ef3NnMGttgvHS7mUUWw7cTMemtY/cPsw3n
oe2+4mGXpt7VGWPJ6zOn1V2Xfaz5AxgvBzexodmeGWmCbWWTDOGpSZIIqeAe2/IYvnXgWbomUj2T
lNlwUP7bV64lzuRIeoodqDxi1OKRs/e0i08dH/6JPWtk6KKaZd3xj2jAXkY3BvZGK1xrhZvRNktP
fNA0ubQrxyxV4jHpSkWoXJPKYqBetxeRpgsMWwE5b/bxaYxUDSWmzCLbStqQuy02LY4/cWLsqH8i
HYNbyEeJczkJSzH2855DwsXp932F9hXEKQSQ1UHa568iRAa3uFyeNVolMBmeIrzT4cTXm37KMuvA
xb8XXTqiDELRgKg8h/dGWwP9iSDaIzcYJSCbA/uNR3N2sNahV/hbsSA70vEdW66tCOOpj8FRkIgU
Q5ybLUopiTzeTpFWUoTXdR3knwQMzLANOyQX50fWhDktLeAX3UcIA2EEQHPgi6TBsrWzaLGm7emC
H7bY+GPlV6YumiEpDhslxGBgROApz+yyhROzduIEzwngDh48iD3jO0nBRAUUXtHcoq+ZWbI5EWWa
WTieFjoKsaVoN92T88J4vlz+asXqpb+hEcdotbdueZ47vyiqhpA2UoRQwpoLtpNHxR0382jR8RzT
k8xYMyCMMfR33Uc8rxdrA9tpuHPyS7pPOTRPZ1arepa4rUsybiWnnU9a39ZXxdur+XoGAGMdoXLi
M789axnfNxDr8omiDcvF1f3OhPf18efjF/nfz6PGvv6zlrlwWwYBgcBZbKH4WKHODLHEURDxbHDF
QWGePftHUlZWjsy8guYnH2EgwORfZ5cuavEqgGhLnL6+sycvRP1Fbux/fheIn3wCCN4N/qdPMwA4
2fYOeoe7kc6kcfCbh8n+r7YwAOjs6QCl9DNx5t7dew+MOf8CcuqqoLxlhwgAAAAASUVORK5CYII=
</Image>
</OpenSearchDescription>
+38
View File
@@ -0,0 +1,38 @@
<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;">
<p>
Powered by <a href="https://github.com/shaarli/Shaarli" target="_blank" rel="noopener" style="color:var(--text-main);">Shaarli</a>.
{if="$is_logged_in"}
<span class="footer-divider">|</span> <a href="{$base_path}/admin/tools" style="color:var(--text-main);">Administration</a>
{/if}
</p>
<div class="footer-feeds" style="margin-top: 0.5rem;">
<a href="{$base_path}/feed/rss" title="RSS Feed"><i class="mdi mdi-rss"></i> RSS</a>
<span style="margin: 0 0.5rem;">•</span>
<a href="{$base_path}/feed/atom" title="Atom Feed">Atom</a>
<span style="margin: 0 0.5rem;">•</span>
<a href="{$base_path}/daily" title="Daily">Daily</a>
</div>
{loop="$plugins_footer.text"}
<div class="plugin-footer-text">{$value}</div>
{/loop}
{loop="$plugins_footer.js"}
<script src="{$root_path}/{$value}"></script>
{/loop}
</footer>
</div><!-- /.content-container -->
<!-- Bulk Actions Bar (for multi-select) -->
<div class="bulk-actions-bar" id="bulk-actions-bar">
<div class="bulk-info">
<span id="bulk-count">0</span> links selected - <a class="bulk-select-all" id="bulk-select-all">select all</a>
</div>
<div class="bulk-buttons">
<button class="bulk-btn bulk-btn-cancel" id="bulk-cancel">CANCEL</button>
<button class="bulk-btn bulk-btn-delete" id="bulk-delete">DELETE</button>
<button class="bulk-btn bulk-btn-public" id="bulk-public">SET PUBLIC</button>
<button class="bulk-btn bulk-btn-private" id="bulk-private">SET PRIVATE</button>
</div>
</div>
</div><!-- /.main-content -->
+253
View File
@@ -0,0 +1,253 @@
<!--
{$isCalledFromBookmarklet=isset($_GET['source']) && $_GET['source'] == 'bookmarklet'}
-->
{if="$isCalledFromBookmarklet"}
{ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
{else}
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<i class="mdi mdi-bookmark-multiple"></i>
</div>
<a href="{$titleLink}" class="sidebar-brand">{$shaarlititle}</a>
</div>
<nav class="sidebar-nav">
<div class="sidebar-section">
<div class="sidebar-section-title">Navigation</div>
<a href="{$base_path}/" class="sidebar-link{if=" $pageName=='linklist'"} active{/if}">
<i class="mdi mdi-bookmark-multiple-outline"></i>
<span>All Bookmarks</span>
</a>
<a href="{$base_path}/tags/cloud" class="sidebar-link{if=" $pageName=='tagcloud'"} active{/if}">
<i class="mdi mdi-tag-multiple-outline"></i>
<span>Tag Cloud</span>
</a>
<a href="{$base_path}/picture-wall?{function=" ltrim($searchcrits, '&' )"}" class="sidebar-link{if="
$pageName=='picwall'"} active{/if}">
<i class="mdi mdi-image-multiple-outline"></i>
<span>Picture Wall</span>
</a>
<a href="{$base_path}/daily" class="sidebar-link{if=" $pageName=='daily'"} active{/if}">
<i class="mdi mdi-calendar-today"></i>
<span>Daily</span>
</a>
</div>
{if="$is_logged_in"}
<div class="sidebar-section">
<div class="sidebar-section-title">Admin</div>
<a href="{$base_path}/admin/tools" class="sidebar-link">
<i class="mdi mdi-tools"></i>
<span>Tools</span>
</a>
<a href="{$base_path}/admin/configure" class="sidebar-link">
<i class="mdi mdi-cog-outline"></i>
<span>Settings</span>
</a>
</div>
{/if}
</nav>
<div class="sidebar-footer">
{if="$is_logged_in"}
<a href="{$base_path}/admin/add-shaare" class="sidebar-add-btn">
<i class="mdi mdi-plus"></i>
<span>New Bookmark</span>
</a>
{/if}
<div class="theme-toggle-wrapper">
<span class="theme-toggle-label">
<i class="mdi mdi-weather-sunny" id="theme-icon-light"></i>
<span>Light Mode</span>
</span>
<label class="theme-switch">
<input type="checkbox" id="theme-toggle-checkbox">
<span class="theme-slider"></span>
</label>
</div>
</div>
</aside>
<!-- Sidebar Overlay for Mobile -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<!-- Main Content Wrapper -->
<div class="main-content">
<!-- Top Header Bar -->
<header class="header-main">
<div class="header-inner">
<div class="header-left">
<button class="mobile-menu-btn" id="mobile-menu-btn" title="Toggle Menu">
<i class="mdi mdi-menu"></i>
</button>
<a href="{$titleLink}" class="header-brand">{$shaarlititle}</a>
</div>
<!-- Main Navigation -->
<nav class="header-nav">
<a href="{$base_path}/" class="header-nav-link{if=" $pageName=='linklist'"} active{/if}">
<i class="mdi mdi-home-outline"></i>
<span>HOME</span>
</a>
<a href="{$base_path}/tags/cloud" class="header-nav-link{if=" $pageName=='tagcloud'"} active{/if}">
<i class="mdi mdi-tag-multiple"></i>
<span>TAG CLOUD</span>
</a>
<a href="{$base_path}/picture-wall?{function=" ltrim($searchcrits, '&' )"}" class="header-nav-link{if="
$pageName=='picwall'"} active{/if}">
<i class="mdi mdi-image-multiple"></i>
<span>PICTURE WALL</span>
</a>
<a href="{$base_path}/daily" class="header-nav-link{if=" $pageName=='daily'"} active{/if}">
<i class="mdi mdi-calendar"></i>
<span>DAILY</span>
</a>
<button class="header-nav-link" id="search-toggle-btn" title="Search (Press S)">
<i class="mdi mdi-magnify"></i>
<span>SEARCH</span>
</button>
</nav>
<!-- Header Actions -->
<div class="header-actions">
<!-- Filter Button -->
<div class="filter-dropdown" id="filter-dropdown">
<button class="header-action-btn" id="filter-toggle-btn" title="Filters">
<i class="mdi mdi-filter-variant"></i>
</button>
<!-- Filter Panel -->
<div class="filter-panel" id="filter-panel">
<div class="filter-header">
<span class="filter-title">Filters</span>
<button class="filter-close" id="filter-close-btn">
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="filter-body">
<div class="filter-section">
<div class="filter-section-title">Links Per Page</div>
<a href="{$base_path}/?nb=20" class="filter-option">20 links</a>
<a href="{$base_path}/?nb=50" class="filter-option">50 links</a>
<a href="{$base_path}/?nb=100" class="filter-option">100 links</a>
</div>
<div class="filter-section">
<div class="filter-section-title">Custom value</div>
<form method="GET" action="{$base_path}/">
<input type="number" name="nb" class="filter-input" placeholder="20" min="1"
max="500">
</form>
</div>
{if="$is_logged_in"}
<div class="filter-section">
<div class="filter-section-title">Filters</div>
<div class="filter-toggle-row">
<span class="filter-toggle-label">Only private links</span>
<label class="toggle-switch">
<input type="checkbox" id="filter-private">
<span class="toggle-slider"></span>
</label>
</div>
<div class="filter-toggle-row">
<span class="filter-toggle-label">Only public links</span>
<label class="toggle-switch">
<input type="checkbox" id="filter-public">
<span class="toggle-slider"></span>
</label>
</div>
<div class="filter-toggle-row">
<span class="filter-toggle-label">Untagged links</span>
<label class="toggle-switch">
<input type="checkbox" id="filter-untagged">
<span class="toggle-slider"></span>
</label>
</div>
</div>
{/if}
</div>
</div>
</div>
{if="$is_logged_in"}
<!-- Multi-select Toggle -->
<button class="header-action-btn" id="select-mode-btn" title="Multi-select">
<i class="mdi mdi-checkbox-multiple-marked-outline"></i>
</button>
{/if}
<!-- RSS Feed -->
<a href="{$base_path}/feed/rss" class="header-action-btn" title="RSS Feed">
<i class="mdi mdi-rss"></i>
</a>
<!-- Plugins Support -->
{if="isset($plugins_header.buttons_toolbar)"}
{loop="$plugins_header.buttons_toolbar"}
{$value.html}
{/loop}
{/if}
{if="$is_logged_in"}
<a href="{$base_path}/admin/tools" class="header-action-btn" title="Tools">
<i class="mdi mdi-cog-outline"></i>
</a>
<a href="{$base_path}/admin/logout" class="header-action-btn" title="Logout">
<i class="mdi mdi-logout-variant"></i>
</a>
{else}
<a href="{$base_path}/login" class="header-action-btn" title="Login">
<i class="mdi mdi-login-variant"></i>
</a>
{/if}
</div>
</div>
</header>
<!-- Search Overlay -->
<div class="search-overlay" id="search-overlay">
<div class="search-modal">
<form method="GET" action="{$base_path}/" id="search-form">
<div class="search-modal-header">
<i class="mdi mdi-magnify search-modal-icon"></i>
<input type="text" name="searchterm" class="search-modal-input" id="search-modal-input"
placeholder="Search..." autocomplete="off" value="{if="
isset($search_term)"}{$search_term}{/if}">
<div class="search-modal-actions">
<button type="submit" name="searchtags" class="search-type-btn" id="search-tags-btn">
<i class="mdi mdi-tag"></i> tags
</button>
<button type="submit" class="search-type-btn active" id="search-all-btn">
<i class="mdi mdi-magnify"></i> search
</button>
</div>
</div>
</form>
<div class="search-hint">
Press <kbd class="search-kbd">ESC</kbd> to close or <kbd class="search-kbd">Enter</kbd> to search
</div>
</div>
</div>
<!-- Content Container -->
<div class="content-container">
<!-- Feedback Messages -->
{if="!empty($plugin_errors) && $is_logged_in"}
{loop="plugin_errors"}
<div class="alert alert-danger">{$value}</div>
{/loop}
{/if}
{if="!empty($global_errors)"}
{loop="global_errors"}
<div class="alert alert-danger">{$value}</div>
{/loop}
{/if}
{if="!empty($global_successes)"}
{loop="global_successes"}
<div class="alert alert-success">{$value}</div>
{/loop}
{/if}
{/if}
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="custom"}
{include="includes"}
</head>
<body>
{include="page.header"}
You body goes here...
{include="page.footer"}
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="picwall"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="container content-container">
{if="count($linksToDisplay) === 0 && $is_logged_in"}
<div class="alert alert-warning text-center">
{'There is no cached thumbnail.'|t}
<a href="{$base_path}/admin/thumbnails">{'Try to synchronize them.'|t}</a>
</div>
{/if}
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="picwall-container">
{loop="$linksToDisplay"}
<div class="picwall-pictureframe">
<img src="{$root_path}/{$value.thumbnail}#"
loading="lazy"
alt="{$value.title}"
style="width: 100%; height: 100%; object-fit: cover;" />
<div class="picwall-overlay" style="position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.7); padding: 0.5rem; transform: translateY(100%); transition: transform 0.2s ease;">
<a class="picwall-link" href="{$value.real_url}" style="color: white; font-size: 0.9rem; text-decoration: none; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
{$value.title}
</a>
</div>
{loop="$value.picwall_plugin"}
{$value}
{/loop}
</div>
{/loop}
</div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
<style>
.picwall-pictureframe:hover .picwall-overlay {
transform: translateY(0);
}
</style>
{include="page.footer"}
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="pluginsadmin"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="pluginadmindiv" class="page-pluginadmin container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<noscript>
<p>{'You need to enable Javascript to change plugin loading order.'|t}</p>
</noscript>
<h1>{'Plugin administration'|t}</h1>
<p>Drag and drop your plugin to change the order in which they'll be called. Uncheck enabled plugin to disable them and vice-versa.</p>
<form action="{$base_path}/admin/plugins" method="post">
<input type="hidden" name="token" value="{$token}">
<div class="card">
<div class="card-header">{'Enabled Plugins'|t}</div>
{if="count($enabledPlugins)==0"}
<div class="card-body">
<p class="text-center text-muted">{'No plugin enabled.'|t}</p>
</div>
{else}
<ul id="list-plugin-enabled" class="list-group list-sortable list-checkable" style="margin:0; padding:0; list-style:none;">
{loop="$enabledPlugins"}
<li class="list-group-item list-item-sortable" data-line="{$key}" data-order="{$counter}">
<div class="checkbox-wrapper">
<input type="checkbox" name="{$key}" id="checkbox-{$key}" checked style="width:auto;">
</div>
<input type="hidden" name="order_{$key}" value="{$counter}">
<div class="list-group-item-content">
<label for="checkbox-{$key}" class="list-group-item-label" style="cursor:pointer; margin:0;">{function="str_replace('_', ' ', $key)"}</label>
<div class="list-group-item-sublabel">{$value.description}</div>
</div>
<div class="list-sortable-handle mdi mdi-drag"></div>
</li>
{/loop}
</ul>
{/if}
</div>
<div class="card">
<div class="card-header">{'Disabled Plugins'|t}</div>
{if="count($disabledPlugins)==0"}
<div class="card-body">
<p class="text-center text-muted">{'No plugin disabled.'|t}</p>
</div>
{else}
<ul class="list-group list-sortable list-checkable" style="margin:0; padding:0; list-style:none;">
{loop="$disabledPlugins"}
<li class="list-group-item list-item-sortable" data-line="{$key}" data-order="{$counter}">
<div class="checkbox-wrapper">
<input type="checkbox" id="checkbox-{$key}" name="{$key}" style="width:auto;">
</div>
<div class="list-group-item-content">
<label for="checkbox-{$key}" class="list-group-item-label" style="cursor:pointer; margin:0;">{function="str_replace('_', ' ', $key)"}</label>
<div class="list-group-item-sublabel">{$value.description}</div>
</div>
</li>
{/loop}
</ul>
{/if}
</div>
<div class="card" style="background:transparent; border:none; box-shadow:none; margin-top:-1rem;">
<div class="card-body" style="padding:0; display:flex; justify-content:space-between; align-items:center;">
<p class="text-muted" style="margin:0; font-size:0.9rem;">
{"More plugins available"|t}
<a href="{$root_path}/doc/html/Community-&-Related-software/#third-party-plugins" target="_blank" rel="noopener">{"in the documentation"|t}</a>.
</p>
<button type="submit" class="btn btn-primary">{'Save plugins'|t}</button>
</div>
</div>
</form>
<form action="{$base_path}/admin/plugins" method="post" style="margin-top:2rem;">
<input type="hidden" name="token" value="{$token}">
<div class="card">
<div class="card-header">{'Plugin Parameters'|t}</div>
<div class="card-body">
{if="count($enabledPlugins)==0"}
<p class="text-center text-muted">{'No plugin enabled.'|t}</p>
{else}
{$count=0}
{loop="$enabledPlugins"}
{if="count($value.parameters) > 0"}
{if="$count>0"}
<hr style="margin: 1.5rem 0; border-top: 1px solid var(--border);">
{/if}
{$count=$count+1}
<h3 style="font-size:1.1rem; margin:0 0 1rem 0; color:var(--primary);">{function="str_replace('_', ' ', $key)"}</h3>
{loop="$value.parameters"}
<div class="form-group row">
<div class="col-sm-4">
<label class="form-label" for="{$key}" {if="isset($value.desc)"}title="{$value.desc}"{/if}>{function="str_replace('_', ' ', $key)"}</label>
{if="isset($value.desc)"}<div class="sublabel">{$value.desc}</div>{/if}
</div>
<div class="col-sm-8">
<input type="text" class="form-control" name="{$key}" value="{$value.value}" id="{$key}" {if="isset($value.desc)"}placeholder="{$value.desc}"{/if}>
</div>
</div>
{/loop}
{/if}
{/loop}
{if="$count==0"}
<p class="text-center text-muted">{'No parameter available.'|t}</p>
{/if}
{/if}
</div>
<div class="card-footer">
<button type="submit" name="parameters_form" class="btn btn-primary">{'Save parameters'|t}</button>
</div>
</div>
</form>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+115
View File
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="server"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="page-server container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1>{'Server administration'|t}</h1>
<div class="card">
<div class="card-header">{'General'|t}</div>
<div class="card-body">
<div class="key-value-list">
<div class="key-value-item">
<div class="key-value-label">{'Index URL'|t}</div>
<div class="key-value-data"><a href="{$index_url}" title="{$pagetitle}">{$index_url}</a></div>
</div>
<div class="key-value-item">
<div class="key-value-label">{'Base path'|t}</div>
<div class="key-value-data">{$base_path}</div>
</div>
<div class="key-value-item">
<div class="key-value-label">{'Client IP'|t}</div>
<div class="key-value-data">{$client_ip}</div>
</div>
<div class="key-value-item">
<div class="key-value-label">{'Trusted reverse proxies'|t}</div>
<div class="key-value-data">
{if="count($trusted_proxies) > 0"}
<ul style="margin: 0; padding: 0; list-style: none;">
{loop="$trusted_proxies"}
<li>{$value}</li>
{/loop}
</ul>
{else}
{'N/A'|t}
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{include="server.requirements"}
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Version'|t}</div>
<div class="card-body">
<div class="key-value-list">
<div class="key-value-item">
<div class="key-value-label">{'Current version'|t}</div>
<div class="key-value-data">{$current_version}</div>
</div>
<div class="key-value-item">
<div class="key-value-label">{'Latest release'|t}</div>
<div class="key-value-data"><a href="{$release_url}" title="{'Visit releases page on Github'|t}" target="_blank" rel="noopener">{$latest_version}</a></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Thumbnails'|t}</div>
<div class="card-body">
<div class="key-value-list">
<div class="key-value-item">
<div class="key-value-label">{'Thumbnails status'|t}</div>
<div class="key-value-data">
{if="$thumbnails_mode==='all'"}
<span class="text-success">{'All'|t}</span>
{elseif="$thumbnails_mode==='common'"}
<span class="text-success">{'Only common media hosts'|t}</span>
{else}
<span class="text-muted">{'None'|t}</span>
{/if}
</div>
</div>
</div>
</div>
{if="$thumbnails_mode!=='none'"}
<div class="card-footer">
<a href="{$base_path}/admin/thumbnails" title="{'Synchronize all link thumbnails'|t}" class="btn btn-primary">{'Synchronize thumbnails'|t}</a>
</div>
{/if}
</div>
</div>
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Cache'|t}</div>
<div class="card-footer">
<a href="{$base_path}/admin/clear-cache?type=main" class="btn btn-secondary">{'Clear main cache'|t}</a>
<a href="{$base_path}/admin/clear-cache?type=thumbnails" class="btn btn-secondary">{'Clear thumbnails cache'|t}</a>
</div>
</div>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Permissions'|t}</div>
<div class="card-body">
{if="count($permissions) > 0"}
<p><i class="mdi mdi-close text-error"></i> {'There are permissions that need to be fixed.'|t}</p>
<ul style="padding-left: 1.5rem;">
{loop="$permissions"}
<li>{$value}</li>
{/loop}
</ul>
{else}
<p class="text-success" style="margin: 0;"><i class="mdi mdi-check"></i> {'All read/write permissions are properly set.'|t}</p>
{/if}
</div>
</div>
</div>
<div class="col-md-10 col-md-offset-1">
<div class="card">
<div class="card-header">PHP</div>
<div class="card-body">
<p style="margin-bottom: 1.5rem;">
<strong>{'Running PHP'|t} {$php_version}</strong>
{if="$php_has_reached_eol"}
<span class="text-warning"><i class="mdi mdi-circle"></i> {'End of life: '|t} {$php_eol}</span>
{else}
<span class="text-success"><i class="mdi mdi-circle"></i></span>
{/if}
</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{'Extension'|t}</th>
<th>{'Usage'|t}</th>
<th>{'Status'|t}</th>
<th class="text-center">{'Loaded'|t}</th>
</tr>
</thead>
<tbody>
{loop="$php_extensions"}
<tr>
<td><strong>{$value.name}</strong></td>
<td>{$value.desc}</td>
<td>{$value.required ? t('Required') : t('Optional')}</td>
<td class="text-center">
{if="$value.loaded"}
<i class="mdi mdi-check-circle text-success" title="{'Loaded'|t}"></i>
{else}
{if="$value.required"}
<i class="mdi mdi-close-circle text-error" title="{'Not loaded'|t}"></i>
{else}
<i class="mdi mdi-alert-circle text-warning" title="{'Not loaded'|t}"></i>
{/if}
{/if}
</td>
</tr>
{/loop}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tag.cloud"}
{include="includes"}
</head>
<body>
{include="page.header"}
{include="tag.sort"}
<div class="container page-tag-cloud">
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="card">
<div class="card-header">{'Tag Cloud'|t}</div>
<div class="card-body">
<div id="cloudtag" class="tag-cloud">
{loop="$tags"}
<a href="{$base_path}/?searchtags={$tags_url.$key1}{$tags_separator|urlencode}{$search_tags_url}" style="font-size:{$value.size}em;" class="cloud-tag">{$key}</a>
<span class="count-badge">{$value.count}</span>
{loop="$value.tag_plugin"}
{$value}
{/loop}
{/loop}
</div>
</div>
</div>
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
<style>
.tag-cloud {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: baseline;
gap: 0.5rem 0.25rem;
line-height: 1.5;
}
.cloud-tag {
color: var(--primary);
text-decoration: none;
transition: color 0.2s;
}
.cloud-tag:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.count-badge {
font-size: 0.7em;
color: var(--text-muted);
vertical-align: super;
margin-right: 0.5rem;
}
</style>
{include="page.footer"}
</body>
</html>
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tag.list"}
{include="includes"}
</head>
<body>
{include="page.header"}
{include="tag.sort"}
<div class="container">
{$countTags=count($tags)}
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="taglist" class="card">
<div class="card-header">
<div class="pull-right" style="font-size: 0.9rem;">
<a href="{$base_path}/?searchtags={$search_tags|urlencode}" title="{'List all links with those tags'|t}">{$countTags} {'tags'|t}</a>
</div>
{'Tag list'|t}
</div>
<div class="card-body" style="padding-bottom: 0;">
<form class="card-search" method="get">
<input type="hidden" name="do" value="taglist">
<div class="form-group">
<input type="search" name="searchtags" class="form-control" placeholder="{'Filter by tag'|t}..."
{if="!empty($search_tags)"}
value="{$search_tags}"
{/if}
autocomplete="off" data-multiple data-autofirst data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}"
>
</div>
</form>
</div>
<div class="list-group list-group-flush">
{loop="tags"}
<div class="list-group-item">
<div class="list-group-item-content" style="display: flex; align-items: center; justify-content: space-between;">
<div style="flex: 1;">
<a href="{$base_path}/?searchtags={$key|urlencode} {$search_tags|urlencode}" class="tag-link" style="font-weight: 500; font-size: 1rem;">{$key}</a>
{loop="$value.tag_plugin"}
{$value}
{/loop}
</div>
<div style="margin-right: 1rem;">
<span class="badge" style="background: var(--border-light); padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.8rem;">{$value}</span>
</div>
</div>
<div class="list-group-item-action">
<a href="{$base_path}/add-tag/{$key|urlencode}" title="{'Add tag'|t} {$key} {'to the filters'|t}" class="btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
<i class="mdi mdi-magnify-plus-outline"></i>
</a>
{if="$is_logged_in"}
<a href="{$base_path}/admin/tags?fromtag={$key|urlencode}" title="{'Rename tag'|t} {$key}" data-tag="{$key}" class="rename-tag btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
<i class="mdi mdi-pencil"></i>
</a>
<a href="#" class="delete-tag btn btn-danger btn-sm" data-tag="{$key}" title="{'Delete tag'|t} {$key}" style="padding: 0.25rem 0.5rem;">
<i class="mdi mdi-delete"></i>
</a>
{/if}
</div>
</div>
{/loop}
</div>
</div>
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>
{if="$is_logged_in"}
<input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}">
{/if}
{include="page.footer"}
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<div class="subheader" style="margin-bottom: 1.5rem;">
<div class="container text-center">
<div class="btn-group" role="group" aria-label="Tag View">
<a class="btn {if="$pageName === 'tag.cloud'"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/tags/cloud" title="{'Cloud'|t}">{'Cloud'|t}</a>
<a class="btn {if="$pageName === 'tag.list' && $_GET['sort'] === 'usage'"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/tags/list?sort=usage" title="{'Most used'|t}">{'Most used'|t}</a>
<a class="btn {if="$pageName === 'tag.list' && $_GET['sort'] === 'alpha'"}btn-primary{else}btn-secondary{/if}" href="{$base_path}/tags/list?sort=alpha" title="{'Alphabetical'|t}">{'Alphabetical'|t}</a>
</div>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
<?php
$theme_info = array(
'name' => 'Professional',
'version' => '1.0.0',
'author' => 'Antigravity',
'description' => 'A professional, modern theme with specific light and dark modes.',
'copyright' => '2026'
);
?>
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="thumbnails"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div class="page-thumbnails container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="card-header">{'Thumbnails update'|t}</div>
<div class="card-body">
<div class="thumbnail-placeholder" style="width: {$thumbnails_width}px; height: {$thumbnails_height}px;">
</div>
<div class="thumbnail-link-title">
Loading...
</div>
<div class="progress-bar">
<div class="progress-actual"></div>
</div>
<div class="progress-counter">
<span class="progress-current">0</span> / <span class="progress-total">{$ids|count}</span>
</div>
<input type="hidden" name="ids" value="{function="implode(',', $ids)"}" />
</div>
</div>
</div>
</div>
</div>
{include="page.footer"}
</body>
</html>
+193
View File
@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
<head>
{$pageName="tools"}
{include="includes"}
</head>
<body>
{include="page.header"}
<div id="toolsdiv" class="page-tools container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<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>
<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>
</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>
<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>
</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>
<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>
</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>
<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>
</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>
<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>
</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>
<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>
</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>
<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>
</a>
</div>
</div>
</div>
</div>
{if="!empty($linkcount)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">{'Statistics'|t}</div>
<div class="card-body">
<div class="key-value-list">
<div class="key-value-item">
<div class="key-value-label">Total Links</div>
<div class="key-value-data">{$linkcount}</div>
</div>
<div class="key-value-item">
<div class="key-value-label">Private Links</div>
<div class="key-value-data">{$privateLinkcount}</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/if}
{if="isset($tools_plugin)"}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">Plugin settings</div>
<div class="card-body">
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
</div>
</div>
</div>
{/if}
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="card">
<div class="card-header">Bookmarklet</div>
<div class="card-body">
<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)+'&amp;title='%20+%20encodeURIComponent(title)+'&amp;description='%20+%20encodeURIComponent(desc)+'&amp;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&amp;post='+'&amp;description='%20+%20encodeURIComponent(desc)+'&amp;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>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<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;">
<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
</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
</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
</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
</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
</a>
</div>
<p class="text-center text-muted" style="margin-top: 1.5rem;">
Other <a href="https://shaarli.readthedocs.io/en/master/Community-and-related-software.html" target="_blank">{'Community and related software'|t}</a>.
</p>
</div>
</div>
</div>
</div>
{loop="$tools_plugin"}
{$value}
{/loop}
</div>
{include="page.footer"}
</body>
</html>