





document.querySelector(".rate-text").style.width = "400px";
или document.querySelectorAll(".rate-text").style.width = "400px";
Есть вероятность, что это поможет... .rate-text {
word-break: break-word; /* старый тег, для совместимости */
overflow-wrap: anywhere; /* новый тег */
}
.note {
overflow-wrap: anywhere;
}
/* Окно для текста в списке аниме/манги кастомной высоты */
.b-input textarea {
min-height: 400px !important;
}
/* Для "включения" чекбоксов на моей теме сайта (у меня кастомные не грузятся) */
html body form.b-form .checkbox input[type="checkbox"] {
width: 13px !important;
height: 13px !important;
opacity: 1 !important;
position: static !important;
margin-left: 0 !important;
}
/* Главный контейнер */
#profiles_show .art-container {
display: flex;
gap: 15px; /* Расстояние между картинками */
margin-bottom: 15px; /* Отступ внизу */
}
#profiles_show .art-column {
flex: 1; /* Каждая колонка будет занимать равное кол-во места */
min-width: 0;
}
#profiles_show .art-showcase {
height: 700px; /* Высота арта */
width: 100%; /* Ширина арта */
background-size: cover;
background-position: center;
border-radius: 15px; /* Закругление углов */
transition: transform 0.3s ease;
}
/* Больше артов - копируйте и увеличивайте число (напр. art3 -> art4) */
#profiles_show .art-1 {
background-image: url();
}
#profiles_show .art-2 {
background-image: url();
}
#profiles_show .art-3 {
background-image: url();
}
[div=art-container]
[div=art-column]
[div=art-showcase art-1][/div]
[/div]
[div=art-column]
[div=art-showcase art-2][/div]
[/div]
[div=art-column]
[div=art-showcase art-3][/div]
[/div]
[/div]
[div=collapsed d-inline-flex p-1 m-0]
Flex-контейнер
[/div]
// ==UserScript==
// @name SCaWC
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Shikimori Character and Words Counter. Adds per-editor character and word counters to Shikimori editors (anime list + comments) and prevents overflow from long strings.
// @author NotSoOff
// @match https://shikimori.one/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// Simple word counting function
function countWords(text) {
if (!text || text.trim() === '') return 0;
return text.trim().split(/\s+/).length;
}
function createCounter(container) {
const counter = document.createElement("div");
Object.assign(counter.style, {
position: "absolute",
bottom: "4px",
right: "8px",
background: "transparent",
color: "rgb(var(--color-text-hint, var(--light-color, 128, 128, 128)))",
padding: "2px 6px",
borderRadius: "4px",
fontSize: "11px",
fontFamily: "monospace",
pointerEvents: "none"
});
counter.textContent = "0c • 0w";
// Ensure parent is positioned
const style = window.getComputedStyle(container);
if (style.position === "static") {
container.style.position = "relative";
}
container.appendChild(counter);
return counter;
}
function updateCounter(counter, text) {
const chars = text.length;
const words = countWords(text);
counter.textContent = `${chars}c • ${words}w`;
}
function handleTextarea(area) {
if (area.dataset.charCounterAttached) return;
area.dataset.charCounterAttached = "true";
// Prevent overflow from long unbroken strings
area.style.whiteSpace = "pre-wrap";
area.style.wordBreak = "break-word";
const container = area.closest(".b-input") || area.parentElement;
const counter = createCounter(container);
function refresh() {
updateCounter(counter, area.value);
}
["input", "keyup", "paste", "cut"].forEach(evt =>
area.addEventListener(evt, refresh)
);
refresh();
}
function handleProseMirror(editor) {
if (editor.dataset.charCounterAttached) return;
editor.dataset.charCounterAttached = "true";
// Prevent overflow from long unbroken strings
editor.style.whiteSpace = "pre-wrap";
editor.style.wordBreak = "break-word";
const container = editor.closest(".editor-container") || editor.parentElement;
const counter = createCounter(container);
function getText() {
let text = editor.innerText || "";
text = text.trim();
if (text === "" || text === "\n") return "";
return text;
}
function refresh() {
updateCounter(counter, getText());
}
["input", "keyup", "paste", "cut"].forEach(evt =>
editor.addEventListener(evt, refresh)
);
refresh();
}
function observeInputs() {
document.querySelectorAll("textarea.text.optional, textarea#reason").forEach(handleTextarea);
document.querySelectorAll(".ProseMirror").forEach(handleProseMirror);
}
observeInputs();
const observer = new MutationObserver(() => observeInputs());
observer.observe(document.body, { childList: true, subtree: true });
})();
// ==UserScript==
// @name Shikimori Various Statistic (FIX)
// @namespace http://shikimori.one/
// @version 2.0.0
// @description Adds new statistics in various places (viewing history, episode lists, chronology stats, etc.) using modern JavaScript. Revised version of Chortowod's outdated script. (https://openuserjs.org/users/Chortowod)
// @author NotSoOff
// @match *://shikimori.org/*
// @match *://shikimori.one/*
// @match *://shikimori.me/*
// @icon https://www.google.com/s2/favicons?domain=shikimori.me&sz=64
// @license MIT
// @connect api.jikan.moe
// @connect shikimori.one
// @copyright 2025, NotSoOff (https://shikimori.one/NotSoOff)
// @require https://gist.githubusercontent.com/Chortowod/814b010c68fc97e5f900df47bf79059c/raw/chtw_settings.js?v1
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
const SCRIPT_NAME = 'Shikimori Various Statistic';
const siteName = "https://shikimori.one";
const settings = new ChtwSettings('chtwVarStat', '<a target="_blank" href="https://shikimori.one/NotSoOff">Разная статистика</a>');
let config = { debug: true };
// --- Logging ---
const log = (...args) => { if (config.debug) console.log(`[${SCRIPT_NAME}]`, ...args); };
const error = (...args) => console.error(`[${SCRIPT_NAME}]`, ...args);
const warn = (...args) => console.warn(`[${SCRIPT_NAME}]`, ...args);
// --- Initialization ---
function initSettings() {
settings.createOption('history', 'История просмотра тайтла', true);
settings.createOption('avTitleFr', 'Средн. оценка тайтла друзей', true);
settings.createOption('avTitle', 'Средн. оценка тайтла', true);
settings.createOption('avProfileFr', 'Средняя оценка друга', true);
settings.createOption('allProfileFr', 'Всего тайтлов друга', true);
settings.createOption('showEpisodes', 'Показать список эпизодов', true);
settings.createOption('historyDateWidth', 'Ширина блока с датой эпизода (px)', '115', 'number');
settings.createOption('epWrapBackground', 'Цвет блока с рейтингом эпизодов', '#0000', 'color');
settings.createOption('epWrapPadding', 'Отступ слева у блока с рейтингом эпизодов', false);
settings.createOption('isDebug', 'Режим отладки', false);
config.debug = settings.getOption('isDebug');
console.log(`[${SCRIPT_NAME}] Initialized. Debug mode is ${config.debug ? 'ON' : 'OFF'}.`);
}
function initStyle() {
const dateWidth = settings.getOption('historyDateWidth') || 115;
const epWrapBackground = settings.getOption('epWrapBackground') || '#0000';
const epWrapPadding = settings.getOption('epWrapPadding') ? '10' : '0';
GM_addStyle(`
.chtw-ep-wrap { display: flex; gap: 10px; align-items: center; }
.chtw-ep-number, .chtw-ep-score { width: 40px; text-align: center; flex-shrink: 0; }
.chtwEpisodesDateWatchedWrapper .chtw-watched-body, .chtwEpisodesDateWatchedWrapper .chtw-watched-detail-content { padding-left: ${epWrapPadding}px; background: ${epWrapBackground}; }
.chtw-ep-date { width: ${dateWidth}px; flex-shrink: 0; }
.chtw-ep-title { flex-grow: 1; }
.chtw-ep-filler { color: #7e7e7e; } .chtw-ep-recap { color: #663a02; } .chtw-ep-great { color: #00d600; }
.chtw-ep-epic { color: #00d600; font-weight: bold; } .chtw-ep-good { color: #86d600; } .chtw-ep-bad { color: #e81c00; }
.chtw-watched-item { display: flex; justify-content: space-between; }
.chtw-watched-detail-content { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.3s ease-out; }
.chtw-watched-detail-content.expanded { grid-template-rows: 1fr; }
.chtw-watched-detail-content > div { overflow: hidden; }
.chtw-watched-detail-content .chtw-watched-item { flex-direction: column; align-items: center; border-bottom: 1px solid #7f7f7f38; text-align: center; padding: 5px 0; }
.chtw-watched-detail-content .chtw-watched-item:last-child { border: none; }
#chtwWatchedButtonShowAll, #chtwEpisodesWatchedButtonShowAll { color: var(--link-color); text-align: center; width: 100%; cursor: pointer; background: none; border: none; padding: 10px 0 5px; }
#chtwWatchedButtonShowAll:hover, #chtwEpisodesWatchedButtonShowAll:hover { color: var(--link-hover-color); }
.chtw-watched-detail-header { text-align: center; font-weight: bold; margin: 10px 0 8px; display: flex; justify-content: center; align-items: center; }
.chtw-watched-detail-header::before, .chtw-watched-detail-header::after { content: ""; display: block; height: 2px; min-width: 50%; }
.chtw-watched-detail-header::before { background: linear-gradient(to right, transparent, #7b8084); }
.chtw-watched-detail-header::after { background: linear-gradient(to left, transparent, #7b8084); }
#chtwEpisodesShowMoreButton { background: #1c1c1c; padding: 2px 10px; border-radius: 5px; cursor: pointer; border: none; color: white; margin-top: 10px; }
#chtwEpisodesShowMoreButton:hover { background: #3b3b3b; }
@media screen and (max-width: 768px) { .chtw-ep-title { width: 40%; } .chtw-ep-wrap { gap: 5px; } }
`);
}
// --- Utilities ---
const getLocale = () => document.body.dataset.locale;
const formatDate = (dateString) => new Date(dateString).toLocaleDateString(getLocale() === 'ru' ? 'ru-RU' : 'en-GB', { year: 'numeric', month: 'long', day: 'numeric' });
const formatTime = (minutes) => {
if (!minutes) return '---';
const hours = minutes / 60;
if (hours > 24) {
const days = hours / 24;
return days > 30 ? `${(days / 30).toFixed(2)} месяцев` : `${days.toFixed(2)} дней`;
}
return `${hours.toFixed(2)} часов`;
};
// --- Feature: Chronology Statistics ---
function calculateTime(animes) {
const stat = { minutesToWatch: 0, minutesWatched: 0, minutesWatching: 0, overall: animes.length, toWatch: [], watched: [], watching: [], notInStat: [] };
animes.forEach(anime => {
const episodes = anime.episodes || anime.episodesAired;
if (!anime.duration || !episodes) {
stat.notInStat.push(anime);
} else {
const totalMinutes = anime.duration * episodes;
if (anime.status === 'completed') { stat.minutesWatched += totalMinutes; stat.watched.push(anime); }
else if (anime.status === 'watching') { stat.minutesWatching += totalMinutes; stat.watching.push(anime); }
else { stat.minutesToWatch += totalMinutes; stat.toWatch.push(anime); }
}
});
stat.minutesOverall = stat.minutesWatched + stat.minutesToWatch + stat.minutesWatching;
return stat;
}
function appendChronologyStat(stat) {
const notInStatString = stat.notInStat.map(anime => `<p><a class="b-link bubbled-processed" style="text-decoration: none;" href="/animes/${anime.id}">${anime.russian || anime.name}</a></p>`).join('');
const parentElement = document.querySelector('.b-animes-menu');
if (!parentElement) { warn("Could not find '.b-animes-menu' to inject chronology stats."); return; }
const newStatElement = document.createElement('div');
newStatElement.className = 'block chtw-chronology-stats';
newStatElement.innerHTML = `
<div class="subheadline m8">Статистика</div>
<div><b>Всего времени</b>: ${formatTime(stat.minutesOverall)} (${stat.overall} шт.)</div>
<div><b>Просмотрено</b>: ${formatTime(stat.minutesWatched)} (${stat.watched.length} шт.)</div>
<div><b>Смотрю</b>: ${formatTime(stat.minutesWatching)} (${stat.watching.length} шт.)</div>
<div><b>Осталось</b>: ${formatTime(stat.minutesToWatch)} (${stat.toWatch.length} шт.)</div>
<div><b>Не учтено</b> (${stat.notInStat.length} шт.):</div>
<details style="cursor: pointer"><summary style="color:var(--link-color)">Показать/скрыть</summary>${notInStatString}</details>`;
parentElement.prepend(newStatElement);
log("Chronology stats appended.");
}
async function showChronologyStat() {
if (!settings.getOption('showEpisodes') || !document.body.classList.contains('p-animes-chronology') || document.querySelector('.chtw-chronology-stats')) return;
log("Running Chronology Stats");
const animeNodes = document.querySelectorAll(".b-db_entry-variant-list_item");
if (animeNodes.length === 0) return;
const animeIDs = Array.from(animeNodes, item => item.dataset.id);
const animesData = [];
try {
for (let i = 0; i < animeIDs.length; i += 50) {
const batchIDs = animeIDs.slice(i, i + 50);
const response = await fetch(`${siteName}/api/graphql`, {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: `{ animes(limit: 50, ids: "${batchIDs.join(',')}") { id name russian duration episodes episodesAired } }` })
});
if (!response.ok) throw new Error(`GraphQL fetch failed with status ${response.status}`);
const result = await response.json();
if (result.data?.animes) animesData.push(...result.data.animes);
}
log(`Fetched ${animesData.length} of ${animeIDs.length} animes for chronology.`);
animesData.forEach(anime => {
const statusInput = document.querySelector(`.b-db_entry-variant-list_item[data-id="${anime.id}"] .b-user_rate input[name="user_rate[status]"]`);
anime.status = statusInput ? statusInput.value : 'planned';
});
const stat = calculateTime(animesData);
log("Calculated Chronology Stats:", stat);
appendChronologyStat(stat);
} catch (e) {
error("Failed to fetch or process chronology stats:", e);
}
}
// --- Feature: Watch History ---
async function showWatchHistory() {
if (!settings.getOption('history') || document.getElementById('chtwDateWatchedWrapper')) return;
const path = window.location.pathname;
const pathRegex = /\/(?:animes|mangas|ranobe)\/(\d+)/;
const match = path.match(pathRegex);
// Get ID from URL
const targetId = match ? match[1] : null;
let userId = null;
// Get ID from "data" atribute in <body>
try {
const userDataString = document.body.dataset.user;
if (userDataString) {
userId = JSON.parse(userDataString).id;
}
} catch (e) {
error("Could not parse user data from body attribute:", e);
}
// Check after getting ID
if (!targetId || !userId) {
warn("Could not determine targetId from URL or userId from body.", { targetId, userId });
return;
}
log("Running Watch History for targetId:", targetId, "and userId:", userId);
try {
const targetType = path.startsWith('/animes/') ? 'Anime' : 'Manga';
const url = `${siteName}/api/users/${userId}/history?target_id=${targetId}&limit=100&target_type=${targetType}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`History fetch failed with status ${response.status}`);
const data = await response.json();
log(`Fetched ${data.length} history entries.`);
if (data.length === 0) return;
const parentBlock = document.querySelector('.b-animes-menu');
if (!parentBlock) {
warn("Could not find '.b-animes-menu' to inject history block.");
return;
}
const isRu = getLocale() === 'ru';
const completedEntries = data.filter(e => e.description.includes(isRu ? 'Просмотрено' : 'Completed') || e.description.includes(isRu ? 'Прочитано' : 'Completed')).reverse();
const historyBlock = document.createElement('div');
historyBlock.id = 'chtwDateWatchedWrapper';
historyBlock.className = "block";
historyBlock.style.fontSize = '12px';
historyBlock.innerHTML = `
<div class="subheadline">${isRu ? 'История' : 'History'}</div>
<div class="chtw-watched-body"></div>
<div class="chtw-watched-detail-content"><div><div class="chtw-watched-detail-header"></div></div></div>
<button type="button" id="chtwWatchedButtonShowAll">${isRu ? 'показать/скрыть подробнее' : 'show/hide details'}</button>`;
const body = historyBlock.querySelector('.chtw-watched-body');
completedEntries.forEach((entry, index) => {
const text = isRu ? (entry.target.url.startsWith('/anime') ? 'Просмотрено' : 'Прочитано') : 'Completed';
body.innerHTML += `<div class="chtw-watched-item"><div>${text} #${index + 1}:</div><div>${formatDate(entry.created_at)}</div></div>`;
});
const detail = historyBlock.querySelector('.chtw-watched-detail-content > div');
data.forEach(entry => {
detail.innerHTML += `<div class="chtw-watched-item"><div>${formatDate(entry.created_at)}</div><div>${entry.description}</div></div>`;
});
parentBlock.prepend(historyBlock);
log("History block appended successfully.");
historyBlock.querySelector('#chtwWatchedButtonShowAll').addEventListener('click', e => e.currentTarget.previousElementSibling.classList.toggle('expanded'));
} catch (e) {
error("Failed to fetch or display watch history:", e);
}
}
// --- Feature: Episodes List ---
function createNewEpisodeEntry(episodeData) {
const score = episodeData.score || '???', dateStr = episodeData.aired ? formatDate(episodeData.aired) : 'N/A', title = episodeData.title || 'N/A';
const entry = document.createElement('div');
entry.className = "chtw-ep-wrap";
let colorClass = '';
if (episodeData.filler) colorClass = "chtw-ep-filler"; else if (episodeData.recap) colorClass = "chtw-ep-recap";
else if (episodeData.score >= 4.9) colorClass = "chtw-ep-epic"; else if (episodeData.score > 4.8) colorClass = "chtw-ep-great";
else if (episodeData.score > 4.6) colorClass = "chtw-ep-good"; else if (episodeData.score < 4) colorClass = "chtw-ep-bad";
if (colorClass) entry.classList.add(colorClass);
entry.innerHTML = `<span class="chtw-ep-number">#${episodeData.mal_id}</span><span class="chtw-ep-title">${title}</span><span class="chtw-ep-date">${dateStr}</span><span class="chtw-ep-score">${score}</span>`;
return entry;
}
function getAndAppendBatchEpisodes(animeID, page = 1) {
log(`Fetching episodes for ${animeID}, page ${page}`);
const container = document.getElementById('chtwEpisodesDateWatchedWrapper');
if (!container) return;
GM_xmlhttpRequest({
method: "GET", url: `https://api.jikan.moe/v4/anime/${animeID}/episodes?page=${page}`,
onload: function(response) {
if (response.status < 200 || response.status >= 300) { error(`Jikan API request failed with status ${response.status}`); return; }
const respData = JSON.parse(response.responseText);
if (!respData.data || respData.data.length === 0) {
log('No more episodes found.');
document.getElementById('chtwEpisodesShowMoreButton')?.remove();
return;
}
const { data: episodesData, pagination } = respData;
const detailContent = container.querySelector('.chtw-watched-detail-content > div');
episodesData.forEach(ep => detailContent.append(createNewEpisodeEntry(ep)));
let moreButton = document.getElementById('chtwEpisodesShowMoreButton');
if (pagination.has_next_page) {
if (!moreButton) {
moreButton = document.createElement('button');
moreButton.id = 'chtwEpisodesShowMoreButton'; moreButton.type = 'button';
detailContent.append(moreButton);
}
moreButton.textContent = getLocale() === 'ru' ? 'подгрузить ещё' : 'load more';
moreButton.onclick = () => getAndAppendBatchEpisodes(animeID, page + 1);
} else { moreButton?.remove(); }
},
onerror: err => error("Jikan API request failed:", err)
});
}
function showEpisodesRating() {
if (!settings.getOption('showEpisodes') || document.getElementById('chtwEpisodesDateWatchedWrapper')) return;
if (!window.location.pathname.startsWith('/animes/')) return;
const path = window.location.pathname;
const match = path.match(/\/animes\/(\d+)/);
const targetId = match ? match[1] : null;
if (!targetId) return;
log("Running Episodes Rating for targetId:", targetId);
const isRu = getLocale() === 'ru';
const episodesBlock = document.createElement('div');
episodesBlock.id = 'chtwEpisodesDateWatchedWrapper';
episodesBlock.className = "block";
episodesBlock.style.fontSize = '12px';
episodesBlock.innerHTML = `
<div class="subheadline">${isRu ? 'Эпизоды' : 'Episodes'}</div>
<div class.chtw-watched-body" style="font-weight: bold;">
<div class="chtw-ep-wrap">
<span class="chtw-ep-number">Эп.</span><span class="chtw-ep-title">Название</span><span class="chtw-ep-date">Дата</span><span class="chtw-ep-score">Оценка</span>
</div>
</div>
<div class="chtw-watched-detail-content"><div></div></div>
<button type="button" id="chtwEpisodesWatchedButtonShowAll">${isRu ? 'показать/скрыть все' : 'show/hide all'}</button>`;
document.querySelector(".b-db_entry")?.insertAdjacentElement('afterend', episodesBlock);
episodesBlock.querySelector('#chtwEpisodesWatchedButtonShowAll').addEventListener('click', e => e.currentTarget.previousElementSibling.classList.toggle('expanded'));
getAndAppendBatchEpisodes(targetId, 1);
}
// --- Feature: Average Ratings & Totals ---
function calculateAverageScore(scoreData) {
let sumScore = 0, totalCount = 0;
scoreData.forEach(item => {
const score = item.name ?? item[0], count = item.value ?? item[1];
sumScore += parseInt(count) * parseInt(score); totalCount += parseInt(count);
});
return totalCount > 0 ? (sumScore / totalCount).toFixed(2) : 'N/A';
}
function calculateAllTitles(scoreData) {
return scoreData.reduce((sum, item) => sum + parseInt(item.value), 0);
}
function calculateAverageFriendsRating() {
const friendRates = document.querySelectorAll("div[class*=friend-rate] div.status");
let sum = 0, count = 0;
friendRates.forEach(rate => {
const score = parseInt(rate.textContent.replace(/[^-0-9]/g, ''), 10);
if (!isNaN(score)) { sum += score; count++; }
});
const result = count > 0 ? sum / count : 0;
return result ? result.toFixed(2).replace('.00', '') : false;
}
function appendAverageFriendsRating() {
if (!settings.getOption('avTitleFr')) return;
const element = document.querySelector(".block > .friend-rate");
if (!element) return;
const avg = calculateAverageFriendsRating();
if (avg && element.previousElementSibling) {
log("Updating Average Friends Rating.");
element.previousElementSibling.textContent = `${getLocale() === 'ru' ? 'У друзей' : 'Friends'} (средняя: ${avg})`;
}
}
function appendAverageRating() {
if (!settings.getOption('avProfileFr')) return;
const element = document.querySelector(".p-user_rates.p-profiles .mini-charts > .scores > #scores");
if (!element) return;
const stats = JSON.parse(element.dataset.stats || '[]');
if (stats.length === 0) return;
const avg = calculateAverageScore(stats);
const headline = element.closest('.scores')?.querySelector('div.subheadline');
if (avg !== 'N/A' && headline) {
log("Updating Average Profile Rating.");
headline.textContent = `${getLocale() === 'ru' ? 'Оценки' : 'Scores'} (средняя: ${avg})`;
}
}
function appendAverageTitleRating() {
if (!settings.getOption('avTitle')) return;
const element = document.querySelector("#rates_scores_stats");
if (!element) return;
const stats = JSON.parse(element.dataset.stats || '[]');
if (stats.length === 0) return;
const avg = calculateAverageScore(stats);
if (avg !== 'N/A' && element.previousElementSibling) {
log("Updating Average Title Rating.");
element.previousElementSibling.textContent = `${getLocale() === 'ru' ? 'Оценки' : 'Scores'} (средняя: ${avg})`;
}
}
function appendOverallTitles() {
if (!settings.getOption('allProfileFr')) return;
const element = document.querySelector(".p-user_rates.p-profiles .mini-charts > .types > #types");
if (!element) return;
const stats = JSON.parse(element.dataset.stats || '[]');
if (stats.length === 0) return;
const total = calculateAllTitles(stats);
const headline = element.closest('.types')?.querySelector('div.subheadline');
if (total && headline) {
log("Updating Overall Profile Titles count.");
headline.textContent = `${getLocale() === 'ru' ? 'Типы' : 'Kinds'} (всего: ${total})`;
}
}
// --- Main Execution Logic ---
function runAllFeatures() {
initSettings();
log(`Ready event fired. Running features on: ${window.location.href}`);
// Add new blocks to the page
showWatchHistory();
showEpisodesRating();
showChronologyStat();
// Modify existing blocks on the page
appendAverageTitleRating();
appendAverageFriendsRating();
appendAverageRating();
appendOverallTitles();
}
// --- Script Bootstrapping ---
function ready(fn) {
document.addEventListener('page:load', fn);
document.addEventListener('turbolinks:load', fn);
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
initStyle();
ready(runAllFeatures);
})();
f2f3f4RtD - приложения для переноса списка аниме в формат Markdown файлов на ПК. ссылка