Greasy Fork

Greasy Fork is available in English.

shikiCommentsGraphLib

Lib для графа комментариев Shikimori

当前为 2025-10-16 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/552715/1678737/shikiCommentsGraphLib.js

// ==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;
})();