Anime Website (Custom) Buttons Plus

A script that adds buttons on Anime Planet, MAL, Kitsu, Anilist and aniDB for searching various sites.

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

  1. // ==UserScript==
  2. // @author Deathwing
  3. // @name Anime Website (Custom) Buttons Plus
  4. // @include https://www.anime-planet.com/anime/*
  5. // @include http://myanimelist.net/anime/*
  6. // @include https://myanimelist.net/anime/*
  7. // @include https://anilist.co/*
  8. // @include https://kitsu.io/*
  9. // @include https://anidb.net/anime/*
  10. // @exclude https://www.anime-planet.com/anime/
  11. // @exclude https://www.anime-planet.com/anime/all?name=*
  12. // @exclude https://www.anime-planet.com/anime/recommendations/*
  13. // @exclude https://myanimelist.net/anime/producer*
  14. // @description A script that adds buttons on Anime Planet, MAL, Kitsu, Anilist and aniDB for searching various sites.
  15. // @version 2.812
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_listValues
  19. // @grant GM_deleteValue
  20. // @grant GM_addStyle
  21. // @grant window.onurlchange
  22. // @namespace https://greasyfork.org/users/18375
  23. // ==/UserScript==
  24.  
  25.  
  26. var header;
  27. var headerQueryString = '';
  28. var outerButtonsDiv;
  29. var host = document.location.host;
  30. var malHost = 'myanimelist.net';
  31. var apHost = 'www.anime-planet.com';
  32. var alHost = 'anilist.co';
  33. var kHost = 'kitsu.io';
  34. var adHost = 'anidb.net';
  35.  
  36. var hideList = [];
  37.  
  38. var buttDivLeft = 0;
  39. var autoHide = GM_getValue('setting:autoHide', false);
  40.  
  41. if (host === apHost) {
  42. headerQueryString = '#siteContainer h1';
  43. header = getElement(headerQueryString);
  44. main();
  45. }
  46. else if (host === malHost) {
  47. headerQueryString = 'h1.title-name strong';
  48. header = getElement(headerQueryString);
  49. main();
  50. }
  51. else if (host === alHost) {
  52. headerQueryString = 'div.content h1';
  53. getSPAHeader(headerQueryString);
  54. }
  55. else if (host === kHost) {
  56. headerQueryString = '.media--title';
  57. getSPAHeader(headerQueryString);
  58. }
  59. else if (host === adHost) {
  60. headerQueryString = 'h1.anime';
  61. header = getElement(headerQueryString);
  62. main();
  63. }
  64.  
  65. function getSPAHeader(queryString) {
  66. header = getElement(queryString);
  67.  
  68. if (header) {
  69. main();
  70. }
  71. else {
  72. setTimeout(getSPAHeader, 300, queryString);
  73. }
  74. }
  75.  
  76. function getElement(atr) {
  77. return document.querySelector(atr);
  78. }
  79.  
  80. function addHeaderEventListeners() {
  81. header.addEventListener('mouseover', showEditButton);
  82. header.addEventListener('mouseout', hideEditButton);
  83. }
  84.  
  85.  
  86. function main() {
  87. var animeName;
  88. var reff;
  89.  
  90. if (host === apHost || host === malHost) {
  91. animeName = getAnimeName();
  92. }
  93. else if (host === adHost) {
  94. animeName = getAnimeName().replace('Anime: ', '');
  95. }
  96. else if (host === kHost) {
  97. animeName = getAnimeName(header.firstElementChild);
  98. reff = header.firstElementChild;
  99. extractNameOnChange();
  100. }
  101. else if (host === alHost) {
  102. animeName = getAnimeName(header.firstChild);
  103. extractNameOnChange();
  104. }
  105.  
  106. function extractNameOnChange() {
  107. window.addEventListener('urlchange', (el) => {
  108. var animeButtonsDiv = document.querySelector('.animeButtons');
  109. if (el.url.includes('/anime/')) {
  110. if (animeButtonsDiv == null) {
  111. header = document.querySelector(headerQueryString);
  112. header.appendChild(outerButtonsDiv);
  113. addHeaderEventListeners();
  114. }
  115. else {
  116. animeButtonsDiv.style.display = 'block';
  117. }
  118. if (host === kHost) {
  119. reff = header.firstElementChild;
  120. }
  121. if (getAnimeName(reff) !== animeName) {
  122. animeName = getAnimeName(reff);
  123. var urlsObjs = setSearchURLs();
  124. var animeButtons = document.querySelectorAll('.animeButton');
  125. var customButtonsObjs = getAnimeButtonsFromStorage();
  126.  
  127. animeButtons.forEach(b => {
  128. if (b.className.includes('stockButton')) {
  129. b.href = urlsObjs.find(o => o.name === b.title).url;
  130. }
  131. else {
  132. b.href = customButtonsObjs.find(o => o.title === b.title).url
  133. .replace('ANIMENAME', animeName);
  134. }
  135. });
  136. }
  137. }
  138. else {
  139. if (animeButtonsDiv != null) {
  140. animeButtonsDiv.style.display = 'none';
  141. }
  142. }
  143. });
  144. }
  145.  
  146. function getAnimeName(ref = header) {
  147. return ref.textContent.replace('(TV)', '').trim();
  148. }
  149.  
  150. function creteButton(icon, searchUrl, title, isStock) {
  151. var buttImg = createHTMLElement("img", null, null,
  152. [{ n: 'style', v: 'width:16px;height:16px;margin-right:2px;' }]);
  153.  
  154. if (icon) {
  155. buttImg.src = icon;
  156. }
  157. else {
  158. buttImg.src = getIconUrl(searchUrl);
  159. }
  160.  
  161. var button = createHTMLElement("a", null, 'animeButton', [{ n: 'id', v: `animeButton${makeButtonId(title)}` },
  162. { n: 'href', v: searchUrl }, { n: 'target', v: "_blank" }, { n: 'title', v: title }]);
  163.  
  164. if (isStock) {
  165. button.className += ' stockButton';
  166. }
  167.  
  168. button.appendChild(buttImg);
  169. return button;
  170. }
  171.  
  172. //Set buttons with information
  173. var malSearchUrl;
  174. var alSearchUrl;
  175. var apSearchUrl;
  176. var kSearchUrl;
  177. var adSearchUrl;
  178. var ytSearchUrl;
  179. var gSearchUrl;
  180. var nySearchUrl;
  181. var ampSearchUrl;
  182.  
  183. function setSearchURLs() {
  184. malSearchUrl = `http://myanimelist.net/anime.php?q=${animeName}`;
  185. alSearchUrl = `https://anilist.co/search/anime?search=${animeName}&sort=SEARCH_MATCH`;
  186. apSearchUrl = `https://www.anime-planet.com/anime/all?name=${animeName}`;
  187. kSearchUrl = `https://kitsu.io/anime?text=${animeName}`;
  188. adSearchUrl = `https://anidb.net/anime/?adb.search=${animeName}&do.search=1`;
  189. ytSearchUrl = `https://www.youtube.com/results?search_query=${animeName} trailer`;
  190. gSearchUrl = `https://google.com/search?tbm=isch&biw=&bih=&gbv=2&q=${animeName}`;
  191. nySearchUrl = `https://nyaa.si/?f=0&c=1_2&q=${animeName}`;
  192. ampSearchUrl = `https://animixplay.to${location.pathname}`;
  193.  
  194. return [
  195. { name: malTitle, url: malSearchUrl },
  196. { name: alTitle, url: alSearchUrl },
  197. { name: apTitle, url: apSearchUrl },
  198. { name: kTitle, url: kSearchUrl },
  199. { name: adTitle, url: adSearchUrl },
  200. { name: ytTitle, url: ytSearchUrl },
  201. { name: gTitle, url: gSearchUrl },
  202. { name: nyTitle, url: nySearchUrl },
  203. { name: ampTitle, url: ampSearchUrl }
  204. ];
  205. }
  206.  
  207. setSearchURLs();
  208.  
  209. //MAL Button
  210. var icon = null;
  211. var malTitle = "Search MyAnimeList";
  212.  
  213. var malButton = creteButton(icon, malSearchUrl, malTitle, true);
  214.  
  215.  
  216. //Anilist Button
  217. var alTitle = "Search Anilist";
  218.  
  219. var alButton = creteButton(icon, alSearchUrl, alTitle, true);
  220.  
  221.  
  222. //Anime-Planet Button
  223. var apTitle = "Search Anime-Planet";
  224.  
  225. var apButton = creteButton(icon, apSearchUrl, apTitle, true);
  226.  
  227. //Kitsu Button
  228. var kTitle = "Search Kitsu";
  229.  
  230. var kButton = creteButton(icon, kSearchUrl, kTitle, true);
  231.  
  232. //aniDB Button
  233. var adTitle = "Search aniDB";
  234.  
  235. var adButton = creteButton(icon, adSearchUrl, adTitle, true);
  236.  
  237.  
  238. //YouTube Button
  239. var ytTitle = 'YouTube Trailer';
  240.  
  241. var ytButton = creteButton(icon, ytSearchUrl, ytTitle, true);
  242.  
  243.  
  244. //Google Images button
  245. var gTitle = "Search with Google Images";
  246.  
  247. var giButton = creteButton(icon, gSearchUrl, gTitle, true);
  248.  
  249.  
  250. //Nyaa button
  251. var nyTitle = "Search Nyaa";
  252.  
  253. var nyButton = creteButton(icon, nySearchUrl, nyTitle, true);
  254.  
  255.  
  256. //AniMixPlay button
  257. var ampTitle = "Go to AniMixPlay";
  258.  
  259. var ampButton = creteButton(icon, ampSearchUrl, ampTitle, true);
  260.  
  261.  
  262. //Edit button
  263. var ebTitle = "Edit Custom Buttons";
  264.  
  265. var arrowButtonIcon = createHTMLElement('i', null, 'arrowButton fa fa-angle-right', [{ n: 'title', v: ebTitle },
  266. { n: 'style', v: 'font-size:16px;vertical-align: text-top;transition: all 0.3s linear 0s;left:-18px;position: relative;' }]);
  267.  
  268. var editButtonIcon = createHTMLElement('i', null, 'editButton fa fa-edit', [{ n: 'title', v: ebTitle },
  269. { n: 'style', v: 'font-size:16px;vertical-align: text-top;transition: all 0.3s linear 0s;opacity:0;' }]);
  270.  
  271. var editButton = createHTMLElement('div', null, null,
  272. [{ n: 'style', v: 'width:16px;height:16px;margin-right:2px;display:inline;' }]);
  273.  
  274. if (!autoHide) {
  275. editButtonIcon.style.opacity = '1';
  276. arrowButtonIcon.style.opacity = '0';
  277. }
  278.  
  279. addHeaderEventListeners();
  280.  
  281. editButton.append(editButtonIcon, arrowButtonIcon);
  282. editButton.addEventListener('click', () => { togglePopup(true); });
  283.  
  284. var customButtons = [];
  285. var customButtonsObj = [];
  286.  
  287. if (!(GM_listValues()).includes('setting:buttonsNames')) {
  288. var values = GM_listValues();
  289.  
  290. for (var i = 0; i < values.length; i++) {
  291. if (!values[i].includes('setting:')) {
  292. customButtonsObj.push(JSON.parse(GM_getValue(values[i], '{}')));
  293. GM_deleteValue(values[i]);
  294. }
  295. }
  296.  
  297. setAnimeButtonsToStorage(customButtonsObj);
  298. }
  299. else {
  300. customButtonsObj = getAnimeButtonsFromStorage();
  301. }
  302.  
  303. customButtonsObj.forEach((b) => {
  304. customButtons.push(creteButton(b.icon, b.url.replace('ANIMENAME', animeName), b.title));
  305. });
  306.  
  307. //Add Website Buttons
  308. if (host === apHost) {
  309. appendButtons([malButton, alButton, kButton, adButton]);
  310. outerButtonsDiv.style.top = '6px';
  311. arrowButtonIcon.style.verticalAlign = '';
  312. editButtonIcon.style.verticalAlign = '';
  313. }
  314. else if (host === alHost) {
  315. appendButtons([malButton, apButton, kButton, adButton]);
  316. outerButtonsDiv.style.top = '8px';
  317. }
  318. else if (host === malHost) {
  319. appendButtons([apButton, alButton, kButton, adButton]);
  320. outerButtonsDiv.style.top = '2px';
  321. }
  322. else if (host === kHost) {
  323. appendButtons([malButton, apButton, alButton, adButton]);
  324. outerButtonsDiv.style.top = '2px';
  325. arrowButtonIcon.style.verticalAlign = '';
  326. editButtonIcon.style.verticalAlign = '';
  327. }
  328. else if (host === adHost) {
  329. appendButtons([malButton, apButton, alButton, kButton]);
  330. outerButtonsDiv.style.top = '9px';
  331. }
  332.  
  333. function appendButtons(mainButtonsArray) {
  334. header.appendChild(document.createTextNode(" "));
  335.  
  336. var allButtonsArray = mainButtonsArray.concat([ytButton, giButton, nyButton, ampButton], customButtons, editButton);
  337. var buttonsDiv = createHTMLElement('div', null, 'animeButtons',
  338. [{ n: 'style', v: 'position:relative;transition: all 0.4s cubic-bezier(0.79, 0.88, 0.16, 0.98) 0s;' }]);
  339. outerButtonsDiv = createHTMLElement('div', null, null,
  340. [{ n: 'style', v: 'display:inline-block;position:relative;overflow:hidden;' }]);
  341.  
  342. // buttonsDiv.appendChild(allButtonsArray);
  343. outerButtonsDiv.appendChild(buttonsDiv);
  344.  
  345. allButtonsArray.forEach((b) => {
  346. buttonsDiv.appendChild(b);
  347. if (b.id !== '') {
  348. hideList.push({
  349. bId: b.id,
  350. h: 'show'
  351. });
  352. }
  353. });
  354.  
  355. header.appendChild(outerButtonsDiv);
  356.  
  357. getHideList();
  358. hideButtons();
  359. addButtonPopup();
  360. hideEditButton();
  361. }
  362.  
  363. var fontAwesome = '@import url("https://use.fontawesome.com/releases/v5.15.2/css/all.css");';
  364. GM_addStyle(fontAwesome);
  365. }
  366.  
  367.  
  368. function getHideList() {
  369. var hideListNew;
  370.  
  371. if ((GM_listValues()).includes('hideList')) {
  372. hideListNew = GM_getValue('hideList', '[]');
  373. GM_deleteValue('hideList');
  374. }
  375. else {
  376. hideListNew = GM_getValue('setting:hideList', '[]');
  377. }
  378.  
  379. if (!hideListNew || hideListNew === undefined || hideListNew === 'undefined') {
  380. hideListNew = '[]';
  381. }
  382.  
  383. concatHideList(JSON.parse(hideListNew));
  384. }
  385.  
  386. function concatHideList(v) {
  387. v.forEach(b => {
  388. var item = hideList.find(n => n.bId === b.bId);
  389.  
  390. if (item) {
  391. return Object.assign(item, b);
  392. }
  393.  
  394. hideList.push(b);
  395. });
  396. }
  397.  
  398. function hideButtons() {
  399. buttDivLeft = 0;
  400.  
  401. hideList.forEach((o) => {
  402. var button = getElement(`#${o.bId}`);
  403.  
  404. if (button) {
  405. if (o.h === 'show') {
  406. button.style.display = '';
  407. buttDivLeft++;
  408. }
  409. else if (o.h === 'hide') {
  410. button.style.display = 'none';
  411. }
  412. }
  413. });
  414. }
  415.  
  416. function makeButtonId(buttonName) {
  417. var result = 0;
  418.  
  419. for (var i = 0; i < buttonName.length; i++) {
  420. result += buttonName.charCodeAt(i);
  421. }
  422.  
  423. return result * buttonName.charCodeAt(buttonName.length - 1);
  424. }
  425.  
  426.  
  427. function getPopup() {
  428. return getElement('.buttonPopup');
  429. }
  430.  
  431. function showEditButton() {
  432. var editButton = getElement('.editButton');
  433.  
  434. if (autoHide) {
  435. var arrowButton = getElement('.arrowButton');
  436. var buttonsDiv = getElement('.animeButtons');
  437. buttonsDiv.style.left = '0%';
  438. arrowButton.style.opacity = '0';
  439. }
  440.  
  441. editButton.style.opacity = '1';
  442. }
  443.  
  444. function hideEditButton() {
  445. var editButton = getElement('.editButton');
  446.  
  447. if (autoHide) {
  448. var buttonsDiv = getElement('.animeButtons');
  449. var arrowButton = getElement('.arrowButton');
  450. hideButtons();
  451. buttonsDiv.style.left = `-${buttDivLeft * 18}px`;
  452. arrowButton.style.opacity = '1';
  453. }
  454.  
  455. editButton.style.opacity = '0';
  456. }
  457.  
  458. function addAndCancelButtonsHandler(e) {
  459. var targetEl = e.target;
  460.  
  461. if (targetEl.className === 'addButton') {
  462. addButtonLogic(GM_listValues());
  463. }
  464. else if (targetEl.className === 'cancelButton') {
  465. togglePopup(false);
  466. }
  467. }
  468.  
  469. function addAndEditTabButtonsHandler(e) {
  470. var target = e.target;
  471.  
  472. if (target.className.includes('Text')) {
  473. target = e.target.parentElement;
  474. }
  475.  
  476. if (target.className === 'addTab' && target.style.color === 'white') {
  477. var editTab = target.parentElement.children[1];
  478. hideTabSection(editTab, target);
  479. }
  480. else if (target.className === 'editTab' && target.style.color === 'white') {
  481. var addTab = target.parentElement.firstElementChild;
  482. hideTabSection(addTab, target);
  483. }
  484. }
  485.  
  486. function hideTabSection(toHide, toShow) {
  487. toHide.style.color = 'white';
  488. toHide.style.backgroundColor = '#d8d8d8';
  489. toShow.style.color = 'black';
  490. toShow.style.backgroundColor = 'white';
  491.  
  492. var sectionToHide;
  493. var sectionToShow;
  494.  
  495. if (toHide.className === 'addTab') {
  496. sectionToHide = getElement('.addSection');
  497. sectionToShow = getElement('.editSection');
  498. }
  499. else {
  500. sectionToHide = getElement('.editSection');
  501. sectionToShow = getElement('.addSection');
  502. }
  503.  
  504. sectionToHide.style.opacity = '0';
  505.  
  506. setTimeout(() => {
  507. sectionToHide.style.display = 'none';
  508. sectionToShow.style.display = 'flex';
  509. setTimeout(() => sectionToShow.style.opacity = '1', 50);
  510. }, 200);
  511. }
  512.  
  513. function togglePopup(show) {
  514. var popUp = getPopup();
  515.  
  516. if (show) {
  517. header.removeEventListener('mouseout', hideEditButton);
  518. popUp.style.opacity = '1';
  519. popUp.style.top = '50%';
  520. }
  521. else {
  522. header.addEventListener('mouseout', hideEditButton);
  523. hideEditButton();
  524. popUp.style.opacity = '0';
  525. popUp.style.top = '-100%';
  526. }
  527. }
  528.  
  529. function getAnimeButtonsFromStorage() {
  530. return JSON.parse(GM_getValue('setting:buttonsNames', '[]'));
  531. }
  532.  
  533. function setAnimeButtonsToStorage(buttonsNames) {
  534. GM_setValue('setting:buttonsNames', JSON.stringify(buttonsNames));
  535. }
  536.  
  537. function addButtonLogic() {
  538. var titleField = getElement('.titleInput');
  539. var searchField = getElement('.URLInput');
  540. var iconField = getElement('.iconInput');
  541.  
  542. var buttons = getAnimeButtonsFromStorage();
  543.  
  544. if (titleField.value === '') {
  545. toggleMsgBox(true, 'Title cannot be empty!');
  546. }
  547. else if (searchField.value === '') {
  548. toggleMsgBox(true, 'Search URL cannot be empty!');
  549. }
  550. else if (!searchField.value.includes('ANIMENAME')) {
  551. toggleMsgBox(true, 'Search URL must contain ANIMENAME!');
  552. }
  553. else if (buttons.find((o) => o.title === titleField.value)) {
  554. toggleMsgBox(true, 'Button with the same name already exists!');
  555. }
  556. else {
  557. if (iconField.value === '') {
  558. iconField.value = getIconUrl(searchField.value);
  559. }
  560.  
  561. var newButton = {
  562. title: titleField.value,
  563. url: searchField.value,
  564. icon: iconField.value
  565. };
  566.  
  567. buttons.push(newButton);
  568.  
  569. setAnimeButtonsToStorage(buttons);
  570.  
  571. hideList.push({ bId: `animeButton${makeButtonId(titleField.value)}`, h: 'show' });
  572. GM_setValue('setting:hideList', JSON.stringify(hideList));
  573.  
  574. toggleMsgBox(true, `Button ${titleField.value} added succsessfully! Reload to see it!`, true);
  575.  
  576. titleField.value = '';
  577. searchField.value = '';
  578. iconField.value = '';
  579. }
  580. }
  581.  
  582. function getIconUrl(fromUrl) {
  583. var regex = /(?:https?:\/\/)(w{0,3}\.?[\s\S]+?\.\w+)\//;
  584. var result = '';
  585.  
  586. if (regex.test(fromUrl)) {
  587. result = `https://www.google.com/s2/favicons?domain=${fromUrl.match(regex)[1]}`;
  588. }
  589.  
  590. return result;
  591. }
  592.  
  593. function toggleMsgBox(toggle, msg, showReload) {
  594. var msgBox = getElement('.addMsgBox');
  595.  
  596. if (msg) {
  597. msgBox.firstElementChild.textContent = msg;
  598. }
  599.  
  600. if (showReload) {
  601. msgBox.children[1].style.display = 'inline';
  602. }
  603. else {
  604. msgBox.children[1].style.display = 'none';
  605. }
  606.  
  607. if (toggle) {
  608. msgBox.style.opacity = '1';
  609. msgBox.style.bottom = '15%';
  610. }
  611. else {
  612. msgBox.style.opacity = '0';
  613. setTimeout(() => { msgBox.style.bottom = '150%'; }, 250);
  614. }
  615. }
  616.  
  617. function hideAndDeleteHandler(e) {
  618. var target = e.target;
  619. var buttParent = target.parentElement;
  620. var button = getElement(`#${buttParent.className}`);
  621.  
  622. if (target.classList.contains('removeButton')) {
  623. button.remove();
  624. target.parentElement.remove();
  625. var buttonsObjs = getAnimeButtonsFromStorage();
  626. buttonsObjs = buttonsObjs.filter((o) => buttParent.textContent !== o.title);
  627. setAnimeButtonsToStorage(buttonsObjs);
  628. hideList = hideList.filter(obj => obj.bId !== button.id);
  629.  
  630. GM_setValue('setting:hideList', JSON.stringify(hideList));
  631. }
  632. else if (target.classList.contains('hideButton')) {
  633. if (button.style.display === 'none') {
  634. button.style.display = '';
  635. concatHideList([{ bId: button.id, h: 'show' }]);
  636. target.classList.replace('fa-eye-slash', 'fa-eye');
  637. // target.setAttribute('src', iconVisible);
  638. }
  639. else {
  640. button.style.display = 'none';
  641. concatHideList([{ bId: button.id, h: 'hide' }]);
  642. target.classList.replace('fa-eye', 'fa-eye-slash');
  643. // target.setAttribute('src', iconInvisible);
  644. }
  645.  
  646. GM_setValue('setting:hideList', JSON.stringify(hideList));
  647.  
  648. hideButtons();
  649. }
  650. }
  651.  
  652. function msgButtonsHandler(e) {
  653. var target = e.target;
  654.  
  655. if (target.className === 'reloadButton') {
  656. location.reload();
  657. }
  658. else if (target.className === 'closeButton') {
  659. toggleMsgBox(false);
  660. }
  661. }
  662.  
  663. function settingsHandler(e) {
  664. var target = e.target;
  665.  
  666. if (target.className === 'editCheckbox') {
  667. autoHide = target.checked;
  668. GM_setValue('setting:autoHide', autoHide);
  669. }
  670. }
  671.  
  672. function popupClickHandler(e) {
  673. if (!e.target.className.includes('infoBox')) {
  674. var infoBoxes = document.querySelectorAll('.infoBox');
  675. infoBoxes.forEach(b => {
  676. if (b.style.opacity === '1') {
  677. hideInfoBox(b);
  678. }
  679. });
  680. }
  681. }
  682.  
  683. function URLQuestionmarkHandler(e) {
  684. showInfoBox(e.target.parentElement.lastElementChild);
  685. var iconInfoBox = getElement('.iconInfoBox');
  686. hideInfoBox(iconInfoBox);
  687. }
  688.  
  689. function iconQuestionmarkHandler(e) {
  690. showInfoBox(e.target.parentElement.lastElementChild);
  691. var URLInfoBox = getElement('.URLInfoBox');
  692. hideInfoBox(URLInfoBox);
  693. }
  694.  
  695. function hideInfoBox(infoBox) {
  696. infoBox.style.opacity = '0';
  697. setTimeout(() => infoBox.style.display = 'none', 300);
  698. }
  699.  
  700. function showInfoBox(infoBox) {
  701. infoBox.style.display = 'inline-block';
  702. setTimeout(() => infoBox.style.opacity = '1', 10);
  703. }
  704.  
  705. function addButtonPopup() {
  706. var questionmarkIcon = 'https://www.flaticon.com/svg/static/icons/svg/1828/1828940.svg';
  707.  
  708. var style = 'margin:auto;text-align: center;display:block;margin-bottom: 5px;';
  709. var popUp = createHTMLElement('div', null, 'buttonPopup',
  710. [{ n: 'style', v: 'position:fixed;top:-100%;left:50%;transform: translate(-50%, -50%);background-color:white;width:400px;height:560px;box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);border-radius: 8px;font-size:medium;z-index:9999;opacity:0;transition: all 0.7s cubic-bezier(0.45, -0.24, 0.43, 1.14) 0s;' }]);
  711.  
  712. var tabs = createHTMLElement('div', null, 'popupTabs',
  713. [{ n: 'style', v: 'width: 100%;height: 40px;cursor: default;' }]);
  714. var addTab = createHTMLElement('div', null, 'addTab',
  715. [{ n: 'style', v: 'height: 100%;width: 50%;background-color: white;left: 50%;border-top-left-radius: 8px;text-align: center;transition: all 0.2s linear 0s;' }]);
  716. var textTabsStyle = 'position: relative;top: 11px;font-weight: bold;';
  717. var addTabText = createHTMLElement('div', 'ADD', 'addTabText',
  718. [{ n: 'style', v: textTabsStyle }]);
  719. addTab.appendChild(addTabText);
  720.  
  721. var editTab = createHTMLElement('div', null, 'editTab',
  722. [{ n: 'style', v: 'top: -40px;height: 100%;width: 50%;background-color: #d8d8d8;left: 50%;position: relative;border-top-right-radius: 8px;text-align: center;color: white;transition: all 0.2s linear 0s;' }]);
  723. var editTabText = createHTMLElement('div', 'EDIT', 'editTabText',
  724. [{ n: 'style', v: textTabsStyle }]);
  725. editTab.appendChild(editTabText);
  726.  
  727. tabs.append(addTab, editTab);
  728.  
  729. var addSection = createHTMLElement('div', null, 'addSection',
  730. [{ n: 'style', v: 'height: calc(100% - 40px);width:100%;transition: all 0.2s linear 0s;display:flex; flex-direction: column;' }]);
  731. var addSectionContents = createHTMLElement('div');
  732. var addSectionTitle = createHTMLElement('h2', 'ADD CUSTOM BUTTON', null,
  733. [{ n: 'style', v: style + 'margin-top: 25px' }]);
  734. var title = createHTMLElement('h3', 'Title', null,
  735. [{ n: 'style', v: style + 'margin-top: 20px' }]);
  736. var titleInput = createHTMLElement('input', null, 'titleInput', [{ n: 'placeholder', v: 'Button title' }, { n: 'style', v: style }]);
  737. var URLTitle = createHTMLElement('h3', 'Search URL', null,
  738. [{ n: 'style', v: style + 'margin-top: 20px' }]);
  739. var URLQm = createHTMLElement('i', null, 'URLQuestionmark questionmark fa fa-question-circle', [{ n: 'style', v: 'font-size:16px;margin-left:5px;' }]);
  740. var infoBoxStyle = 'width: 90%;display: inline-block;position: absolute;margin-left: 10px;background-color: white;border-radius: 8px;box-shadow: rgba(0,0,0, 0.3) 0px 0px 10px;transition: opacity 0.3s linear;opacity: 0;padding: 10px;font-weight: normal;font-size: medium;';
  741. var URLInfoBox = createHTMLElement('div', 'To get the search URL first go the site you want to add and search the term "ANIMENAME" in the search field. Then copy the full URL (including http://) in the field below. (exaple: https://myanimelist.net/search/all?q=ANIMENAME)', 'URLInfoBox infoBox',
  742. [{ n: 'style', v: infoBoxStyle }]);
  743. URLTitle.append(URLQm, URLInfoBox);
  744. var URLInput = createHTMLElement('input', null, 'URLInput', [{ n: 'placeholder', v: 'Search URL' }, { n: 'style', v: style + 'width:80%' }]);
  745. var iconTitle = createHTMLElement('h3', 'Icon URL', null,
  746. [{ n: 'style', v: style + 'margin-top: 20px' }]);
  747. var iconQm = createHTMLElement('i', null, 'iconQuestionmark questionmark fa fa-question-circle', [{ n: 'style', v: 'font-size:16px;margin-left:5px;' }]);
  748. var iconInfoBox = createHTMLElement('div', null, 'iconInfoBox infoBox',
  749. [{ n: 'style', v: infoBoxStyle }]);
  750. iconInfoBox.innerHTML = '(<b>Leave empty for automatic icon parse</b>)<br />Link to icon for the button. <br />The easiest way to get it is to copy this link "https://www.google.com/s2/favicons?domain=" and place the website url at the end (example: https://www.google.com/s2/favicons?domain=myanimelist.net).';
  751. iconTitle.append(iconQm, iconInfoBox);
  752. var iconInput = createHTMLElement('input', null, 'iconInput', [{ n: 'placeholder', v: 'Icon URL' }, { n: 'style', v: style + 'width:80%' }]);
  753.  
  754. var msgBoxDiv = createHTMLElement('div', null, 'addMsgBox',
  755. [{ n: 'style', v: 'width: 86%;position: absolute;margin-left: 7%;bottom: 150%;background-color: white;border-radius: 8px;box-shadow: rgba(0,0,0, 0.4) 0px 0px 15px;text-align: center;transition: opacity 0.2s linear;opacity:0' }]);
  756. var msgText = createHTMLElement('div', 'Button added succsessfully! Reload to see it!', 'addMgsText',
  757. [{ n: 'style', v: 'margin: 10px;' }]);
  758. var reloadButton = createHTMLElement('button', 'RELOAD', 'reloadButton',
  759. [{ n: 'style', v: 'margin: 10px;margin-right:0px;width:90px;' }]);
  760. var closeButton = createHTMLElement('button', 'CLOSE', 'closeButton',
  761. [{ n: 'style', v: 'margin: 10px;width:90px;' }]);
  762. msgBoxDiv.append(msgText, reloadButton, closeButton);
  763.  
  764. var buttonsDiv = createHTMLElement('div', null, 'addAndCancelButtons',
  765. [{ n: 'style', v: style + 'margin-bottom: 10px;' }]);
  766. var addButton = createHTMLElement('button', 'ADD', 'addButton',
  767. [{ n: 'style', v: 'width:90px;margin:5px' }]);
  768. var cancelButton = createHTMLElement('button', 'CANCEL', 'cancelButton',
  769. [{ n: 'style', v: 'width:90px;margin:5px' }]);
  770.  
  771. var editSection = createHTMLElement('div', null, 'editSection',
  772. [{ n: 'style', v: 'display: flex;flex-direction: column;height: calc(100% - 40px);width:100%;display:none;transition: all 0.2s linear 0s;' }]);
  773. var editSectionTitle = createHTMLElement('h2', 'EDIT CUSTOM BUTTONS', null,
  774. [{ n: 'style', v: style + 'margin-top: 25px' }]);
  775. var animeButtonsList = createHTMLElement('ul', null, 'buttonsList',
  776. [{ n: 'style', v: 'list-style: none;margin-top: 25px;padding-left: 40px;overflow: hidden;overflow-y: auto;flex: 1 1 auto;' }]);
  777. var animeButtons = document.querySelectorAll('.animeButton');
  778.  
  779. var settingsDiv = createHTMLElement('div', null, 'settingsDiv', [{ n: 'style', v: 'padding: 0px 30px;' }]);
  780. var hideEditCheckbox = createHTMLElement('input', null, 'editCheckbox', [{ n: 'id', v: 'editCheckbox' }, { n: 'type', v: 'checkbox' }, { n: 'value', v: 'editCheckbox' }]);
  781.  
  782. if (autoHide) {
  783. hideEditCheckbox.setAttribute('checked', true);
  784. }
  785.  
  786. var hideEditCheckboxLabel = createHTMLElement('label', 'Auto hide buttons (show on mouseover)', null, [{ n: 'for', v: 'editCheckbox' }, { n: 'style', v: 'padding-left:5px;' }]);
  787. var exportButton = createHTMLElement('button', 'Export custom buttons.', null,
  788. [{ n: 'style', v: 'padding: 4px;' }]);
  789. exportButton.addEventListener('click', exportCustomButtons);
  790.  
  791. var imortExportDiv = createHTMLElement('div', null, null,
  792. [{ n: 'style', v: 'display: flex;justify-content: space-evenly;margin: 7px 0;' }]);
  793. var importInput = createHTMLElement('input', null, null,
  794. [{ n: 'type', v: 'file' }, { n: 'accept', v: 'application/json' }, { n: 'style', v: 'display: none;' }]);
  795. importInput.addEventListener('change', onImportChange);
  796. var importButton = createHTMLElement('button', 'Import custom buttons.', null,
  797. [{ n: 'style', v: 'padding: 4px;' }]);
  798. importButton.addEventListener('click', () => { importInput.click(); });
  799. imortExportDiv.append(exportButton, importButton, importInput);
  800.  
  801. settingsDiv.append(hideEditCheckbox, hideEditCheckboxLabel, imortExportDiv);
  802.  
  803. var editButtonsDiv = createHTMLElement('div', null, 'addAndCancelButtons',
  804. [{ n: 'style', v: style + 'margin-bottom: 10px;' }]);
  805. var cancelButtonEdit = createHTMLElement('button', 'CLOSE', 'cancelButton',
  806. [{ n: 'style', v: 'width:90px;margin:5px' }]);
  807. editButtonsDiv.appendChild(cancelButtonEdit);
  808.  
  809. createAndAppendEditListEntry(animeButtonsList, animeButtons);
  810.  
  811. buttonsDiv.append(addButton, cancelButton);
  812. addSectionContents.append(addSectionTitle, title, titleInput, URLTitle, URLInput, iconTitle, iconInput);
  813. addSection.append(addSectionContents, buttonsDiv);
  814.  
  815. editSection.append(editSectionTitle, animeButtonsList, settingsDiv, editButtonsDiv);
  816.  
  817. popUp.append(tabs, addSection, editSection, msgBoxDiv);
  818. var html = getElement('html');
  819. html.appendChild(popUp);
  820.  
  821. buttonsDiv.addEventListener('click', addAndCancelButtonsHandler);
  822. editButtonsDiv.addEventListener('click', addAndCancelButtonsHandler);
  823. tabs.addEventListener('click', addAndEditTabButtonsHandler);
  824. animeButtonsList.addEventListener('click', hideAndDeleteHandler);
  825. msgBoxDiv.addEventListener('click', msgButtonsHandler);
  826. settingsDiv.addEventListener('click', settingsHandler);
  827. URLQm.addEventListener('mouseover', URLQuestionmarkHandler);
  828. iconQm.addEventListener('mouseover', iconQuestionmarkHandler);
  829. popUp.addEventListener('click', popupClickHandler);
  830. }
  831.  
  832. function createAndAppendEditListEntry(animeButtonsList, animeButtons) {
  833. animeButtons.forEach((b) => {
  834. var listEl = createHTMLElement('li', null, b.id,
  835. [{ n: 'style', v: 'width:90%;margin-top:5px;border-bottom-style: inset;border-bottom-width: thin;' }]);
  836. var imgUrl = b.firstElementChild.getAttribute('src');
  837. var img = createHTMLElement('img', null, null, [{ n: 'src', v: imgUrl }, { n: 'style', v: 'width: 16px;height: 16px;' }]);
  838. var hideIcon = createHTMLElement('i', null, 'hideButton fa fa-eye', [{ n: 'title', v: 'Toggle Hide' }, { n: 'style', v: 'font-size:16px;position: relative;left: 82%;' }]);
  839. var removeIcon = createHTMLElement('i', null, 'removeButton fa fa-trash-alt', [{ n: 'title', v: 'DELETE' }, { n: 'style', v: 'font-size:16px;position: relative;left: 85%;' }]);
  840. var span = createHTMLElement('span', b.getAttribute('title'), null,
  841. [{ n: 'style', v: 'margin-left:5px;bottom: 2px;position: relative;right: 16px;' }]);
  842.  
  843. if (b.style.display === 'none') {
  844. hideIcon.classList.replace('fa-eye', 'fa-eye-slash');
  845. }
  846.  
  847. listEl.append(img, hideIcon, span);
  848.  
  849. if (!b.className.includes('stockButton')) {
  850. listEl.insertBefore(removeIcon, span);
  851. span.style.right = '32px';
  852. }
  853.  
  854. animeButtonsList.appendChild(listEl);
  855. });
  856. }
  857.  
  858. function onImportChange(e) {
  859. var reader = new FileReader();
  860. reader.readAsText(e.target.files[0]);
  861. reader.onload = (e) => {
  862. importCustomButtons(e.target.result);
  863. };
  864. }
  865.  
  866. function importCustomButtons(buttons) {
  867. GM_setValue('setting:buttonsNames', buttons);
  868. toggleMsgBox(true, 'Import successful, reload the page for it to take effect.', true);
  869. }
  870.  
  871. function exportCustomButtons() {
  872. var customButtonsStr = GM_getValue('setting:buttonsNames', '[]');
  873. downloadJson(`custom-buttons-plus-export-${new Date().toISOString()}`, customButtonsStr);
  874. }
  875.  
  876. function downloadJson(filename, jsonString) {
  877. var element = createHTMLElement('a', null, null,
  878. [{ n: 'href', v: 'data:application/json;charset=utf-8,' + encodeURIComponent(jsonString) },
  879. { n: 'download', v: `${filename}.json` }]);
  880.  
  881. element.style.display = 'none';
  882. document.body.appendChild(element);
  883. element.click();
  884. document.body.removeChild(element);
  885. }
  886.  
  887. function createHTMLElement(tag, textContent, className, attributes) {
  888. var element = document.createElement(tag);
  889.  
  890. if (className) {
  891. element.className = className;
  892. }
  893. if (textContent) {
  894. element.textContent = textContent;
  895. }
  896. if (attributes) {
  897. attributes.forEach((a) => {
  898. element.setAttribute(a.n, a.v);
  899. });
  900. }
  901.  
  902. return element;
  903. }