Mudanças entre as edições de "Widget:C.Background"
Ir para navegação
Ir para pesquisar
m |
m |
||
| Linha 95: | Linha 95: | ||
request.onsuccess = function () { | request.onsuccess = function () { | ||
var | var entry = request.result; | ||
if ( | if (entry && entry.blob instanceof Blob) { | ||
resolve(entry); | |||
resolve( | } else if (entry && entry instanceof Blob) { | ||
resolve({ blob: entry, meta: {}, cachedAt: 0 }); | |||
} else { | } else { | ||
resolve(null); | resolve(null); | ||
| Linha 119: | Linha 120: | ||
* @param {Blob} blob - Blob da imagem | * @param {Blob} blob - Blob da imagem | ||
*/ | */ | ||
function set(url, blob) { | function set(url, blob, meta) { | ||
initDB() | initDB() | ||
.then(function (database) { | .then(function (database) { | ||
var transaction = database.transaction([STORE_NAME], "readwrite"); | var transaction = database.transaction([STORE_NAME], "readwrite"); | ||
var store = transaction.objectStore(STORE_NAME); | var store = transaction.objectStore(STORE_NAME); | ||
store.put(blob, url); | store.put( | ||
{ | |||
blob: blob, | |||
meta: meta || {}, | |||
cachedAt: Date.now(), | |||
}, | |||
url | |||
); | |||
}) | }) | ||
.catch(function () { | .catch(function () { | ||
// Falha silenciosa - cache opcional | // Falha silenciosa - cache opcional | ||
}); | }); | ||
} | |||
function extractMeta(response) { | |||
return { | |||
etag: | |||
response.headers.get("ETag") || | |||
response.headers.get("Etag") || | |||
"", | |||
lastModified: response.headers.get("Last-Modified") || "", | |||
}; | |||
} | |||
function metaMatches(a, b) { | |||
if (!a || !b) return false; | |||
if (a.etag && b.etag) return a.etag === b.etag; | |||
if (a.lastModified && b.lastModified) | |||
return a.lastModified === b.lastModified; | |||
return false; | |||
} | |||
function headMeta(url) { | |||
return fetch(url, { method: "HEAD", cache: "no-store" }).then(function ( | |||
response | |||
) { | |||
if (!response.ok) { | |||
throw new Error("HEAD falhou: " + response.status); | |||
} | |||
return extractMeta(response); | |||
}); | |||
} | |||
function fetchBlob(url) { | |||
return fetch(url, { mode: "cors", cache: "no-store" }).then(function ( | |||
response | |||
) { | |||
if (!response.ok) { | |||
throw new Error( | |||
"Falha ao carregar imagem: " + response.status | |||
); | |||
} | |||
var meta = extractMeta(response); | |||
return response.blob().then(function (blob) { | |||
return { blob: blob, meta: meta }; | |||
}); | |||
}); | |||
} | } | ||
| Linha 138: | Linha 191: | ||
function loadImage(url) { | function loadImage(url) { | ||
return get(url) | return get(url) | ||
.then(function ( | .then(function (cachedEntry) { | ||
if ( | if (cachedEntry && cachedEntry.blob instanceof Blob) { | ||
return | return headMeta(url) | ||
.then(function (remoteMeta) { | |||
var hasRemoteMeta = | |||
remoteMeta && | |||
(remoteMeta.etag || remoteMeta.lastModified); | |||
if (hasRemoteMeta && metaMatches(cachedEntry.meta, remoteMeta)) { | |||
return URL.createObjectURL(cachedEntry.blob); | |||
} | |||
return fetchBlob(url).then(function (fresh) { | |||
set(url, fresh.blob, fresh.meta); | |||
return URL.createObjectURL(fresh.blob); | |||
}); | |||
}) | |||
.catch(function () { | |||
return URL.createObjectURL(cachedEntry.blob); | |||
}); | |||
} | } | ||
// Carrega a imagem e salva no cache | // Carrega a imagem e salva no cache | ||
return | return fetchBlob(url).then(function (fresh) { | ||
set(url, fresh.blob, fresh.meta); | |||
return URL.createObjectURL(fresh.blob); | |||
}); | |||
}) | }) | ||
.catch(function () { | .catch(function () { | ||
Edição das 22h00min de 23 de janeiro de 2026
<script>
/**
* @typedef {Object} MediaWiki
* @property {Object} util
* @property {Function} util.wikiScript
* @property {Function} util.wikiUrlencode
* @property {Object} config
* @property {Function} config.get
*/
// @ts-ignore - MediaWiki global
var mw = window.mw || {};
(function () {
/**
* Builds a MediaWiki file URL from a filename
* @param {string} fileName - The filename (with or without "Arquivo:" prefix)
* @returns {string} The full URL to the file
*/
function buildBgURL(fileName) {
if (!fileName) return "";
var base =
window.mw && mw.util && typeof mw.util.wikiScript === "function"
? mw.util.wikiScript()
: window.mw && mw.config
? mw.config.get("wgScript") || "/index.php"
: "/index.php";
var path =
"Especial:FilePath/" + fileName.replace(/^Arquivo:|^File:/, "");
if (window.mw && mw.util && typeof mw.util.wikiUrlencode === "function") {
return base + "?title=" + mw.util.wikiUrlencode(path);
} else {
return base + "?title=" + encodeURIComponent(path).replace(/%2F/g, "/");
}
}
/**
* Sistema de cache persistente usando IndexedDB
*/
var BgCache = (function () {
var DB_NAME = "character-bg-cache";
var DB_VERSION = 1;
var STORE_NAME = "backgrounds";
var db = null;
/**
* Inicializa o banco de dados IndexedDB
*/
function initDB() {
return new Promise(function (resolve, reject) {
if (db) {
resolve(db);
return;
}
if (!window.indexedDB) {
reject(new Error("IndexedDB não disponível"));
return;
}
var request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = function () {
reject(request.error);
};
request.onsuccess = function () {
db = request.result;
resolve(db);
};
request.onupgradeneeded = function (event) {
var database = event.target.result;
if (!database.objectStoreNames.contains(STORE_NAME)) {
database.createObjectStore(STORE_NAME);
}
};
});
}
/**
* Obtém imagem do cache
* @param {string} url - URL da imagem
* @returns {Promise<string|null>} URL do blob ou null se não estiver em cache
*/
function get(url) {
return initDB()
.then(function (database) {
return new Promise(function (resolve, reject) {
var transaction = database.transaction([STORE_NAME], "readonly");
var store = transaction.objectStore(STORE_NAME);
var request = store.get(url);
request.onsuccess = function () {
var entry = request.result;
if (entry && entry.blob instanceof Blob) {
resolve(entry);
} else if (entry && entry instanceof Blob) {
resolve({ blob: entry, meta: {}, cachedAt: 0 });
} else {
resolve(null);
}
};
request.onerror = function () {
reject(request.error);
};
});
})
.catch(function () {
return Promise.resolve(null);
});
}
/**
* Salva imagem no cache
* @param {string} url - URL da imagem
* @param {Blob} blob - Blob da imagem
*/
function set(url, blob, meta) {
initDB()
.then(function (database) {
var transaction = database.transaction([STORE_NAME], "readwrite");
var store = transaction.objectStore(STORE_NAME);
store.put(
{
blob: blob,
meta: meta || {},
cachedAt: Date.now(),
},
url
);
})
.catch(function () {
// Falha silenciosa - cache opcional
});
}
function extractMeta(response) {
return {
etag:
response.headers.get("ETag") ||
response.headers.get("Etag") ||
"",
lastModified: response.headers.get("Last-Modified") || "",
};
}
function metaMatches(a, b) {
if (!a || !b) return false;
if (a.etag && b.etag) return a.etag === b.etag;
if (a.lastModified && b.lastModified)
return a.lastModified === b.lastModified;
return false;
}
function headMeta(url) {
return fetch(url, { method: "HEAD", cache: "no-store" }).then(function (
response
) {
if (!response.ok) {
throw new Error("HEAD falhou: " + response.status);
}
return extractMeta(response);
});
}
function fetchBlob(url) {
return fetch(url, { mode: "cors", cache: "no-store" }).then(function (
response
) {
if (!response.ok) {
throw new Error(
"Falha ao carregar imagem: " + response.status
);
}
var meta = extractMeta(response);
return response.blob().then(function (blob) {
return { blob: blob, meta: meta };
});
});
}
/**
* Carrega imagem e aplica cache
* @param {string} url - URL da imagem
* @returns {Promise<string>} URL do blob (do cache ou recém-carregada)
*/
function loadImage(url) {
return get(url)
.then(function (cachedEntry) {
if (cachedEntry && cachedEntry.blob instanceof Blob) {
return headMeta(url)
.then(function (remoteMeta) {
var hasRemoteMeta =
remoteMeta &&
(remoteMeta.etag || remoteMeta.lastModified);
if (hasRemoteMeta && metaMatches(cachedEntry.meta, remoteMeta)) {
return URL.createObjectURL(cachedEntry.blob);
}
return fetchBlob(url).then(function (fresh) {
set(url, fresh.blob, fresh.meta);
return URL.createObjectURL(fresh.blob);
});
})
.catch(function () {
return URL.createObjectURL(cachedEntry.blob);
});
}
// Carrega a imagem e salva no cache
return fetchBlob(url).then(function (fresh) {
set(url, fresh.blob, fresh.meta);
return URL.createObjectURL(fresh.blob);
});
})
.catch(function () {
// Em caso de erro, retorna a URL original
return Promise.resolve(url);
});
}
return {
loadImage: loadImage,
};
})();
/**
* Applies background image to an element (com cache persistente)
* @param {HTMLElement} el - The element to apply background to
*/
function applyBg(el) {
try {
var url = el.getAttribute("data-bg-url");
if (!url) {
var f = el.getAttribute("data-bg-file");
if (f) {
url = buildBgURL(f);
el.setAttribute("data-bg-url", url);
}
}
if (url) {
// Tenta carregar do cache primeiro
BgCache.loadImage(url)
.then(function (blobUrl) {
el.style.backgroundImage = 'url("' + blobUrl + '")';
// Remove o atributo temporário se existir
el.removeAttribute("data-bg-loading");
})
.catch(function () {
// Fallback para URL original em caso de erro
el.style.backgroundImage = 'url("' + url + '")';
});
// Aplica URL original imediatamente enquanto carrega do cache (progressive enhancement)
if (!el.hasAttribute("data-bg-loading")) {
el.setAttribute("data-bg-loading", "1");
el.style.backgroundImage = 'url("' + url + '")';
}
}
} catch (e) {
/* no-op */
}
}
// Aplicar backgrounds imediatamente
document.querySelectorAll("[data-bg-url], [data-bg-file]").forEach(applyBg);
// Apply to future elements (AJAX)
new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.type === "childList") {
m.addedNodes.forEach(function (n) {
if (n.nodeType === 1) {
if (
n.hasAttribute &&
(n.hasAttribute("data-bg-file") ||
n.hasAttribute("data-bg-url"))
) {
applyBg(n);
}
if (n.querySelectorAll) {
n.querySelectorAll("[data-bg-file], [data-bg-url]").forEach(
applyBg
);
}
}
});
} else if (m.type === "attributes" && m.target) {
applyBg(m.target);
}
});
}).observe(document.body, {
attributes: true,
attributeFilter: ["data-bg-file", "data-bg-url"],
childList: true,
subtree: true,
});
})();
</script>