Advertisement
franklinglzhou

AoPS enhanced userscript

May 2nd, 2023 (edited)
15
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.37 KB | None | 0 0
  1. // Functions for settings UI elements
  2. var settings_ui = {
  3. toggle: label => (name, value, settings_manager) => {
  4. var checkbox_label = document.createElement('label');
  5. var checkbox = document.createElement('input');
  6. checkbox.type = 'checkbox';
  7. checkbox.name = name;
  8. checkbox.checked = value;
  9. checkbox.addEventListener('change', e => settings_manager.set(name, e.target.checked));
  10. checkbox_label.appendChild(checkbox);
  11. checkbox_label.appendChild(document.createTextNode(' ' + label));
  12. return checkbox_label;
  13. },
  14. select: (label, options) => (name, value, settings_manager) => {
  15. var select_label = document.createElement('label');
  16. select_label.innerText = label + ' ';
  17. var select = document.createElement('select');
  18. select.name = name;
  19. for (var option of options) {
  20. var option_element = document.createElement('option');
  21. option_element.value = option[0];
  22. option_element.innerText = option[1];
  23. select.appendChild(option_element);
  24. }
  25. select.value = value;
  26. select.addEventListener('change', e => settings_manager.set(name, e.target.value));
  27. select_label.appendChild(select);
  28. return select_label;
  29. },
  30. }
  31.  
  32.  
  33. .cmty-posting-button-row{
  34. height: min-content !important;
  35. display: flow-root;
  36. }
  37. #feed-wrapper{
  38. display: inline;
  39. }
  40. #feed-topic{
  41. top: 0;
  42. left: 0;
  43. width: 100%;
  44. height: 100%;
  45. }
  46. .cmty-no-tablet{
  47. display: inline !important;
  48. }
  49. #feed-topic .cmty-topic-jump{
  50. position: fixed;
  51. top: 32px;
  52. bottom: auto;
  53. left: auto;
  54. right: 10px;
  55. z-index: 1000;
  56. font-size: 24px;
  57. }
  58. #feed-topic .cmty-topic-jump-top{
  59. right: 40px;
  60. }
  61. .cmty-upload-modal{
  62. display: inline;
  63. }
  64. .aops-modal-body{
  65. width: 100% !important;
  66. }
  67.  
  68. #feed-tabs .cmty-postbox-inner-box{
  69. width: 100% !important;
  70. max-width: none !important;
  71. }
  72.  
  73. #feed-topic .cmty-topic-posts-outer-wrapper > .aops-scroll-outer > .aops-scroll-inner {
  74. left: 0;
  75. width: 100% !important;
  76. }
  77.  
  78. #feed-topic .cmty-postbox-inner-box {
  79. max-width: 100% !important;
  80. }
  81. `,
  82. "Dark": `
  83. .cmty-topic-jump{
  84. color: #000;
  85. }
  86. *{
  87. scrollbar-color: #04a3af #333533;
  88. }
  89. ::-webkit-scrollbar-track{
  90. background: #333533;
  91. }
  92. ::-webkit-scrollbar-thumb{
  93. background: #04a3af;
  94. }
  95. .cmty-topic-posts-top *:not(.cmty-item-tag){
  96. color: black !important;
  97. }
  98. #page-wrapper img:not([class*='latex']):not([class*='asy']),
  99. #feed-wrapper img:not([class*='latex']):not([class*='asy']){
  100. filter: hue-rotate(180deg) invert(1);
  101. }
  102. .bbcode_smiley[src*='latex']{
  103. filter: none !important;
  104. }
  105. .cmty-topic-posts-top,
  106. .cmty-postbox-inner-box,
  107. .cmty-topic-posts-bottom,
  108. .aops-scroll-outer{
  109. background: #ddd !important;
  110. }
  111. .aops-scroll-slider{
  112. background: #222 !important;
  113. }
  114. iframe{
  115. filter: invert(1) hue-rotate(180deg);
  116. }
  117.  
  118. #page-wrapper,
  119. .aops-modal-wrapper,
  120. #feed-wrapper > * > *{
  121. filter: invert(1) hue-rotate(180deg);
  122. }
  123. body{
  124. background: #111 !important;
  125. }`,
  126. }
  127.  
  128. var quote_schemes = {
  129. 'AoPS': AoPS.Community ? AoPS.Community.Views.Post.prototype.onClickQuote : function () { alert("Quoting failed") }, // Uses dummy function as a fallback when community is undefined.
  130. 'Enhanced': function () { this.topic.appendToReply("[quote name=\"" + this.model.get("username") + "\" url=\"/community/p" + this.model.get("post_id") + "\"]\n" + this.model.get("post_canonical").trim() + "\n[/quote]\n\n") },
  131. 'Link': function () { this.topic.appendToReply(`@[url=https://aops.com/community/p${this.model.get("post_id")}]${this.model.get("username")} (#${this.model.get("post_number")}):[/url]`); },
  132. 'Hide': function () {
  133. this.topic.appendToReply(`[hide=Post #${this.model.get("post_number")} by ${this.model.get("username")}]
  134. [url=https://aops.com/community/user/${this.model.get("poster_id")}]${this.model.get('username')}[/url] [url=https://aops.com/community/p${this.model.get("post_id")}](view original)[/url]
  135. ${this.model.get('post_canonical').trim()}
  136. [/hide]
  137.  
  138. `);
  139. },
  140. };
  141.  
  142. class EnhancedSettingsManager {
  143. /** Default settings */
  144. DEFAULTS = {
  145. notifications: true,
  146. post_links: true,
  147. feed_moderation: true,
  148. kill_top: false,
  149. quote_primary: 'Enhanced',
  150. quote_secondary: 'Enhanced',
  151. theme: 'None',
  152. };
  153.  
  154. /**
  155. * Constructor
  156. * @param {string} storage_variable - Variable to use when reading or writing settings
  157. */
  158. constructor(storage_variable) {
  159. this.storage_variable = storage_variable;
  160. this._settings = JSON.parse(localStorage.getItem(this.storage_variable) || '{}');
  161. this.hooks = {};
  162. }
  163.  
  164. /**
  165. * Retrieves a setting.
  166. * @param {string} setting - Setting to retrieve
  167. */
  168. get = setting => (setting in this._settings ? this._settings : this.DEFAULTS)[setting];
  169.  
  170. /**
  171. * Sets a setting.
  172. * @param {string} setting - Setting to change
  173. * @param {*} value - Value to set
  174. */
  175. set(setting, value) {
  176. this._settings[setting] = value;
  177. localStorage.setItem(this.storage_variable, JSON.stringify(this._settings));
  178. // Run hooks
  179. if (setting in this.hooks) for (var hook of this.hooks[setting]) hook(value);
  180. }
  181.  
  182. /**
  183. * Add a hook that will be called when the associated setting is changed.
  184. * @param {string} setting - Setting to add a hook to
  185. * @param {function} callback - Callback to run when the setting is changed
  186. * @param {boolean} run_on_add - Whether to immediately run the hook
  187. */
  188. add_hook(setting, callback, run_on_add = true) {
  189. setting in this.hooks ? this.hooks[setting].push(callback) : this.hooks[setting] = [callback];
  190. if (run_on_add) callback(this.get(setting));
  191. }
  192.  
  193. // No functions for removing hooks, do it manually by modifying the hooks attribute.
  194. }
  195.  
  196. var enhanced_settings = new EnhancedSettingsManager('enhanced_settings');
  197. // Old settings adapter
  198. for (var setting of ['quote_primary', 'quote_secondary']) {
  199. var setting_value = enhanced_settings.get(setting);
  200. if (setting_value.toLowerCase() == setting_value) enhanced_settings.set(setting, setting_value[0].toUpperCase() + setting_value.slice(1));
  201. }
  202.  
  203. // Themes
  204. {
  205. const theme_element = document.createElement('style');
  206.  
  207. enhanced_settings.add_hook('theme', value => {
  208. theme_element.textContent = themes[value];
  209. if (value != 'None') {
  210. document.head.appendChild(theme_element);
  211. } else if (theme_element.parentNode) theme_element.parentNode.removeChild(theme_element);
  212. window.dispatchEvent(new Event('resize')); // Recalculate sizes of elements
  213. });
  214. }
  215.  
  216. // Simplified header
  217. {
  218. const menubar_wrapper = document.querySelector('.menubar-links-outer');
  219. const login_wrapper = document.querySelector('.menu-login-wrapper')
  220. if (!(menubar_wrapper && login_wrapper)) return _ => null;
  221. var kill_element = document.createElement('style');
  222. const menubar_wrapper_normal_position = menubar_wrapper.nextSibling;
  223. const login_wrapper_normal_position = login_wrapper.nextSibling;
  224. kill_element.textContent = `
  225. #header {
  226. display: none !important;
  227. }
  228. .menubar-links-outer {
  229. position: absolute;
  230. z-index: 1000;
  231. top: 0;
  232. right: 0;
  233. flex-direction: row-reverse;
  234. }
  235.  
  236. .menubar-labels{
  237. line-height: 10px;
  238. margin-right: 10px;
  239. }
  240.  
  241. .menubar-label-link.selected{
  242. color: #fff !important;
  243. }
  244.  
  245. .menu-login-item {
  246. color: #fff !important;
  247. }
  248. #small-footer-wrapper {
  249. display: none !important;
  250. }
  251. .login-dropdown-divider {
  252. display:none !important;
  253. }
  254. .login-dropdown-content {
  255. padding: 12px 12px 12px !important;
  256. border-top: 2.4px #009fad solid;
  257. }
  258.  
  259. .menu-login-wrapper .login-dropdown-label {
  260. color: #606060;
  261. }
  262.  
  263. .menu-login-wrapper {
  264. height: 35px;
  265. margin-bottom: -35px; /* Hack to fix neighboring .site heights */
  266. }
  267. `;
  268.  
  269. enhanced_settings.add_hook('kill_top', value => {
  270. if (value) {
  271. document.getElementById('header-wrapper').before(menubar_wrapper);
  272. document.querySelector('.sharedsite-links > .site:last-child').after(login_wrapper);
  273. document.head.appendChild(kill_element);
  274. } else {
  275. menubar_wrapper_normal_position.before(menubar_wrapper);
  276. login_wrapper_normal_position.before(login_wrapper)
  277. if (kill_element.parentNode) kill_element.parentNode.removeChild(kill_element);
  278. }
  279. window.dispatchEvent(new Event('resize')); // Recalculate sizes of elements
  280. })
  281. }
  282.  
  283. // Feed moderator icon
  284. {
  285. const style = document.createElement('style');
  286. style.textContent = '#feed-topic .cmty-topic-moderate{ display: inline !important; }';
  287. enhanced_settings.add_hook('feed_moderation', value => {
  288. if (value) {
  289. document.head.appendChild(style);
  290. } else {
  291. if (style.parentNode) style.parentNode.removeChild(style);
  292. }
  293. }, true)
  294. }
  295.  
  296. // Notifications
  297. {
  298. var notify_functions = [
  299. AoPS.Ui.Flyout.display,
  300. a => {
  301. var textextract = document.createElement("div");
  302. textextract.innerHTML = a.replace('<br>', '\n');
  303. var y = $(textextract).text()
  304. var notification = new Notification("AoPS Enhanced", { body: y, icon: 'https://artofproblemsolving.com/online-favicon.ico', tag: y });
  305. setTimeout(notification.close.bind(notification), 5000);
  306. }
  307. ];
  308.  
  309. enhanced_settings.add_hook('notifications', value => {
  310. if (value && Notification.permission != "granted") Notification.requestPermission();
  311. AoPS.Ui.Flyout.display = notify_functions[+value];
  312. }, true);
  313. }
  314.  
  315. function show_enhanced_configurator() {
  316. UI_ELEMENTS = {
  317. notifications: settings_ui.toggle('Notifications'),
  318. post_links: settings_ui.toggle('Post links'),
  319. feed_moderation: settings_ui.toggle('Feed moderate icon'),
  320. kill_top: settings_ui.toggle('Simplify UI'),
  321. quote_primary: settings_ui.select('Primary quote', Object.keys(quote_schemes).map(k => [k, k])),
  322. quote_secondary: settings_ui.select('Ctrl quote', Object.keys(quote_schemes).map(k => [k, k])),
  323. theme: settings_ui.select('Theme', Object.keys(themes).map(k => [k, k])),
  324. }
  325. var settings_modal = document.createElement('div');
  326. for (var key in UI_ELEMENTS) {
  327. settings_modal.appendChild(UI_ELEMENTS[key](key, enhanced_settings.get(key), enhanced_settings));
  328. settings_modal.appendChild(document.createElement('br'));
  329. }
  330. alert(settings_modal);
  331. }
  332.  
  333. // Add "Enhanced" option to login dropdown
  334. {
  335. const el = document.querySelector('.login-dropdown-content');
  336. if (el === null) return;
  337. var enhanced_settings_element = document.createElement('a');
  338. enhanced_settings_element.classList.add('menu-item');
  339. enhanced_settings_element.innerText = 'Enhanced';
  340. enhanced_settings_element.addEventListener('click', e => { e.preventDefault(); show_enhanced_configurator(); });
  341. el.appendChild(enhanced_settings_element);
  342. }
  343.  
  344. // Prevent errors when trying to modify AoPS Community on pages where it doesn't exist
  345. if (AoPS.Community) {
  346. AoPS.Community.Views.Post.prototype.onClickQuote = function (e) {
  347. quote_schemes[enhanced_settings.get(e.ctrlKey ? 'quote_secondary' : 'quote_primary')].call(this);
  348. };
  349.  
  350. // Direct links
  351. (() => {
  352. var real_onClickDirectLink = AoPS.Community.Views.Post.prototype.onClickDirectLink;
  353. function direct_link_function(e) {
  354. var url = 'https://aops.com/community/p' + this.model.get("post_id");
  355. navigator.clipboard.writeText(url);
  356. AoPS.Ui.Flyout.display(`URL copied: ${url}`);
  357. }
  358. AoPS.Community.Views.Post.prototype.onClickDirectLink = function (e) {
  359. (enhanced_settings.get('post_links') ? direct_link_function : real_onClickDirectLink).call(this, e);
  360. }
  361. })();
  362. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement