Pixiv - Fast add bookmark

Add, edit or remove bookmark (illustration/animation/manga/novel) with one click.

  1. // ==UserScript==
  2. // @name Pixiv - Fast add bookmark
  3. // @namespace https://www.github.com/soosad
  4. // @version 2.3.0
  5. // @description Add, edit or remove bookmark (illustration/animation/manga/novel) with one click.
  6. // @author https://www.github.com/soosad
  7. // @match *://www.pixiv.net/*
  8. // @run-at document-end
  9. // @require https://unpkg.com/react@16/umd/react.production.min.js
  10. // @require https://unpkg.com/react-dom@16/umd/react-dom.production.min.js
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function main() {
  15. const isReact = () => typeof globalInitData === 'object';
  16. const token = () => (isReact() ? globalInitData.token : pixiv.context.token);
  17. const getUserId = () => (isReact() ? globalInitData.userData.id : pixiv.user.id);
  18. const rc = React.Component;
  19. const rce = React.createElement;
  20. const rdr = ReactDOM.render;
  21. const rdcp = ReactDOM.createPortal;
  22. const pfb = {
  23. pathData: {
  24. heartPathBorder:
  25. 'M21,5.5 C24.8659932,5.5 28,8.63400675 28,12.5 C28,18.2694439 24.2975093,'
  26. + '23.1517313 17.2206059,27.1100183'
  27. + 'C16.4622493,27.5342993 15.5379984,27.5343235 14.779626,27.110148 C7.70250208,'
  28. + '23.1517462 4,18.2694529 4,12.5'
  29. + 'C4,8.63400691 7.13400681,5.5 11,5.5 C12.829814,5.5 14.6210123,6.4144028 16,7.8282366'
  30. + 'C17.3789877,6.4144028 19.170186,5.5 21,5.5 Z',
  31. heartPathBackground:
  32. 'M16,11.3317089 C15.0857201,9.28334665 13.0491506,7.5 11,7.5'
  33. + 'C8.23857625,7.5 6,9.73857647 6,12.5 C6,17.4386065 9.2519779,21.7268174 15.7559337,'
  34. + '25.3646328'
  35. + 'C15.9076021,25.4494645 16.092439,25.4494644 16.2441073,25.3646326 C22.7480325,'
  36. + '21.7268037 26,17.4385986 26,12.5'
  37. + 'C26,9.73857625 23.7614237,7.5 21,7.5 C18.9508494,7.5 16.9142799,9.28334665 16,'
  38. + '11.3317089 Z',
  39. padlockPathBorder:
  40. 'M29.9796 20.5234C31.1865 21.2121 32 22.511 32 24V28C32 30.2091 30.2091 '
  41. + '32 28 32H21'
  42. + 'C18.7909 32 17 30.2091 17 28V24C17 22.511 17.8135 21.2121 19.0204 20.5234C19.2619 '
  43. + '17.709 21.623 15.5 24.5 15.5'
  44. + 'C27.377 15.5 29.7381 17.709 29.9796 20.5234Z',
  45. padlockPathBackground:
  46. 'M28 22C29.1046 22 30 22.8954 30 24V28C30 29.1046 29.1046 30 28 30H21'
  47. + 'C19.8954 30 19 29.1046 19 28V24C19 22.8954 19.8954 22 21 22V21C21 19.067 '
  48. + '22.567 17.5 24.5 17.5'
  49. + 'C26.433 17.5 28 19.067 28 21V22ZM23 21C23 20.1716 23.6716 19.5 24.5 19.5C25.3284 19.5 '
  50. + '26 20.1716 26 21V22H23V21Z',
  51. dotMenu:
  52. 'M16,18 C14.8954305,18 14,17.1045695 14,16 C14,14.8954305 14.8954305,14 16,'
  53. + '14 C17.1045695,14 18,14.8954305 18,16 C18,17.1045695 17.1045695,18 16,18 Z M9,'
  54. + '18 C7.8954305,18 7,17.1045695 7,16 C7,14.8954305 7.8954305,14 9,14 C10.1045695,'
  55. + '14 11,14.8954305 11,16 C11,17.1045695 10.1045695,18 9,18 Z M23,18 C21.8954305,18 21,'
  56. + '17.1045695 21,16 C21,14.8954305 21.8954305,14 23,14 C24.1045695,14 25,14.8954305 25,'
  57. + '16 C25,17.1045695 24.1045695,18 23,18 Z',
  58. },
  59. classList: {
  60. MAIN_ID: 'pfbMain',
  61. PORTAL_ID: 'pfbPortal',
  62. BUTTON_HEART: 'pfb_button-heart',
  63. ANCHOR_HEART: 'pfb_anchor-heart',
  64. HEART_EMPTY: 'pfb_heart-empty',
  65. HEART_PUBLIC: 'pfb_heart-public',
  66. HEART_PRIVATE: 'pfb_heart-private',
  67. HEART_BORDER: 'pfb_heart-border',
  68. HEART_BACKGROUND: 'pfb_heart-background',
  69. PADLOCK_BORDER: 'pfb_padlock-border',
  70. PADLOCK_BACKGROUND: 'pfb_padlock-background',
  71. NIGHT_THEME: 'pfb_night-theme',
  72. MAIN_CONTAINER: 'pfb_container',
  73. LIGHT_PANEL: 'pfb_light-panel',
  74. ADVANCED_PANEL: 'pfb_advanced-panel',
  75. ADD_BUTTON: 'pfb_add-button',
  76. REMOVE_BUTTON: 'pfb_remove-button',
  77. MORE_BUTTON: 'pfb_more-button',
  78. BUTTON_CONTAINER: 'pfb_button-container',
  79. BOOKMAKRED: 'pfb_bookmarked',
  80. ACTION_SECTION: 'pfb_action-section',
  81. COMMENT_SECTION: 'pfb_comment-section',
  82. TITLE_SECTION: 'pfb_title-section',
  83. TAGS_SECTION: 'pfb_tags-section',
  84. WORKS_TAGS: 'pfb_work-tags',
  85. TITLE_TAG_LIST: 'pfb_title-tag-list',
  86. TAG: 'pfb_tag',
  87. TAG_ADDED: 'pfb_tag-added',
  88. ADVANCED_PANEL_SECTION: 'pfb_section',
  89. PANEL: 'pfb_panel',
  90. ADVANCED_PANEL_HEADER: 'pfb_header',
  91. TITLE: 'pfb_title',
  92. CLOSE_ADVANCED_PANEL: 'pfb_close-advanced-panel',
  93. ACTION_BUTTONS: 'pfb_action-btns',
  94. ACTION_THEME: 'pfb_action-theme',
  95. NIGHT_THEME_BUTTON: 'pfb_night-theme-btn',
  96. LIGHT_THEME_BUTTON: 'pfb_light-theme-btn',
  97. FLOAT_CONTAINER_ID: 'pfb_float-container',
  98. SVG_BOOKMARKED: 'btBeIl',
  99. SVG_NONE: 'inFaFn',
  100. FLOAT_BUTTON_CONTAINER: 'pfb_f-btn-container',
  101. FLOAT_BUTTON: 'pfb_f-btn',
  102. FLOAT_BTN_BOOKMARKED: 'pfb_f-bookmarked',
  103. FLOAT_SVG_HEART: 'pfb_f-svg-heart',
  104. FLOAT_PATH_BORDER: 'pfb_f-path-heart-border',
  105. FLOAT_PATH_BACKGROUND: 'pfb_f-path-heart-background',
  106. FLOAT_PATH_PADLOCK_BORDER: 'pfb_f-path-padlock-border',
  107. FLOAT_PATH_PADLOCK_BACKGROUND: 'pfb_f-path-padlock-background',
  108. FLOAT_SVG_LINE: 'pfb_f-svg-line',
  109. },
  110. selectorsList: {
  111. currentWorkHeartSelector: '#root main section section > div:nth-child(3)',
  112. currentWorkHeartNavSelector: 'article > div > figure + section .jcSCsn + div > div > button',
  113. currentNovelHeartSelector: 'article section > div:nth-child(3)',
  114. heartButtonSelector: 'button.bAzGIE',
  115. heartImgSelector: '._one-click-bookmark',
  116. placeIllustrationSelector: '#root main section section',
  117. placeNovelSelector: 'article section',
  118. portalIllustrationSelector: '#root figure > figcaption ul + div',
  119. portalNovelSelector: '#root article footer + ul + div',
  120. numberOfBookmarksSelector: 'dd[title=Bookmarks]',
  121. pixivWelcomeTitle: 'h2.welcome',
  122. pixivErrorTitle: 'h2.error-title',
  123. tagNovel: '.tags > .tag > a.text',
  124. tagIllust: 'figcaption footer > ul > li a',
  125. closestDivCont: 'div[width]',
  126. closestDivContNovel: 'li',
  127. closestDivContNovelSize: 'section > div > div > div',
  128. anchorNovel: 'a[href*="/novel/show.php?id="]',
  129. anchorIllust: 'a[href*="/artworks/"]',
  130. },
  131. regexList: {
  132. novelPath: new RegExp('^\\/novel\\/show\\.php$', 'g'),
  133. novelPath2: new RegExp('\\/novel\\/show\\.php\\?.*id=\\d+.*'),
  134. illustPath: new RegExp('\\/artworks\\/\\d+', 'g',),
  135. },
  136. urlList: {
  137. workData(workType, id) {
  138. return `https://www.pixiv.net/ajax/${workType}/${id}`;
  139. },
  140. bookmarkDataUrl(workType, illustId) {
  141. return `https://www.pixiv.net/ajax/${workType}/${illustId}/bookmarkData`;
  142. },
  143. bookmarkTagsUrl(worksType, userId) {
  144. return `https://www.pixiv.net/ajax/user/${userId}/${worksType}/bookmark/tags`;
  145. },
  146. illustBookmarkUrl(illustId) {
  147. return `https://www.pixiv.net/bookmark_add.php?type=illust&illust_id=${illustId}`;
  148. },
  149. novelBookmarkUrl(novelId) {
  150. return `https://www.pixiv.net/novel/bookmark_add.php?id=${novelId}`;
  151. },
  152. novelBookmarkDetailUrl(novelId) {
  153. return `https://www.pixiv.net/novel/bookmark_detail.php?id=${novelId}`;
  154. },
  155. },
  156. scriptData: {
  157. cssVersion: '220',
  158. isUserScript: true,
  159. pfbnightTitle: 'pfbnight',
  160. },
  161. fetchData: {
  162. urlList: {
  163. addIllustBookmarkUrl: '/ajax/illusts/bookmarks/add',
  164. removeIllustBookmarkUrl: '/rpc/index.php',
  165. removeNovelBookmarkUrl: '/ajax/novels/bookmarks/delete',
  166. addNovelBookmarkUrl: '/ajax/novels/bookmarks/add',
  167. },
  168. args: {
  169. getArgs: {
  170. credentials: 'same-origin',
  171. headers: { Accept: 'application/json' },
  172. },
  173. bookmarkAdd: {
  174. credentials: 'same-origin',
  175. headers: {
  176. Accept: 'application/json',
  177. 'Content-Type': 'application/json; charset=utf-8',
  178. 'X-CSRF-Token': token(),
  179. },
  180. method: 'POST',
  181. },
  182. bookmarkRemove: {
  183. credentials: 'same-origin',
  184. headers: {
  185. Accept: 'application/json',
  186. 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
  187. 'X-CSRF-Token': token(),
  188. },
  189. method: 'POST',
  190. },
  191. },
  192. body: {
  193. illustRemove(bookmarkId) {
  194. return `mode=delete_illust_bookmark&bookmark_id=${bookmarkId}`;
  195. },
  196. novelRemove(bookmarkId) {
  197. return `del=1&book_id=${bookmarkId}`;
  198. },
  199. },
  200. },
  201. data: {},
  202. elementsList: {
  203. path([d, className]) {
  204. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  205. path.classList.add(className);
  206. path.setAttribute('d', d);
  207. return path;
  208. },
  209. heart(className, d) {
  210. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  211. svg.setAttribute('viewBox', '0 0 32 32');
  212. svg.setAttribute('width', '32');
  213. svg.setAttribute('height', '32');
  214. svg.classList.add(className);
  215. d.forEach((pathD) => {
  216. const path = this.path(pathD);
  217. svg.appendChild(path);
  218. });
  219. return svg;
  220. },
  221. publicHeart() {
  222. const { HEART_PUBLIC, HEART_BORDER, HEART_BACKGROUND } = pfb.classList;
  223. const { heartPathBorder, heartPathBackground } = pfb.pathData;
  224. const heart = this.heart(HEART_PUBLIC, [
  225. [heartPathBorder, HEART_BORDER],
  226. [heartPathBackground, HEART_BACKGROUND],
  227. ]);
  228. return heart;
  229. },
  230. privateHeart() {
  231. const {
  232. HEART_PRIVATE,
  233. HEART_BORDER,
  234. HEART_BACKGROUND,
  235. PADLOCK_BORDER,
  236. PADLOCK_BACKGROUND,
  237. } = pfb.classList;
  238. const {
  239. heartPathBorder,
  240. heartPathBackground,
  241. padlockPathBorder,
  242. padlockPathBackground,
  243. } = pfb.pathData;
  244. const heart = this.heart(HEART_PRIVATE, [
  245. [heartPathBorder, HEART_BORDER],
  246. [heartPathBackground, HEART_BACKGROUND],
  247. [padlockPathBorder, PADLOCK_BORDER],
  248. [padlockPathBackground, PADLOCK_BACKGROUND],
  249. ]);
  250. return heart;
  251. },
  252. emptyHeart() {
  253. const { HEART_EMPTY, HEART_BORDER, HEART_BACKGROUND } = pfb.classList;
  254. const { heartPathBorder, heartPathBackground } = pfb.pathData;
  255. const heart = this.heart(HEART_EMPTY, [
  256. [heartPathBorder, HEART_BORDER],
  257. [heartPathBackground, HEART_BACKGROUND],
  258. ]);
  259. return heart;
  260. },
  261. buttonNovel(props) {
  262. const { bookmarkCount, bookmarkId } = props;
  263. const { novelBookmarkUrl, novelBookmarkDetailUrl } = pfb.urlList;
  264. const { illustId } = pfb.data;
  265. return rce(
  266. 'div',
  267. null,
  268. bookmarkId
  269. ? rce(
  270. 'a',
  271. {
  272. href: novelBookmarkDetailUrl(illustId),
  273. className: 'bookmark-count _ui-tooltip',
  274. 'data-tooltip': `${bookmarkCount} Bookmarks`,
  275. },
  276. rce('i', { className: '_icon _bookmark-icon-inline' }),
  277. bookmarkCount,
  278. )
  279. : null,
  280. rce(
  281. 'a',
  282. {
  283. href: novelBookmarkUrl(illustId),
  284. className: `_bookmark-toggle-button ${
  285. bookmarkId ? 'bookmarked edit-bookmark' : 'add-bookmark'
  286. }`,
  287. },
  288. !bookmarkId ? rce('span', { className: 'bookmark-icon' }) : null,
  289. rce(
  290. 'span',
  291. { className: 'description' },
  292. bookmarkId ? 'Edit bookmark' : 'Add to bookmarks',
  293. ),
  294. ),
  295. );
  296. },
  297. svgHeart(props) {
  298. const { isPrivateBookmark, bookmarkId } = props;
  299. const {
  300. HEART_EMPTY,
  301. HEART_PRIVATE,
  302. HEART_PUBLIC,
  303. HEART_BORDER,
  304. HEART_BACKGROUND,
  305. PADLOCK_BORDER,
  306. PADLOCK_BACKGROUND,
  307. } = pfb.classList;
  308. const {
  309. heartPathBorder,
  310. heartPathBackground,
  311. padlockPathBorder,
  312. padlockPathBackground,
  313. } = pfb.pathData;
  314.  
  315. return rce(
  316. 'svg',
  317. {
  318. className: `${HEART_EMPTY} ${
  319. bookmarkId ? `${isPrivateBookmark ? HEART_PRIVATE : HEART_PUBLIC}` : ''
  320. }`,
  321. viewBox: '0 0 32 32',
  322. width: 32,
  323. height: 32,
  324. },
  325. rce('path', { className: HEART_BORDER, d: heartPathBorder }),
  326. rce('path', { className: HEART_BACKGROUND, d: heartPathBackground }),
  327. isPrivateBookmark
  328. ? rce('path', { className: PADLOCK_BORDER, d: padlockPathBorder })
  329. : null,
  330. isPrivateBookmark
  331. ? rce('path', { className: PADLOCK_BACKGROUND, d: padlockPathBackground })
  332. : null,
  333. );
  334. },
  335. buttonHeart(props) {
  336. const {
  337. bookmarkId, addBookmark, isPrivateBookmark, disabledButtons,
  338. } = props;
  339. const { BUTTON_HEART } = pfb.classList;
  340. const { illustBookmarkUrl } = pfb.urlList;
  341. return bookmarkId
  342. ? rce(
  343. 'a',
  344. { className: BUTTON_HEART, href: illustBookmarkUrl(pfb.data.illustId) },
  345. rce(pfb.elementsList.svgHeart, { isPrivateBookmark, bookmarkId }),
  346. )
  347. : rce(
  348. 'button',
  349. {
  350. className: BUTTON_HEART,
  351. onClick: e => addBookmark(e, 0),
  352. disabled: disabledButtons,
  353. },
  354. rce(pfb.elementsList.svgHeart, { isPrivateBookmark, bookmarkId }),
  355. );
  356. },
  357. svgLine(props) {
  358. return rce(
  359. 'svg',
  360. { viewBox: props.viewBox },
  361. props.lines.map((l, i) => rce('line', {
  362. key: i, x1: l[0], y1: l[1], x2: l[2], y2: l[3],
  363. })),
  364. );
  365. },
  366. svgPath(props) {
  367. return rce('svg', null, props.d.map((d, i) => rce('path', { d, key: i })));
  368. },
  369. buttonLight(props) {
  370. const { BUTTON_CONTAINER } = pfb.classList;
  371. return rce(
  372. 'div',
  373. { className: BUTTON_CONTAINER },
  374. rce('button', { type: 'button', ...props }, props.children),
  375. );
  376. },
  377. themeButton(props) {
  378. const { onClick, className, title } = props;
  379. return rce(
  380. 'button',
  381. { onClick, className, title },
  382. rce('svg', null, rce('rect', { x: 0, y: 0 })),
  383. );
  384. },
  385. header(props) {
  386. const {
  387. BOOKMAKRED,
  388. ADVANCED_PANEL_HEADER,
  389. TITLE,
  390. CLOSE_ADVANCED_PANEL,
  391. ACTION_BUTTONS,
  392. ACTION_THEME,
  393. NIGHT_THEME_BUTTON,
  394. LIGHT_THEME_BUTTON,
  395. } = pfb.classList;
  396. const {
  397. toggleMoreOptions,
  398. bookmarkId,
  399. isPrivateBookmark,
  400. addBookmark,
  401. removeBookmark,
  402. disabledButtons,
  403. } = props;
  404. return rce(
  405. 'div',
  406. { className: ADVANCED_PANEL_HEADER },
  407. rce(
  408. 'div',
  409. { className: TITLE },
  410. rce('h1', null, rce('div', null, rce('span', null, 'Update bookmark'))),
  411. rce(
  412. 'button',
  413. { className: CLOSE_ADVANCED_PANEL, onClick: e => toggleMoreOptions(e, false) },
  414. rce(pfb.elementsList.svgLine, {
  415. viewBox: '0 0 8 8',
  416. lines: [['1', '1', '7', '7'], ['7', '1', '1', '7']],
  417. }),
  418. ),
  419. ),
  420. rce(
  421. 'div',
  422. { className: ACTION_BUTTONS },
  423. rce(
  424. 'button',
  425. {
  426. className: `${isPrivateBookmark === false ? BOOKMAKRED : ''}`,
  427. onClick: e => addBookmark(e, '0'),
  428. disabled: disabledButtons,
  429. },
  430. 'Public',
  431. ),
  432. rce(
  433. 'button',
  434. {
  435. className: `${isPrivateBookmark === true ? BOOKMAKRED : ''}`,
  436. onClick: e => addBookmark(e, '1'),
  437. disabled: disabledButtons,
  438. },
  439. 'Private',
  440. ),
  441. bookmarkId
  442. ? rce(
  443. 'button',
  444. {
  445. onClick: e => removeBookmark(e, bookmarkId),
  446. disabled: disabledButtons,
  447. },
  448. 'Remove',
  449. )
  450. : null,
  451. ),
  452. rce(
  453. 'div',
  454. { className: ACTION_THEME },
  455. rce('span', null, 'Theme:'),
  456. rce(
  457. 'div',
  458. null,
  459. rce(pfb.elementsList.themeButton, {
  460. className: NIGHT_THEME_BUTTON,
  461. title: 'Night',
  462. onClick: e => pfb.changeTheme(e, true),
  463. }),
  464. rce(pfb.elementsList.themeButton, {
  465. className: LIGHT_THEME_BUTTON,
  466. title: 'Light',
  467. onClick: e => pfb.changeTheme(e, false),
  468. }),
  469. ),
  470. ),
  471. );
  472. },
  473. inputSection(props) {
  474. const {
  475. className, count, limit, onChange, value, title, placeholder, maxLength,
  476. } = props;
  477. return rce(
  478. 'div',
  479. null,
  480. rce(
  481. 'div',
  482. { className },
  483. rce('h2', null, title),
  484. rce('div', null, rce('span', null, count), rce('span', null, limit)),
  485. ),
  486. rce('input', {
  487. type: 'text', placeholder, value, onChange, maxLength,
  488. }),
  489. );
  490. },
  491. tagList(props) {
  492. const { TAG_ADDED, TITLE_TAG_LIST, TAG } = pfb.classList;
  493. const {
  494. listOfTags, tagsForBookmark, addTagToInput, text,
  495. } = props;
  496. return rce(
  497. 'div',
  498. null,
  499. rce('span', { className: TITLE_TAG_LIST }, text),
  500. listOfTags.map((item, id) => rce(
  501. 'span',
  502. {
  503. className: `${TAG} ${
  504. tagsForBookmark.includes(`${item.tag}`.toLowerCase()) ? TAG_ADDED : ''
  505. }`,
  506. key: id,
  507. onClick: e => addTagToInput(e, item.tag),
  508. },
  509. `${item.tag} (${item.cnt})`,
  510. )),
  511. );
  512. },
  513. action(props) {
  514. const {
  515. handleChange,
  516. inputTags,
  517. inputComment,
  518. workTags,
  519. addTagToInput,
  520. userTags,
  521. sortUserTags,
  522. workTagsLowerCase,
  523. nameSort,
  524. countSort,
  525. } = props;
  526. const { privateTags, publicTags } = userTags;
  527. const {
  528. ACTION_SECTION,
  529. COMMENT_SECTION,
  530. TITLE_SECTION,
  531. TAGS_SECTION,
  532. WORKS_TAGS,
  533. TITLE_TAG_LIST,
  534. TAG,
  535. TAG_ADDED,
  536. } = pfb.classList;
  537. const charCount = inputComment.length;
  538. const tags = inputTags.match(/\S+/g, '') || [];
  539. const tagsForBookmark = tags.map(item => `${item}`.toLowerCase());
  540. const tagsCount = tagsForBookmark.length;
  541. return rce(
  542. 'div',
  543. { className: ACTION_SECTION },
  544. rce(
  545. 'div',
  546. { className: COMMENT_SECTION },
  547. rce(pfb.elementsList.inputSection, {
  548. className: TITLE_SECTION,
  549. count: charCount,
  550. limit: '/140',
  551. onChange: e => handleChange(e, true),
  552. value: inputComment,
  553. title: 'Bookmark comment',
  554. placeholder: 'Leave a comment...',
  555. maxLength: 140,
  556. isTagsSection: false,
  557. }),
  558. ),
  559. rce(
  560. 'div',
  561. { className: TAGS_SECTION },
  562. rce(pfb.elementsList.inputSection, {
  563. className: TITLE_SECTION,
  564. count: tagsCount,
  565. limit: '/10',
  566. onChange: e => handleChange(e, false),
  567. value: inputTags,
  568. title: 'Bookmark tags',
  569. placeholder: 'Add tags for your favourite bookmark',
  570. maxLength: 140,
  571. isTagsSection: true,
  572. }),
  573. rce(
  574. 'div',
  575. { className: WORKS_TAGS },
  576. rce(
  577. 'div',
  578. null,
  579. rce('span', { className: TITLE_TAG_LIST }, 'Tags for this work'),
  580. rce(
  581. 'div',
  582. null,
  583. [['Lower case', true], ['Orignal', false]].map((item, i) => rce(
  584. 'span',
  585. {
  586. className: 'pfb_work-tags-options',
  587. key: i,
  588. onClick: e => workTagsLowerCase(e, item[1]),
  589. },
  590. item[0],
  591. )),
  592. ),
  593. ),
  594. rce(
  595. 'div',
  596. null,
  597. workTags.map((item, id) => rce(
  598. 'span',
  599. {
  600. className: `${TAG} ${
  601. tagsForBookmark.includes(`${item}`.toLowerCase()) ? TAG_ADDED : ''
  602. }`,
  603. key: id,
  604. onClick: e => addTagToInput(e, item),
  605. },
  606. item,
  607. )),
  608. ),
  609. ),
  610. rce(
  611. 'div',
  612. { className: WORKS_TAGS },
  613. rce(
  614. 'div',
  615. null,
  616. rce('span', { className: TITLE_TAG_LIST }, 'Your bookmark tags'),
  617. rce(
  618. 'div',
  619. null,
  620. [
  621. ['Sort by name', 0, 'nameSort', nameSort],
  622. ['Sort by count', 1, 'countSort', countSort],
  623. ].map((item, i) => rce(
  624. 'span',
  625. {
  626. className: 'pfb_work-tags-options',
  627. key: i,
  628. onClick: e => sortUserTags(e, item[1], item[2], item[3]),
  629. },
  630. item[0],
  631. )),
  632. ),
  633. ),
  634. rce(
  635. 'div',
  636. null,
  637. publicTags.length
  638. ? rce(pfb.elementsList.tagList, {
  639. listOfTags: publicTags,
  640. addTagToInput,
  641. tagsForBookmark,
  642. text: 'Public:',
  643. })
  644. : null,
  645. privateTags.length
  646. ? rce(pfb.elementsList.tagList, {
  647. listOfTags: privateTags,
  648. addTagToInput,
  649. tagsForBookmark,
  650. text: 'Private:',
  651. })
  652. : null,
  653. ),
  654. ),
  655. ),
  656. );
  657. },
  658. advancedPanel(props) {
  659. const { ADVANCED_PANEL, ADVANCED_PANEL_SECTION, PANEL } = pfb.classList;
  660. const {
  661. toggleMoreOptions,
  662. bookmarkId,
  663. isPrivateBookmark,
  664. addBookmark,
  665. inputComment,
  666. workTags,
  667. userTags,
  668. workTagsLowerCase,
  669. inputTags,
  670. detectClickOutsidePanel,
  671. addTagToInput,
  672. sortUserTags,
  673. nameSort,
  674. countSort,
  675. handleChange,
  676. removeBookmark,
  677. disabledButtons,
  678. } = props;
  679. return rce(
  680. 'div',
  681. { className: ADVANCED_PANEL, onClick: e => detectClickOutsidePanel(e), title: '' },
  682. rce(
  683. 'div',
  684. { className: ADVANCED_PANEL_SECTION },
  685. rce(
  686. 'div',
  687. { className: PANEL },
  688. rce(pfb.elementsList.header, {
  689. toggleMoreOptions,
  690. bookmarkId,
  691. isPrivateBookmark,
  692. addBookmark,
  693. removeBookmark,
  694. disabledButtons,
  695. }),
  696. rce(pfb.elementsList.action, {
  697. handleChange,
  698. userTags,
  699. workTagsLowerCase,
  700. sortUserTags,
  701. workTags,
  702. nameSort,
  703. countSort,
  704. addTagToInput,
  705. inputComment,
  706. inputTags,
  707. }),
  708. ),
  709. ),
  710. );
  711. },
  712. lightPanel(props) {
  713. const {
  714. LIGHT_PANEL, MORE_BUTTON, ADD_BUTTON, REMOVE_BUTTON, BOOKMAKRED,
  715. } = pfb.classList;
  716. const { dotMenu } = pfb.pathData;
  717. const { buttonLight, svgLine, svgPath } = pfb.elementsList;
  718. const {
  719. isPrivateBookmark,
  720. bookmarkId,
  721. removeBookmark,
  722. addBookmark,
  723. toggleMoreOptions,
  724. disabledButtons,
  725. } = props;
  726. return rce(
  727. 'div',
  728. { className: LIGHT_PANEL },
  729. rce(
  730. buttonLight,
  731. {
  732. className: MORE_BUTTON,
  733. title: 'More options',
  734. onClick: e => toggleMoreOptions(e, true),
  735. },
  736. rce(svgPath, { d: [dotMenu] }),
  737. ),
  738. bookmarkId
  739. ? rce(
  740. buttonLight,
  741. {
  742. className: REMOVE_BUTTON,
  743. title: 'Remove bookmark',
  744. onClick: e => removeBookmark(e, bookmarkId),
  745. disabled: disabledButtons,
  746. },
  747. rce(svgLine, {
  748. viewBox: '0 0 8 8',
  749. lines: [['1', '1', '7', '7'], ['7', '1', '1', '7']],
  750. }),
  751. )
  752. : null,
  753. rce(
  754. buttonLight,
  755. {
  756. className: `${ADD_BUTTON} ${isPrivateBookmark === true ? BOOKMAKRED : ''}`,
  757. onClick: e => addBookmark(e, '1'),
  758. disabled: disabledButtons,
  759. },
  760. 'Private',
  761. ),
  762. rce(
  763. buttonLight,
  764. {
  765. className: `${ADD_BUTTON} ${isPrivateBookmark === false ? BOOKMAKRED : ''}`,
  766. onClick: e => addBookmark(e, '0'),
  767. disabled: disabledButtons,
  768. },
  769. 'Public',
  770. ),
  771. );
  772. },
  773. floatContainer() {
  774. const { FLOAT_CONTAINER_ID } = pfb.classList;
  775. const div = document.createElement('div');
  776. div.id = FLOAT_CONTAINER_ID;
  777. return div;
  778. },
  779. },
  780.  
  781. miniBookmarkInitialize() {
  782. const { FLOAT_CONTAINER_ID } = this.classList;
  783. document.body.appendChild(this.elementsList.floatContainer());
  784. class Mini extends rc {
  785. constructor(props) {
  786. super(props);
  787. this.state = {
  788. illustsMini: {},
  789. novelsMini: {},
  790. detected: false,
  791. show: false,
  792. currentWork: {
  793. currentId: null,
  794. isNovel: null,
  795. position: { top: '0px', left: '0px' },
  796. },
  797. btnsDisabled: true,
  798. };
  799. this.detectHeart = this.detectHeart.bind(this);
  800. }
  801.  
  802. componentDidMount() {
  803. document.addEventListener('mouseover', this.detectHeart);
  804. }
  805.  
  806. detectHeart(e) {
  807. const { target } = e;
  808. if (target.closest(`#${FLOAT_CONTAINER_ID}`)) return;
  809. const { heartButtonSelector, heartImgSelector } = pfb.selectorsList;
  810. const button = target.closest(heartButtonSelector) || target.closest(heartImgSelector);
  811. if (!button) {
  812. if (this.state.detected) {
  813. this.setState({ detected: false, show: false, btnsDisabled: true });
  814. }
  815. return;
  816. }
  817. if (this.state.detected) return;
  818. this.setState({ detected: true });
  819. const ss = (stateM, isNovel, id, isPrivate, bookmarkId, position, show, btnsDisabled) => {
  820. this.setState(prevState => ({
  821. [stateM]: {
  822. ...prevState[stateM],
  823. [id]: { id, isPrivate, bookmarkId },
  824. },
  825. currentWork: { currentId: id, position, isNovel },
  826. show,
  827. btnsDisabled,
  828. }));
  829. };
  830. let id;
  831. let isBookmarked;
  832. let isNovel;
  833. if (pfb.data.isReactApp) {
  834. const { novelPath } = pfb.regexList;
  835. const {
  836. closestDivCont,
  837. closestDivContNovel,
  838. closestDivContNovelSize,
  839. anchorNovel,
  840. anchorIllust,
  841. } = pfb.selectorsList;
  842. const { SVG_BOOKMARKED, SVG_NONE } = pfb.classList;
  843. const elem = button.closest(closestDivCont)
  844. || button.closest(closestDivContNovel)
  845. || button.closest(closestDivContNovelSize);
  846. if (!elem) return;
  847. const { href } = elem.querySelector(anchorNovel) || elem.querySelector(anchorIllust);
  848. isNovel = new URL(href).pathname.match(novelPath);
  849. const illId = href.match(/\/artworks\/\d+/)[0].match(/\d+/)[0];
  850. id = +illId;
  851. const svg = button.querySelector('svg');
  852. isBookmarked = svg.classList.contains(SVG_BOOKMARKED)
  853. || !svg.classList.contains(SVG_NONE);
  854. } else {
  855. const { id: workId, type } = button.dataset;
  856. isNovel = type === 'novel';
  857. id = workId;
  858. isBookmarked = button.classList.contains('on');
  859. }
  860. const { top, left } = button.getBoundingClientRect();
  861. const position = {
  862. top: `${top + window.pageYOffset}px`,
  863. left: `${left + window.pageXOffset}px`,
  864. };
  865. const stateM = isNovel ? 'novelsMini' : 'illustsMini';
  866. if (isBookmarked) {
  867. if (this.state[stateM][id]) {
  868. this.setState({
  869. currentWork: { currentId: id, position, isNovel },
  870. show: true,
  871. btnsDisabled: false,
  872. });
  873. return;
  874. }
  875. pfb.loadBookmarkData(isNovel, id).then((response) => {
  876. const { error, body } = response;
  877. if (error) return;
  878. const { private: isPriv = null, id: bookId = null } = body.bookmarkData || {};
  879. ss(stateM, isNovel, id, isPriv, bookId, position, true, false);
  880. });
  881. } else ss(stateM, isNovel, id, null, null, position, true, false);
  882. }
  883.  
  884. addBookmark(e, illustId, isNovel, restrict) {
  885. this.setState({ btnsDisabled: true });
  886. const { bookmarkAdd } = pfb.fetchData.args;
  887. const id = isNovel ? 'novel_id' : 'illust_id';
  888. const data = {
  889. [id]: illustId,
  890. restrict,
  891. comment: '',
  892. tags: [],
  893. };
  894. const body = JSON.stringify(data);
  895. const args = { ...bookmarkAdd, body };
  896. const ss = (stateM, props) => {
  897. this.setState(prevState => ({
  898. [stateM]: {
  899. ...prevState[stateM],
  900. [illustId]: {
  901. ...prevState[stateM][illustId],
  902. ...props,
  903. },
  904. },
  905. btnsDisabled: false,
  906. }));
  907. pfb.updateHeart(illustId, +restrict, isNovel);
  908. };
  909. pfb.saveBookmark(isNovel, args).then((response) => {
  910. const { body: respBody, error } = response;
  911. if (error) this.setState({ btnsDisabled: false });
  912. if (isNovel) {
  913. pfb.loadBookmarkData(isNovel, illustId).then((resp) => {
  914. const { error: err, body: bd } = resp;
  915. if (err) return;
  916. const { private: isPrivate = null, id: bookmarkId = null } = bd.bookmarkData || {};
  917. ss('novelsMini', { isPrivate, bookmarkId });
  918. });
  919. } else {
  920. const { last_bookmark_id: bookmarkId } = respBody;
  921. const isPrivateBookmark = !!+restrict;
  922. if (bookmarkId) ss('illustsMini', { isPrivate: isPrivateBookmark, bookmarkId });
  923. else ss('illustsMini', { isPrivate: isPrivateBookmark });
  924. }
  925. });
  926. }
  927.  
  928. removeBookmark(e, id, bookmarkId, isNovel) {
  929. this.setState({ btnsDisabled: true });
  930. const { bookmarkRemove } = pfb.fetchData.args;
  931. const { illustRemove, novelRemove } = pfb.fetchData.body;
  932. if (!bookmarkId) return;
  933. const body = isNovel ? novelRemove(bookmarkId) : illustRemove(bookmarkId);
  934. const args = { ...bookmarkRemove, body };
  935. const stateM = isNovel ? 'novelsMini' : 'illustsMini';
  936. pfb.removeBookmark(isNovel, args).then((response) => {
  937. if (!response.error) {
  938. this.setState(prevState => ({
  939. [stateM]: {
  940. ...prevState[stateM],
  941. [id]: {
  942. ...prevState[stateM][id],
  943. isPrivate: null,
  944. bookmarkId: null,
  945. },
  946. },
  947. btnsDisabled: false,
  948. }));
  949. pfb.updateHeart(id, 2, isNovel);
  950. } else this.setState({ btnsDisabled: false });
  951. });
  952. }
  953.  
  954. render() {
  955. const {
  956. currentWork: {
  957. currentId,
  958. isNovel,
  959. position: { top, left },
  960. },
  961. btnsDisabled: disabled,
  962. detected,
  963. show,
  964. } = this.state;
  965. const workType = isNovel ? 'novelsMini' : 'illustsMini';
  966. const { id, bookmarkId, isPrivate } = this.state[workType][currentId] || {};
  967. const {
  968. heartPathBorder,
  969. heartPathBackground,
  970. padlockPathBorder,
  971. padlockPathBackground,
  972. } = pfb.pathData;
  973. const {
  974. FLOAT_BUTTON_CONTAINER,
  975. FLOAT_BUTTON,
  976. FLOAT_BTN_BOOKMARKED,
  977. FLOAT_SVG_HEART,
  978. FLOAT_PATH_BORDER,
  979. FLOAT_PATH_BACKGROUND,
  980. FLOAT_PATH_PADLOCK_BORDER,
  981. FLOAT_PATH_PADLOCK_BACKGROUND,
  982. FLOAT_SVG_LINE,
  983. } = pfb.classList;
  984. const style = { top, left };
  985. return show && detected
  986. ? rce(
  987. 'div',
  988. { style },
  989. rce(
  990. 'div',
  991. { className: FLOAT_BUTTON_CONTAINER },
  992. rce(
  993. 'button',
  994. {
  995. onClick: e => this.addBookmark(e, id, isNovel, 0),
  996. disabled,
  997. className: `${FLOAT_BUTTON} ${
  998. bookmarkId && !isPrivate ? FLOAT_BTN_BOOKMARKED : ''
  999. }`,
  1000. title: 'Public bookmark',
  1001. },
  1002. rce(
  1003. 'svg',
  1004. { className: FLOAT_SVG_HEART, viewBox: '0 0 34 34' },
  1005. rce('path', { className: FLOAT_PATH_BORDER, d: heartPathBorder }),
  1006. rce('path', {
  1007. className: FLOAT_PATH_BACKGROUND,
  1008. d: heartPathBackground,
  1009. }),
  1010. ),
  1011. ),
  1012. ),
  1013. rce(
  1014. 'div',
  1015. { className: FLOAT_BUTTON_CONTAINER },
  1016. rce(
  1017. 'button',
  1018. {
  1019. onClick: e => this.addBookmark(e, id, isNovel, 1),
  1020. disabled,
  1021. className: `${FLOAT_BUTTON} ${
  1022. bookmarkId && isPrivate ? FLOAT_BTN_BOOKMARKED : ''
  1023. }`,
  1024. title: 'Private bookmark',
  1025. },
  1026. rce(
  1027. 'svg',
  1028. { className: FLOAT_SVG_HEART, viewBox: '0 0 34 34' },
  1029. rce('path', { className: FLOAT_PATH_BORDER, d: heartPathBorder }),
  1030. rce('path', {
  1031. className: FLOAT_PATH_BACKGROUND,
  1032. d: heartPathBackground,
  1033. }),
  1034. rce('path', {
  1035. className: FLOAT_PATH_PADLOCK_BORDER,
  1036. d: padlockPathBorder,
  1037. }),
  1038. rce('path', {
  1039. className: FLOAT_PATH_PADLOCK_BACKGROUND,
  1040. d: padlockPathBackground,
  1041. }),
  1042. ),
  1043. ),
  1044. ),
  1045. bookmarkId
  1046. ? rce(
  1047. 'div',
  1048. { className: FLOAT_BUTTON_CONTAINER },
  1049. rce(
  1050. 'button',
  1051. {
  1052. onClick: e => this.removeBookmark(e, currentId, bookmarkId, isNovel),
  1053. className: FLOAT_BUTTON,
  1054. title: 'Remove bookmark',
  1055. disabled,
  1056. },
  1057. rce(
  1058. 'svg',
  1059. { className: FLOAT_SVG_LINE, viewBox: '0 0 8 8' },
  1060. rce('line', {
  1061. x1: '1', y1: '1', x2: '7', y2: '7',
  1062. }),
  1063. rce('line', {
  1064. x1: '7', y1: '1', x2: '1', y2: '7',
  1065. }),
  1066. ),
  1067. ),
  1068. )
  1069. : null,
  1070. )
  1071. : null;
  1072. }
  1073. }
  1074. rdr(rce(Mini, null), document.getElementById(FLOAT_CONTAINER_ID));
  1075. },
  1076. pfbElementsInitialize() {
  1077. const { isNovel } = this.data;
  1078. const { MAIN_ID, PORTAL_ID } = this.classList;
  1079. const {
  1080. placeIllustrationSelector,
  1081. placeNovelSelector,
  1082. portalIllustrationSelector,
  1083. portalNovelSelector,
  1084. currentNovelHeartSelector,
  1085. currentWorkHeartSelector,
  1086. } = this.selectorsList;
  1087. const placeSelector = isNovel ? placeNovelSelector : placeIllustrationSelector;
  1088. // const portalSelector = isNovel ? portalNovelSelector : placeIllustrationSelector;
  1089. const mainParent = document.querySelector(placeSelector);
  1090. const portalParent = isNovel
  1091. ? document.querySelector(portalNovelSelector)
  1092. : document.querySelector(placeIllustrationSelector).parentElement.parentElement;
  1093. const position = 'beforeend';
  1094. if (isNovel && portalParent) {
  1095. portalParent.insertAdjacentHTML('beforeend', '<span id="pfb-nv"></span>');
  1096. }
  1097. mainParent.insertAdjacentHTML(position, `<div id="${MAIN_ID}"></div>`);
  1098. if (portalParent && !document.getElementById('pfbPortal')) {
  1099. portalParent.insertAdjacentHTML('afterend', `<div id="${PORTAL_ID}"></div>`);
  1100. }
  1101. const modalRoot = document.getElementById(`${PORTAL_ID}`);
  1102. const mainBookBtn = isNovel ? currentNovelHeartSelector : currentWorkHeartSelector;
  1103. const buttonRoot = document.querySelector(mainBookBtn);
  1104. class Modal extends rc {
  1105. constructor(props) {
  1106. super(props);
  1107. this.container = document.createElement('div');
  1108. }
  1109.  
  1110. componentDidMount() {
  1111. if (this.props.btn) {
  1112. const childs = [...this.props.place.querySelectorAll('*')];
  1113. childs.forEach(n => n.remove());
  1114. }
  1115. this.props.place.appendChild(this.container);
  1116. }
  1117.  
  1118. componentWillUnmount() {
  1119. this.props.place.removeChild(this.container);
  1120. }
  1121.  
  1122. render() {
  1123. return rdcp(this.props.children, this.container);
  1124. }
  1125. }
  1126. class Container extends rc {
  1127. constructor(props) {
  1128. super(props);
  1129. this.state = {
  1130. bookmarkId: null,
  1131. isPrivateBookmark: null,
  1132. showAdvancedPanel: false,
  1133. disabledButtons: true,
  1134. nameSort: true,
  1135. countSort: true,
  1136. bookmarkCount: 1,
  1137. inputTags: '',
  1138. inputComment: '',
  1139. workTags: [],
  1140. originalWorkTags: [],
  1141. allTags: [],
  1142. userTags: { publicTags: [], privateTags: [] },
  1143. userTagsOriginal: { publicTags: [], privateTags: [] },
  1144. };
  1145. this.toggleMoreOptions = this.toggleMoreOptions.bind(this);
  1146. this.addBookmark = this.addBookmark.bind(this);
  1147. this.removeBookmark = this.removeBookmark.bind(this);
  1148. this.handleChange = this.handleChange.bind(this);
  1149. this.addTagToInput = this.addTagToInput.bind(this);
  1150. this.detectClickOutsidePanel = this.detectClickOutsidePanel.bind(this);
  1151. this.workTagsLowerCase = this.workTagsLowerCase.bind(this);
  1152. this.sortUserTags = this.sortUserTags.bind(this);
  1153. }
  1154.  
  1155. componentDidMount() {
  1156. this.fetchUserTags();
  1157. this.fetchWorkData();
  1158. }
  1159.  
  1160. fetchWorkData() {
  1161. const { isNovel, illustId } = pfb.data;
  1162. pfb.loadWorkData(isNovel, illustId).then((response) => {
  1163. if (response.error) {
  1164. this.getWorkTags();
  1165. this.fetchBookmarkData();
  1166. } else {
  1167. const {
  1168. bookmarkData,
  1169. tags: { tags },
  1170. isOriginal,
  1171. userName,
  1172. bookmarkCount,
  1173. } = response.body;
  1174. const {
  1175. private: isPrivateBookmark = null, id: bookmarkId = null,
  1176. } = bookmarkData || {};
  1177. let workTags = [];
  1178. if (userName) workTags.push(userName);
  1179. if (isOriginal) workTags.push('Original');
  1180. if (tags) {
  1181. tags.forEach((i) => {
  1182. if (!i) return;
  1183. workTags.push(i.tag);
  1184. if (i.romaji) workTags.push(i.romaji);
  1185. if (i.translation) {
  1186. const transTags = Object.values(i.translation);
  1187. workTags = [...workTags, ...transTags];
  1188. }
  1189. });
  1190. }
  1191. const mappedWT = workTags.map(tag => tag.replace(/\s+/g, ''));
  1192. this.setState({
  1193. isPrivateBookmark,
  1194. bookmarkId,
  1195. workTags: mappedWT,
  1196. originalWorkTags: mappedWT,
  1197. bookmarkCount,
  1198. disabledButtons: false,
  1199. });
  1200. }
  1201. });
  1202. }
  1203.  
  1204. addTagToInput(e, tag) {
  1205. this.setState(prevState => ({ inputTags: `${prevState.inputTags} ${tag}` }));
  1206. }
  1207.  
  1208. handleChange(e, isCommentInput) {
  1209. const { target } = e;
  1210. const propState = isCommentInput ? 'inputComment' : 'inputTags';
  1211. this.setState({ [propState]: target.value });
  1212. }
  1213.  
  1214. fetchUserTags() {
  1215. const { userId, isNovel } = pfb.data;
  1216. pfb.loadUserTags(userId, isNovel).then((response) => {
  1217. const { error, body } = response;
  1218. if (error) return;
  1219. const { private: privateTags, public: publicTags } = body;
  1220. privateTags.shift();
  1221. publicTags.shift();
  1222. const pub = publicTags.map(i => i.tag);
  1223. const priv = privateTags.map(i => i.tag);
  1224. const allTags = [...pub, ...priv, ...this.state.workTags];
  1225. this.setState({
  1226. userTags: { publicTags, privateTags },
  1227. userTagsOriginal: { publicTags, privateTags },
  1228. allTags,
  1229. });
  1230. });
  1231. }
  1232.  
  1233. getWorkTags() {
  1234. const { isNovel } = pfb.data;
  1235. const { tagNovel, tagIllust } = pfb.selectorsList;
  1236. const tagSelector = isNovel ? tagNovel : tagIllust;
  1237. const tagsEl = document.querySelectorAll(tagSelector);
  1238. const tags = [...tagsEl].map(item => item.innerText.replace(/\s+/g, ''));
  1239. this.setState({ workTags: tags, originalWorkTags: tags });
  1240. }
  1241.  
  1242. sortUserTags(e, type, property, rev) {
  1243. let publicTags;
  1244. let privateTags;
  1245. const {
  1246. publicTags: oldPublicTags,
  1247. privateTags: oldPrivateTags,
  1248. } = this.state.userTagsOriginal;
  1249. const sortTagsByCount = (arr, isDesc) => arr.sort((vote1, vote2) => {
  1250. if (+vote1.cnt > +vote2.cnt) return isDesc ? -1 : 1;
  1251. if (+vote1.cnt < +vote2.cnt) return isDesc ? 1 : -1;
  1252. if (`${vote1.tag}` > `${vote2.tag}`) return 1;
  1253. if (`${vote1.tag}` < `${vote2.tag}`) return -1;
  1254. return 0;
  1255. });
  1256. const sortTagsByName = (arr, isDesc) => arr.sort((vote1, vote2) => {
  1257. if (`${vote1.tag}` > `${vote2.tag}`) return isDesc ? -1 : 1;
  1258. if (`${vote1.tag}` < `${vote2.tag}`) return isDesc ? 1 : -1;
  1259. if (+vote1.cnt > +vote2.cnt) return 1;
  1260. if (+vote1.cnt < +vote2.cnt) return -1;
  1261. return 0;
  1262. });
  1263. switch (type) {
  1264. case 1:
  1265. publicTags = sortTagsByCount(oldPublicTags, rev);
  1266. privateTags = sortTagsByCount(oldPrivateTags, rev);
  1267. break;
  1268. case 0:
  1269. default:
  1270. publicTags = sortTagsByName(oldPublicTags, rev);
  1271. privateTags = sortTagsByName(oldPrivateTags, rev);
  1272. break;
  1273. }
  1274. this.setState({ userTags: { publicTags, privateTags }, [property]: !rev });
  1275. }
  1276.  
  1277. workTagsLowerCase(e, bool) {
  1278. this.setState(prevState => ({
  1279. workTags: bool
  1280. ? prevState.originalWorkTags.map(i => `${i}`.toLowerCase())
  1281. : prevState.originalWorkTags,
  1282. }));
  1283. }
  1284.  
  1285. fetchBookmarkData() {
  1286. const { isNovel, illustId } = pfb.data;
  1287. pfb.loadBookmarkData(isNovel, illustId).then((data) => {
  1288. const { error, body } = data;
  1289. if (error) return;
  1290. const {
  1291. private: isPrivateBookmark = null, id: bookmarkId = null,
  1292. } = body.bookmarkData || {};
  1293. this.setState({ isPrivateBookmark, bookmarkId, disabledButtons: false });
  1294. });
  1295. }
  1296.  
  1297. toggleMoreOptions(e, show) {
  1298. this.setState({ showAdvancedPanel: show });
  1299. if (show) document.body.classList.add('pfb_overflow');
  1300. else document.body.classList.remove('pfb_overflow');
  1301. }
  1302.  
  1303. detectClickOutsidePanel(e) {
  1304. const {
  1305. target: { classList },
  1306. } = e;
  1307. if (classList.contains('pfb_section') || classList.contains('pfb_advanced-panel')) {
  1308. this.toggleMoreOptions(null, false);
  1309. }
  1310. }
  1311.  
  1312. addBookmark(e, restrict) {
  1313. this.setState({ disabledButtons: true });
  1314. const { inputComment, inputTags } = this.state;
  1315. const { illustId, isNovel } = pfb.data;
  1316. const { bookmarkAdd } = pfb.fetchData.args;
  1317. const id = isNovel ? 'novel_id' : 'illust_id';
  1318. const data = {
  1319. [id]: illustId,
  1320. restrict,
  1321. comment: inputComment,
  1322. tags: inputTags.match(/\S+/g, '') || [],
  1323. };
  1324. const body = JSON.stringify(data);
  1325. const args = { ...bookmarkAdd, body };
  1326. pfb.saveBookmark(isNovel, args).then((response) => {
  1327. const { body: respBody, error } = response;
  1328. if (error) this.setState({ disabledButtons: false });
  1329. if (isNovel) this.fetchBookmarkData();
  1330. else {
  1331. const { last_bookmark_id: bookmarkId } = respBody;
  1332. const isPrivateBookmark = !!+restrict;
  1333. if (bookmarkId) {
  1334. this.setState({ bookmarkId, isPrivateBookmark, disabledButtons: false });
  1335. } else this.setState({ isPrivateBookmark, disabledButtons: false });
  1336. if (!isNovel) {
  1337. const { currentWorkHeartNavSelector } = pfb.selectorsList;
  1338. const parent = document.querySelector(currentWorkHeartNavSelector);
  1339. if (parent) pfb.replaceHeartSVG(parent, +restrict);
  1340. }
  1341. }
  1342. });
  1343. }
  1344.  
  1345. removeBookmark(e, id) {
  1346. this.setState({ disabledButtons: true });
  1347. const { isNovel } = pfb.data;
  1348. const { bookmarkId } = this.state;
  1349. const { bookmarkRemove } = pfb.fetchData.args;
  1350. const { illustRemove, novelRemove } = pfb.fetchData.body;
  1351. if (!bookmarkId) return;
  1352. const body = isNovel ? novelRemove(id) : illustRemove(id);
  1353. const args = { ...bookmarkRemove, body };
  1354. pfb.removeBookmark(isNovel, args).then((response) => {
  1355. if (!response.error) {
  1356. this.setState({ bookmarkId: null, isPrivateBookmark: null, disabledButtons: false });
  1357. if (!isNovel) {
  1358. const { currentWorkHeartNavSelector } = pfb.selectorsList;
  1359. const parent = document.querySelector(currentWorkHeartNavSelector);
  1360. if (parent) pfb.replaceHeartSVG(parent, 2);
  1361. }
  1362. } else this.setState({ disabledButtons: false });
  1363. });
  1364. }
  1365.  
  1366. render() {
  1367. const {
  1368. bookmarkId,
  1369. isPrivateBookmark,
  1370. showAdvancedPanel,
  1371. disabledButtons,
  1372. inputComment,
  1373. workTags,
  1374. userTags,
  1375. bookmarkCount,
  1376. nameSort,
  1377. countSort,
  1378. inputTags,
  1379. } = this.state;
  1380. const {
  1381. addBookmark,
  1382. removeBookmark,
  1383. toggleMoreOptions,
  1384. handleChange,
  1385. sortUserTags,
  1386. addTagToInput,
  1387. detectClickOutsidePanel,
  1388. workTagsLowerCase,
  1389. } = this;
  1390. const { MAIN_CONTAINER } = pfb.classList;
  1391. return rce(
  1392. 'div',
  1393. { className: MAIN_CONTAINER },
  1394. rce(pfb.elementsList.lightPanel, {
  1395. bookmarkId,
  1396. isPrivateBookmark,
  1397. addBookmark,
  1398. removeBookmark,
  1399. toggleMoreOptions,
  1400. disabledButtons,
  1401. }),
  1402. showAdvancedPanel
  1403. ? rce(
  1404. Modal,
  1405. { place: modalRoot, btn: false },
  1406. rce(pfb.elementsList.advancedPanel, {
  1407. bookmarkId,
  1408. detectClickOutsidePanel,
  1409. isPrivateBookmark,
  1410. addBookmark,
  1411. removeBookmark,
  1412. addTagToInput,
  1413. nameSort,
  1414. countSort,
  1415. userTags,
  1416. sortUserTags,
  1417. workTagsLowerCase,
  1418. workTags,
  1419. handleChange,
  1420. toggleMoreOptions,
  1421. disabledButtons,
  1422. inputComment,
  1423. inputTags,
  1424. }),
  1425. )
  1426. : null,
  1427. !pfb.data.isNovel && buttonRoot
  1428. ? rce(
  1429. Modal,
  1430. { place: buttonRoot, btn: true },
  1431. rce(pfb.elementsList.buttonHeart, {
  1432. bookmarkId,
  1433. isPrivateBookmark,
  1434. addBookmark,
  1435. disabledButtons,
  1436. }),
  1437. )
  1438. : null,
  1439. );
  1440. }
  1441. }
  1442. rdr(rce(Container, null), document.getElementById(`${MAIN_ID}`));
  1443. },
  1444. css() {
  1445. const style = document.createElement('style');
  1446. style.type = 'text/css';
  1447. style.innerText = '#root .pfb_container{margin-right:20px}#root .pfb_light-panel{width:auto}.pfb_li'
  1448. + 'ght-panel{width:400px;display:flex;flex-direction:row-reverse}.pfb_light-panel>d'
  1449. + 'iv:first-child{border-radius:0px 8px 8px 0px}.pfb_light-panel>div:last-child{bor'
  1450. + 'der-radius:8px 0px 0px 8px}.pfb_button-container{height:32px;transition:backgrou'
  1451. + 'nd-color 0.2s}.pfb_button-container>button{border:none;background:none;margin:0;'
  1452. + 'padding:0;height:32px;cursor:pointer;font-weight:700;line-height:1;font-size:14p'
  1453. + 'x}.pfb_container button:focus,.pfb_advanced-panel button:focus,#pfb_float-contai'
  1454. + 'ner button:focus{outline:0}.pfb_button-container>button>svg{width:32px;height:32'
  1455. + 'px}.pfb_add-button{padding:9px 14px !important}.pfb_remove-button{padding:8px 11'
  1456. + 'px !important;width:38px}.pfb_remove-button>svg{stroke-linecap:round;stroke-widt'
  1457. + 'h:2;width:16px !important;height:16px !important}.pfb_more-button{padding:0px 3p'
  1458. + 'x !important}.pfb_overflow{overflow:hidden}.pfb_advanced-panel{display:flex;widt'
  1459. + 'h:100%;height:100%;z-index:9999;position:fixed;font-size:20px;line-height:24px;f'
  1460. + 'ont-weight:bold;top:0;left:0;overflow:auto}.pfb_section{width:800px;display:flex'
  1461. + ';margin:auto;padding:40px;flex:none}.pfb_panel{width:100%;border-radius:8px}.pfb'
  1462. + '_header{border-radius:8px 8px 0 0}.pfb_title{align-items:center;flex:none;displa'
  1463. + 'y:flex;padding:16px;line-height:1;text-align:center;font-size:16px;font-weight:7'
  1464. + '00}.pfb_title>h1{flex:auto;padding:0 24px;font-size:18px;margin:0}.pfb_title>h1>'
  1465. + 'div{justify-content:center;display:flex;align-items:center}.pfb_close-advanced-p'
  1466. + 'anel{align-self:flex-start;box-sizing:content-box;padding:4px;width:16px;margin-'
  1467. + 'left:-24px;border:none;flex:none;outline:none;background:transparent;line-height'
  1468. + ':0;font-size:0;cursor:pointer}.pfb_close-advanced-panel>svg{stroke-linecap:round'
  1469. + ';stroke-width:2px;width:16px;height:16px}.pfb_action-btns{display:flex;margin:16'
  1470. + 'px 16px 26px 16px;justify-content:center}.pfb_action-btns>button:first-child{bor'
  1471. + 'der-radius:50px 0 0 50px}.pfb_action-btns>button:last-child{border-radius:0 50px'
  1472. + ' 50px 0}.pfb_action-btns>button{background:none;padding:12px 0 !important;width:'
  1473. + '200px;line-height:1;border:none;font-size:14px;font-weight:700;cursor:pointer;ma'
  1474. + 'rgin:0;transition:background-color 0.4s}.pfb_action-theme{display:flex;justify-c'
  1475. + 'ontent:flex-end;margin:0 24px}.pfb_action-theme>span{margin-right:10px;font-size'
  1476. + ':14px;margin-top:2px}.pfb_action-theme>div{padding:5px 8px 0 8px}.pfb_action-the'
  1477. + 'me>div>button{border:none;background:none;line-height:1;padding:0;cursor:pointer'
  1478. + ';margin:0}.pfb_action-theme>div>button>svg{width:20px;height:20px}.pfb_action-th'
  1479. + 'eme>div>button>svg>rect{width:100%;height:100%}.pfb_night-theme-btn{margin-right'
  1480. + ':5px !important}.pfb_action-section{width:100%;border-radius:0 0 8px 8px}.pfb_co'
  1481. + 'mment-section{padding:25px 35px 35px;border-bottom:1px solid}.pfb_tags-section>d'
  1482. + 'iv,.pfb_comment-section>div{text-align:center}.pfb_tags-section>div>input,.pfb_c'
  1483. + 'omment-section>div>input{overflow:hidden;resize:none;font-size:14px;height:25px;'
  1484. + 'padding:6px 10px;border:none;border-radius:4px;width:643px;border:1px solid}.pfb'
  1485. + '_title-section{display:flex;padding:0px 35px;justify-content:space-between}.pfb_'
  1486. + 'title-section>h2{margin:5px 0 10px 0;font-size:16px}.pfb_title-section>div{font-'
  1487. + 'size:12px;padding-top:6px;margin-right:5px}.pfb_work-tags-options{margin-right:5'
  1488. + 'px;font-size:12px;cursor:pointer}.pfb_tags-section{padding:20px 35px}.pfb_tags-s'
  1489. + 'ection>.pfb_title-section{padding-bottom:25px}.pfb_tags-section>div:nth-child(1)'
  1490. + '{padding-bottom:30px;position:relative}.pfb_work-tags{font-size:14px;padding:0 3'
  1491. + '2px;margin-bottom:16px}.pfb_work-tags>div{display:flex;flex-wrap:wrap}.pfb_work-'
  1492. + 'tags>div:nth-child(1){margin:0 2px;justify-content:space-between}.pfb_work-tags>'
  1493. + 'div:nth-child(2){border:1px solid;border-radius:5px;padding:5px 4px 3px}.pfb_wor'
  1494. + 'k-tags>div:nth-child(2)>div{display:flex;flex-wrap:wrap}.pfb_work-tags>div:nth-c'
  1495. + 'hild(2)>div:first-child{border-bottom:1px solid;margin-bottom:3px;padding-bottom'
  1496. + ':2px}.pfb_work-tags>div:nth-child(2)>div:last-child{border-bottom:none;margin-bo'
  1497. + 'ttom:0;padding-bottom:0}.pfb_title-tag-list{margin:0 3px}.pfb_tag{padding:0px 5p'
  1498. + 'x;font-size:12px;margin:0px 1px 2px;cursor:pointer;font-size:12px;border-radius:'
  1499. + '3px;transition:background-color 0.4s ease 0s}.pfb_tag-added{background:none}.pfb'
  1500. + '_tag-added:hover{background:none}.pfb_button-heart{display:inline-block;box-sizi'
  1501. + 'ng:content-box;padding:0;color:inherit;background:none;border:none;line-height:1'
  1502. + ';height:32px;cursor:pointer}.pfb_heart-empty,.pfb_heart-public,.pfb-heart-privat'
  1503. + 'e{box-sizing:border-box;line-height:0;font-size:0px;vertical-align:top;transitio'
  1504. + 'n:color 0.2s ease 0s, fill 0.2s ease 0s}.pfb_heart-background{transition:fill 0.'
  1505. + '2s ease 0s}.pfb_padlock-border,.pfb_padlock-background{fill-rule:evenodd;clip-ru'
  1506. + 'le:evenodd}.pfb_action-btns>button:disabled,button.pfb_add-button:disabled{opaci'
  1507. + 'ty:0.4}#pfb_float-container>div{z-index:9999;position:absolute;display:flex;heig'
  1508. + 'ht:36px}#pfb_float-container>div>div:first-child>button{border-radius:5px 0 0 5p'
  1509. + 'x}#pfb_float-container>div>div:last-child>button{border-radius:0 5px 5px 0}#pfb_'
  1510. + 'float-container>div>.pfb_f-btn-container:nth-child(3)>button{padding-top:3px}#pf'
  1511. + 'b_float-container button:disabled{opacity:0.9}#pfb_float-container .pfb_f-btn-co'
  1512. + 'ntainer{background:none !important}.pfb_f-btn{padding:0 2px;border:none;width:40'
  1513. + 'px;height:36px;cursor:pointer}.pfb_f-svg-heart{padding:3px 0 0 3px;height:33px}.'
  1514. + 'pfb_f-svg-heart>path{fill-rule:evenodd}.pfb_f-svg-line{stroke-linecap:round;stro'
  1515. + 'ke-width:2;width:18px !important;height:18px !important}.pfb_bookmarked{color:#0'
  1516. + '086e0 !important}.pfb_light-panel .pfb_button-container{background-color:#ebebeb'
  1517. + '}.pfb_light-panel .pfb_button-container>button{color:#666}.pfb_light-panel .pfb_'
  1518. + 'button-container:hover{background-color:#dcdcdc}.pfb_light-panel .pfb_more-butto'
  1519. + 'n>svg{fill:#666}.pfb_light-panel .pfb_remove-button>svg{stroke:#666}.pfb_advance'
  1520. + 'd-panel{color:#333333;background-color:#00000066}.pfb_panel{background:#eee}.pfb'
  1521. + '_header{background:#fff}.pfb_title{color:#333}.pfb_close-advanced-panel>svg{stro'
  1522. + 'ke:#ccc}.pfb_action-btns>button{color:#666;background-color:#ededed}.pfb_action-'
  1523. + 'btns>button:hover{background-color:#e2e2e2 !important}.pfb_action-theme>span{col'
  1524. + 'or:#333}.pfb_action-theme>div{background:#eee}.pfb_night-theme-btn rect{fill:#22'
  1525. + '2}.pfb_light-theme-btn rect{fill:#fff}.pfb_comment-section{border-color:#fff}.pf'
  1526. + 'b_tags-section>div>input,.pfb_comment-section>div>input{background-color:#fff;bo'
  1527. + 'rder-color:#222}.pfb_work-tags>div:nth-child(2){border-color:#666;background-col'
  1528. + 'or:#ccc}.pfb_work-tags>div:nth-child(2)>div:first-child{border-color:#222}.pfb_t'
  1529. + 'ag{color:#fff;background-color:#3e5b71}.pfb_tag:hover{background-color:#3f7186}.'
  1530. + 'pfb_tag-added{color:#3e5b71;background:none}'
  1531. + '.pfb_tags-section input[type="text"],.pfb_comment-se'
  1532. + 'ction input[type="text"]{color:#333;background-color:#fff}.pfb_tags-section inpu'
  1533. + 't[type="text"]:focus,.pfb_comment-section input[type="text"]:focus{color:#333;ba'
  1534. + 'ckground-color:#fff}.pfb_heart-empty{color:#1f1f1f;fill:#222}.pfb_padlock-border'
  1535. + ',.pfb_heart-background{fill:#fff}.pfb_heart-public,.pfb_heart-private{color:#ff4'
  1536. + '060;fill:#ff4060}.pfb_heart-public .pfb_heart-background,.pfb_heart-private .pfb'
  1537. + '_heart-background{fill:#ff4060}.pfb_padlock-background{fill:#1f1f1f}.pfb_f-btn{b'
  1538. + 'ackground-color:#222}.pfb_f-btn:hover{background-color:#333}#pfb_float-container'
  1539. + ' button:disabled{background-color:#777}.pfb_f-path-heart-border,.pfb_f-path-padl'
  1540. + 'ock-background{fill:#111}.pfb_f-path-heart-background,.pfb_f-path-padlock-border'
  1541. + '{fill:#fff}.pfb_f-bookmarked .pfb_f-path-heart-background{fill:#ff4060}.pfb_f-sv'
  1542. + 'g-line{stroke:#fff}.pfb_night-theme .pfb_light-panel .pfb_button-container{backg'
  1543. + 'round-color:#333}.pfb_night-theme .pfb_light-panel .pfb_button-container>button{'
  1544. + 'color:#fff}.pfb_night-theme .pfb_light-panel .pfb_button-container:hover{backgro'
  1545. + 'und-color:#555}.pfb_night-theme .pfb_light-panel .pfb_more-button>svg{fill:#fff}'
  1546. + '.pfb_night-theme .pfb_light-panel .pfb_remove-button>svg{stroke:#fff}.pfb_night-'
  1547. + 'theme .pfb_work-tags>div:nth-child(2){border-color:#222;background-color:#555}.p'
  1548. + 'fb_night-theme .pfb_tag-added{background:none;color:#fff}.pfb_night-theme .pfb_p'
  1549. + 'anel{color:#fff;background-color:#333}.pfb_night-theme .pfb_header{background-co'
  1550. + 'lor:#444}.pfb_night-theme .pfb_title{color:#fff}.pfb_night-theme .pfb_action-the'
  1551. + 'me>span{color:#fff}.pfb_night-theme .pfb_action-theme>div{background-color:#333}'
  1552. + '.pfb_night-theme .pfb_comment-section{border-color:#222}.pfb_night-theme .pfb_ac'
  1553. + 'tion-section input{background-color:#444;color:#fff}.pfb_night-theme .pfb_action'
  1554. + '-section input[type="text"]:focus{background-color:#444;color:#fff}.pfb_night-th'
  1555. + 'eme .pfb_action-btns>button{color:#fff;background-color:#333}.pfb_night-theme .p'
  1556. + 'fb_action-btns>button:hover{background-color:#555 !important}';
  1557. document.head.appendChild(style);
  1558. },
  1559. updateHeart(id, type, isNovel) {
  1560. if (pfb.data.isReactApp) {
  1561. const { heartButtonSelector, closestDivContNovelSize } = pfb.selectorsList;
  1562. const a = isNovel ? `a[href*="/novel/show.php?id=${id}"]` : `a[href*="/artworks/${id}"]`;
  1563. const elems = [...document.querySelectorAll(`div[width] ${a}`)];
  1564. elems.forEach((elem) => {
  1565. const div = isNovel
  1566. ? elem.closest('li') || elem.closest(closestDivContNovelSize)
  1567. : elem.closest('div[width]');
  1568. const button = div.querySelector(heartButtonSelector);
  1569. pfb.replaceHeartSVG(button, type);
  1570. });
  1571. } else {
  1572. const dataType = isNovel ? 'novel' : 'illust';
  1573. const elems = [
  1574. ...document.querySelectorAll(
  1575. `div._one-click-bookmark[data-type="${dataType}"][data-id="${id}"]`,
  1576. ),
  1577. ];
  1578. elems.forEach(elem => pfb.replaceHeartImg(elem, type));
  1579. }
  1580. },
  1581. replaceHeartImg(button, type) {
  1582. if (!button) return;
  1583. switch (type) {
  1584. case 0:
  1585. button.classList.remove('private');
  1586. button.classList.add('on');
  1587. break;
  1588. case 1:
  1589. button.classList.add('on', 'private');
  1590. break;
  1591. case 2:
  1592. default:
  1593. button.classList.remove('on', 'private');
  1594. break;
  1595. }
  1596. },
  1597. replaceHeartSVG(parent, heartType) {
  1598. if (!parent) return;
  1599. const child = parent.querySelector('*');
  1600. let heart;
  1601. switch (heartType) {
  1602. case 0:
  1603. heart = this.elementsList.publicHeart();
  1604. break;
  1605. case 1:
  1606. heart = this.elementsList.privateHeart();
  1607. break;
  1608. case 2:
  1609. heart = this.elementsList.emptyHeart();
  1610. break;
  1611. default:
  1612. break;
  1613. }
  1614. if (parent) parent.replaceChild(heart, child);
  1615. },
  1616. changeTheme(e, isNight) {
  1617. const { pfbnightTitle } = pfb.scriptData;
  1618. const { NIGHT_THEME } = this.classList;
  1619. const data = JSON.stringify({ night: isNight });
  1620. localStorage.setItem(pfbnightTitle, data);
  1621. const method = isNight ? 'add' : 'remove';
  1622. document.body.classList[method](NIGHT_THEME);
  1623. },
  1624. async loadLocalStorage(title) {
  1625. const data = await localStorage.getItem(title);
  1626. return data ? JSON.parse(data) : {};
  1627. },
  1628. async loadWorkData(isNovel, illustId) {
  1629. const workType = isNovel ? 'novel' : 'illust';
  1630. const { workData } = this.urlList;
  1631. const url = workData(workType, illustId);
  1632. const { getArgs } = this.fetchData.args;
  1633. const response = await fetch(url, getArgs);
  1634. return response.json();
  1635. },
  1636. async loadBookmarkData(isNovel, illustId) {
  1637. const workType = isNovel ? 'novel' : 'illust';
  1638. const { bookmarkDataUrl } = this.urlList;
  1639. const { getArgs } = this.fetchData.args;
  1640. const url = bookmarkDataUrl(workType, illustId);
  1641. const response = await fetch(url, getArgs);
  1642. return response.json();
  1643. },
  1644. async loadUserTags(userId, isNovel) {
  1645. const { bookmarkTagsUrl } = this.urlList;
  1646. const { getArgs } = this.fetchData.args;
  1647. const worksType = isNovel ? 'novels' : 'illusts';
  1648. const url = bookmarkTagsUrl(worksType, userId);
  1649. const response = await fetch(url, getArgs);
  1650. return response.json();
  1651. },
  1652. async saveBookmark(isNovel, args) {
  1653. const { addIllustBookmarkUrl, addNovelBookmarkUrl } = this.fetchData.urlList;
  1654. const url = isNovel ? addNovelBookmarkUrl : addIllustBookmarkUrl;
  1655. const response = await fetch(url, args);
  1656. return response.json();
  1657. },
  1658. async removeBookmark(isNovel, args) {
  1659. const { removeIllustBookmarkUrl, removeNovelBookmarkUrl } = this.fetchData.urlList;
  1660. const url = isNovel ? removeNovelBookmarkUrl : removeIllustBookmarkUrl;
  1661. const response = await fetch(url, args);
  1662. return response.json();
  1663. },
  1664. runObserver() {
  1665. const { placeIllustrationSelector, placeNovelSelector } = this.selectorsList;
  1666. const { MAIN_ID } = this.classList;
  1667. const { illustPath, novelPath2 } = this.regexList;
  1668. const observer = new MutationObserver(() => {
  1669. const elementIllust = document.querySelector(placeIllustrationSelector);
  1670. const elementNovel = document.querySelector(placeNovelSelector);
  1671. const isIllust = window.location.href.match(illustPath);
  1672. const isNovel = window.location.href.match(novelPath2);
  1673. if (elementIllust && isIllust) {
  1674. const mc = isIllust ? MAIN_ID : 'pfb-nv';
  1675. if (!document.getElementById(mc)) {
  1676. const m1 = window.location.pathname.match(/\/artworks\/\d+/);
  1677. if (m1) {
  1678. const m2 = m1[0].match(/\d+/);
  1679. if (m2) {
  1680. const illustId = +m2[0];
  1681. this.data.illustId = illustId;
  1682. this.pfbElementsInitialize();
  1683. }
  1684. }
  1685. }
  1686. }
  1687. });
  1688. observer.observe(document, {
  1689. childList: true,
  1690. subtree: true,
  1691. });
  1692. },
  1693. initialize() {
  1694. const { pixivWelcomeTitle, pixivErrorTitle } = this.selectorsList;
  1695. const { novelPath } = this.regexList;
  1696. const { pfbnightTitle } = pfb.scriptData;
  1697. const welcomeTitle = document.querySelector(pixivWelcomeTitle);
  1698. const errorTitle = document.querySelector(pixivErrorTitle);
  1699. const isNovel = window.location.pathname.match(novelPath);
  1700. if (welcomeTitle || errorTitle) return;
  1701. this.data.token = token();
  1702. this.data.isReactApp = isReact();
  1703. this.data.userId = getUserId();
  1704. this.data.isNovel = isNovel;
  1705. this.css();
  1706. this.loadLocalStorage(pfbnightTitle).then((localData) => {
  1707. if (localData.night) {
  1708. const { NIGHT_THEME } = this.classList;
  1709. document.body.classList.add(NIGHT_THEME);
  1710. }
  1711. });
  1712. this.miniBookmarkInitialize();
  1713. if (this.data.isReactApp) this.runObserver();
  1714. },
  1715. };
  1716. pfb.initialize();
  1717. }());