// ==UserScript==
// @name shikiCommentsGraphLib
// @namespace https://shikimori.one/
// @version 3.0
// @description Lib для графа комментариев Shikimori
// @license MIT
// @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// ==/UserScript==
; (function () {
'use strict';
// === Базовая математика (оставляем из оригинала) ===
class ShikiMath {
static is_above(x, y, x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return ((((dy * x) - (dx * y)) + (dx * y1)) - (dy * x1)) <= 0;
}
static sector(x1, y1, x2, y2, rx, ry) {
const lb_to_rt = this.is_above(x2, y2, x1 - rx, y1 - ry, x1, y1);
const lt_to_rb = this.is_above(x2, y2, x1 - rx, y1 + ry, x1, y1);
if (lb_to_rt && lt_to_rb) return 'top';
if (!lb_to_rt && lt_to_rb) return 'right';
if (!lb_to_rt && !lt_to_rb) return 'bottom';
return 'left';
}
static square_cutted_line(x1, y1, x2, y2, rx1, ry1, rx2, ry2) {
let f_x1, f_x2, f_y1, f_y2;
const dx = x2 - x1, dy = y2 - y1;
const y = x => (((dy * x) + (dx * y1)) - (dy * x1)) / dx;
const x = y => (((dx * y) - (dx * y1)) + (dy * x1)) / dy;
const target = this.sector(x1, y1, x2, y2, rx1, ry1);
if (target === 'right') { f_x1 = x1 + rx1; f_y1 = y(f_x1); f_x2 = x2 - rx2; f_y2 = y(f_x2); }
if (target === 'left') { f_x1 = x1 - rx1; f_y1 = y(f_x1); f_x2 = x2 + rx2; f_y2 = y(f_x2); }
if (target === 'top') { f_y1 = y1 + ry1; f_x1 = x(f_y1); f_y2 = y2 - ry2; f_x2 = x(f_y2); }
if (target === 'bottom') { f_y1 = y1 - ry1; f_x1 = x(f_y1); f_y2 = y2 + ry2; f_x2 = x(f_y2); }
return { x1: f_x1, y1: f_y1, x2: f_x2, y2: f_y2 };
}
}
// === Класс CommentNode ===
const SELECT_SCALE = 1.5;
const BORDER_OFFSET = 3;
class CommentNode {
constructor(data, size = 64) {
Object.assign(this, data);
this.width = this.height = size;
this.rx = this.ry = size / 2;
this.selected = false;
}
get d3Node() { return this._d3Node ||= d3.select(`.node#${this.id}`); }
get d3Image() { return this._d3Image ||= this.d3Node.select('image'); }
deselect(boundX, boundY, tick) {
this.selected = false;
this._animate(this.width, this.height, boundX, boundY, tick);
this._hideTooltip();
}
select(boundX, boundY, tick) {
this.selected = true;
this._animate(this.width * SELECT_SCALE, this.height * SELECT_SCALE, boundX, boundY, tick);
this._loadTooltip();
}
_animate(newW, newH, boundX, boundY, tick) {
const iw = d3.interpolate(this.width, newW);
const ih = d3.interpolate(this.height, newH);
this.d3Node.transition().duration(300).tween('resize', () => t => {
this.width = iw(t); this.height = ih(t);
this.rx = this.width / 2; this.ry = this.height / 2;
this.d3Node.attr('transform', `translate(${boundX(this) - this.rx}, ${boundY(this) - this.ry})`);
this.d3Image.attr({ width: this.width, height: this.height });
tick();
});
}
async _loadTooltip() {
try {
const tooltip = document.querySelector('.sticky-tooltip');
tooltip.style.display = 'block';
tooltip.querySelector('.inner').innerHTML = '<i>Загрузка...</i>';
const { data } = await axios.get(`https://shikimori.one/api/comments/${this.id}`);
const html = data.html_body || '(нет данных)';
tooltip.querySelector('.inner').innerHTML = html;
} catch (e) {
console.error('Tooltip error:', e);
}
}
_hideTooltip() {
const tooltip = document.querySelector('.sticky-tooltip');
if (tooltip) tooltip.style.display = 'none';
}
}
// === Граф комментариев ===
class CommentsGraph extends window.FranchiseGraph {
constructor(data) {
super(data);
this.image_w = this.image_h = 64;
this.nodes_data = data.nodes.map(node => new CommentNode(node, 64));
}
_append_nodes() {
this.d3_node = this.d3_svg.append('g').selectAll('.node')
.data(this.nodes_data)
.enter().append('g')
.attr({ class: 'node', id: d => d.id })
.call(this.d3_force.drag())
.on('click', d => {
if (d3.event?.defaultPrevented) return;
this._node_selected(d);
});
this.d3_node.append('path').attr({ class: 'border_outer', d: '' });
this.d3_image_container = this.d3_node.append('g').attr({ class: 'image-container' });
this.d3_image_container.append('image')
.attr({
width: d => d.width,
height: d => d.height,
'xlink:href': d => d.avatar
});
this.d3_image_container.append('path')
.attr({ class: 'border_inner', d: d => `M 0,0 ${d.width},0 ${d.width},${d.height} 0,${d.height} 0,0` });
}
}
// === Экспорт ===
window.ShikiMath = ShikiMath;
window.CommentNode = CommentNode;
window.CommentsGraph = CommentsGraph;
})();