View difference between Paste ID: 0YsT9hWq and gJxt50pz
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name         Wykop - losowe położenie plusów
3
// @namespace    wykop-losowe-plusy
4-
// @version      1.0.0
4+
// @version      1.1.0
5-
// @description  Umieszcza plusy wpisów i komentarzy w losowych miejscach na mikroblogu i stronach pojedynczych wpisów.
5+
// @description  Umieszcza plusy wpisów i komentarzy w losowych miejscach na mikroblogu, stronach wpisów i profilach użytkowników.
6
// @match        https://wykop.pl/*
7
// @run-at       document-idle
8
// @grant        none
9
// ==/UserScript==
10
11
(() => {
12
  'use strict';
13
14
  const positions = ['author', 'content', 'actions', 'top-right', 'bottom-right'];
15
  const placements = new WeakMap();
16
  const observerOptions = {
17
    childList: true,
18
    subtree: true,
19
    attributes: true,
20
    attributeFilter: ['class'],
21
  };
22
  let scheduled = false;
23
24
  const style = document.createElement('style');
25
  style.textContent = `
26-
    :is(.microblog, .entry-page) .item .voting.tm-random-plus {
26+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-item .tm-random-plus {
27
      display: inline-flex !important;
28
      flex: 0 0 auto !important;
29
      width: max-content !important;
30
      margin: 0 !important;
31
      transform: none !important;
32
    }
33
34-
    :is(.microblog, .entry-page) .item .voting.tm-plus-floating {
34+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-item .tm-plus-floating {
35
      position: absolute !important;
36
      inset: auto !important;
37
      left: var(--tm-plus-left) !important;
38
      top: var(--tm-plus-top) !important;
39
      z-index: 2;
40
    }
41
42-
    :is(.microblog, .entry-page) .tm-plus-header {
42+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-header {
43
      position: relative !important;
44
    }
45
46-
    :is(.microblog, .entry-page) .tm-plus-author,
46+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-author,
47-
    :is(.microblog, .entry-page) .tm-plus-actions {
47+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-actions {
48
      flex-wrap: wrap !important;
49
      height: auto !important;
50
    }
51
52-
    :is(.microblog, .entry-page) .tm-plus-slot {
52+
    :is(.microblog, .entry-page, .profile-page) .tm-plus-actions {
53
      display: flex !important;
54
    }
55
56
    .profile-page .tm-plus-author > .tm-plus-slot {
57
      margin-inline-end: 8px;
58-
    :is(.microblog, .entry-page) .tm-plus-row {
58+
      vertical-align: middle;
59-
      display: flex;
59+
60
61
    :is(.microblog, .entry-page, .profile-page) .tm-plus-slot {
62
      display: inline-block;
63
      flex: 0 0 auto;
64-
    :is(.microblog, .entry-page) .tm-plus-row-right {
64+
65-
      justify-content: flex-end;
65+
66
67
    :is(.microblog, .entry-page, .profile-page) .tm-plus-item .tm-plus-row {
68-
    :is(.microblog, .entry-page) .tm-plus-slot.tm-plus-top-right {
68+
      display: flex !important;
69
      align-items: center;
70
      margin: 8px 0 0 34px;
71
    }
72
73
    :is(.microblog, .entry-page, .profile-page) .tm-plus-slot.tm-plus-top-right,
74
    :is(.microblog, .entry-page, .profile-page) .tm-plus-slot.tm-plus-bottom-right {
75-
    const voting = item.querySelector('.voting');
75+
76
    }
77-
    const footer = item.querySelector(':scope > footer');
77+
78
  document.head.append(style);
79
80
  function placeVoting(item) {
81
    const voting = item.querySelector('.voting, .rating-box');
82
    const username = item.querySelector(':scope > header .username');
83
    const footer = item.querySelector(':scope > footer, :scope > .edit-wrapper > footer');
84
85
    if (!voting || !username || !footer) {
86
      return;
87
    }
88
89
    const author = username.closest('.tooltip-slot') || username;
90
    const actions = footer.querySelector(':scope > .actions > ul') || footer;
91
    item.classList.add('tm-plus-item');
92
93
    let placement = placements.get(item);
94
95
    if (!placement) {
96
      const position = positions[Math.floor(Math.random() * positions.length)];
97
      placement = { position, voting: null, slot: null };
98
      placements.set(item, placement);
99
      resizeObserver.observe(item);
100
    }
101
102
    if (placement.voting !== voting) {
103
      if (placement.voting) {
104-
      username.parentElement.classList.add('tm-plus-author');
104+
105
      }
106
107-
    if (placement.position === 'actions') {
107+
108-
      footer.classList.add('tm-plus-actions');
108+
109
    }
110
111
    voting.classList.add('tm-random-plus');
112-
      const slot = document.createElement('span');
112+
113
    if (placement.position === 'author') {
114
      author.parentElement.classList.add('tm-plus-author');
115
    }
116
117
    if (placement.position === 'actions' || placement.position === 'bottom-right') {
118
      actions.classList.add('tm-plus-actions');
119-
          username.before(slot);
119+
120
121
    if (!placement.slot?.isConnected) {
122
      const inActions = placement.position === 'actions' || placement.position === 'bottom-right';
123
      const slot = document.createElement(inActions && actions.tagName === 'UL' ? 'li' : 'span');
124-
          username.parentElement.append(slot);
124+
125
      slot.setAttribute('aria-hidden', 'true');
126
      placement.slot = slot;
127-
        case 'content':
127+
128-
        case 'bottom-right': {
128+
129
        case 'author':
130
          author.before(slot);
131
          break;
132
133-
          if (placement.position === 'bottom-right') {
133+
134-
            row.classList.add('tm-plus-row-right');
134+
135-
            footer.after(row);
135+
          const headerRight = item.querySelector(':scope > header > .right') || author.parentElement;
136-
          } else {
136+
          headerRight.append(slot);
137-
            footer.before(row);
137+
138-
          }
138+
139
        case 'content': {
140
          const row = document.createElement('div');
141
          row.className = 'tm-plus-row';
142
          row.append(slot);
143
144-
          footer.prepend(slot);
144+
          footer.before(row);
145
          break;
146
        }
147
148
        case 'bottom-right':
149
          slot.classList.add('tm-plus-bottom-right');
150
          actions.append(slot);
151
          break;
152
153
        case 'actions':
154
          actions.prepend(slot);
155
          break;
156
      }
157
    }
158
159
    // Oryginalny element pozostaje na miejscu, żeby nie zakłócać aktualizacji Vue
160
    item.querySelector(':scope > header').classList.add('tm-plus-header');
161
    voting.classList.add('tm-plus-floating');
162
163
    const parent = voting.offsetParent;
164
165
    if (!parent) {
166
      return;
167
    }
168
169
    const { slot } = placement;
170
    const votingRect = voting.getBoundingClientRect();
171
    slot.style.width = `${votingRect.width}px`;
172
    slot.style.height = `${votingRect.height}px`;
173
174
    if (!slot.getClientRects().length) {
175
      voting.classList.remove('tm-plus-floating');
176-
    if (!/^\/(?:mikroblog|wpis)(?:\/|$)/.test(location.pathname)) {
176+
177
    }
178
179
    const slotRect = slot.getBoundingClientRect();
180
    const parentRect = parent.getBoundingClientRect();
181
    const left = slotRect.left - parentRect.left - parent.clientLeft + parent.scrollLeft;
182
    const top = slotRect.top - parentRect.top - parent.clientTop + parent.scrollTop;
183-
      const items = document.querySelectorAll(':is(.microblog, .entry-page) .thread > .item');
183+
184
    voting.style.setProperty('--tm-plus-left', `${left}px`);
185
    voting.style.setProperty('--tm-plus-top', `${top}px`);
186
  }
187
188
  function updateVoting() {
189
    scheduled = false;
190
191
    if (!/^\/(?:mikroblog|wpis|ludzie)(?:\/|$)/.test(location.pathname)) {
192
      return;
193
    }
194
195
    observer.disconnect();
196
197
    try {
198
      const items = document.querySelectorAll(
199
        ':is(.microblog, .entry-page, .profile-page) .thread > .item, .profile-page .entry > article',
200
      );
201
202
      for (const item of items) {
203
        placeVoting(item);
204
      }
205
    } finally {
206
      observer.observe(document.body, observerOptions);
207
    }
208
  }
209
210
  function scheduleUpdate() {
211
    if (scheduled) {
212
      return;
213
    }
214
215
    scheduled = true;
216
    requestAnimationFrame(updateVoting);
217
  }
218
219
  const observer = new MutationObserver(scheduleUpdate);
220
  const resizeObserver = new ResizeObserver(scheduleUpdate);
221
222
  observer.observe(document.body, observerOptions);
223
  updateVoting();
224
})();
225