🏠 返回首頁 

Greasy Fork is available in English.

w4tchdoge's AO3 Bookmark Maker

Modified/Forked from "Ellililunch AO3 Bookmark Maker" (https://greasyfork.org/en/scripts/458631). Script is out-of-the-box setup to automatically add title, author, status, summary, and last read date to the description in an "collapsible" section so as to not clutter the bookmark.


安装此脚本?
  1. // ==UserScript==
  2. // @name w4tchdoge's AO3 Bookmark Maker
  3. // @namespace https://github.com/w4tchdoge
  4. // @version 2.14.1-20250323_121532
  5. // @description Modified/Forked from "Ellililunch AO3 Bookmark Maker" (https://greasyfork.org/en/scripts/458631). Script is out-of-the-box setup to automatically add title, author, status, summary, and last read date to the description in an "collapsible" section so as to not clutter the bookmark.
  6. // @author w4tchdoge
  7. // @homepage https://github.com/w4tchdoge/MISC-UserScripts
  8. // @match *://archiveofourown.org/*chapters/*
  9. // @match *://archiveofourown.org/*works/*
  10. // @exclude *://archiveofourown.org/*works/*/bookmarks
  11. // @exclude *://archiveofourown.org/*works/*/navigate
  12. // @exclude *://archiveofourown.org/*works/*/latest
  13. // @match *://archiveofourown.org/series/*
  14. // @match *://archiveofourown.org/users/*/preferences*
  15. // @icon https://archiveofourown.org/favicon.ico
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment-with-locales.min.js
  17. // @run-at document-end
  18. // @license GNU GPLv3
  19. // @history 2.14.1 — Fix autoAutoTag not working when user has IncludeFandom set to true
  20. // @history 2.14.0 — Add option for AutoTag to include the fandom of the work/series when autotagging (https://greasyfork.org/en/scripts/467885/discussions/291232); Add new variable `title_URL` which is just the URL of the work/series as plaintext (https://greasyfork.org/en/scripts/467885/discussions/290711); Make the title of each work in `series_works_titles_summaries` a hyperlink to the work (https://greasyfork.org/en/scripts/467885/discussions/290546); Add new work-only variable `series_link` which add information about any series' the work may be a part of (https://greasyfork.org/en/scripts/467885/discussions/290546)
  21. // @history 2.13.0 — Add a new default variable title_HTML that can be used in the workInfo customisation function. for more details about title_HTML please refer to the "USER CONFIGURABLE SETTINGS" section at the bottom of the script
  22. // @history 2.12.1 — Fix BSP_conditional and TSP_conditional always being true because it was checking for `on_summary_page != null` instead of `on_summary_page == false`
  23. // @history 2.12.0 — Add setting to toggle whether script always runs on any work page or only runs on the summary page of work
  24. // @history 2.11.1 — Add an exclude rule for compatibility with my go to latest chapter userscript
  25. // @history 2.11.0 — Add settings for automatically marking a bookmark as a rec, running autotag, and adding bookmark to collection(s)
  26. // @history 2.10.2 — Have the canon AO3 word count autotag method fetch the tags from the tag search page instead of hardcoding them. Fallback to the hardcoded values when something in the fetch fails
  27. // @history 2.10.1 — Restrict the userscript to `/users/*/preferences` pages instead of all `/users/*` pages
  28. // @history 2.10.0 — Add four new variables to be used in `workInfo`: fform_tags_list_HTML, fform_tags_list_TXT, fform_tags_comma_HTML, and fform_tags_comma_TXT. These variables add the freeform/additional tags of a work into the bookmark in a collapsible <details> element. Additional details are provided in the Bookmark content configuration section
  29. // @history 2.9.0 — Add functionality to switch between the original AutoTag implementation (https://greasyfork.org/en/scripts/467885/discussions/198028) and the implementation that uses the canonical AO3 `Wordcount: Over *` tags (https://greasyfork.org/en/scripts/467885/discussions/255399)
  30. // @history 2.8.5 — Rename `series_works_summaries` to `series_works_titles_summaries` to indicate that it's functionality has been changed such that for series with more than 10 works it outputs just the title instead of the title and the summary, as the summaries for more than 10 works are unlikely to fit inside a bookmark
  31. // @history 2.8.4 — Fix missing authors on Anonymous works
  32. // @history 2.8.3 — Fix script erroring at series_works_summaries when series contains a work whose title is not a hyperlink (e.g. Mystery Works)
  33. // @history 2.8.2 — Replace the old AutoTag functionality with a new one (https://greasyfork.org/en/scripts/467885/discussions/255399) with plans to add settings to switch between the two in 2.9.0
  34. // @history 2.8.1 — Add @exclude rule so that userscript doesn't run on /navigate pages
  35. // @history 2.8.0 — Refactor/rewrite the portions of the script related to exctracting all the information needed for the bookmark from the current page. Ensure the Summary Page button is added when reading a chapter from a page that does not include "works" in the URL (e.g. https://archiveofourown.org/chapters/141155299)
  36. // @history 2.7.3 — Make bottom and top summary page appear when reading a chapter from a URL such as https://archiveofourown.org/chapters/142383091
  37. // @history 2.7.2 — Update all the other presets to reflect changes made to the default preset
  38. // @history 2.7.1 — Clarify how the 'relationships' variable in workInfo now functions, especially with regards to it's new functionality in series bookmarks
  39. // @history 2.7.0 — Add all unique relationship tags in all the works on a series page to the series bookmark
  40. // @history 2.6.3 — Fix some minor messups I made in 2.6.2 before they break something or the other
  41. // @history 2.6.2 — Fix author_HTML only retrieving the first author in multi-author works/series
  42. // @history 2.6.1 — Fixes incompatibilty with users's skins caused by not using cloneNode(true) when retrieving relationship tags and subsequently removing all classes from them. credit to @notdoingthateither on Greasy Fork for the fix
  43. // @history 2.6.0 — Add a new default variable author_HTML that can be used in the workInfo customisation function. for more details about author_HTML please refer to line 1871
  44. // @history 2.5.0 — Add toggle in the dropdown present on the user's preferences page for showing/not showing the AutoTag button when making/editing a bookmark
  45. // @history 2.4.5 — Add exlude rule for pages listing bookmarks as the script isn't designed to run on those pages
  46. // @history 2.4.4 — Add a fallback for retrieving the "Entire Work" button in case it's been modified but is still somewhat recognisable in the DOM
  47. // @history 2.4.3 — Fix script not working on Firefox browsers due to a lack of support for the :has() CSS selector and a Firefox specific error caused by not using Optional Chaining
  48. // @history 2.4.2 — Fix a bug where the script errored on single chapter works
  49. // @history 2.4.1 — Add an option to have a version of the "Summary Page" button in the top nav buttons. Defaults to false
  50. // @history 2.4.0 — Add an Auto Tag button that automatically adds the completion status and word count to the user tags area
  51. // @history 2.3.0 — Fairly large reworks of series summaries which includes the addition of the series_works_summaries var (which only does anything when bookmarking a series) that can be used in workInfo to add the summaries of the works in the series to the series bookmark; Added moment.js as a library in case anyone wants to use it to set their own date var
  52. // @history 2.2.2 — Fix a possible styling issue that may occur with the dropdown menu
  53. // @history 2.2.1 — Fix Relationships subsection for works with no relationship tags by adding a "No Relationships" to the subsection when there are no relationship tags
  54. // @history 2.2.0 — Add a 'relationships' var that can be used in workInfo to add the work's relationship tags to the bookmark. Default config now set to include said var in workInfo
  55. // @history 2.1.2 — Replace the bottom entire work button with a summary page button that works better on large works
  56. // @history 2.1.1 — Adjusted script execution condition to allow for works with no summary
  57. // @history 2.1.0 — Tweaked the bookmarking process which should hopefully make it easier to configure. Also rewrote *some* of the code to hopefully make it better perfoming
  58. // @history 2.0.9 — Add functionality to retrieve the ID of the work or series being bookmarked
  59. // @history 2.0.8 — Make some if statements in the localStorage section more readable
  60. // @history 2.0.7 — Fix bottomEntireWork not functioning because of the placement of the code responsible for putting the button there being inside an if statement that only executes on specific pages
  61. // @history 2.0.6 — Fix even more localStorage issues, this time caused by my own incompetence
  62. // @history 2.0.5 — Fixing localStorage stuff
  63. // @history 2.0.4 — More summary related bugfixes
  64. // @history 2.0.3 — Fix the script replacing an existing summary in the bookmark notes with 'undefined'
  65. // @history 2.0.2 — Alert the user that their input for a new divider value has been accepted + Minor styling changes in the setting dropdown
  66. // @history 2.0.1 — Fix if statements that used variables that needed to be true or false by using a function to convert the 'true' & 'false' strings from localStorage to booleans
  67. // @history 2.0.0 — Implement localStorage for the majority of user settings. Presets must still be set via editing the script
  68. // @history 1.0.0 — Initial Publishing to GresyFork
  69. // ==/UserScript==
  70. /*
  71. // THIS USERSCRIPT RELIES HEAVILY ON TEMPLATE LITERALS
  72. // FOR INFORMATION ON WHETHER YOUR BROWSER IS COMPATIBLE WITH TEMPLATE LITERALS PLEASE VISIT THE FOLLOWING WEBPAGE
  73. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#browser_compatibility
  74. */
  75. // NOTICE TO NEW USERS
  76. // Configuration area for the autogenerated portion of the bookmark at the bottom of the userscript
  77. // Various settings that can be tweaked by the end user are accessible in a menu on your Preferences page:
  78. // (https://archiveofourown.org/users/{YOUR_USERNAME}/preferences)
  79. // IF YOU HAVE ANY QUESTIONS AT ALL DO NOT HESITATE TO PM ME ON GREASYFORK
  80. (function () {
  81. const s_t = performance.now();
  82. console.log(`Starting w4BM userscript execution: ${performance.now() - s_t}ms`);
  83. /* Dictionary of "constants" that can be changed by the end user to affect how the script functions */
  84. let ini_settings_dict = {
  85. divider: `</details>\n\n`,
  86. autoPrivate: false,
  87. autoRecommend: false,
  88. autoAutoTag: false,
  89. autoCollections: ``,
  90. includeFandom: false,
  91. showAutoTagButton: true,
  92. bottomSummaryPage: true,
  93. topSummaryPage: false,
  94. simpleWorkSummary: false,
  95. FWS_asBlockquote: true,
  96. alwaysInjectBookmark: true,
  97. AutoTag_type: 0,
  98. splitSelect: 1,
  99. };
  100. /* EXPLANATION OF THE "CONSTANTS" THAT CAN BE CHANGED BY THE END USER
  101. divider : String which is used to indicate where the bookmark should be split in half
  102. autoPrivate : If true, automatically checks the checkbox to private the bookmark
  103. showAutoTagButton : If true, shows the "Auto Tag" button when bookmarking a work
  104. bottomSummaryPage : If true, checks if the current page is an entire work page or a work page that is not on the first chapter.
  105. If the aforementioned checks are passed, Adds a "Summary Page" button to the bottom nav bar to easily navigate to a page where the summary exists so it can be picked up by the userscript.
  106. This is done due to the fact that the last read date will not update when updating a bookmark from the latest chapter.
  107. topSummaryPage : If true, also adds a Summary Page button to the top nav bar next to the "Entire Work" button
  108. simpleWorkSummary : If true, uses the original method to retrieve the work summary (least hassle, but includes the 'Summary' heading element which some users may find annoying).
  109. If false, retrieves the work summary in a way (which I call the fancy way) that allows more flexibility when customising newBookmarkNotes
  110. FWS_asBlockquote : If using the fancy work summary method, set whether you want to retrieve the summary as a blockquote.
  111. For more information on the effects of changing simpleWorkSummary and FWS_asBlockquote, please look at where simpleWorkSummary is first used in the script, it should be around line 1697
  112. AutoTag_type : Determines how the AutoTag function works.
  113. 0 indicates it is using the original behaviour of the AutoTag function suggested by `oliver t` in https://greasyfork.org/en/scripts/467885/discussions/198028 that was present when AutoTag was first added to the userscript
  114. 1 indicates it is using the behaviour suggested by `prismbox` in https://greasyfork.org/en/scripts/467885/discussions/255399 where canonical AO3 Wordcount tags are used for the word count tagging
  115. splitSelect : splitSelect changes which half of bookmarkNotes your initial bookmark is supposed to live in.
  116. Valid values are 0 and 1.
  117. e.g.
  118. If you want the final bookmark (after pasting of autogenerated text) to look like the below text (and have configured it as such at the bottom of the script):
  119. {bookmarkNotes}
  120. <hr />
  121. {title} by {author}<br />
  122. {AO3_status}<br />
  123. {summary}<br />
  124. Then you can set divider = '<hr />' and splitSelect = 0
  125. What this does is it replaces anything AFTER the <hr /> with the autogenerated bookmark text you've defined at the bottom while keeping your own text (e.g. "@ Chapter 2" or "Chapter 8 ripped my heart out").
  126. If you instead want something like the following:
  127. {title} by {author}<br />
  128. {AO3_status}<br />
  129. {summary}<br />
  130. <hr />
  131. {bookmarkNotes}
  132. Then you can set divider = '<hr />' and splitSelect = 1, which replaces everything BEFORE your bookmark notes.
  133. Another way to explain it is that the script works by taking the current contents of your bookmark and splitting it into two pieces along a user defined "divider", thus creating an array. Depending on the way you want the bookmark to be structured, the part of the bookmark that you want to keep could be before the divider or after the divider. splitSelect lets you tell the script which half of the array contains the bit of the bookmark you want to keep. If it's the first half splitSelect is 0, if it's the last half splitSelect is 1.
  134. */
  135. // from https://stackoverflow.com/a/1414175/11750206 ; used to convert the 'true' & 'false' strings in localStorage to actual booleans
  136. const stringToBoolean = (stringValue) => {
  137. switch (stringValue?.toLowerCase()?.trim()) {
  138. case "true":
  139. case "yes":
  140. case "1":
  141. return true;
  142. case "false":
  143. case "no":
  144. case "0":
  145. case null:
  146. case undefined:
  147. return false;
  148. default:
  149. return JSON.parse(stringValue);
  150. }
  151. };
  152. // Declare user-configurable variables
  153. let
  154. divider,
  155. autoPrivate,
  156. autoRecommend,
  157. autoAutoTag,
  158. autoCollections,
  159. includeFandom,
  160. showAutoTagButton,
  161. bottomSummaryPage,
  162. topSummaryPage,
  163. simpleWorkSummary,
  164. FWS_asBlockquote,
  165. alwaysInjectBookmark,
  166. AutoTag_type,
  167. splitSelect,
  168. workInfo;
  169. // localStorage stuff
  170. if (typeof Storage != `undefined`) { // If localStorage exists
  171. console.log(`
  172. w4tchdoge's AO3 Bookmark Maker UserScript Log
  173. --------------------
  174. localStorage exists`
  175. );
  176. switch (Boolean(localStorage.getItem(`w4BM_divider`))) {
  177. // Execute if statement only if w4BM_divider is not set in localStorage
  178. case false:
  179. console.log(`
  180. w4tchdoge's AO3 Bookmark Maker UserScript Log
  181. --------------------
  182. 'w4BM_divider' is not set in the localStorage
  183. Now setting it to '${ini_settings_dict.divider.replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`)}'`
  184. );
  185. // set the divider in localStorage and current script execution to the default value
  186. // this will only happen on the first ever execution of the script, because this action only happens when:
  187. // a) localStorage exists
  188. // b) w4BM_divider does not exist in the localStorage
  189. divider = ini_settings_dict.divider;
  190. localStorage.setItem(`w4BM_divider`, ini_settings_dict.divider);
  191. break;
  192. // Execute if statement only if w4BM_divider is set in localStorage
  193. case true:
  194. console.log(`
  195. w4tchdoge's AO3 Bookmark Maker UserScript Log
  196. --------------------
  197. 'w4BM_divider' IS SET in the localStorage`
  198. );
  199. divider = localStorage.getItem(`w4BM_divider`);
  200. break;
  201. default:
  202. console.log(`Error in retrieving localStorage variable w4BM_divider`);
  203. break;
  204. }
  205. // doing the same thing as the first if else on line 233
  206. switch (Boolean(localStorage.getItem(`w4BM_autoPrivate`))) {
  207. case false:
  208. console.log(`
  209. w4tchdoge's AO3 Bookmark Maker UserScript Log
  210. --------------------
  211. 'w4BM_autoPrivate' is not set in the localStorage
  212. Now setting it to '${ini_settings_dict.autoPrivate}'`
  213. );
  214. autoPrivate = ini_settings_dict.autoPrivate;
  215. localStorage.setItem(`w4BM_autoPrivate`, ini_settings_dict.autoPrivate);
  216. break;
  217. case true:
  218. console.log(`
  219. w4tchdoge's AO3 Bookmark Maker UserScript Log
  220. --------------------
  221. 'w4BM_autoPrivate' IS SET in the localStorage`
  222. );
  223. autoPrivate = stringToBoolean(localStorage.getItem(`w4BM_autoPrivate`));
  224. break;
  225. default:
  226. console.log(`Error in retrieving localStorage variable w4BM_autoPrivate`);
  227. break;
  228. }
  229. // doing the same thing as the first if else on line 233
  230. switch (Boolean(localStorage.getItem(`w4BM_autoRecommend`))) {
  231. case false:
  232. console.log(`
  233. w4tchdoge's AO3 Bookmark Maker UserScript Log
  234. --------------------
  235. 'w4BM_autoRecommend' is not set in the localStorage
  236. Now setting it to '${ini_settings_dict.autoRecommend}'`
  237. );
  238. autoRecommend = ini_settings_dict.autoRecommend;
  239. localStorage.setItem(`w4BM_autoRecommend`, ini_settings_dict.autoRecommend);
  240. break;
  241. case true:
  242. console.log(`
  243. w4tchdoge's AO3 Bookmark Maker UserScript Log
  244. --------------------
  245. 'w4BM_autoRecommend' IS SET in the localStorage`
  246. );
  247. autoRecommend = stringToBoolean(localStorage.getItem(`w4BM_autoRecommend`));
  248. break;
  249. default:
  250. console.log(`Error in retrieving localStorage variable w4BM_autoRecommend`);
  251. break;
  252. }
  253. // doing the same thing as the first if else on line 233
  254. switch (Boolean(localStorage.getItem(`w4BM_autoAutoTag`))) {
  255. case false:
  256. console.log(`
  257. w4tchdoge's AO3 Bookmark Maker UserScript Log
  258. --------------------
  259. 'w4BM_autoAutoTag' is not set in the localStorage
  260. Now setting it to '${ini_settings_dict.autoAutoTag}'`
  261. );
  262. autoAutoTag = ini_settings_dict.autoAutoTag;
  263. localStorage.setItem(`w4BM_autoAutoTag`, ini_settings_dict.autoAutoTag);
  264. break;
  265. case true:
  266. console.log(`
  267. w4tchdoge's AO3 Bookmark Maker UserScript Log
  268. --------------------
  269. 'w4BM_autoAutoTag' IS SET in the localStorage`
  270. );
  271. autoAutoTag = stringToBoolean(localStorage.getItem(`w4BM_autoAutoTag`));
  272. break;
  273. default:
  274. console.log(`Error in retrieving localStorage variable w4BM_autoAutoTag`);
  275. break;
  276. }
  277. // doing the same thing as the first if else on line 233
  278. switch (Boolean(localStorage.getItem(`w4BM_autoCollections`))) {
  279. case false:
  280. console.log(`
  281. w4tchdoge's AO3 Bookmark Maker UserScript Log
  282. --------------------
  283. 'w4BM_autoCollections' is not set in the localStorage
  284. Now setting it to '${ini_settings_dict.autoCollections}'`
  285. );
  286. autoCollections = ini_settings_dict.autoCollections;
  287. localStorage.setItem(`w4BM_autoCollections`, ini_settings_dict.autoCollections);
  288. break;
  289. case true:
  290. console.log(`
  291. w4tchdoge's AO3 Bookmark Maker UserScript Log
  292. --------------------
  293. 'w4BM_autoCollections' IS SET in the localStorage`
  294. );
  295. autoCollections = localStorage.getItem(`w4BM_autoCollections`);
  296. break;
  297. default:
  298. console.log(`Error in retrieving localStorage variable w4BM_autoCollections`);
  299. break;
  300. }
  301. // doing the same thing as the first if else on line 233
  302. switch (Boolean(localStorage.getItem(`w4BM_includeFandom`))) {
  303. case false:
  304. console.log(`
  305. w4tchdoge's AO3 Bookmark Maker UserScript Log
  306. --------------------
  307. 'w4BM_includeFandom' is not set in the localStorage
  308. Now setting it to '${ini_settings_dict.includeFandom}'`
  309. );
  310. includeFandom = ini_settings_dict.includeFandom;
  311. localStorage.setItem(`w4BM_includeFandom`, ini_settings_dict.includeFandom);
  312. break;
  313. case true:
  314. console.log(`
  315. w4tchdoge's AO3 Bookmark Maker UserScript Log
  316. --------------------
  317. 'w4BM_includeFandom' IS SET in the localStorage`
  318. );
  319. includeFandom = localStorage.getItem(`w4BM_includeFandom`);
  320. break;
  321. default:
  322. console.log(`Error in retrieving localStorage variable w4BM_includeFandom`);
  323. break;
  324. }
  325. // doing the same thing as the first if else on line 233
  326. switch (Boolean(localStorage.getItem(`w4BM_showAutoTagButton`))) {
  327. case false:
  328. console.log(`
  329. w4tchdoge's AO3 Bookmark Maker UserScript Log
  330. --------------------
  331. 'w4BM_showAutoTagButton' is not set in the localStorage
  332. Now setting it to '${ini_settings_dict.showAutoTagButton}'`
  333. );
  334. showAutoTagButton = ini_settings_dict.showAutoTagButton;
  335. localStorage.setItem(`w4BM_showAutoTagButton`, ini_settings_dict.showAutoTagButton);
  336. break;
  337. case true:
  338. console.log(`
  339. w4tchdoge's AO3 Bookmark Maker UserScript Log
  340. --------------------
  341. 'w4BM_showAutoTagButton' IS SET in the localStorage`
  342. );
  343. showAutoTagButton = stringToBoolean(localStorage.getItem(`w4BM_showAutoTagButton`));
  344. break;
  345. default:
  346. console.log(`Error in retrieving localStorage variable w4BM_showAutoTagButton`);
  347. break;
  348. }
  349. // doing the same thing as the first if else on line 233
  350. switch (Boolean(localStorage.getItem(`w4BM_bottomSummaryPage`))) {
  351. case false:
  352. console.log(`
  353. w4tchdoge's AO3 Bookmark Maker UserScript Log
  354. --------------------
  355. 'w4BM_bottomSummaryPage' is not set in the localStorage
  356. Now setting it to '${ini_settings_dict.bottomSummaryPage}'`
  357. );
  358. bottomSummaryPage = ini_settings_dict.bottomSummaryPage;
  359. localStorage.setItem(`w4BM_bottomSummaryPage`, ini_settings_dict.bottomSummaryPage);
  360. break;
  361. case true:
  362. console.log(`
  363. w4tchdoge's AO3 Bookmark Maker UserScript Log
  364. --------------------
  365. 'w4BM_bottomSummaryPage' IS SET in the localStorage`
  366. );
  367. bottomSummaryPage = stringToBoolean(localStorage.getItem(`w4BM_bottomSummaryPage`));
  368. break;
  369. default:
  370. console.log(`Error in retrieving localStorage variable w4BM_bottomSummaryPage`);
  371. break;
  372. }
  373. // doing the same thing as the first if else on line 233
  374. switch (Boolean(localStorage.getItem(`w4BM_topSummaryPage`))) {
  375. case false:
  376. console.log(`
  377. w4tchdoge's AO3 Bookmark Maker UserScript Log
  378. --------------------
  379. 'w4BM_topSummaryPage' is not set in the localStorage
  380. Now setting it to '${ini_settings_dict.topSummaryPage}'`
  381. );
  382. topSummaryPage = ini_settings_dict.topSummaryPage;
  383. localStorage.setItem(`w4BM_topSummaryPage`, ini_settings_dict.topSummaryPage);
  384. break;
  385. case true:
  386. console.log(`
  387. w4tchdoge's AO3 Bookmark Maker UserScript Log
  388. --------------------
  389. 'w4BM_topSummaryPage' IS SET in the localStorage`
  390. );
  391. topSummaryPage = stringToBoolean(localStorage.getItem(`w4BM_topSummaryPage`));
  392. break;
  393. default:
  394. console.log(`Error in retrieving localStorage variable w4BM_topSummaryPage`);
  395. break;
  396. }
  397. // doing the same thing as the first if else on line 233
  398. switch (Boolean(localStorage.getItem(`w4BM_simpleWorkSummary`))) {
  399. case false:
  400. console.log(`
  401. w4tchdoge's AO3 Bookmark Maker UserScript Log
  402. --------------------
  403. 'w4BM_simpleWorkSummary' is not set in the localStorage
  404. Now setting it to '${ini_settings_dict.simpleWorkSummary}'`
  405. );
  406. simpleWorkSummary = ini_settings_dict.simpleWorkSummary;
  407. localStorage.setItem(`w4BM_simpleWorkSummary`, ini_settings_dict.simpleWorkSummary);
  408. break;
  409. case true:
  410. console.log(`
  411. w4tchdoge's AO3 Bookmark Maker UserScript Log
  412. --------------------
  413. 'w4BM_simpleWorkSummary' IS SET in the localStorage`
  414. );
  415. simpleWorkSummary = stringToBoolean(localStorage.getItem(`w4BM_simpleWorkSummary`));
  416. break;
  417. default:
  418. console.log(`Error in retrieving localStorage variable w4BM_simpleWorkSummary`);
  419. break;
  420. }
  421. // doing the same thing as the first if else on line 233
  422. switch (Boolean(localStorage.getItem(`w4BM_FWS_asBlockquote`))) {
  423. case false:
  424. console.log(`
  425. w4tchdoge's AO3 Bookmark Maker UserScript Log
  426. --------------------
  427. 'w4BM_FWS_asBlockquote' is not set in the localStorage
  428. Now setting it to '${ini_settings_dict.FWS_asBlockquote}'`
  429. );
  430. FWS_asBlockquote = ini_settings_dict.FWS_asBlockquote;
  431. localStorage.setItem(`w4BM_FWS_asBlockquote`, ini_settings_dict.FWS_asBlockquote);
  432. break;
  433. case true:
  434. console.log(`
  435. w4tchdoge's AO3 Bookmark Maker UserScript Log
  436. --------------------
  437. 'w4BM_FWS_asBlockquote' IS SET in the localStorage`
  438. );
  439. FWS_asBlockquote = stringToBoolean(localStorage.getItem(`w4BM_FWS_asBlockquote`));
  440. break;
  441. default:
  442. console.log(`Error in retrieving localStorage variable w4BM_FWS_asBlockquote`);
  443. break;
  444. }
  445. // doing the same thing as the first if else on line 233
  446. switch (Boolean(localStorage.getItem(`w4BM_alwaysInjectBookmark`))) {
  447. case false:
  448. console.log(`
  449. w4tchdoge's AO3 Bookmark Maker UserScript Log
  450. --------------------
  451. 'w4BM_alwaysInjectBookmark' is not set in the localStorage
  452. Now setting it to '${ini_settings_dict.alwaysInjectBookmark}'`
  453. );
  454. alwaysInjectBookmark = ini_settings_dict.alwaysInjectBookmark;
  455. localStorage.setItem(`w4BM_alwaysInjectBookmark`, ini_settings_dict.alwaysInjectBookmark);
  456. break;
  457. case true:
  458. console.log(`
  459. w4tchdoge's AO3 Bookmark Maker UserScript Log
  460. --------------------
  461. 'w4BM_alwaysInjectBookmark' IS SET in the localStorage`
  462. );
  463. alwaysInjectBookmark = stringToBoolean(localStorage.getItem(`w4BM_alwaysInjectBookmark`));
  464. break;
  465. default:
  466. console.log(`Error in retrieving localStorage variable w4BM_alwaysInjectBookmark`);
  467. break;
  468. }
  469. // doing the same thing as the first if else on line 233
  470. switch (Boolean(localStorage.getItem(`w4BM_AutoTag_type`))) {
  471. case false:
  472. console.log(`
  473. w4tchdoge's AO3 Bookmark Maker UserScript Log
  474. --------------------
  475. 'w4BM_AutoTag_type' is not set in the localStorage
  476. Now setting it to '${ini_settings_dict.AutoTag_type}'`
  477. );
  478. AutoTag_type = ini_settings_dict.AutoTag_type;
  479. localStorage.setItem(`w4BM_AutoTag_type`, ini_settings_dict.AutoTag_type);
  480. break;
  481. case true:
  482. console.log(`
  483. w4tchdoge's AO3 Bookmark Maker UserScript Log
  484. --------------------
  485. 'w4BM_AutoTag_type' IS SET in the localStorage`
  486. );
  487. AutoTag_type = parseInt(localStorage.getItem(`w4BM_AutoTag_type`));
  488. break;
  489. default:
  490. console.log(`Error in retrieving localStorage variable w4BM_AutoTag_type`);
  491. break;
  492. }
  493. // doing the same thing as the first if else on line 233
  494. switch (Boolean(localStorage.getItem(`w4BM_splitSelect`))) {
  495. case false:
  496. console.log(`
  497. w4tchdoge's AO3 Bookmark Maker UserScript Log
  498. --------------------
  499. 'w4BM_splitSelect' is not set in the localStorage
  500. Now setting it to '${ini_settings_dict.splitSelect}'`
  501. );
  502. splitSelect = ini_settings_dict.splitSelect;
  503. localStorage.setItem(`w4BM_splitSelect`, ini_settings_dict.splitSelect);
  504. break;
  505. case true:
  506. console.log(`
  507. w4tchdoge's AO3 Bookmark Maker UserScript Log
  508. --------------------
  509. 'w4BM_splitSelect' IS SET in the localStorage`
  510. );
  511. splitSelect = parseInt(localStorage.getItem(`w4BM_splitSelect`));
  512. break;
  513. default:
  514. console.log(`Error in retrieving localStorage variable w4BM_splitSelect`);
  515. break;
  516. }
  517. }
  518. else { // if localStorage does not exist
  519. divider = ini_settings_dict.divider;
  520. autoPrivate = ini_settings_dict.autoPrivate;
  521. autoRecommend = ini_settings_dict.autoRecommend;
  522. autoAutoTag = ini_settings_dict.autoAutoTag;
  523. autoCollections = ini_settings_dict.autoCollections;
  524. includeFandom = ini_settings_dict.includeFandom;
  525. showAutoTagButton = ini_settings_dict.showAutoTagButton;
  526. bottomSummaryPage = ini_settings_dict.bottomSummaryPage;
  527. topSummaryPage = ini_settings_dict.topSummaryPage;
  528. simpleWorkSummary = ini_settings_dict.simpleWorkSummary;
  529. FWS_asBlockquote = ini_settings_dict.FWS_asBlockquote;
  530. AutoTag_type = ini_settings_dict.AutoTag_type;
  531. splitSelect = ini_settings_dict.splitSelect;
  532. }
  533. // Log the current value of the vars in localStorage
  534. let log_string = `
  535. w4tchdoge's AO3 Bookmark Maker UserScript Log
  536. --------------------
  537. Logging the current state of vars used by the script
  538. localStorage vars:`;
  539. const log_string_localStorage_maxSpacing = Object.keys(ini_settings_dict).reduce((a, b) => a.length <= b.length ? b : a).length + 1;
  540. Object.keys(ini_settings_dict).forEach((key) => {
  541. let spacing = log_string_localStorage_maxSpacing - key.toString().length;
  542. if (key == `divider`) {
  543. log_string += `\n${key.toString()}${" ".repeat(spacing)}: ${localStorage.getItem(`w4BM_${key}`).replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`)}`;
  544. } else {
  545. log_string += `\n${key.toString()}${" ".repeat(spacing)}: ${localStorage.getItem(`w4BM_${key}`)}`;
  546. }
  547. });
  548. log_string += `
  549. current script vars (list may be incomplete):
  550. divider : ${divider.replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`)}
  551. autoPrivate : ${autoPrivate}
  552. showAutoTagButton : ${showAutoTagButton}
  553. bottomSummaryPage : ${bottomSummaryPage}
  554. topSummaryPage : ${topSummaryPage}
  555. simpleWorkSummary : ${simpleWorkSummary}
  556. FWS_asBlockquote : ${FWS_asBlockquote}
  557. alwaysInjectBookmark : ${alwaysInjectBookmark}
  558. AutoTag_type : ${AutoTag_type}
  559. splitSelect : ${splitSelect}`;
  560. console.log(log_string);
  561. // add main element that all querySelector operations will be done on
  562. const main = document.querySelector(`div#main`);
  563. // Get current page URL
  564. // const currPgURL = window.location.href;
  565. const currPgURL = new URL(window.location);
  566. if (currPgURL.pathname.includes(`users`) && currPgURL.pathname.includes(`preferences`)) {
  567. addPrefDropdown();
  568. }
  569. // Create and add dropdown menu to configure the script
  570. function addPrefDropdown() {
  571. // unescapeSlashes is from https://stackoverflow.com/a/48855846/11750206
  572. function unescapeSlashes(str) {
  573. // add another escaped slash if the string ends with an odd
  574. // number of escaped slashes which will crash JSON.parse
  575. let parsedStr = str.replace(/(^|[^\\])(\\\\)*\\$/, "$&\\");
  576. // escape unescaped double quotes to prevent error with
  577. // added double quotes in json string
  578. parsedStr = parsedStr.replace(/(^|[^\\])((\\\\)*")/g, "$1\\$2");
  579. try {
  580. parsedStr = JSON.parse(`"${parsedStr}"`);
  581. } catch (e) {
  582. return str;
  583. }
  584. return parsedStr;
  585. }
  586. // get the header menu
  587. const header_menu = document.querySelector(`ul.primary.navigation.actions`);
  588. // create and insert the menu button
  589. const w4BM_settingMenu = Object.assign(document.createElement(`li`), {
  590. className: `dropdown`,
  591. id: `w4BM_settings_dropdown`,
  592. innerHTML: `<a>Bookmark Maker Settings</a>`
  593. });
  594. if (document.querySelector(`#navbar_bookmarks_button`) !== null) {
  595. document.querySelector(`#navbar_bookmarks_button`).before(w4BM_settingMenu);
  596. } else {
  597. header_menu.querySelector(`li.search`).before(w4BM_settingMenu);
  598. }
  599. // create and append dropdown menu
  600. const w4BM_dropMenu = Object.assign(document.createElement(`ul`), {
  601. className: `menu dropdown-menu`,
  602. style: `width: auto;`
  603. });
  604. w4BM_settingMenu.append(w4BM_dropMenu);
  605. // create a more info li element to dropdown
  606. const w4BM_moreInfo_liElm = Object.assign(document.createElement(`li`), {
  607. style: `padding: 0.5em; text-align: left;`,
  608. innerHTML: `<div><em><p>This menu is for changing values of constants.</p><br /><p>Preset selection is still done via editing the userscript.</p><br /><p>For more info on setup check script code.</p><br /><p>If you have any questions, don't hesitate to PM me on Greasy Fork</p></em></div>`
  609. });
  610. // append the more info li element to dropdown
  611. w4BM_dropMenu.append(w4BM_moreInfo_liElm);
  612. // create the "— Settings —" li element to dropdown
  613. const w4BM_settings_liElm = Object.assign(document.createElement(`li`), {
  614. innerHTML: `<a style="padding: 0.5em 0.5em 0.25em; text-align: center; font-weight: bold;">&mdash; Settings &mdash;</a>`
  615. });
  616. // append the "— Settings —" li element to dropdown
  617. w4BM_dropMenu.append(w4BM_settings_liElm);
  618. // create the general area where the divider input will go
  619. const w4BM_divider_input_area = Object.assign(document.createElement(`li`), {
  620. className: `w4BM_divider_input_area`,
  621. id: `w4BM_divider_input_area`,
  622. style: `padding-top: .75em;`,
  623. innerHTML: `<p style='padding: .75em .5em .5em;'>Divider text:<br /></p>`
  624. });
  625. // create the text input box for the divider value
  626. const w4BM_divider_input_box = (function () {
  627. let div_in_box = Object.assign(document.createElement(`input`), {
  628. type: `text`,
  629. id: `w4BM_divider_input_box`,
  630. name: `w4BM_divider_input_box`,
  631. style: `width: 16em; margin-left: 0.2em;`
  632. });
  633. div_in_box.setAttribute(`value`, localStorage.getItem(`w4BM_divider`).replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`) || `divider\\n\\n`);
  634. return div_in_box;
  635. })();
  636. // Add the text input box inside the input area
  637. w4BM_divider_input_area.append(w4BM_divider_input_box);
  638. // create divider input submit button
  639. const w4BM_divider_input_btn = Object.assign(document.createElement(`button`), {
  640. id: `w4BM_divider_input_btn`,
  641. style: `margin-left: 0.3em;`,
  642. innerHTML: `Enter`
  643. });
  644. // append divider input submit button
  645. w4BM_divider_input_box.after(w4BM_divider_input_btn);
  646. // make the divider input submit button actually do what it's supposed to
  647. w4BM_divider_input_btn.addEventListener(`click`, function () {
  648. const input_value = unescapeSlashes(document.querySelector(`#w4BM_divider_input_box`).value);
  649. localStorage.setItem(`w4BM_divider`, input_value);
  650. console.log(`
  651. w4tchdoge's AO3 Bookmark Maker UserScript Log
  652. --------------------
  653. New value set for the 'divider' variable in localStorage.
  654. New value: '${input_value.replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`)}'`
  655. );
  656. alert(`w4tchdoge's AO3 Bookmark Maker
  657. New value set for the 'divider' variable in localStorage.
  658. New value: '${input_value.replace(/\n/gi, `\\n`).replace(/\t/gi, `\\t`).replace(/\r/gi, `\\r`)}'`
  659. );
  660. });
  661. // append divider input area to dropdown
  662. w4BM_settings_liElm.after(w4BM_divider_input_area);
  663. // create the general area where the collections input will go
  664. const w4BM_autoCollections_input_area = Object.assign(document.createElement(`li`), {
  665. className: `w4BM_autoCollections_input_area`,
  666. id: `w4BM_autoCollections_input_area`,
  667. style: `padding-top: .75em;`,
  668. innerHTML: `<p style='padding: .75em .5em .5em;'>List of collections (comma separated):<br /></p>`
  669. });
  670. // create the text input box for the comma separated list of collections
  671. const w4BM_autoCollections_input_box = (function () {
  672. let div_in_box = Object.assign(document.createElement(`input`), {
  673. type: `text`,
  674. id: `w4BM_autoCollections_input_box`,
  675. name: `w4BM_autoCollections_input_box`,
  676. style: `width: 16em; margin-left: 0.2em;`
  677. });
  678. div_in_box.setAttribute(`value`, localStorage.getItem(`w4BM_autoCollections`));
  679. return div_in_box;
  680. })();
  681. // Add the text input box inside the input area
  682. w4BM_autoCollections_input_area.append(w4BM_autoCollections_input_box);
  683. // create divider input submit button
  684. const w4BM_autoCollections_input_btn = Object.assign(document.createElement(`button`), {
  685. id: `w4BM_autoCollections_input_btn`,
  686. style: `margin-left: 0.3em;`,
  687. innerHTML: `Enter`
  688. });
  689. // append divider input submit button
  690. w4BM_autoCollections_input_box.after(w4BM_autoCollections_input_btn);
  691. // make the divider input submit button actually do what it's supposed to
  692. w4BM_autoCollections_input_btn.addEventListener(`click`, function () {
  693. const input_value = document.querySelector(`#w4BM_autoCollections_input_box`).value.split(`,`).filter(elm => elm.length !== 0).map(elm => elm.trim()).join(`, `);
  694. localStorage.setItem(`w4BM_autoCollections`, input_value);
  695. console.log(`
  696. w4tchdoge's AO3 Bookmark Maker UserScript Log
  697. --------------------
  698. New value set for the 'autoCollections' variable in localStorage.
  699. New value:
  700. ${input_value}`
  701. );
  702. alert(`w4tchdoge's AO3 Bookmark Maker
  703. New value set for the 'autoCollections' variable in localStorage.
  704. New value:
  705. ${input_value}`
  706. );
  707. });
  708. // append autocollections input area to dropdown
  709. w4BM_divider_input_area.after(w4BM_autoCollections_input_area);
  710. // create button - auto private bookmarks - yes
  711. const w4BM_autoPrivate_yes = Object.assign(document.createElement(`li`), {
  712. className: `w4BM_autoPrivate_yes`,
  713. id: `w4BM_autoPrivate_yes`,
  714. innerHTML: `<a>Auto Private Bookmarks: YES</a>`
  715. });
  716. w4BM_autoPrivate_yes.addEventListener(`click`, function (event) {
  717. localStorage.setItem(`w4BM_autoPrivate`, false);
  718. w4BM_autoPrivate_yes.replaceWith(w4BM_autoPrivate_no);
  719. });
  720. // create button - auto private bookmarks - no
  721. const w4BM_autoPrivate_no = Object.assign(document.createElement(`li`), {
  722. className: `w4BM_autoPrivate_no`,
  723. id: `w4BM_autoPrivate_no`,
  724. innerHTML: `<a>Auto Private Bookmarks: NO</a>`
  725. });
  726. w4BM_autoPrivate_no.addEventListener(`click`, function (event) {
  727. localStorage.setItem(`w4BM_autoPrivate`, true);
  728. w4BM_autoPrivate_no.replaceWith(w4BM_autoPrivate_yes);
  729. });
  730. // create button - auto recommend bookmarks - yes
  731. const w4BM_autoRecommend_yes = Object.assign(document.createElement(`li`), {
  732. className: `w4BM_autoRecommend_yes`,
  733. id: `w4BM_autoRecommend_yes`,
  734. innerHTML: `<a>Auto Recommend Bookmarks: YES</a>`
  735. });
  736. w4BM_autoRecommend_yes.addEventListener(`click`, function (event) {
  737. localStorage.setItem(`w4BM_autoRecommend`, false);
  738. w4BM_autoRecommend_yes.replaceWith(w4BM_autoRecommend_no);
  739. });
  740. // create button - auto recommend bookmarks - no
  741. const w4BM_autoRecommend_no = Object.assign(document.createElement(`li`), {
  742. className: `w4BM_autoRecommend_no`,
  743. id: `w4BM_autoRecommend_no`,
  744. innerHTML: `<a>Auto Recommend Bookmarks: NO</a>`
  745. });
  746. w4BM_autoRecommend_no.addEventListener(`click`, function (event) {
  747. localStorage.setItem(`w4BM_autoRecommend`, true);
  748. w4BM_autoRecommend_no.replaceWith(w4BM_autoRecommend_yes);
  749. });
  750. // create button - auto autotag bookmarks - yes
  751. const w4BM_autoAutoTag_yes = Object.assign(document.createElement(`li`), {
  752. className: `w4BM_autoAutoTag_yes`,
  753. id: `w4BM_autoAutoTag_yes`,
  754. innerHTML: `<a>Auto-run AutoTag: YES</a>`
  755. });
  756. w4BM_autoAutoTag_yes.addEventListener(`click`, function (event) {
  757. localStorage.setItem(`w4BM_autoAutoTag`, false);
  758. w4BM_autoAutoTag_yes.replaceWith(w4BM_autoAutoTag_no);
  759. });
  760. // create button - auto autotag bookmarks - no
  761. const w4BM_autoAutoTag_no = Object.assign(document.createElement(`li`), {
  762. className: `w4BM_autoAutoTag_no`,
  763. id: `w4BM_autoAutoTag_no`,
  764. innerHTML: `<a>Auto-run AutoTag: NO</a>`
  765. });
  766. w4BM_autoAutoTag_no.addEventListener(`click`, function (event) {
  767. localStorage.setItem(`w4BM_autoAutoTag`, true);
  768. w4BM_autoAutoTag_no.replaceWith(w4BM_autoAutoTag_yes);
  769. });
  770. // create button - show AutoTag button - yes
  771. const w4BM_showAutoTagButton_yes = Object.assign(document.createElement(`li`), {
  772. className: `w4BM_showAutoTagButton_yes`,
  773. id: `w4BM_showAutoTagButton_yes`,
  774. innerHTML: `<a>Show AutoTag Button: YES</a>`
  775. });
  776. w4BM_showAutoTagButton_yes.addEventListener(`click`, function (event) {
  777. localStorage.setItem(`w4BM_showAutoTagButton`, false);
  778. w4BM_showAutoTagButton_yes.replaceWith(w4BM_showAutoTagButton_no);
  779. });
  780. // create button - show AutoTag button - no
  781. const w4BM_showAutoTagButton_no = Object.assign(document.createElement(`li`), {
  782. className: `w4BM_showAutoTagButton_no`,
  783. id: `w4BM_showAutoTagButton_no`,
  784. innerHTML: `<a>Show AutoTag Button: NO</a>`
  785. });
  786. w4BM_showAutoTagButton_no.addEventListener(`click`, function (event) {
  787. localStorage.setItem(`w4BM_showAutoTagButton`, true);
  788. w4BM_showAutoTagButton_no.replaceWith(w4BM_showAutoTagButton_yes);
  789. });
  790. // create button - include fandom in AutoTag - yes
  791. const w4BM_includeFandomButton_yes = Object.assign(document.createElement(`li`), {
  792. className: `w4BM_includeFandomButton_yes`,
  793. id: `w4BM_includeFandomButton_yes`,
  794. innerHTML: `<a>Include Fandom on AutoTag: YES</a>`
  795. });
  796. w4BM_includeFandomButton_yes.addEventListener(`click`, function (event) {
  797. localStorage.setItem(`w4BM_includeFandom`, false);
  798. w4BM_includeFandomButton_yes.replaceWith(w4BM_includeFandomButton_no);
  799. });
  800. // create button - include fandom in AutoTag - no
  801. const w4BM_includeFandomButton_no = Object.assign(document.createElement(`li`), {
  802. className: `w4BM_includeFandomButton_no`,
  803. id: `w4BM_includeFandomButton_no`,
  804. innerHTML: `<a>Include Fandom on AutoTag: NO</a>`
  805. });
  806. w4BM_includeFandomButton_no.addEventListener(`click`, function (event) {
  807. localStorage.setItem(`w4BM_includeFandom`, true);
  808. w4BM_includeFandomButton_no.replaceWith(w4BM_includeFandomButton_yes);
  809. });
  810. // create button - AutoTag type 0
  811. const w4BM_AutoTagType_0 = Object.assign(document.createElement(`li`), {
  812. className: `w4BM_AutoTagType_0`,
  813. id: `w4BM_AutoTagType_0`,
  814. innerHTML: `<a>AutoTag Type: Original</a>`
  815. });
  816. w4BM_AutoTagType_0.addEventListener(`click`, function (event) {
  817. localStorage.setItem(`w4BM_AutoTag_type`, 1);
  818. w4BM_AutoTagType_0.replaceWith(w4BM_AutoTagType_1);
  819. });
  820. // create button - AutoTag type 1
  821. const w4BM_AutoTagType_1 = Object.assign(document.createElement(`li`), {
  822. className: `w4BM_AutoTagType_1`,
  823. id: `w4BM_AutoTagType_1`,
  824. innerHTML: `<a>AutoTag Type: Canon AO3 Wordcount tags</a>`
  825. });
  826. w4BM_AutoTagType_1.addEventListener(`click`, function (event) {
  827. localStorage.setItem(`w4BM_AutoTag_type`, 0);
  828. w4BM_AutoTagType_1.replaceWith(w4BM_AutoTagType_0);
  829. });
  830. // create button - add "Summary Page" button to bottom navbar
  831. const w4BM_bottomSummaryPage_yes = Object.assign(document.createElement(`li`), {
  832. className: `w4BM_bottomSummaryPage_yes`,
  833. id: `w4BM_bottomSummaryPage_yes`,
  834. innerHTML: `<a>"Summary Page" button in bottom navbar: YES</a>`
  835. });
  836. w4BM_bottomSummaryPage_yes.addEventListener(`click`, function (event) {
  837. localStorage.setItem(`w4BM_bottomSummaryPage`, false);
  838. w4BM_bottomSummaryPage_yes.replaceWith(w4BM_bottomSummaryPage_no);
  839. });
  840. // create button - do not add "Summary Page" button to bottom navbar
  841. const w4BM_bottomSummaryPage_no = Object.assign(document.createElement(`li`), {
  842. className: `w4BM_bottomSummaryPage_no`,
  843. id: `w4BM_bottomSummaryPage_no`,
  844. innerHTML: `<a>"Summary Page" button in bottom navbar: NO</a>`
  845. });
  846. w4BM_bottomSummaryPage_no.addEventListener(`click`, function (event) {
  847. localStorage.setItem(`w4BM_bottomSummaryPage`, true);
  848. w4BM_bottomSummaryPage_no.replaceWith(w4BM_bottomSummaryPage_yes);
  849. });
  850. // create button - add "Summary Page" button to top navbar
  851. const w4BM_topSummaryPage_yes = Object.assign(document.createElement(`li`), {
  852. className: `w4BM_topSummaryPage_yes`,
  853. id: `w4BM_topSummaryPage_yes`,
  854. innerHTML: `<a>"Summary Page" button in top navbar: YES</a>`
  855. });
  856. w4BM_topSummaryPage_yes.addEventListener(`click`, function (event) {
  857. localStorage.setItem(`w4BM_topSummaryPage`, false);
  858. w4BM_topSummaryPage_yes.replaceWith(w4BM_topSummaryPage_no);
  859. });
  860. // create button - do not add "Summary Page" button to top navbar
  861. const w4BM_topSummaryPage_no = Object.assign(document.createElement(`li`), {
  862. className: `w4BM_topSummaryPage_no`,
  863. id: `w4BM_topSummaryPage_no`,
  864. innerHTML: `<a>"Summary Page" button in top navbar: NO</a>`
  865. });
  866. w4BM_topSummaryPage_no.addEventListener(`click`, function (event) {
  867. localStorage.setItem(`w4BM_topSummaryPage`, true);
  868. w4BM_topSummaryPage_no.replaceWith(w4BM_topSummaryPage_yes);
  869. });
  870. // create button - use simple summary
  871. const w4BM_simpleWorkSummary_yes = Object.assign(document.createElement(`li`), {
  872. className: `w4BM_simpleWorkSummary_yes`,
  873. id: `w4BM_simpleWorkSummary_yes`,
  874. innerHTML: `<a>Use a simpler work summary: YES</a>`
  875. });
  876. w4BM_simpleWorkSummary_yes.addEventListener(`click`, function (event) {
  877. localStorage.setItem(`w4BM_simpleWorkSummary`, false);
  878. w4BM_simpleWorkSummary_yes.replaceWith(w4BM_simpleWorkSummary_no);
  879. });
  880. // create button - don't use simple summary
  881. const w4BM_simpleWorkSummary_no = Object.assign(document.createElement(`li`), {
  882. className: `w4BM_simpleWorkSummary_no`,
  883. id: `w4BM_simpleWorkSummary_no`,
  884. innerHTML: `<a>Use a simpler work summary: NO</a>`
  885. });
  886. w4BM_simpleWorkSummary_no.addEventListener(`click`, function (event) {
  887. localStorage.setItem(`w4BM_simpleWorkSummary`, true);
  888. w4BM_simpleWorkSummary_no.replaceWith(w4BM_simpleWorkSummary_yes);
  889. });
  890. // create button - always overwrite bookmark notes
  891. const w4BM_alwaysInjectBookmark_no = Object.assign(document.createElement(`li`), {
  892. className: `w4BM_alwaysInjectBookmark_no`,
  893. id: `w4BM_alwaysInjectBookmark_no`,
  894. innerHTML: `<a>Always run script on valid pages: NO</a>`
  895. });
  896. w4BM_alwaysInjectBookmark_no.addEventListener(`click`, function (event) {
  897. localStorage.setItem(`w4BM_alwaysInjectBookmark`, true);
  898. w4BM_alwaysInjectBookmark_no.replaceWith(w4BM_alwaysInjectBookmark_yes);
  899. });
  900. // create button - don't always overwrite bookmark notes
  901. const w4BM_alwaysInjectBookmark_yes = Object.assign(document.createElement(`li`), {
  902. className: `w4BM_alwaysInjectBookmark_yes`,
  903. id: `w4BM_alwaysInjectBookmark_yes`,
  904. innerHTML: `<a>Always run script on valid pages: YES</a>`
  905. });
  906. w4BM_alwaysInjectBookmark_yes.addEventListener(`click`, function (event) {
  907. localStorage.setItem(`w4BM_alwaysInjectBookmark`, false);
  908. w4BM_alwaysInjectBookmark_yes.replaceWith(w4BM_alwaysInjectBookmark_no);
  909. });
  910. // create button - use blockquotes when using fancy work summary
  911. const w4BM_FWS_asBlockquote_yes = Object.assign(document.createElement(`li`), {
  912. className: `w4BM_FWS_asBlockquote_yes`,
  913. id: `w4BM_FWS_asBlockquote_yes`,
  914. innerHTML: `<a>Use blockquote w/ fancy work summary: YES</a>`
  915. });
  916. w4BM_FWS_asBlockquote_yes.addEventListener(`click`, function (event) {
  917. localStorage.setItem(`w4BM_FWS_asBlockquote`, false);
  918. w4BM_FWS_asBlockquote_yes.replaceWith(w4BM_FWS_asBlockquote_no);
  919. });
  920. // create button - dont use blockquotes when using fancy work summary
  921. const w4BM_FWS_asBlockquote_no = Object.assign(document.createElement(`li`), {
  922. className: `w4BM_FWS_asBlockquote_no`,
  923. id: `w4BM_FWS_asBlockquote_no`,
  924. innerHTML: `<a>Use blockquote w/ fancy work summary: NO</a>`
  925. });
  926. w4BM_FWS_asBlockquote_no.addEventListener(`click`, function (event) {
  927. localStorage.setItem(`w4BM_FWS_asBlockquote`, true);
  928. w4BM_FWS_asBlockquote_no.replaceWith(w4BM_FWS_asBlockquote_yes);
  929. });
  930. // create button - set splitSelect to 1
  931. const w4BM_splitSelect_1 = Object.assign(document.createElement(`li`), {
  932. className: `w4BM_splitSelect_1`,
  933. id: `w4BM_splitSelect_1`,
  934. innerHTML: `<a>splitSelect: 1</a>`
  935. });
  936. w4BM_splitSelect_1.addEventListener(`click`, function (event) {
  937. localStorage.setItem(`w4BM_splitSelect`, 0);
  938. w4BM_splitSelect_1.replaceWith(w4BM_splitSelect_0);
  939. });
  940. // create button - set splitSelect to 0
  941. const w4BM_splitSelect_0 = Object.assign(document.createElement(`li`), {
  942. className: `w4BM_splitSelect_0`,
  943. id: `w4BM_splitSelect_0`,
  944. innerHTML: `<a>splitSelect: 0</a>`
  945. });
  946. w4BM_splitSelect_0.addEventListener(`click`, function (event) {
  947. localStorage.setItem(`w4BM_splitSelect`, 1);
  948. w4BM_splitSelect_0.replaceWith(w4BM_splitSelect_1);
  949. });
  950. // append binary choice buttons to the dropdown menu
  951. if (typeof (Storage) !== `undefined`) {
  952. // auto privating bookmarks
  953. if (autoPrivate == true || autoPrivate == `true`) {
  954. w4BM_dropMenu.append(w4BM_autoPrivate_yes);
  955. }
  956. else {
  957. w4BM_dropMenu.append(w4BM_autoPrivate_no);
  958. }
  959. // auto recommending bookmarks
  960. if (autoRecommend == true || autoRecommend == `true`) {
  961. w4BM_dropMenu.append(w4BM_autoRecommend_yes);
  962. }
  963. else {
  964. w4BM_dropMenu.append(w4BM_autoRecommend_no);
  965. }
  966. // auto autotagging bookmarks
  967. if (autoAutoTag == true || autoAutoTag == `true`) {
  968. w4BM_dropMenu.append(w4BM_autoAutoTag_yes);
  969. }
  970. else {
  971. w4BM_dropMenu.append(w4BM_autoAutoTag_no);
  972. }
  973. // showing AutoTag button
  974. if (showAutoTagButton == true || showAutoTagButton == `true`) {
  975. w4BM_dropMenu.append(w4BM_showAutoTagButton_yes);
  976. }
  977. else {
  978. w4BM_dropMenu.append(w4BM_showAutoTagButton_no);
  979. }
  980. //
  981. if (includeFandom == true || includeFandom == `true`) {
  982. w4BM_dropMenu.append(w4BM_includeFandomButton_yes);
  983. } else {
  984. w4BM_dropMenu.append(w4BM_includeFandomButton_no);
  985. }
  986. // choosing AutoTag type button
  987. if (AutoTag_type === 0 || AutoTag_type == 0 || AutoTag_type == `0`) {
  988. w4BM_dropMenu.append(w4BM_AutoTagType_0);
  989. } else {
  990. w4BM_dropMenu.append(w4BM_AutoTagType_1);
  991. }
  992. // adding "Summary Page" to bottom navbar
  993. if (bottomSummaryPage == true || bottomSummaryPage == `true`) {
  994. w4BM_dropMenu.append(w4BM_bottomSummaryPage_yes);
  995. }
  996. else {
  997. w4BM_dropMenu.append(w4BM_bottomSummaryPage_no);
  998. }
  999. // adding "Summary Page" to top navbar
  1000. if (topSummaryPage == true || topSummaryPage == `true`) {
  1001. w4BM_dropMenu.append(w4BM_topSummaryPage_yes);
  1002. }
  1003. else {
  1004. w4BM_dropMenu.append(w4BM_topSummaryPage_no);
  1005. }
  1006. // using a simple work summary
  1007. if (simpleWorkSummary == true || simpleWorkSummary == `true`) {
  1008. w4BM_dropMenu.append(w4BM_simpleWorkSummary_yes);
  1009. }
  1010. else {
  1011. w4BM_dropMenu.append(w4BM_simpleWorkSummary_no);
  1012. }
  1013. // using blockquotes w/ fancy work summary
  1014. if (FWS_asBlockquote == true || FWS_asBlockquote == `true`) {
  1015. w4BM_dropMenu.append(w4BM_FWS_asBlockquote_yes);
  1016. }
  1017. else {
  1018. w4BM_dropMenu.append(w4BM_FWS_asBlockquote_no);
  1019. }
  1020. // always injecting into bookmark
  1021. if (alwaysInjectBookmark == true || alwaysInjectBookmark == `true`) {
  1022. w4BM_dropMenu.append(w4BM_alwaysInjectBookmark_yes);
  1023. } else {
  1024. w4BM_dropMenu.append(w4BM_alwaysInjectBookmark_no);
  1025. }
  1026. // setting the splitSelect
  1027. if (splitSelect == 1) {
  1028. w4BM_dropMenu.append(w4BM_splitSelect_1);
  1029. }
  1030. else {
  1031. w4BM_dropMenu.append(w4BM_splitSelect_0);
  1032. }
  1033. }
  1034. }
  1035. // ------------------------------------------------------
  1036. // move the summary page check into its own boolean variable because it's going to be used for more than just BSP_conditional and TSP_conditional
  1037. const on_summary_page = !Boolean(main.querySelector(`li.chapter.previous`));
  1038. const
  1039. BSP_conditional = (currPgURL.pathname.includes(`chapters`) && on_summary_page == false && bottomSummaryPage),
  1040. TSP_conditional = (currPgURL.pathname.includes(`chapters`) && on_summary_page == false && topSummaryPage);
  1041. console.log(`
  1042. All conditions met for "Summary Page" button in the bottom nav bar?: ${BSP_conditional}
  1043. All conditions met for "Summary Page" button in the top nav bar?: ${TSP_conditional}`
  1044. );
  1045. function Creat###mmaryPageButton() {
  1046. // Creating the "Summary Page" buttons
  1047. // Make the href for the "Summary Page" button
  1048. const sum_pg_href = (new URL(main.querySelector(`li.chapter.entire a`)?.href)).pathname;
  1049. // Create the bottom "Summary Page" button
  1050. const btm_sum_pg = Object.assign(document.createElement(`li`), {
  1051. className: `bottomSummaryPage`,
  1052. id: `bottomSummaryPage`,
  1053. style: `padding-left: 0.5663em;`,
  1054. innerHTML: `<a href="${sum_pg_href}">Summary Page</a>`
  1055. });
  1056. // Create the top "Summary Page" button
  1057. const top_sum_pg = Object.assign(document.createElement(`li`), {
  1058. className: `topSummaryPage`,
  1059. id: `topSummaryPage`,
  1060. style: `padding-left: 0.31696592em;`,
  1061. innerHTML: `<a href="${sum_pg_href}">SP</a>`
  1062. });
  1063. // Get the "↑ Top" button that's in the bottom nav bar
  1064. const toTop_xp = `.//*[@id="feedback"]//*[@role="navigation"]//li[*[text()[contains(.,"Top")]]]`;
  1065. const toTop_btn = document.evaluate(toTop_xp, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1066. // Get the Entire Work button in the top nav bar
  1067. // Define the var which the Entire Work button will be stored in
  1068. const entiWork_topnavBTN = (function () {
  1069. // At least one script modifies the top navigation bar to the extent that the original XPath doesn't work, so this is me adding a fallback
  1070. // Original XPath
  1071. const entiWork_topnavBTN_xPath = `.//*[contains(concat(" ",normalize-space(@class)," ")," work ")][contains(concat(" ",normalize-space(@class)," ")," navigation ")][contains(concat(" ",normalize-space(@class)," ")," actions ")]//*[contains(concat(" ",normalize-space(@class)," ")," chapter ")][contains(concat(" ",normalize-space(@class)," ")," entire ")][count(.//a[contains(@href,"view_full_work=true")]) > 0]`;
  1072. let entiWork_topnavBTN = document.evaluate(entiWork_topnavBTN_xPath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1073. if (Boolean(entiWork_topnavBTN) == false) {
  1074. // Fallback XPath
  1075. const entiWork_topnavBTN_xPath = `.//li[contains(concat(" ",normalize-space(@class)," ")," entire ")][count(.//a[contains(@href,"view_full_work=true")]) > 0]`;
  1076. entiWork_topnavBTN = document.evaluate(entiWork_topnavBTN_xPath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1077. return entiWork_topnavBTN;
  1078. } else {
  1079. return entiWork_topnavBTN;
  1080. }
  1081. })();
  1082. // Debug code
  1083. // console.log(btm_sum_pg);
  1084. // console.log(top_sum_pg);
  1085. // console.log(toTop_btn);
  1086. // console.log(entiWork_topnavBTN);
  1087. return [toTop_btn, entiWork_topnavBTN, top_sum_pg, btm_sum_pg];
  1088. }
  1089. if (BSP_conditional) {
  1090. // If true, add a "Summary Page" button after the "↑ Top" button in the bottom navbar to take you to a page where the summary exists and can be used by the userscript
  1091. const [toTop_btn, , , btm_sum_pg] = Creat###mmaryPageButton();
  1092. // console.log(`\ntoTop_btn:\n${toTop_btn.outerHTML}\n\nbtm_sum_pg:\n${btm_sum_pg.outerHTML}`);
  1093. toTop_btn.after(btm_sum_pg);
  1094. }
  1095. if (TSP_conditional) {
  1096. // If true, adds summary page btn to top navbar
  1097. const [, entiWork_topnavBTN, top_sum_pg,] = Creat###mmaryPageButton();
  1098. // console.log(`\nentiWork_topnavBTN:\n${entiWork_topnavBTN.outerHTML}\n\ntop_sum_pg:\n${top_sum_pg.outerHTML}`);
  1099. entiWork_topnavBTN.after(top_sum_pg);
  1100. }
  1101. // determine whether the current page is one where the bookmark script can run
  1102. // adds onto the old method of just checking whether the URL has works or series by also taking into account whether the user always wants the script to run
  1103. const script_execute_conditional = (() => {
  1104. const
  1105. worksInURL = currPgURL.pathname.includes(`works`),
  1106. seriesInURL = currPgURL.pathname.includes(`series`);
  1107. let output = Boolean(worksInURL || seriesInURL);
  1108. if (alwaysInjectBookmark == true) {
  1109. // output = Boolean(worksInURL || seriesInURL);
  1110. return output;
  1111. }
  1112. if (alwaysInjectBookmark == false) {
  1113. output = Boolean((worksInURL && on_summary_page) || seriesInURL);
  1114. return output;
  1115. }
  1116. // Fallback to the normal boolean expression in case the if statements dont run
  1117. return output;
  1118. })();
  1119. if (script_execute_conditional) {
  1120. if (autoPrivate) { // for auto-privating your bookmarks
  1121. main.querySelector(`#bookmark_private`).checked = true;
  1122. }
  1123. if (autoRecommend) { // for auto-recommending your bookmarks
  1124. main.querySelector(`#bookmark_rec`).checked = true;
  1125. }
  1126. if (Boolean(autoCollections) == true) {
  1127. console.log(`AUTOCOLLECTIONS ARE DEFINED`);
  1128. const coll_lstor = localStorage.getItem(`w4BM_autoCollections`);
  1129. let coll_input = document.evaluate(`.//dd[count(preceding-sibling::dt[count(.//label[starts-with(@for,"bookmark_collection")]) > 0]) > 0]/ul/li/input`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1130. coll_input.value = coll_lstor;
  1131. } else {
  1132. console.log(`AUTOCOLLECTIONS ARE NOT DEFINED`);
  1133. }
  1134. // Define variables used in date configuration
  1135. const
  1136. currdate = new Date(),
  1137. dd = String(currdate.getDate()).padStart(2, `0`),
  1138. mm = String(currdate.getMonth() + 1).padStart(2, `0`), //January is 0
  1139. yyyy = currdate.getFullYear(),
  1140. hh = String(currdate.getHours()).padStart(2, `0`),
  1141. mins = String(currdate.getMinutes()).padStart(2, `0`);
  1142. // Get already existing bookmark notes
  1143. const curr_notes = main.querySelector(`#bookmark_notes`).textContent.split(divider).at(`-${splitSelect}`);
  1144. // Define function used in the Auto Tag feature
  1145. function StatusForAutoTag() {
  1146. // Look for HTML DOM element only present on series pages
  1147. const seriesTrue = document.evaluate(`.//*[@id="main"]//span[text()="Series"]`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1148. // Check if current page is a series page
  1149. if (seriesTrue != undefined) {
  1150. const
  1151. statusCheck_XPath = `//dl[contains(concat(" ",normalize-space(@class)," ")," series ")][contains(concat(" ",normalize-space(@class)," ")," meta ")][contains(concat(" ",normalize-space(@class)," ")," group ")]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[contains(text(), "Complete")]/following-sibling::*[1]`,
  1152. status_check = document.evaluate(statusCheck_XPath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
  1153. switch (status_check) {
  1154. case `Yes`:
  1155. return `Complete`;
  1156. case `No`:
  1157. return `Work In Progress`;
  1158. default:
  1159. break;
  1160. }
  1161. }
  1162. else {
  1163. const status_check = document.querySelector(`.work.meta.group dl.stats dt.status`);
  1164. if (Boolean(status_check) == false) { // For single chapter works which are always complete
  1165. return `Complete`;
  1166. }
  1167. else { // For multi chapter works
  1168. switch (status_check.textContent) {
  1169. case `Completed:`:
  1170. return `Complete`;
  1171. case `Updated:`:
  1172. return `Work in Progress`;
  1173. default:
  1174. break;
  1175. }
  1176. }
  1177. }
  1178. }
  1179. function FindID(search_term) {
  1180. const currPg_pathname_arr = currPgURL.pathname.split(`/`);
  1181. const pathname_idx = currPg_pathname_arr.indexOf(`${search_term}`);
  1182. const AO3_id = currPg_pathname_arr.at(parseInt(pathname_idx + 1)).toString();
  1183. return AO3_id;
  1184. }
  1185. // Make the button used in Auto Tag
  1186. if (document.querySelector(`#bookmark-form`) && showAutoTagButton) {
  1187. // Get element in bookmark form to append button to
  1188. const
  1189. yourTags_xp = `.//div[@id="main"]//div[@id="bookmark-form"]//dt/label[text() = 'Your tags']`,
  1190. yourTags_elem = document.evaluate(yourTags_xp, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1191. // Create button element
  1192. const autoTag_btn_elem = Object.assign(document.createElement(`label`), {
  1193. id: `w4BM_autoTag_elem`,
  1194. className: `actions`,
  1195. style: `font-size: 0.85em`,
  1196. innerHTML: `<a id='w4BM_autoTag_btn'>Auto Tag</a>`
  1197. });
  1198. // Append button element
  1199. yourTags_elem.after(autoTag_btn_elem);
  1200. // Select the parent dt element to which the button will be a child of
  1201. let yourTags_parent_dt_elem = (function () {
  1202. const yourTags_parent_dt_xp = `.//div[@id="main"]//div[@id="bookmark-form"]//dt[./label[text() = 'Your tags']]`;
  1203. const parent_elm = document.evaluate(yourTags_parent_dt_xp, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;;
  1204. return parent_elm;
  1205. })();
  1206. // Change the display style of the parent dt of the yourTags_elems to contents
  1207. yourTags_parent_dt_elem.style.display = `contents`;
  1208. // Add click listener to autoTag_btn_elem
  1209. autoTag_btn_elem.addEventListener(`click`, AutoTag);
  1210. }
  1211. // Look for HTML DOM element only present on series pages
  1212. const seriesTrue = document.evaluate(`.//*[@id="main"]//span[text()="Series"]`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1213. // Extract all details used in bookmark configuration and assign them to variables
  1214. const [title, title_HTML, title_URL] = (function () {
  1215. if (seriesTrue != undefined) {
  1216. // Retrieve series title
  1217. const srs_title = main.querySelector(`:scope > h2.heading`).textContent.trim();
  1218. const srs_title_HTML = `<a href="/series/${FindID(`series`)}">${srs_title}</a>`;
  1219. const srs_URL = `https://archiveofourown.org/series/${FindID(`series`)}`;
  1220. return [srs_title, srs_title_HTML, srs_URL];
  1221. }
  1222. else {
  1223. // Retrieve work title
  1224. const wrk_title = main.querySelector(`#workskin .title.heading`).textContent.trim();
  1225. const wrk_title_HTML = `<a href="/works/${FindID(`works`)}">${wrk_title}</a>`;
  1226. const wrk_URL = `https://archiveofourown.org/works/${FindID(`works`)}`;
  1227. return [wrk_title, wrk_title_HTML, wrk_URL];
  1228. }
  1229. })();
  1230. const [author, author_HTML] = (function () {
  1231. function AnonCheck(input) {
  1232. const array = Array.from(input.querySelectorAll(`a`));
  1233. return (!Array.isArray(array) || !array.length);
  1234. }
  1235. if (seriesTrue != undefined) {
  1236. // Retrieve series author
  1237. const
  1238. series_author_xpath = `.//*[@id="main"]//dl[contains(concat(" ",normalize-space(@class)," ")," series ")][contains(concat(" ",normalize-space(@class)," ")," meta ")][contains(concat(" ",normalize-space(@class)," ")," group ")]//dt[text()[contains(.,"Creator")]]/following-sibling::*[1]/self::dd`,
  1239. series_author_element = document.evaluate(series_author_xpath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  1240. const srs_authors = series_author_element.textContent.trim();
  1241. // check if series_author_element contains a link
  1242. // if it does, assign contents of the outerHTML of the <a> tag to author_HTML as a string
  1243. // if it doesnt, make author_HTML identical to author
  1244. if (AnonCheck(series_author_element)) {
  1245. const srs_authors_HTML = srs_authors;
  1246. return [srs_authors, srs_authors_HTML];
  1247. } else {
  1248. let auth_str_arr = [];
  1249. Array.from(series_author_element.querySelectorAll(`a`)).forEach(function (el) {
  1250. const el_c = el.cloneNode(true);
  1251. el_c.removeAttribute(`rel`);
  1252. auth_str_arr.push(el_c.outerHTML);
  1253. });
  1254. const srs_authors_HTML = auth_str_arr.join(`, `);
  1255. return [srs_authors, srs_authors_HTML];
  1256. }
  1257. }
  1258. else {
  1259. // Retrieve work author
  1260. const work_author_element = main.querySelector(`#workskin > .preface .byline`);
  1261. const wrk_authors = work_author_element.textContent.trim(); // fic author
  1262. // check if work_author_element contains a link
  1263. // if it does, assign contents of the outerHTML of the <a> tag to author_HTML as a string
  1264. // if it doesnt, make author_HTML identical to author
  1265. if (AnonCheck(work_author_element)) {
  1266. const wrk_authors_HTML = wrk_authors;
  1267. return [wrk_authors, wrk_authors_HTML];
  1268. } else {
  1269. let auth_str_arr = [];
  1270. Array.from(work_author_element.querySelectorAll(`a`)).forEach(function (el) {
  1271. const el_c = el.cloneNode(true);
  1272. el_c.removeAttribute(`rel`);
  1273. auth_str_arr.push(el_c.outerHTML);
  1274. });
  1275. const wrk_authors_HTML = auth_str_arr.join(`, `);
  1276. return [wrk_authors, wrk_authors_HTML];
  1277. }
  1278. }
  1279. })();
  1280. const AO3_status = (function () {
  1281. if (seriesTrue != undefined) {
  1282. // Get the last updated date of the series
  1283. const updated = main.querySelectorAll(`.series.meta.group dd`)[2].textContent;
  1284. // Retrieve series completion status
  1285. const pub_xp = `//dl[contains(concat(" ",normalize-space(@class)," ")," series ")][contains(concat(" ",normalize-space(@class)," ")," meta ")][contains(concat(" ",normalize-space(@class)," ")," group ")]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[contains(text(), "Complete")]/following-sibling::*[1]`;
  1286. const complete = document.evaluate(pub_xp, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.toLowerCase();
  1287. if (complete == `no`) {
  1288. const srs_AO3_status = `Updated: ${updated}`;
  1289. return srs_AO3_status;
  1290. }
  1291. if (complete == `yes`) {
  1292. const srs_AO3_status = `Completed: ${updated}`;
  1293. return srs_AO3_status;
  1294. }
  1295. }
  1296. else {
  1297. // Retrieve work status
  1298. if (Boolean(main.querySelector(`.status`))) {
  1299. // Retrieval method for multi-chapter works
  1300. const wrk_AO3_status = `${main.querySelector(`dt.status`).textContent} ${main.querySelector(`dd.status`).textContent}`;
  1301. return wrk_AO3_status;
  1302. }
  1303. else {
  1304. // Retrieval method for single chapter works
  1305. const wrk_AO3_status = `${main.querySelector(`dt.published`).textContent} ${main.querySelector(`dd.published`).textContent}`;
  1306. return wrk_AO3_status;
  1307. }
  1308. }
  1309. })();
  1310. const relationships = (function () {
  1311. if (seriesTrue != undefined) {
  1312. // Get all relationship tags present in series' works and add them to series bookmark
  1313. // Retrieve relationship tags
  1314. const raw_rels_arr = Array.from(document.querySelectorAll(`ul.tags > li.relationships > a.tag`));
  1315. let rels_arr = [];
  1316. raw_rels_arr.forEach(function (element, index, array) {
  1317. const element_clone = element.cloneNode(true);
  1318. element_clone.removeAttribute(`class`);
  1319. const rel_string = element_clone.outerHTML;
  1320. // console.log(`Content in array ${array} at index ${index}: ${array[index]}`);
  1321. // console.log(element_clone.outerHTML);
  1322. rels_arr.push(`• ${rel_string}`);
  1323. });
  1324. // Remove duplicates in rels_arr
  1325. rels_arr = [...new Set(rels_arr)];
  1326. // const srs_relationships = (function () {
  1327. // // Attempt to add entries from rels_arr to relationships regardless of whether rels_arr is empty or not
  1328. // let srs_rels = `<details><summary>Relationship Tags:</summary>\n${rels_arr.join(`\n`)}</details>`;
  1329. // // Check if rels_arr is empty, indicating no relationship tags
  1330. // if (!Array.isArray(rels_arr) || !rels_arr.length) {
  1331. // // If empty, set 'relationships' var to indicate no relationship tags
  1332. // srs_rels = `<details><summary>Relationship Tags:</summary>\n• <em><strong>No Relationship Tags</strong></em></details>`;
  1333. // }
  1334. // return srs_rels;
  1335. // })();
  1336. // return srs_relationships
  1337. // Check if rels_arr is empty, indicating no relationship tags
  1338. if (!Array.isArray(rels_arr) || !rels_arr.length) {
  1339. // If empty, set 'relationships' var to indicate no relationship tags
  1340. const srs_rels = `<details><summary>Relationship Tags:</summary>\n• <em><strong>No Relationship Tags</strong></em></details>`;
  1341. return srs_rels;
  1342. } else {
  1343. // If not empty, use values in rels_arr to set 'relationships'
  1344. const srs_rels = `<details><summary>Relationship Tags:</summary>\n${rels_arr.join(`\n`)}</details>`;
  1345. return srs_rels;
  1346. }
  1347. }
  1348. else {
  1349. // Retrieve relationship tags
  1350. const raw_rels_arr = Array.from(document.querySelectorAll(`.relationship.tags ul a`));
  1351. let rels_arr = [];
  1352. raw_rels_arr.forEach(function (el) {
  1353. const el_c = el.cloneNode(true);
  1354. el_c.removeAttribute(`class`);
  1355. const rel_string = `• ${el_c.outerHTML}`;
  1356. rels_arr.push(rel_string);
  1357. });
  1358. // Add Relationship tags to 'relationships' var
  1359. // Check if rels_arr is empty, indicating no relationship tags
  1360. if (!Array.isArray(rels_arr) || !rels_arr.length) {
  1361. // If empty, set 'relationships' var to indicate no relationship tags
  1362. const wrk_rels = `<details><summary>Relationship Tags:</summary>\n• <em><strong>No Relationship Tags</strong></em></details>`;
  1363. return wrk_rels;
  1364. } else {
  1365. // If not empty, fill 'relationships' var using rels_arr
  1366. const wrk_rels = `<details><summary>Relationship Tags:</summary>\n${rels_arr.join(`\n`)}</details>`;
  1367. return wrk_rels;
  1368. }
  1369. }
  1370. })();
  1371. const [fform_tags_list_HTML, fform_tags_list_TXT, fform_tags_comma_HTML, fform_tags_comma_TXT] = (function () {
  1372. if (seriesTrue != undefined) { return [``, ``, ``, ``]; }
  1373. else {
  1374. // Retrieve relationship tags
  1375. const raw_freeform_arr = Array.from(document.querySelectorAll(`.freeform.tags > ul a`));
  1376. let
  1377. freeform_arr_ls_HTML = [],
  1378. freeform_arr_ls_TXT = [],
  1379. freeform_arr_comma_HTML = [],
  1380. freeform_arr_comma_TXT = [];
  1381. raw_freeform_arr.forEach(function (el) {
  1382. const el_c = el.cloneNode(true);
  1383. el_c.removeAttribute(`class`);
  1384. const fform_lh_str = `• ${el_c.outerHTML}`;
  1385. const fform_lt_str = `• ${el_c.textContent.trim()}`;
  1386. const fform_ch_str = `${el_c.outerHTML}`;
  1387. const fform_ct_str = `${el_c.textContent.trim()}`;
  1388. freeform_arr_ls_HTML.push(fform_lh_str);
  1389. freeform_arr_ls_TXT.push(fform_lt_str);
  1390. freeform_arr_comma_HTML.push(fform_ch_str);
  1391. freeform_arr_comma_TXT.push(fform_ct_str);
  1392. });
  1393. // Add Relationship tags to 'relationships' var
  1394. // Check if rels_arr is empty, indicating no relationship tags
  1395. if (!Array.isArray(raw_freeform_arr) || !raw_freeform_arr.length) {
  1396. // If empty, set 'relationships' var to indicate no relationship tags
  1397. const wrk_fforms = `<details><summary>Additional Tags:</summary>\n• <em><strong>No Additional Tags</strong></em></details>`;
  1398. return [wrk_fforms, wrk_fforms, wrk_fforms, wrk_fforms];
  1399. } else {
  1400. // If not empty, fill 'relationships' var using rels_arr
  1401. const wrk_fforms_lh = `<details><summary>Additional Tags:</summary>\n${freeform_arr_ls_HTML.join(`\n`)}</details>`;
  1402. const wrk_fforms_lt = `<details><summary>Additional Tags:</summary>\n${freeform_arr_ls_TXT.join(`\n`)}</details>`;
  1403. const wrk_fforms_ch = `<details><summary>Additional Tags:</summary>\n${freeform_arr_comma_HTML.join(`, `)}</details>`;
  1404. const wrk_fforms_ct = `<details><summary>Additional Tags:</summary>\n${freeform_arr_comma_TXT.join(`, `)}</details>`;
  1405. return [wrk_fforms_lh, wrk_fforms_lt, wrk_fforms_ch, wrk_fforms_ct];
  1406. }
  1407. }
  1408. })();
  1409. // console.log([fform_tags_list_HTML, fform_tags_list_TXT, fform_tags_comma_HTML, fform_tags_comma_TXT]);
  1410. const summary_default_value = `<em><strong>NO SUMMARY</strong></em>`;
  1411. const summary = ((function () {
  1412. if (seriesTrue != undefined) {
  1413. // Check if there is a series summary
  1414. let series_summary;
  1415. switch (Boolean(main.querySelector(`.series.meta.group .userstuff`))) {
  1416. case true: // If series summary exists, retrieve summary
  1417. series_summary = main.querySelector(`.series.meta.group .userstuff`).innerHTML;
  1418. break;
  1419. case false: // Else fill in var with NO SUMMARY string
  1420. series_summary = summary_default_value;
  1421. break;
  1422. default: // If error, fill var asking for bug report
  1423. series_summary = `<em>Error in retrieving series summary, please report this bug at</em> https://greasyfork.org/en/scripts/467885`;
  1424. break;
  1425. }
  1426. // Check if there are series notes
  1427. const series_notes_dt_xp = `.//*[contains(concat(" ",normalize-space(@class)," ")," series ")][contains(concat(" ",normalize-space(@class)," ")," meta ")][contains(concat(" ",normalize-space(@class)," ")," group ")]//dt[text()[contains(.,"Notes:")]]`;
  1428. let series_notes;
  1429. switch (Boolean(document.evaluate(series_notes_dt_xp, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue)) {
  1430. case true: // If series notes exists, retrieve notes
  1431. series_notes = document.evaluate(`${series_notes_dt_xp}/following-sibling::*[1]`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.innerHTML;
  1432. series_notes = `<details><summary>Series Notes:</summary>\n${series_notes}\n</details>`;
  1433. break;
  1434. case false:
  1435. series_notes = ``;
  1436. break;
  1437. default:
  1438. series_notes = ``;
  1439. break;
  1440. }
  1441. // Join series summary and series notes
  1442. const srs_summary = series_summary + series_notes;
  1443. return srs_summary;
  1444. }
  1445. else {
  1446. // Retrieve work summary
  1447. if (simpleWorkSummary) { // the original methos to retrieve the work's summary
  1448. const wrk_summary = main.querySelector(`.summary`).innerHTML;
  1449. return wrk_summary;
  1450. // Example output of the above method:
  1451. // summary will be a var equal to the following string
  1452. // '\n <h3 class="heading">Summary:</h3>\n <blockquote class="userstuff">\n <p>Lorem ipsum dolor...</p>\n </blockquote>\n '
  1453. }
  1454. else if (!simpleWorkSummary && FWS_asBlockquote && main.querySelector(`.summary blockquote`) != null) { // new method #1
  1455. const wrk_summary = main.querySelector(`.summary blockquote`).outerHTML;
  1456. return wrk_summary;
  1457. // Example output of the above method:
  1458. // summary will be a var equal to the following string
  1459. // '<blockquote class="userstuff">\n <p>Lorem ipsum dolor...</p>\n </blockquote>'
  1460. }
  1461. else if (!simpleWorkSummary && !FWS_asBlockquote && main.querySelector(`.summary blockquote`) != null) { // new method #2
  1462. const wrk_summary = main.querySelector(`.summary blockquote`).innerHTML.trim();
  1463. return wrk_summary;
  1464. // Example output of the above method:
  1465. // summary will be a var equal to the following string
  1466. // '<p>Lorem ipsum dolor...</p>'
  1467. }
  1468. }
  1469. })() || summary_default_value);
  1470. const series_works_titles_summaries = (function () {
  1471. function GetPadAmount(input_array) {
  1472. const pad_amt = input_array.length.toString().length;
  1473. if (pad_amt < 2) {
  1474. return 2;
  1475. } else {
  1476. return pad_amt;
  1477. }
  1478. }
  1479. if (seriesTrue != undefined) {
  1480. // Get summaries for each work in series
  1481. const series_children = Array.from(main.querySelector(`.series.work.index.group`).children);
  1482. let srsWkSum_arr = [];
  1483. series_children.forEach((child, index) => {
  1484. let srs_work_sum = summary_default_value;
  1485. const workname = (() => {
  1486. try {
  1487. const work_title_elm = child.querySelector(`.heading a[href*="works"]`);
  1488. const work_title = `<a href="${work_title_elm.getAttribute(`href`)}">${work_title_elm.innerText}</a>`;
  1489. return work_title;
  1490. } catch (error) {
  1491. const work_title = child.querySelector(`.header > h4.heading`).innerText;
  1492. return work_title;
  1493. }
  1494. })();
  1495. const summary_elem = child.querySelector(`.userstuff.summary`);
  1496. if (Boolean(summary_elem) == true) {
  1497. srs_work_sum = summary_elem.outerHTML;
  1498. }
  1499. const series_pagination_mult = (() => {
  1500. const pagination_nav = main.querySelector(`ol.pagination:has(+ .series.work.index.group)`);
  1501. try {
  1502. if (Object.is(pagination_nav, null) || Object.is(pagination_nav, undefined)) {
  1503. throw new Error("Series is not Paginated");
  1504. } else {
  1505. return ((parseInt(pagination_nav.querySelector(`.current`).textContent.trim())) - 1);
  1506. }
  1507. } catch (error) {
  1508. return 0;
  1509. }
  1510. })();
  1511. const wrk_num = (index + (20 * series_pagination_mult) + 1).toString().padStart(GetPadAmount(series_children), `0`);
  1512. if (series_children.length > 10) {
  1513. srsWkSum_arr.push(`• Work ${wrk_num}. ${workname}<br />`);
  1514. } else {
  1515. srsWkSum_arr.push(`<details><summary>Work ${wrk_num}. ${workname} - Summary</summary>\n${srs_work_sum}</details>`);
  1516. }
  1517. });
  1518. const ser_wor_sum = `\n${srsWkSum_arr.join(`\n`)}\n`;
  1519. return ser_wor_sum;
  1520. }
  1521. else {
  1522. const ser_wor_sum = ``;
  1523. return ser_wor_sum;
  1524. }
  1525. })();
  1526. const words = (function () {
  1527. if (seriesTrue != undefined) {
  1528. // Retrieve series word count
  1529. const srs_words = document.evaluate(`.//*[@id="main"]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[text()="Words:"]/following-sibling::*[1]/self::dd`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
  1530. return srs_words;
  1531. }
  1532. else {
  1533. // Retrieve work work count
  1534. const wrk_words = document.evaluate(`.//*[@id="main"]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[text()="Words:"]/following-sibling::*[1]/self::dd`, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
  1535. return wrk_words;
  1536. }
  1537. })();
  1538. const series_link = (function () {
  1539. if (seriesTrue != undefined) { // blank for series pages
  1540. return '';
  1541. }
  1542. else {
  1543. // Get series text elem
  1544. const work_series_info =
  1545. Array
  1546. .from(main.querySelectorAll(`dl.work.meta.group > dd.series > span.series > span.position`))
  1547. .map(elm => `• ${elm.innerHTML}`);
  1548. if (work_series_info.length > 0) {
  1549. const output = `<details><summary>Work's Series</summary>
  1550. ${work_series_info.join(`\n`)}
  1551. </details>`;
  1552. return output;
  1553. } else {
  1554. return '';
  1555. }
  1556. }
  1557. })();
  1558. const ws_id = (function () {
  1559. if (seriesTrue != undefined) {
  1560. const srs_AO3_id = FindID(`series`);
  1561. return srs_AO3_id;
  1562. }
  1563. else {
  1564. const wrk_AO3_id = FindID(`works`);
  1565. return wrk_AO3_id;
  1566. }
  1567. })();
  1568. const lastChapter = (function () {
  1569. if (seriesTrue != undefined) {
  1570. // Have lastChapter be an empty string for series
  1571. const srs_lastChapter = ``;
  1572. return srs_lastChapter;
  1573. }
  1574. else {
  1575. const latest_chapter_number = (function () {
  1576. // XPath for the chapter count element
  1577. const chapter_count_elem_XPath = `.//*[@id="main"]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[text()="Chapters:"]/following-sibling::*[1]/self::dd`;
  1578. const chapter_count_elem_Text = document.evaluate(chapter_count_elem_XPath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.trim();
  1579. const latest_chapter_num = chapter_count_elem_Text.split(`/`).at(0);
  1580. return latest_chapter_num;
  1581. })();
  1582. // Calculate appropriate padding count for lastChapter
  1583. const chapNumPadCount = (function () {
  1584. if (latest_chapter_number.length >= 3) {
  1585. return 3;
  1586. } else {
  1587. return 2;
  1588. }
  1589. })();
  1590. // Retrieve last chapter of work
  1591. const wrk_lastChapter = `Chapter ${latest_chapter_number.padStart(chapNumPadCount, `0`)}`;
  1592. return wrk_lastChapter;
  1593. }
  1594. })();
  1595. // define autotag_status for use in AutoTag()
  1596. const autotag_status = StatusForAutoTag();
  1597. /* ///////////////// USER CONFIGURABLE SETTINGS ///////////////// */
  1598. /*
  1599. // Below are the configurations for the autogenerated bookmark content, including the date configuraton
  1600. // THE CONFIGURATION RELIES HEAVILY ON TEMPLATE LITERALS
  1601. // FOR MORE INFORMATION ON TEMPLATE LITERALS PLEASE VISIT THE FOLLOWING WEBPAGE
  1602. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
  1603. */
  1604. /* ///////////////// Bookmark content configuration section ///////////////// */
  1605. /*
  1606. Variables that can be used when creating the string for newBookmarkNotes:
  1607. - date_string // String to show when the work was last read – User configurable in the Date configuration sub-section
  1608. - title // Title of the work or series
  1609. - title_HTML // Title of the work or series, as an HTML <a> element (a link). e.g. the title_HTML string for AO3 work 54769867 would be '<a href="/works/54769867">Glass Cannon</a>'
  1610. - title_URL // The URL of the work/series being bookmarked as plaintext
  1611. - author // Author of the work or series
  1612. - author_HTML // Author of the work or series, as an HTML <a> element (a link). e.g. the author_HTML string for AO3 work 54769867 would be '<a rel="author" href="/users/nescias/pseuds/nescias">nescias</a>'
  1613. - AO3_status // Status of the work or series. i.e. Completed: 2020-08-23, Updated: 2022-05-08, Published: 2015-06-29
  1614. - relationships // For work bookmarks, it's the Relationship tags present in the work and it will be a collapsible element in your work bookmark. For series bookmarks, it's all of the unique Relationship tags present in all the works in a series and it will be a collapsible element in your series bookmark
  1615. - summary // Summary of the work or series
  1616. - words // Current word count of the work or series
  1617. - ws_id // ID of the work/series being bookmarked
  1618. Variables specific to series:
  1619. - series_works_titles_summaries // Adds all of the summaries of the works in the series to the series bookmark
  1620. Variables specific to works:
  1621. - lastChapter // Last published chapter of the work or series
  1622. - series_link // Info about the series' the work belongs to
  1623. - fform_tags_list_HTML // The freeform tags of a work as links in a list similar to that in the relationships variable
  1624. - fform_tags_list_TXT // The freeform tags of a work as plaintext (so you don't run into the character limit) in a list similar to that in the relationships variable
  1625. - fform_tags_comma_HTML // The freeform tags of a work as comma separated links
  1626. - fform_tags_comma_TXT // The freeform tags of a work as comma separated plaintext (so you don't run into the character limit)
  1627. */
  1628. /* //// Date configuration sub-section //// */
  1629. /*
  1630. // THE CONFIGURATION RELIES HEAVILY ON TEMPLATE LITERALS
  1631. // FOR MORE INFORMATION ON TEMPLATE LITERALS PLEASE VISIT THE FOLLOWING WEBPAGE
  1632. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
  1633. */
  1634. // Setting the date
  1635. // Feel free to use your own date format, you don't need to stick to the presets here.
  1636. // If you dont like the predefined vars for date, and your device supports the require field,
  1637. // I've added the moment.js library which you can use to define your own date var.
  1638. // e.g.
  1639. // const date = moment().format("dddd, MMMM Do YYYY, h:mm:ss a");
  1640. // would give you something like the following string:
  1641. // "Thursday, August 31st 2023, 4:38:35 pm"
  1642. //
  1643. // format guide available here: https://momentjscom.readthedocs.io/en/latest/moment/04-displaying/01-format/
  1644. const
  1645. date = `${yyyy}/${mm}/${dd}`, // Date without time
  1646. date_string = (function () {
  1647. // Make the date string an empty string for series because it doesnt make sense there
  1648. if (Boolean(seriesTrue)) {
  1649. return ``;
  1650. } else {
  1651. return `(Approximate) Last Read: ${date}`;
  1652. }
  1653. })();
  1654. // date = `${yyyy}/${mm}/${dd} ${hh}${mm}hrs`; // Date with time
  1655. console.log(`
  1656. w4tchdoge's AO3 Bookmark Maker UserScript Log
  1657. --------------------
  1658. Date Generated: ${date}
  1659. Date String Generated: ${date_string}`
  1660. );
  1661. /* ///////////// Select from Presets ///////////// */
  1662. // To stop using a preset, wrap it in a block comment (add /* to the start and */ to the end); To start using a preset, do the opposite.
  1663. // Remember to make sure the variables are set correctly for that preset
  1664. // divider and splitSelect are set in the dropdown menu on your User Preferences page
  1665. // new_notes is set at the bottom of the script
  1666. // If you're experiencing any issues with the script, please do not hesitate to PM me on GreasyFork
  1667. // I could have simply made a mistake in the documentation which is messing everything up (I am not infallible XD)
  1668. // ------------------------
  1669. // Preset 1 – With last read date
  1670. // To use this preset, scroll to the top where the constants are defined and set divider and splitSelect to the following values:
  1671. // divider = `</details>\n\n`
  1672. // splitSelect = 1
  1673. // new_notes = `${workInfo}\n\n${curr_notes}`
  1674. workInfo = `<details><summary>Work/Series Details</summary>
  1675. \t${title_HTML} by ${author_HTML}
  1676. \t${AO3_status}
  1677. \tWork/Series ID: ${ws_id}
  1678. \t${relationships}
  1679. \t<details><summary>Work/Series Summary:</summary>
  1680. \t${summary}</details>
  1681. ${date_string}</details>`;
  1682. // ------------------------
  1683. // Preset 2 – Without last read date
  1684. // To use this preset, scroll to the top where the constants are defined and set divider and splitSelect to the following values:
  1685. // divider = `</details>\n\n`
  1686. // splitSelect = 1
  1687. // new_notes = `${workInfo}\n\n${curr_notes}`
  1688. /* workInfo = `<details><summary>Work/Series Details</summary>
  1689. \t${title} by ${author_HTML}
  1690. \t${AO3_status}
  1691. \tWork/Series ID: ${ws_id}
  1692. \t${relationships}
  1693. \t<details><summary>Work/Series Summary:</summary>
  1694. \t${summary}</details>
  1695. </details>`; */
  1696. // ------------------------
  1697. // Preset 3 – Preset 1 but reversed
  1698. // To use this preset, scroll to the top where the constants are defined and set divider and splitSelect to the following values:
  1699. // divider = `<br />\n<details>`
  1700. // splitSelect = 0
  1701. // new_notes = `${curr_notes}<br />\n${workInfo}`
  1702. /* workInfo = `<details><summary>Work/Series Details</summary>
  1703. \t${title} by ${author_HTML}
  1704. \t${AO3_status}
  1705. \tWork/Series ID: ${ws_id}
  1706. \t${relationships}
  1707. \t<details><summary>Work/Series Summary:</summary>
  1708. \t${summary}</details>
  1709. ${date_string}</details>`; */
  1710. // ------------------------
  1711. // Preset 4 – Preset 2 but reversed
  1712. // To use this preset, scroll to the top where the constants are defined and set divider and splitSelect to the following values:
  1713. // divider = `<br />\n<details>`
  1714. // splitSelect = 0
  1715. // new_notes = `${curr_notes}<br />\n${workInfo}`
  1716. /* workInfo = `<details><summary>Work/Series Details</summary>
  1717. \t${title} by ${author_HTML}
  1718. \t${AO3_status}
  1719. \tWork/Series ID: ${ws_id}
  1720. \t${relationships}
  1721. \t<details><summary>Work/Series Summary:</summary>
  1722. \t${summary}</details>
  1723. </details>`; */
  1724. // ------------------------
  1725. // Auto Tag Feature
  1726. async function AutoTag() {
  1727. function inRange(input, minimum, maximum) {
  1728. return input >= minimum && input <= maximum;
  1729. }
  1730. // Find User Tags input box
  1731. let tag_input_box = document.querySelector('.input #bookmark_tag_string_autocomplete');
  1732. // Make array of everything that will go into the aforementionedinput box
  1733. let tag_inputs = [autotag_status];
  1734. // Get word count of work/series
  1735. const AT_words = (function () {
  1736. let
  1737. AT_words_XPath = './/*[@id="main"]//dl[contains(concat(" ",normalize-space(@class)," ")," stats ")]//dt[text()="Words:"]/following-sibling::*[1]/self::dd',
  1738. AT_words = document.evaluate(AT_words_XPath, document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent.toString().replaceAll(/[, \s]/gi, '');
  1739. AT_words = parseInt(AT_words);
  1740. return AT_words;
  1741. })();
  1742. if (includeFandom === true || includeFandom === `true`) {
  1743. if (seriesTrue != undefined) { // Check if current page is a series
  1744. const fandoms_set = new Set(Array.from(main.querySelectorAll(`.header.module .fandoms > a.tag`)).map(elm => elm.textContent.trim()));
  1745. // console.log(fandoms_set);
  1746. fandoms_set.forEach(elm => tag_inputs.push(elm));
  1747. } else {
  1748. const fandoms_set = new Set(Array.from(main.querySelectorAll(`dl.work.meta.group > dd.fandom > ul.commas > li > a.tag`)).map(elm => elm.textContent.trim()));
  1749. // console.log(fandoms_set);
  1750. fandoms_set.forEach(elm => {
  1751. // console.log(elm);
  1752. tag_inputs.push(elm);
  1753. });
  1754. }
  1755. }
  1756. // Original AutoTag Behaviour
  1757. // As suggested by `oliver t` @ https://greasyfork.org/en/scripts/467885/discussions/198028
  1758. if (inRange(AutoTag_type, 0, 1) && AutoTag_type == 0) {
  1759. let word_count_tag = ``;
  1760. // Define Word Count Tag
  1761. // In case you want to add more tags or change the word count range for each tag,
  1762. // here are some recourses on how comparators work in JavaScript:
  1763. // StackOverflow answer that shows you how equalities work: https://stackoverflow.com/a/14718577/11750206
  1764. // An overview of JavaScript's Comparison and Logical Operators: https://www.w3schools.com/js/js_comparisons.asp
  1765. if (AT_words < 2500) { word_count_tag = 'Word Count: Less than 2500'; }
  1766. if (AT_words < 7500 && AT_words >= 2500) { word_count_tag = 'Word Count: 2500 to 7499'; }
  1767. if (AT_words < 15000 && AT_words >= 7500) { word_count_tag = 'Word Count: 7500 to 14999'; }
  1768. if (AT_words < 30000 && AT_words >= 15000) { word_count_tag = 'Word Count: 15000 to 29999'; }
  1769. if (AT_words >= 30000) { word_count_tag = 'Word Count: Greater than 30000'; }
  1770. // Add the autotag to the array of inputs
  1771. tag_inputs.push(word_count_tag);
  1772. }
  1773. // AutoTag using canonical AO3 "Wordcount Over *" tags for the word count
  1774. // As suggested by `prismbox` @ https://greasyfork.org/en/scripts/467885/discussions/255399
  1775. if (inRange(AutoTag_type, 0, 1) && AutoTag_type == 1) {
  1776. const canon_AO3_wc_lims = await (async () => {
  1777. // Try to fetch the "Canon AO3 Wordcount Over *" tag page to get the canon wordcounts
  1778. try {
  1779. // Fetch word count page
  1780. const canon_wc_over_pg_resp_text = await (async () => {
  1781. const fetch_url = `https://archiveofourown.org/tags/search?tag_search[name]=wordcount+over&tag_search[fandoms]=&tag_search[type]=&tag_search[canonical]=T&tag_search[sort_column]=name&tag_search[sort_direction]=asc&commit=Search+Tags`;
  1782. const fetch_resp = await fetch(fetch_url);
  1783. const resp_text = await fetch_resp.text();
  1784. return resp_text;
  1785. })();
  1786. // Parse fetched page to get word counts as an array of ints
  1787. const html_parser = new DOMParser();
  1788. const cwco_pg_HTML = html_parser.parseFromString(canon_wc_over_pg_resp_text, `text/html`);
  1789. const cwco_tags_int_arr = Array.from(cwco_pg_HTML.querySelectorAll(`#main .tag.index.group li`))
  1790. .map(elm => parseInt(elm.firstChild.textContent.trim().replace(/^Freeform: Wordcount: Over | \u200e\(\d+\)$/g, ``).replace(`.`, ``)))
  1791. .sort((a, b) => a.toString().localeCompare(b, undefined, { numeric: true }));
  1792. // Throw error if array is empty
  1793. if (!Array.isArray(cwco_tags_int_arr) || !cwco_tags_int_arr.length) {
  1794. throw new Error(`Fetching of "Canon AO3 Wordcount Over *" tags failed! Switching to hardcoded fallback.`);
  1795. }
  1796. return cwco_tags_int_arr;
  1797. }
  1798. // Catch any thrown error and fallback to hardcoded wordcounts when the above fails
  1799. catch (arrempty_error) {
  1800. console.error(arrempty_error);
  1801. return [1, 10, 20, 30, 50, 100, 150, 200, 500].map(x => x * 1000);
  1802. }
  1803. })();
  1804. // Define Word Count Tag
  1805. // In case you want to add more tags or change the word count range for each tag,
  1806. // here are some recourses on how comparators work in JavaScript:
  1807. // StackOverflow answer that shows you how equalities work: https://stackoverflow.com/a/14718577/11750206
  1808. // An overview of JavaScript's Comparison and Logical Operators: https://www.w3schools.com/js/js_comparisons.asp
  1809. // Add word count tags directly into the tag_inputs array
  1810. canon_AO3_wc_lims.forEach(element => {
  1811. if (AT_words > element) {
  1812. tag_inputs.push(`Wordcount: Over ${(new Intl.NumberFormat({ style: `decimal` }).format(element)).replaceAll(`,`, `.`)}`);
  1813. }
  1814. });
  1815. }
  1816. if (!inRange(AutoTag_type, 0, 1)) { console.log(`AutoTag_type is not between 0 and 1. Please contact me (the script author) on GreasyFork for troubleshooting`); }
  1817. // console.log(tag_inputs);
  1818. // Add all values in the tag_inputs variable to the User Tags input box
  1819. tag_input_box.value = tag_inputs.join(`, `);
  1820. }
  1821. // for automatically triggering the autotag function
  1822. if (document.querySelector(`#bookmark-form`) && document.querySelector(`#w4BM_autoTag_elem`) && showAutoTagButton && autoAutoTag) {
  1823. const autoTag_btn_elem = document.querySelector(`#w4BM_autoTag_elem`);
  1824. autoTag_btn_elem.click();
  1825. }
  1826. // ------------------------
  1827. // You are free to define your own string for the new_notes variable as you see fit
  1828. // Fills the bookmark box with the autogenerated bookmark
  1829. const new_notes = `${workInfo}\n\n${curr_notes}`;
  1830. document.getElementById("bookmark_notes").innerHTML = new_notes;
  1831. }
  1832. console.log(`Ending w4BM userscript execution: ${performance.now() - s_t}ms`);
  1833. })();