feat: améliorer styles dark mode avec background unifié #20293a, bordures notes/cards (1px solid #000 light, #dedfe2 dark), width input 600px→800px, ajout bouton pin corner absolu (top-right, hover scale 1.06), suppression backgrounds/borders/outlines inputs (transparent + !important), nettoyage code dupliqué note-input-collapsed, styles todo-draft-row transparent, et corrections mineures formatage CSS (espaces, indentation)
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* shaarit-rules.js
|
||||
*
|
||||
* Règles métier partagées avec l'application Android ShaarIt.
|
||||
* Source de vérité unique pour :
|
||||
* - la détection des notes / todos / épinglés ;
|
||||
* - les tags techniques cachés par défaut ;
|
||||
* - la détection de type de contenu (video, podcast, radio, music, article,
|
||||
* news, social, repository/dev, shopping, image, pdf, document) ;
|
||||
* - la génération d'URLs internes (notes / todos).
|
||||
*
|
||||
* Non destructif : toutes les conventions existantes du thème web continuent
|
||||
* d'être reconnues en lecture. Les règles Android viennent s'y ajouter.
|
||||
*
|
||||
* Exposé sur `window.ShaarItRules`.
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tags système (cachés par défaut côté UI)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var PRESET_SYSTEM_TAGS = [
|
||||
{ name: "note", desc: "Notes - Identifiant interne", hidden: true },
|
||||
{ name: "shaarli-note", desc: "Notes - Alias legacy", hidden: true },
|
||||
{ name: "todo", desc: "Tâches - Identifiant interne", hidden: true },
|
||||
{ name: "shaarli-todo", desc: "Tâches - Alias legacy", hidden: true },
|
||||
{ name: "shaarli-pin", desc: "Épinglé - Favoris en haut", hidden: true },
|
||||
{ name: "note-color-*", desc: "Couleurs des notes (wildcard)", hidden: true },
|
||||
{ name: "notebg-*", desc: "Fonds des notes (wildcard)", hidden: true },
|
||||
{ name: "notefilter-*", desc: "Filtres des notes (wildcard)", hidden: true },
|
||||
{ name: "font-*", desc: "Couleur de police (wildcard)", hidden: true },
|
||||
{ name: "readitlater", desc: "À lire plus tard", hidden: true },
|
||||
{ name: "brain-dump", desc: "Capture rapide d'idées", hidden: true },
|
||||
{ name: "shaarli-archive", desc: "Archivé", hidden: false },
|
||||
{ name: "shaarli-archiver",desc: "Archivé - Alias legacy", hidden: false }
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Détection d'entités (note / todo / pin) - compatibilité Android + legacy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function toLower(x) { return String(x || "").toLowerCase(); }
|
||||
|
||||
function asTagArray(tags) {
|
||||
if (Array.isArray(tags)) return tags.map(toLower);
|
||||
if (typeof tags === "string") {
|
||||
return tags.split(/[\s,|]+/).map(toLower).filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Détection d'une note.
|
||||
* Compatible avec les règles Android :
|
||||
* - URL `note://` (mais pas `note://todo-`)
|
||||
* - URL `http://shaare` / `/shaare`
|
||||
* - URL `https://shaarit.app/note/...`
|
||||
* - Tag `note`, `#note`, `shaarli-note`
|
||||
*/
|
||||
function isNote(link) {
|
||||
if (!link) return false;
|
||||
var url = String(link.url || "").trim();
|
||||
var tags = asTagArray(link.tags);
|
||||
|
||||
if (url) {
|
||||
var u = url.toLowerCase();
|
||||
if (u.indexOf("note://") === 0 && u.indexOf("note://todo-") !== 0) return true;
|
||||
if (u.indexOf("http://shaare") === 0) return true;
|
||||
if (u.indexOf("/shaare") === 0) return true;
|
||||
if (u.indexOf("https://shaarit.app/note/") === 0) return true;
|
||||
if (u.indexOf("http://shaarit.app/note/") === 0) return true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
if (tags[i] === "note" || tags[i] === "#note" || tags[i] === "shaarli-note") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Détection d'une tâche.
|
||||
* Compatible avec les règles Android :
|
||||
* - URL `note://todo-...`
|
||||
* - URL `https://shaarit.app/todo/...`
|
||||
* - URL `http://shaarli-todo` (legacy web)
|
||||
* - Tag `todo`, `#todo`, `shaarli-todo`
|
||||
*/
|
||||
function isTodo(link) {
|
||||
if (!link) return false;
|
||||
var url = String(link.url || "").trim();
|
||||
var tags = asTagArray(link.tags);
|
||||
|
||||
if (url) {
|
||||
var u = url.toLowerCase();
|
||||
if (u.indexOf("note://todo-") === 0) return true;
|
||||
if (u.indexOf("https://shaarit.app/todo/") === 0) return true;
|
||||
if (u.indexOf("http://shaarit.app/todo/") === 0) return true;
|
||||
if (u.indexOf("http://shaarli-todo") === 0) return true;
|
||||
if (u.indexOf("https://shaarli-todo") === 0) return true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
if (tags[i] === "todo" || tags[i] === "#todo" || tags[i] === "shaarli-todo") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Détection épinglé via tag `shaarli-pin`. */
|
||||
function isPinned(link) {
|
||||
if (!link) return false;
|
||||
var tags = asTagArray(link.tags);
|
||||
return tags.indexOf("shaarli-pin") !== -1;
|
||||
}
|
||||
|
||||
/** Détection archive (tag `shaarli-archive` ou legacy `shaarli-archiver`). */
|
||||
function isArchived(link) {
|
||||
if (!link) return false;
|
||||
var tags = asTagArray(link.tags);
|
||||
return tags.indexOf("shaarli-archive") !== -1 || tags.indexOf("shaarli-archiver") !== -1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Génération d'URLs internes (style Android)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function randomId() {
|
||||
// Identifiant court type base36, 12 caractères.
|
||||
if (global.crypto && global.crypto.getRandomValues) {
|
||||
var arr = new Uint8Array(9);
|
||||
global.crypto.getRandomValues(arr);
|
||||
var out = "";
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
out += ("0" + arr[i].toString(36)).slice(-2);
|
||||
}
|
||||
return out.substring(0, 12);
|
||||
}
|
||||
return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)).substring(0, 12);
|
||||
}
|
||||
|
||||
function generateNoteUrl() {
|
||||
return "https://shaarit.app/note/" + randomId();
|
||||
}
|
||||
|
||||
function generateTodoUrl() {
|
||||
return "https://shaarit.app/todo/" + randomId();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Détection de type de contenu (ContentType, règles Android)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var CONTENT_TYPES = {
|
||||
UNKNOWN: "unknown",
|
||||
ARTICLE: "article",
|
||||
VIDEO: "video",
|
||||
PODCAST: "podcast",
|
||||
IMAGE: "image",
|
||||
PDF: "pdf",
|
||||
REPOSITORY: "repository",
|
||||
DOCUMENT: "document",
|
||||
SOCIAL: "social",
|
||||
SHOPPING: "shopping",
|
||||
NEWSLETTER: "newsletter",
|
||||
MUSIC: "music",
|
||||
RADIO: "radio",
|
||||
NEWS: "news"
|
||||
};
|
||||
|
||||
// Tags auto-ajoutés par type de contenu (conformes règles Android UI).
|
||||
var CONTENT_TYPE_TAGS = {
|
||||
video: ["video"],
|
||||
podcast: ["podcast"],
|
||||
radio: ["radio"],
|
||||
music: ["music"],
|
||||
article: ["article"],
|
||||
news: ["news"],
|
||||
social: ["social"],
|
||||
repository: ["repository", "dev"],
|
||||
shopping: ["shopping"],
|
||||
newsletter: ["newsletter"],
|
||||
image: ["image"],
|
||||
pdf: ["pdf"],
|
||||
document: [],
|
||||
unknown: []
|
||||
};
|
||||
|
||||
function parseHost(url) {
|
||||
try {
|
||||
var u = new URL(url);
|
||||
return (u.hostname || "").toLowerCase();
|
||||
} catch (e) {
|
||||
// Fallback très simple si URL invalide
|
||||
var m = String(url || "").match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]+)/i);
|
||||
return m ? m[1].toLowerCase() : "";
|
||||
}
|
||||
}
|
||||
|
||||
function hostContainsAny(host, list) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (host.indexOf(list[i]) !== -1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Audio : RADIO > PODCAST > MUSIC ---
|
||||
|
||||
var RADIO_HOSTS = [
|
||||
"playerservices.streamtheworld.com", "icecast", "shoutcast",
|
||||
"fluxradios.com", "tunein.com", "radio.garden", "mytuner-radio.com",
|
||||
"iheart.com", "onlineradiobox.com", "radio.net"
|
||||
];
|
||||
|
||||
function isRadio(url, host) {
|
||||
if (/\.(m3u|m3u8|pls)(\?|$)/i.test(url)) return true;
|
||||
if (hostContainsAny(host, RADIO_HOSTS)) return true;
|
||||
if (host.indexOf("stream.") === 0 || host.indexOf("live.") === 0) return true;
|
||||
if (host.indexOf("ici.radio-canada.ca") !== -1 || host.indexOf("radio-canada.ca") !== -1) {
|
||||
if (/\/balados(\/|$)/i.test(url)) return false;
|
||||
if (/\/(direct|premiere|audio-fil)(\/|$)/i.test(url)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var PODCAST_HOSTS = [
|
||||
"podcasts.apple.com", "overcast.fm", "pocketcasts.com", "castbox.fm",
|
||||
"stitcher.com", "acast.com", "anchor.fm", "libsyn.com",
|
||||
"simplecast.com", "buzzsprout.com"
|
||||
];
|
||||
|
||||
function isPodcast(url, host) {
|
||||
if (hostContainsAny(host, PODCAST_HOSTS)) return true;
|
||||
if (/open\.spotify\.com\/(show|episode)\//i.test(url)) return true;
|
||||
if (/\/balados(\/|$)|\/ohdio\/balados(\/|$)/i.test(url)) return true;
|
||||
if (/\.(xml|rss)(\?|$)/i.test(url)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var MUSIC_HOSTS = [
|
||||
"music.apple.com", "deezer.com", "tidal.com", "music.youtube.com",
|
||||
"bandcamp.com", "soundcloud.com", "mixcloud.com", "beatport.com"
|
||||
];
|
||||
|
||||
function isMusic(url, host) {
|
||||
if (hostContainsAny(host, MUSIC_HOSTS)) return true;
|
||||
if (/open\.spotify\.com\/(track|album|artist|playlist)\//i.test(url)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Catégories web ---
|
||||
|
||||
var VIDEO_HOSTS = ["youtube.com", "youtu.be", "vimeo.com", "dailymotion.com", "twitch.tv", "netflix.com"];
|
||||
function isVideo(host) { return hostContainsAny(host, VIDEO_HOSTS); }
|
||||
|
||||
var SOCIAL_HOSTS = [
|
||||
"facebook.com", "instagram.com", "tiktok.com", "twitter.com", "x.com",
|
||||
"linkedin.com", "reddit.com", "snapchat.com", "pinterest.com", "mastodon"
|
||||
];
|
||||
function isSocial(host) { return hostContainsAny(host, SOCIAL_HOSTS); }
|
||||
|
||||
var REPO_HOSTS = ["github.com", "gitlab.com", "bitbucket.org", "stackoverflow.com"];
|
||||
function isRepository(host) { return hostContainsAny(host, REPO_HOSTS); }
|
||||
|
||||
var SHOPPING_HOSTS = ["amazon", "ebay", "etsy.com", "aliexpress.com", "shopify"];
|
||||
function isShopping(host) { return hostContainsAny(host, SHOPPING_HOSTS); }
|
||||
|
||||
var DOCUMENT_HOSTS = [
|
||||
"docs.google.com", "drive.google.com", "notion.so", "trello.com",
|
||||
"jira", "confluence"
|
||||
];
|
||||
function isDocument(host) { return hostContainsAny(host, DOCUMENT_HOSTS); }
|
||||
|
||||
var NEWS_HOSTS = [
|
||||
"news", "nytimes", "lemonde", "bbc", "cnn", "reuters",
|
||||
"theguardian", "lefigaro"
|
||||
];
|
||||
function isNews(host) { return hostContainsAny(host, NEWS_HOSTS); }
|
||||
|
||||
var NEWSLETTER_HOSTS = ["substack", "revue", "mailchimp"];
|
||||
function isNewsletter(host) { return hostContainsAny(host, NEWSLETTER_HOSTS); }
|
||||
|
||||
var IMAGE_HOSTS = ["imgur.com", "flickr.com"];
|
||||
function isImageUrl(url, host) {
|
||||
if (/\.(jpe?g|png|gif|webp|bmp|svg)(\?|$)/i.test(url)) return true;
|
||||
return hostContainsAny(host, IMAGE_HOSTS);
|
||||
}
|
||||
|
||||
function isPdfUrl(url) {
|
||||
return /\.pdf(\?|$)/i.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecte le type de contenu d'une URL (règles Android).
|
||||
* @returns {{type: string, tags: string[]}}
|
||||
*/
|
||||
function detectContentType(url) {
|
||||
var raw = String(url || "").trim();
|
||||
if (!raw) return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
|
||||
|
||||
// URLs internes : pas de détection
|
||||
var low = raw.toLowerCase();
|
||||
if (low.indexOf("note://") === 0 ||
|
||||
low.indexOf("https://shaarit.app/note/") === 0 ||
|
||||
low.indexOf("https://shaarit.app/todo/") === 0 ||
|
||||
low.indexOf("http://shaare") === 0 ||
|
||||
low.indexOf("/shaare") === 0 ||
|
||||
low.indexOf("http://shaarli-todo") === 0) {
|
||||
return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
|
||||
}
|
||||
|
||||
var host = parseHost(raw);
|
||||
|
||||
// 1. Fichiers (priorité haute sur l'extension)
|
||||
if (isPdfUrl(raw)) return { type: CONTENT_TYPES.PDF, tags: CONTENT_TYPE_TAGS.pdf };
|
||||
if (isImageUrl(raw, host))return { type: CONTENT_TYPES.IMAGE, tags: CONTENT_TYPE_TAGS.image };
|
||||
|
||||
// 2. Audio : RADIO > PODCAST > MUSIC
|
||||
if (isRadio(raw, host)) return { type: CONTENT_TYPES.RADIO, tags: CONTENT_TYPE_TAGS.radio };
|
||||
if (isPodcast(raw, host)) return { type: CONTENT_TYPES.PODCAST, tags: CONTENT_TYPE_TAGS.podcast };
|
||||
if (isMusic(raw, host)) return { type: CONTENT_TYPES.MUSIC, tags: CONTENT_TYPE_TAGS.music };
|
||||
|
||||
// 3. Vidéo
|
||||
if (isVideo(host)) return { type: CONTENT_TYPES.VIDEO, tags: CONTENT_TYPE_TAGS.video };
|
||||
|
||||
// 4. Plateformes spécifiques
|
||||
if (isRepository(host)) return { type: CONTENT_TYPES.REPOSITORY, tags: CONTENT_TYPE_TAGS.repository };
|
||||
if (isDocument(host)) return { type: CONTENT_TYPES.DOCUMENT, tags: CONTENT_TYPE_TAGS.document };
|
||||
if (isSocial(host)) return { type: CONTENT_TYPES.SOCIAL, tags: CONTENT_TYPE_TAGS.social };
|
||||
if (isShopping(host)) return { type: CONTENT_TYPES.SHOPPING, tags: CONTENT_TYPE_TAGS.shopping };
|
||||
if (isNewsletter(host)) return { type: CONTENT_TYPES.NEWSLETTER, tags: CONTENT_TYPE_TAGS.newsletter };
|
||||
if (isNews(host)) return { type: CONTENT_TYPES.NEWS, tags: CONTENT_TYPE_TAGS.news };
|
||||
|
||||
return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Fusion d'un tableau de tags existants avec les tags auto-détectés.
|
||||
* Conserve l'ordre existant, ajoute uniquement les tags manquants.
|
||||
*/
|
||||
function mergeAutoTags(existingTags, autoTags) {
|
||||
var list = Array.isArray(existingTags) ? existingTags.slice() : [];
|
||||
var lower = list.map(toLower);
|
||||
(autoTags || []).forEach(function (t) {
|
||||
var clean = String(t || "").trim();
|
||||
if (!clean) return;
|
||||
if (lower.indexOf(clean.toLowerCase()) === -1) {
|
||||
list.push(clean);
|
||||
lower.push(clean.toLowerCase());
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var api = {
|
||||
PRESET_SYSTEM_TAGS: PRESET_SYSTEM_TAGS,
|
||||
CONTENT_TYPES: CONTENT_TYPES,
|
||||
CONTENT_TYPE_TAGS: CONTENT_TYPE_TAGS,
|
||||
|
||||
isNote: isNote,
|
||||
isTodo: isTodo,
|
||||
isPinned: isPinned,
|
||||
isArchived: isArchived,
|
||||
|
||||
generateNoteUrl: generateNoteUrl,
|
||||
generateTodoUrl: generateTodoUrl,
|
||||
|
||||
detectContentType: detectContentType,
|
||||
mergeAutoTags: mergeAutoTags
|
||||
};
|
||||
|
||||
global.ShaarItRules = api;
|
||||
})(typeof window !== "undefined" ? window : this);
|
||||
Reference in New Issue
Block a user