CI / build (push) Failing after 6m2s
Introduce a Node build system for minified CSS/JS with content hashes, add PWA support, i18n helpers, accessibility improvements, and more translatable strings. Move theme configuration from bookmarks to localStorage, add Playwright tests, and set up CI workflows.
168 lines
11 KiB
HTML
168 lines
11 KiB
HTML
<!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}
|
|
{$active_search_tags=[]}
|
|
{if="!empty($search_tags)"}
|
|
{$active_search_tags=tags_str2array($search_tags, $tags_separator)}
|
|
{/if}
|
|
<!-- {* ----- toolbar ----- *} -->
|
|
<div class="content-toolbar">
|
|
<div class="toolbar-left">{include="linklist.paging"}</div>
|
|
<div class="toolbar-right">
|
|
<div class="view-toggle" role="group" aria-label="{'Change view'|t}">
|
|
<button class="view-toggle-btn active" id="view-grid-btn" aria-label="{'Grid view'|t}"
|
|
aria-pressed="true"><i class="mdi mdi-view-grid" aria-hidden="true"></i></button>
|
|
<button class="view-toggle-btn" id="view-list-btn" aria-label="{'List view'|t}"
|
|
aria-pressed="false"><i class="mdi mdi-view-list" aria-hidden="true"></i></button>
|
|
<button class="view-toggle-btn" id="view-compact-btn" aria-label="{'Compact view'|t}"
|
|
aria-pressed="false"><i class="mdi mdi-view-headline"
|
|
aria-hidden="true"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- {* ----- tag search results ----- *} -->
|
|
{if="!empty($search_tags)"}
|
|
<div class="search-results-header">
|
|
<span class="search-count">{$result_count} {'tagged result(s)'|t}</span>
|
|
{loop="$active_search_tags"}
|
|
<span class="search-tag-chip">{$value} <a href="{$base_path}/remove-tag/{function="
|
|
urlencode($value)"}" class="search-tag-close" title="{'Remove tag'|t}"
|
|
aria-label="{'Remove tag'|t} {$value}"><i class="mdi mdi-close"
|
|
aria-hidden="true"></i></a></span>
|
|
{/loop}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- {* ----- no links ----- *} -->
|
|
{if="count($links)==0"}
|
|
{if="in_array('note', $active_search_tags) || in_array('shaarli-note', $active_search_tags) || in_array('todo', $active_search_tags) || in_array('shaarli-todo', $active_search_tags) || in_array('shaarli-archive', $active_search_tags) || in_array('shaarli-archiver', $active_search_tags)"}
|
|
<div class="links-list view-grid" id="links-list"></div>
|
|
{include="linklist.paging"}
|
|
{else}
|
|
<div class="empty-state" role="status" aria-live="polite">
|
|
<div class="empty-state-icon" aria-hidden="true"><i class="mdi mdi-bookmark-off-outline"></i></div>
|
|
<h2 class="empty-state-title">{'No bookmarks found'|t}</h2>
|
|
<p class="empty-state-text">{if="!empty($search_term)"}{'No result for:'|t} <strong>{$search_term}</strong>{else}{'Start adding bookmarks to see them appear here.'|t}{/if}</p>
|
|
</div>
|
|
{/if}
|
|
{else}
|
|
<!-- {* ----- at least one link ----- *} -->
|
|
<div class="links-list view-grid" id="links-list">
|
|
<div class="pinned-section-head" aria-hidden="true"><i class="mdi mdi-pin" aria-hidden="true"></i> {'Pinned bookmarks'|t}</div>
|
|
<div class="normal-section-head" aria-hidden="true">{'Bookmarks'|t}</div>
|
|
<div class="compact-table-head" aria-hidden="true">
|
|
<span class="compact-head-title">{'Title & URL'|t}</span>
|
|
<span class="compact-head-tags">{'Tags'|t}</span>
|
|
<span class="compact-head-date">{'Date'|t}</span>
|
|
<span class="compact-head-actions">{'Actions'|t}</span>
|
|
</div>
|
|
|
|
{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}" aria-label="{'Select this bookmark'|t}"></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;"
|
|
onerror="this.parentElement.style.display='none'"></div>{/if}
|
|
<div class="link-visibility-badge">{if="$value.private"}<i class="mdi mdi-lock" title="{'Private'|t}"
|
|
aria-label="{'Private bookmark'|t}"></i>{else}<i class="mdi mdi-lock-open-variant"
|
|
title="{'Public'|t}" aria-label="{'Public bookmark'|t}"></i>{/if}</div>
|
|
<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'|t}" {/if}>
|
|
{if="isset($value.sticky) && $value.sticky"}<i class="mdi mdi-pin"
|
|
style="color:var(--primary);"
|
|
aria-hidden="true"></i>{/if}{$value.title_html}
|
|
</a>
|
|
<span class="link-url">{$value.url}</span>
|
|
<div class="link-meta">
|
|
<span class="link-date">{$value.created|format_date}</span>
|
|
<a href="{$base_path}/shaare/{$value.shorturl}" class="link-permalink"
|
|
title="{'Permalink'|t}" aria-label="{'Permalink'|t}"><i
|
|
class="mdi mdi-link-variant" aria-hidden="true"></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{if="in_array($value, $active_search_tags)"} is-search-match{/if}" data-tag="{$value}"><a
|
|
class="link-tag-link" href="{$base_path}/add-tag/{$value|urlencode}">{$value}</a>{if="$is_logged_in"}<button type="button" class="tag-remove-btn" data-tag="{$value}" aria-label="{'Delete tag'|t} {$value}" title="{'Delete'|t}">×</button>{/if}</span>{/loop}
|
|
</div>
|
|
<div class="link-actions">
|
|
{if="$is_logged_in"}
|
|
<a href="{$base_path}/admin/shaare/{$value.id}" title="{'Edit'|t}"
|
|
aria-label="{'Edit this bookmark'|t}"><i class="mdi mdi-pencil"
|
|
aria-hidden="true"></i></a>
|
|
<a href="{$base_path}/admin/shaare/{$value.id}/pin?token={$token}"
|
|
title="{'Pin/Unpin'|t}" aria-label="{'Pin or unpin'|t}"><i
|
|
class="mdi mdi-pin" aria-hidden="true"></i></a>
|
|
<a href="{$base_path}/admin/shaare/delete?id={$value.id}&token={$token}"
|
|
title="{'Delete'|t}" aria-label="{'Delete this bookmark'|t}"
|
|
onclick="return confirm('{'Delete this bookmark?'|t}');"><i
|
|
class="mdi mdi-delete" aria-hidden="true"></i></a>
|
|
<button type="button" class="readitlater-toggle-btn" data-tag="readitlater" data-active="0" title="{'Add to Read It Later'|t}" aria-label="{'Add to Read It Later'|t}">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/></svg>
|
|
</button>
|
|
{/if}
|
|
<!-- fullscreen button (in all 3 views) -->
|
|
<a href="#" class="view-desc-btn" data-id="{$value.id}"
|
|
title="{'View description'|t}" aria-label="{'View full description'|t}"><i
|
|
class="mdi mdi-fullscreen" aria-hidden="true"></i></a>
|
|
{loop="$value.link_plugin"}
|
|
<span class="link-plugin">{$value}</span>
|
|
{/loop}
|
|
<a href="{$value.real_url}" target="_blank" rel="noopener"
|
|
title="{'Open link'|t}" aria-label="{'Open link in new tab'|t}"><i
|
|
class="mdi mdi-open-in-new" aria-hidden="true"></i></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/loop}
|
|
</div>
|
|
{include="linklist.paging"}
|
|
{/if}
|
|
{loop="$plugin_end_zone"}
|
|
{$value}
|
|
{/loop}
|
|
</div>
|
|
|
|
<!-- {* ----- description modal ----- *} -->
|
|
<div id="desc-modal" class="modal-overlay" role="dialog" aria-modal="true"
|
|
aria-label="{'Bookmark description'|t}" aria-hidden="true">
|
|
<div class="modal-content">
|
|
<button class="modal-close" id="desc-modal-close" aria-label="{'Close'|t}">×</button>
|
|
<div class="modal-body" id="desc-modal-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- {* ----- QR Code Modal ----- *} -->
|
|
<div id="qrcode-modal" class="qrcode-modal-overlay" role="dialog" aria-modal="true" aria-label="{'QR Code'|t}"
|
|
aria-hidden="true">
|
|
<div class="qrcode-modal-content">
|
|
<button class="qrcode-modal-close" id="qrcode-modal-close" aria-label="{'Close'|t}">×</button>
|
|
<div id="qrcode-modal-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{include="page.footer"}
|
|
</body>
|
|
|
|
</html> |