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.
This commit is contained in:
+87
-45
@@ -124,7 +124,7 @@
|
||||
<div class="card-body">
|
||||
<div class="themes-info">
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
<span>{'Choose your preferred visual theme. The theme configuration will be synchronized across your devices via the shaarit_config bookmark.'|t}</span>
|
||||
<span>{'Choose your preferred visual theme. The theme configuration is saved locally in your browser.'|t}</span>
|
||||
</div>
|
||||
|
||||
<div class="themes-grid" id="themes-grid">
|
||||
@@ -200,11 +200,11 @@
|
||||
<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-label">{'Total Links'|t}</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-label">{'Private Links'|t}</div>
|
||||
<div class="key-value-data">{$privateLinkcount}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,7 +218,7 @@
|
||||
<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-header">{'Plugin settings'|t}</div>
|
||||
<div class="card-body">
|
||||
{loop="$tools_plugin"}
|
||||
{$value}
|
||||
@@ -232,10 +232,10 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="card">
|
||||
<div class="card-header">Bookmarklet</div>
|
||||
<div class="card-header">{'Bookmarklet'|t}</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.
|
||||
{'Drag these buttons to your bookmarks bar to quickly add links from any page.'|t}
|
||||
</p>
|
||||
<div class="bookmarklet-actions">
|
||||
<a class="btn btn-primary" href="javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;var%20desc=document.getSelection().toString();if(desc.length>4000){desc=desc.substr(0,4000)+'...';alert('{function="str_replace(' ', '%20', t('The selected text is too long, it will be truncated.'))"}');}window.open('{$pageabsaddr}admin/shaare?post='%20+%20encodeURIComponent(url)+'&title='%20+%20encodeURIComponent(title)+'&description='%20+%20encodeURIComponent(desc)+'&source=bookmarklet','_blank','menubar=no,height=800,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();">
|
||||
@@ -280,9 +280,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loop="$tools_plugin"}
|
||||
{$value}
|
||||
{/loop}
|
||||
</div>
|
||||
{include="page.footer"}
|
||||
|
||||
@@ -606,19 +603,34 @@
|
||||
if (!container) return;
|
||||
|
||||
const activeTheme = getActiveTheme();
|
||||
|
||||
container.innerHTML = THEMES.map(theme => `
|
||||
<div class="theme-item ${activeTheme === theme.id ? 'active' : ''}" data-theme-id="${theme.id}" data-has-light="${theme.hasLight}">
|
||||
<div class="theme-preview" style="--preview-primary: ${theme.color}; --preview-bg: ${theme.bg}; --preview-surface: ${theme.surface};">
|
||||
<div class="theme-preview-top"></div>
|
||||
<div class="theme-preview-bottom">
|
||||
<div class="theme-preview-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-name">${theme.name}</div>
|
||||
<div class="theme-desc">${theme.desc}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
container.innerHTML = '';
|
||||
|
||||
THEMES.forEach(theme => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'theme-item' + (activeTheme === theme.id ? ' active' : '');
|
||||
item.dataset.themeId = theme.id;
|
||||
item.dataset.hasLight = String(theme.hasLight);
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'theme-preview';
|
||||
preview.style.setProperty('--preview-primary', theme.color);
|
||||
preview.style.setProperty('--preview-bg', theme.bg);
|
||||
preview.style.setProperty('--preview-surface', theme.surface);
|
||||
preview.innerHTML = '<div class="theme-preview-top"></div><div class="theme-preview-bottom"><div class="theme-preview-card"></div></div>';
|
||||
|
||||
const name = document.createElement('div');
|
||||
name.className = 'theme-name';
|
||||
name.textContent = theme.name;
|
||||
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'theme-desc';
|
||||
desc.textContent = theme.desc;
|
||||
|
||||
item.appendChild(preview);
|
||||
item.appendChild(name);
|
||||
item.appendChild(desc);
|
||||
container.appendChild(item);
|
||||
});
|
||||
|
||||
container.querySelectorAll('.theme-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
@@ -739,7 +751,7 @@
|
||||
function showSaveStatus() {
|
||||
const status = document.getElementById('save-status');
|
||||
if (status) {
|
||||
status.textContent = 'Saved!';
|
||||
status.textContent = '{'Saved!'|t}';
|
||||
status.classList.add('show');
|
||||
setTimeout(() => status.classList.remove('show'), 2000);
|
||||
}
|
||||
@@ -759,21 +771,37 @@
|
||||
if (!container) return;
|
||||
|
||||
const hiddenTags = getHiddenTags();
|
||||
container.innerHTML = PRESET_TAGS.map(tag => {
|
||||
container.innerHTML = '';
|
||||
|
||||
PRESET_TAGS.forEach(tag => {
|
||||
const isChecked = hiddenTags.some(ht => ht.toLowerCase() === tag.name.toLowerCase());
|
||||
return `
|
||||
<div class="preset-tag-item">
|
||||
<input type="checkbox" class="preset-tag-checkbox"
|
||||
data-tag="${tag.name}" ${isChecked ? 'checked' : ''}>
|
||||
<div class="preset-tag-info">
|
||||
<div class="preset-tag-name">${tag.name}</div>
|
||||
<div class="preset-tag-desc">${tag.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
const item = document.createElement('div');
|
||||
item.className = 'preset-tag-item';
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'preset-tag-checkbox';
|
||||
checkbox.dataset.tag = tag.name;
|
||||
checkbox.checked = isChecked;
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'preset-tag-info';
|
||||
|
||||
const name = document.createElement('div');
|
||||
name.className = 'preset-tag-name';
|
||||
name.textContent = tag.name;
|
||||
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'preset-tag-desc';
|
||||
desc.textContent = tag.desc;
|
||||
|
||||
info.appendChild(name);
|
||||
info.appendChild(desc);
|
||||
item.appendChild(checkbox);
|
||||
item.appendChild(info);
|
||||
container.appendChild(item);
|
||||
});
|
||||
|
||||
// Add event listeners
|
||||
container.querySelectorAll('.preset-tag-checkbox').forEach(cb => {
|
||||
cb.addEventListener('change', function() {
|
||||
const tagName = this.dataset.tag;
|
||||
@@ -794,23 +822,37 @@
|
||||
if (!container) return;
|
||||
|
||||
const hiddenTags = getHiddenTags();
|
||||
const customTags = hiddenTags.filter(tag =>
|
||||
const customTags = hiddenTags.filter(tag =>
|
||||
!PRESET_TAGS.some(pt => pt.name.toLowerCase() === tag.toLowerCase())
|
||||
);
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
if (customTags.length === 0) {
|
||||
container.innerHTML = '<div class="no-custom-tags">No custom hidden tags</div>';
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'no-custom-tags';
|
||||
empty.textContent = '{'No custom hidden tags'|t}';
|
||||
container.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = customTags.map(tag => `
|
||||
<div class="custom-tag-chip">
|
||||
<span>${tag}</span>
|
||||
<span class="remove-tag" data-tag="${tag}">×</span>
|
||||
</div>
|
||||
`).join('');
|
||||
customTags.forEach(tag => {
|
||||
const chip = document.createElement('div');
|
||||
chip.className = 'custom-tag-chip';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = tag;
|
||||
|
||||
const remove = document.createElement('span');
|
||||
remove.className = 'remove-tag';
|
||||
remove.dataset.tag = tag;
|
||||
remove.textContent = '\u00D7';
|
||||
|
||||
chip.appendChild(label);
|
||||
chip.appendChild(remove);
|
||||
container.appendChild(chip);
|
||||
});
|
||||
|
||||
// Add remove listeners
|
||||
container.querySelectorAll('.remove-tag').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const tagName = this.dataset.tag;
|
||||
|
||||
Reference in New Issue
Block a user