// ==UserScript==
// @name EventListener
// @namespace https://github.com/BrokenEagle/JavaScripts
// @version 23.1
// @description Informs users of new events (flags,appeals,dmails,comments,forums,notes,commentaries,post edits,wikis,pools,bans,feedbacks,mod actions)
// @source https://danbooru.donmai.us/users/23799
// @author BrokenEagle
// @match *://*.donmai.us/*
// @exclude /^https?://\w+\.donmai\.us/.*\.(xml|json|atom)(\?|$)/
// @grant none
// @run-at document-idle
// @downloadURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/EventListener.user.js
// @updateURL https://raw.githubusercontent.com/BrokenEagle/JavaScripts/master/EventListener.user.js
// @require https://cdn.jsdelivr.net/npm/xregexp@5.1.0/xregexp-all.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/module.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/debug.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/utility.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/validate.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/storage.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/notice.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/concurrency.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/network.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/danbooru.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20220515/lib/load.js
// @require https://raw.githubusercontent.com/BrokenEagle/JavaScripts/20240223-menu/lib/menu.js
// ==/UserScript==
/* global JSPLib $ Danbooru XRegExp LZString */
/****Global variables****/
//Library constants
////NONE
//Exterior script variables
const DANBOORU_TOPIC_ID = '14747';
const SERVER_USER_ID = 502584;
//Variables for load.js
const PROGRAM_LOAD_REQUIRED_VARIABLES = ['window.jQuery','window.Danbooru','Danbooru.CurrentUser'];
const PROGRAM_LOAD_REQUIRED_SELECTORS = ['#nav', '#top'];
//Program name constants
const PROGRAM_SHORTCUT = 'el';
const PROGRAM_CLICK = 'click.el';
const PROGRAM_NAME = 'EventListener';
//Main program variable
const EL = {};
//Event types
const POST_QUERY_EVENTS = ['comment', 'note', 'commentary', 'post', 'approval', 'flag', 'appeal'];
const SUBSCRIBE_EVENTS = ['comment', 'note', 'commentary', 'post', 'approval', 'flag', 'appeal', 'forum', 'wiki', 'pool'];
const USER_EVENTS = ['comment', 'note', 'commentary', 'post', 'approval', 'appeal', 'forum', 'wiki', 'pool'];
const ALL_SUBSCRIBES = JSPLib.utility.arrayUnion(SUBSCRIBE_EVENTS, USER_EVENTS);
const OTHER_EVENTS = ['dmail', 'ban', 'feedback', 'mod_action'];
const ALL_EVENTS = JSPLib.utility.arrayUnique(JSPLib.utility.multiConcat(POST_QUERY_EVENTS, SUBSCRIBE_EVENTS, OTHER_EVENTS));
//For factory reset
const LASTID_KEYS = JSPLib.utility.multiConcat(
POST_QUERY_EVENTS.map((type) => `el-pq-${type}lastid`),
SUBSCRIBE_EVENTS.map((type) => `el-${type}lastid`),
OTHER_EVENTS.map((type) => `el-ot-${type}lastid`),
);
const SAVED_KEYS = JSPLib.utility.multiConcat(
POST_QUERY_EVENTS.map((type) => [`el-pq-saved${type}lastid`, `el-pq-saved${type}list`]),
SUBSCRIBE_EVENTS.map((type) => [`el-saved${type}lastid`, `el-saved${type}list`]),
OTHER_EVENTS.map((type) => [`el-ot-saved${type}lastid`, `el-ot-saved${type}list`]),
).flat();
const SUBSCRIBE_KEYS = SUBSCRIBE_EVENTS.map((type) => ([`el-${type}list`, `el-${type}overflow`])).flat();
const LOCALSTORAGE_KEYS = JSPLib.utility.multiConcat(LASTID_KEYS, SAVED_KEYS, SUBSCRIBE_KEYS, [
'el-overflow',
'el-last-seen',
'el-saved-notice',
]);
//Available setting values
const POST_QUERY_ENABLE_EVENTS = ['flag', 'appeal'];
const SUBSCRIBE_ENABLE_EVENTS = ['comment', 'note', 'commentary', 'forum'];
const USER_ENABLE_EVENTS = [];
const OTHER_ENABLE_EVENTS = ['dmail'];
const AUTOSUBSCRIBE_EVENTS = ['comment', 'note', 'commentary', 'post', 'approval', 'flag', 'appeal'];
const MODACTION_EVENTS = [
'user_delete', 'user_ban', 'user_unban', 'user_name_change', 'user_level_change', 'user_approval_privilege', 'user_upload_privilege', 'user_feedback_update',
'user_feedback_delete', 'post_delete', 'post_undelete', 'post_ban', 'post_unban', 'post_permanent_delete', 'post_move_favorites', 'post_regenerate', 'post_regenerate_iqdb',
'post_note_lock_create', 'post_note_lock_delete', 'post_rating_lock_create', 'post_rating_lock_delete', 'post_vote_delete', 'post_vote_undelete', 'pool_delete',
'pool_undelete', 'artist_ban', 'artist_unban', 'comment_update', 'comment_delete', 'comment_vote_delete', 'comment_vote_undelete', 'forum_topic_delete', 'forum_topic_undelete',
'forum_topic_lock', 'forum_post_update', 'forum_post_delete', 'moderation_report_handled', 'moderation_report_rejected', 'tag_alias_create', 'tag_alias_update', 'tag_alias_delete',
'tag_implication_create', 'tag_implication_update', 'tag_implication_delete', 'ip_ban_create', 'ip_ban_delete', 'ip_ban_undelete', 'other',
];
//Main settings
const SETTINGS_CONFIG = {
autolock_notices: {
reset: false,
validate: JSPLib.validate.isBoolean,
hint: "Closing a notice will no longer close all other notices."
},
mark_read_topics: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Reading a forum post from the notice will mark the topic as read."
},
autoclose_dmail_notice: {
reset: false,
validate: JSPLib.validate.isBoolean,
hint: "Will automatically close the DMail notice provided by Danbooru."
},
overflow_only_notice_enabled: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Will display the event notice even no events are found but more can be queried."
},
filter_user_events: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Only show events not created by the user."
},
show_creator_events: {
reset: false,
validate: JSPLib.validate.isBoolean,
hint: "Show subscribe events regardless of subscribe status when creator is the user."
},
filter_untranslated_commentary: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: "Only show new commentary that has translated sections."
},
filter_autobans: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: `Only show bans not created by DanbooruBot.`
},
filter_autofeedback: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: 'Only show feedback not created by an administrative action, e.g. bans or promotions.'
},
filter_post_edits: {
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: "Enter a list of tags to filter out edits when added to or removed from a post.",
},
filter_BUR_edits: {
reset: true,
validate: JSPLib.validate.isBoolean,
hint: `Only show edits not created by DanbooruBot.`
},
filter_users: {
reset: "",
parse: (input)=>(JSPLib.utility.arrayUnique(input.split(/\s*,\s*/).map(Number).filter((num) => (num !== 0)))),
validate: (input)=>(JSPLib.validate.validateIDList(input)),
hint: 'Enter a list of users to filter (comma separated).'
},
recheck_interval: {
reset: 5,
parse: parseInt,
validate: (data) => (Number.isInteger(data) && data > 0),
hint: "How often to check for new events (# of minutes)."
},
post_query_events_enabled: {
allitems: POST_QUERY_EVENTS,
reset: POST_QUERY_ENABLE_EVENTS,
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', POST_QUERY_EVENTS)),
hint: "Select to enable event type."
},
subscribe_events_enabled: {
allitems: SUBSCRIBE_EVENTS,
reset: SUBSCRIBE_ENABLE_EVENTS,
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', SUBSCRIBE_EVENTS)),
hint: "Select to enable event type."
},
user_events_enabled: {
allitems: USER_EVENTS,
reset: USER_ENABLE_EVENTS,
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', USER_EVENTS)),
hint: "Select to enable event type."
},
other_events_enabled: {
allitems: OTHER_EVENTS,
reset: OTHER_ENABLE_EVENTS,
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', OTHER_EVENTS)),
hint: "Select to enable event type."
},
autosubscribe_enabled: {
allitems: AUTOSUBSCRIBE_EVENTS,
reset: [],
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', AUTOSUBSCRIBE_EVENTS)),
hint: "Select to autosubscribe event type."
},
subscribed_mod_actions: {
allitems: MODACTION_EVENTS,
reset: [],
validate: (data) => (JSPLib.menu.validateCheckboxRadio(data, 'checkbox', MODACTION_EVENTS)),
hint: "Select which mod action categories to subscribe to."
},
flag_query: {
reset: "###INITIALIZE###",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
appeal_query: {
reset: "###INITIALIZE###",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
comment_query: {
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
note_query: {
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
commentary_query: {
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
approval_query: {
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a post search query to check.'
},
post_query: {
display: "Edit query",
reset: "",
parse: String,
validate: JSPLib.validate.isString,
hint: 'Enter a list of tags to check. See Additional setting details for more info.'
},
};
const CONTROL_CONFIG = {
post_events: {
allitems: ['post', 'comment', 'note', 'commentary', 'approval'],
value: [],
hint: "Select which events to populate.",
},
operation: {
allitems: ['add', 'subtract', 'overwrite'],
value: ['add'],
hint: "Select how the query will affect existing subscriptions.",
},
search_query: {
value: "",
buttons: ['get'],
hint: 'Enter a post search query to populate. See Help:Cheatsheet for more info.',
},
cache_info: {
value: "Click to populate",
hint: "Calculates the cache usage of the program and compares it to the total usage.",
},
raw_data: {
value: false,
hint: "Select to import/export all program data",
},
data_name: {
value: "",
buttons: ['get', 'save', 'delete', 'list', 'refresh'],
hint: "Click Get to see the data, Save to edit it, and Delete to remove it.
List shows keys in their raw format, and Refresh checks the keys again.",
},
};
const MENU_CONFIG = {
topic_id: DANBOORU_TOPIC_ID,
settings: [{
name: 'general',
},{
name: 'network',
},{
name: 'notice',
},{
name: 'filter',
},{
name: 'subscribe-event',
message: "These events will not be checked unless there are one or more subscribed items.",
},{
name: 'post-query-event',
message: "These events can be searched with a post query. A blank query line will return all events. See Help:Cheatsheet for more info.",
},{
name: 'user-event',
message: "These events will not be checked unless there are one or more subscribed users.",
},{
name: 'other-event',
message: "Except for some exceptions noted below, all events of this type are shown.",
}],
controls: [{
name: 'subscribe',
}],
};
// Default values
const DEFAULT_VALUES = {
subscribeset: {},
userset: {},
openlist: {},
marked_topic: [],
item_overflow: false,
no_limit: false,
events_checked: false,
post_ids: new Set(),
thumbs: {},
};
//CSS Constants
const PROGRAM_CSS = `
#dmail-notice {
display: none;
}
#el-event-notice {
padding: 0.5em;
}
#page #c-comments #a-index .preview {
height: 170px;
display: flex;
flex-direction: column;
}
#c-comments #a-index #p-index-by-comment .preview {
margin-right: 0;
}
.el-post-thumbnail article.post-preview {
width: 160px;
}
.striped .el-monospace-link:link,
.striped .el-monospace-link:visited,
.post-preview .el-monospace-link:link,
.post-preview .el-monospace-link:visited {
font-family: monospace;
color: var(--muted-text-color);
}
.striped .el-monospace-link:hover,
.post-preview .el-monospace-link:hover {
filter: brightness(1.5);
}
#nav #el-subscribe-events {
padding-left: 2em;
font-weight: bold;
}
#el-subscribe-events #el-add-links li {
margin: 0 -6px;
}
#el-subscribe-events .el-subscribed a,
#subnav-unsubscribe-link {
color: mediumseagreen;
}
#el-subscribe-events .el-subscribed a:hover,
#subnav-unsubscribe-link:hover {
filter: brightness(1.5);
}
#el-subscribe-events .el-unsubscribed a,
#subnav-subscribe-link {
color: darkorange;
}
#el-subscribe-events .el-unsubscribed a:hover,
#subnav-subscribe-link:hover {
filter: brightness(1.5);
}
#el-loading-message,
#el-event-controls {
margin-top: 1em;
}
#el-lock-event-notice,
#el-read-event-notice {
font-weight: bold;
color: mediumseagreen;
}
#el-lock-event-notice:not(.el-locked):hover ,
#el-read-event-notice:not(.el-read):hover {
filter: brightness(1.5);
}
.el-event-hidden {
display: none;
}
.el-overflow-notice {
border-top: 1px solid #DDD;
font-weight: bold;
}
#el-lock-event-notice.el-locked,
#el-read-event-notice.el-read {
color: red;
}
#el-reload-event-notice {
font-weight: bold;
color: orange;
}
#el-snooze-event-notice {
font-weight: bold;
color: darkviolet;
}
#el-reload-event-notice:hover {
filter: brightness(1.2);
}
#el-snooze-event-notice:hover {
filter: brightness(1.5);
}
#el-absent-section {
margin: 0.5em;
border: solid 1px grey;
padding: 0.5em;
}
.el-error-message {
color: var(--muted-text-color);
font-weight: bold;
margin-left: 1em;
}
.el-horizontal-rule {
border-top: 4px dashed tan;
margin: 10px 0;
}
div#el-notice {
top: unset;
bottom: 2em;
}`;
const POST_CSS = `
#el-event-notice #el-post-section #el-post-table .col-expand {
width: unset;
}`;
const COMMENT_CSS = `
#el-event-notice #el-comment-section #el-comment-table article.post-preview,
#el-event-notice #el-commentary-section #el-commentary-table article.post-preview {
display: flex !important;
flex-direction: row;
margin-bottom: 1em;
border-bottom: var(--dtext-blockquote-border);
min-height: 14em;
}
#el-event-notice #el-comment-section #el-comment-table .preview {
flex-direction: column;
display: flex;
width: 154px;
height: 170px;
text-align: center;
margin-right: 0;
}
#el-event-notice #el-comment-section #el-comment-table .comment {
padding: 1em;
margin-top: 0;
word-wrap: break-word;
display: flex;
}`;
const FORUM_CSS = `
#el-event-notice #el-forum-section #el-forum-table .author {
padding: 1em 1em 0 1em;
width: 12em;
float: left;
}
#el-event-notice #el-forum-section #el-forum-table .content {
padding: 1em;
margin-left: 14em;
}`;
const WIKI_CSS = `
#el-event-notice #el-wiki-section ins {
background: #cfc;
text-decoration: none;
}
#el-event-notice #el-wiki-section del {
background: #fcc;
text-decoration: none;
}
.el-paragraph-mark {
opacity: 0.25;
}`;
const POOL_CSS = `
#el-event-notice #el-pool-section .el-full-item[data-type="pooldiff"] {
overflow-x: auto;
max-width: 90vw;
}
#el-event-notice #el-pool-section .el-full-item[data-type="pooldiff"] ins {
background: #cfc;
text-decoration: none;
}
#el-event-notice #el-pool-section .el-full-item[data-type="pooldiff"] del {
background: #fcc;
text-decoration: none;
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .el-add-pool-posts {
display: flex;
flex-wrap: wrap;
background-color: rgba(0, 255, 0, 0.2);
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .el-rem-pool-posts {
display: flex;
background-color: rgba(255, 0, 0, 0.2);
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview {
margin: 5px;
padding: 5px;
border: var(--dtext-blockquote-border);
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview-150 {
width: 155px;
height: 175px;
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview-180 {
width: 185px;
height: 205px;
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview-225 {
width: 230px;
height: 250px;
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview-270 {
width: 275px;
height: 300px;
}
#el-event-notice #el-pool-section .el-full-item[data-type="poolposts"] .post-preview-360 {
width: 365px;
height: 390px;
}
.el-paragraph-mark {
opacity: 0.25;
}`;
const FEEDBACK_CSS = `
#el-event-notice #el-feedback-section .feedback-category-positive {
background: var(--success-background-color);
}
#el-event-notice #el-feedback-section .feedback-category-negative {
background: var(--error-background-color);
}
#el-event-notice #el-feedback-section .feedback-category-neutral {
background: unset;
}`;
const BAN_CSS = `
#el-event-notice #el-ban-section tr[data-expired=true] {
background-color: var(--success-background-color);
}
#el-event-notice #el-ban-section tr[data-expired=false] {
background-color: var(--error-background-color);
}`;
const MENU_CSS = `
#el-search-query-display {
margin: 0.5em;
font-size: 150%;
border: var(--dtext-blockquote-border);
padding: 0.5em;
width: 7.5em;
}
#event-listener .jsplib-settings-grouping:not(#el-general-settings) .jsplib-selectors label,
#event-listener #el-subscribe-controls .jsplib-selectors label {
text-align: left;
width: 200px;
letter-spacing: -1px;
}
#event-listener .jsplib-settings-grouping:not(#el-general-settings) .ui-checkboxradio-icon-space {
margin-right: 5px;
}`;
//HTML constants
const NOTICE_BOX = `
You have been gone for days.
This can cause delays and multiple page refreshes for the script to finish processing all updates.
To process them all now, click the "Update" link below, or click "Close this" to process them normally.
Update (...)
`; const EXCESSIVE_NOTICE = `WARNING! You have been gone longer than a month.
Consider resetting the event positions to their most recent values instead by clicking "Reset".
`; const DISMISS_NOTICE = ` `; const SUBSCRIBE_EVENT_SETTINGS_DETAILS = `Subscribe to events using search queries instead of individually.
Warning! Very large lists have issues:
All timestamps are in milliseconds since the epoch (Epoch converter).
TYPE
is a placeholder for all available event types. OP
is a placeholder for the type of operation (pq = post query, ot = other, subscribe has neither a designator nor the dash afterwards).
Note: The raw format of all data keys begins with "el-". which is unused by the cache editor controls.
`; //Time constants const TIMER_POLL_INTERVAL = 100; //Polling interval for checking program status const JQUERY_DELAY = 1; //For jQuery updates that should not be done synchronously const NONSYNCHRONOUS_DELAY = 1; //For operations too costly in events to do synchronously const MAX_ABSENCE = 30.0; //# of days before reset links get shown const MAX_SNOOZE_DURATION = JSPLib.utility.one_hour; //Network constants const QUERY_LIMIT = 100; //The max number of items to grab with each network call const ID_FIELD = 'id'; //Regex constants const POOLS_REGEX = XRegExp.tag()`/pools/(\d+)`; //Other constants const ALL_POST_EVENTS = ['post', 'approval', 'comment', 'note', 'commentary']; const ALL_TRANSLATE_EVENTS = ['note', 'commentary']; //Type configurations const TYPEDICT = { flag: { controller: 'post_flags', addons: {search: {category: 'normal'}}, user: 'creator_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,creator_id,post_id', filter: FilterData, insert: InsertEvents, plural: 'flags', display: "Flags", includes: 'post[uploader_id]', useritem: false, multiinsert: true, }, appeal: { controller: 'post_appeals', user: 'creator_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,creator_id,post_id', filter: FilterData, insert: InsertEvents, plural: 'appeals', display: "Appeals", includes: 'post[uploader_id]', useritem: false, multiinsert: true, }, dmail: { controller: 'dmails', addons: {search: {is_deleted: false}}, only: 'id,from_id', user: 'from_id', filter: FilterData, other_filter: (val)=>(!val.is_read), insert: InsertDmails, plural: 'mail', useritem: true, open: ()=>{OpenItemClick('dmail', AddDmail);}, }, comment: { controller: 'comments', addons: {group_by: 'comment', search: {is_deleted: false}}, user: 'creator_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,creator_id,post_id', limit: 10, filter: FilterData, insert: InsertComments, process: ()=>{JSPLib.utility.setCSSStyle(COMMENT_CSS, 'comment');}, plural: 'comments', display: "Comments", includes: 'post[uploader_id]', useritem: false, subscribe: InitializeCommentIndexLinks, }, forum: { controller: 'forum_posts', user: 'creator_id', creator: ['topic', 'creator_id'], item: 'topic_id', only: 'id,creator_id,topic_id', limit: 10, filter: FilterData, insert: InsertForums, process: ()=>{JSPLib.utility.setCSSStyle(FORUM_CSS, 'forum');}, plural: 'forums', display: "Forums", includes: 'topic[creator_id]', useritem: false, open: ()=>{OpenItemClick('forum', AddForumPost);}, subscribe: InitializeTopicIndexLinks, }, note: { controller: 'note_versions', user: 'updater_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,updater_id,post_id', limit: 10, filter: FilterData, insert: InsertNotes, plural: 'notes', display: "Notes", includes: 'post[uploader_id]', useritem: false, open: ()=>{OpenItemClick('note', AddRenderedNote, AdjustRowspan);}, subscribe: (table)=>{InitializePostNoteIndexLinks('note', table, false);}, }, commentary: { controller: 'artist_commentary_versions', user: 'updater_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,updater_id,post_id,translated_title,translated_description', limit: 10, filter: FilterData, other_filter: IsShownCommentary, insert: InsertEvents, plural: 'commentaries', display: "Artist commentary", includes: 'post[uploader_id]', useritem: false, multiinsert: true, }, post: { controller: 'post_versions', get addons() { let addons = {search: {is_new: false}}; if (EL.user_settings.filter_BUR_edits) { addons.search.updater_id_not_eq = SERVER_USER_ID; } return addons; }, user: 'updater_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,updater_id,post_id,added_tags,removed_tags', limit: 2, filter: FilterData, other_filter: IsShownPostEdit, insert: InsertPosts, process: ()=>{JSPLib.utility.setCSSStyle(POST_CSS, 'post');}, plural: 'edits', display: "Edits", includes: 'post[uploader_id]', useritem: false, customquery: PostCustomQuery, subscribe: (table)=>{InitializePostNoteIndexLinks('post', table, false);}, }, approval: { controller: 'post_approvals', user: 'user_id', creator: ['post', 'uploader_id'], item: 'post_id', only: 'id,user_id,post_id', limit: 10, filter: FilterData, insert: InsertEvents, plural: 'approvals', display: "Approval", includes: 'post[uploader_id]', useritem: false, multiinsert: true, }, wiki: { controller: 'wiki_page_versions', user: 'updater_id', item: 'wiki_page_id', only: 'id,updater_id,wiki_page_id', limit: 10, filter: FilterData, insert: InsertWikis, process: ()=>{JSPLib.utility.setCSSStyle(WIKI_CSS, 'wiki');}, plural: 'wikis', display: "Wikis", useritem: false, open: ()=>{OpenItemClick('wiki', AddWiki);}, subscribe: InitializeWikiIndexLinks, }, pool: { controller: 'pool_versions', user: 'updater_id', item: 'pool_id', only: 'id,updater_id,pool_id', limit: 2, filter: FilterData, insert: InsertPools, process: ()=>{JSPLib.utility.setCSSStyle(POOL_CSS, 'pool');}, plural: 'pools', display: "Pools", useritem: false, open: ()=>{ OpenItemClick('pooldiff', AddPoolDiff); OpenItemClick('poolposts', AddPoolPosts); }, subscribe: InitializePoolIndexLinks, }, feedback: { controller: 'user_feedbacks', user: 'creator_id', only: 'id,creator_id,body', filter: FilterData, other_filter: IsShownFeedback, insert: InsertEvents, process: ()=>{JSPLib.utility.setCSSStyle(FEEDBACK_CSS, 'feedback');}, plural: 'feedbacks', useritem: false, multiinsert: false, }, ban: { controller: 'bans', user: 'banner_id', only: 'id,banner_id', filter: FilterData, other_filter: IsShownBan, insert: InsertEvents, process: ()=>{JSPLib.utility.setCSSStyle(BAN_CSS, 'ban');}, plural: 'bans', useritem: false, multiinsert: false, }, mod_action: { controller: 'mod_actions', get addons() { return {search: {category: EL.user_settings.subscribed_mod_actions.join(',')}}; }, only: 'id,category', filter: (array) => (array.filter((val) => (IsCategorySubscribed(val.category)))), insert: InsertEvents, plural: 'mod actions', useritem: false, multiinsert: false, }, }; //Validate constants const TYPE_GROUPING = '(?:' + ALL_EVENTS.join('|') + ')'; const SUBSCRIBE_GROUPING = '(?:' + ALL_SUBSCRIBES.join('|') + ')'; const ALL_VALIDATE_REGEXES = { setting: 'el-user-settings', bool: [ `el-${SUBSCRIBE_GROUPING}overflow`, 'el-overflow', ], time: [ 'el-last-seen', 'el-process-semaphore', 'el-event-timeout', 'el-saved-timeout', ], id: [ `el-(?:pq-|ot-)?${TYPE_GROUPING}lastid`, `el-(?:pq-|ot-)?saved${TYPE_GROUPING}lastid`, ], idlist: [ `el-(?:us-)?${SUBSCRIBE_GROUPING}list`, `el-(?:pq-|ot-|us-)?saved${TYPE_GROUPING}list`, ], }; const VALIDATE_REGEX = XRegExp.build( Object.keys(ALL_VALIDATE_REGEXES).map((type) => ` ({{${type}}}) `).join('|'), Object.assign({}, ...Object.keys(ALL_VALIDATE_REGEXES).map((type)=>{ let format = ""; if (typeof ALL_VALIDATE_REGEXES[type] === "string") { format = ALL_VALIDATE_REGEXES[type]; } if (Array.isArray(ALL_VALIDATE_REGEXES[type])) { format = ALL_VALIDATE_REGEXES[type].join('|'); } return {[type]: format}; })), 'x'); /****Functions****/ //Validate functions function ValidateProgramData(key,entry) { var checkerror=[]; let match = XRegExp.exec(key, VALIDATE_REGEX); let groups = match?.groups || {}; switch (key) { case groups.setting: checkerror = JSPLib.menu.validateUserSettings(entry); break; case groups.bool: if (!JSPLib.validate.isBoolean(entry)) { checkerror = ["Value is not a boolean."]; } break; case groups.time: if (!Number.isInteger(entry)) { checkerror = ["Value is not an integer."]; } else if (entry < 0) { checkerror = ["Value is not greater than or equal to zero."]; } break; case groups.id: if (!JSPLib.validate.validateID(entry)) { checkerror = ["Value is not a valid ID."]; } break; case groups.idlist: if (!JSPLib.validate.validateIDList(entry)) { checkerror = ["Value is not a valid ID list."]; } break; default: checkerror = ["Not a valid program data key."]; } if (checkerror.length) { JSPLib.validate.outputValidateError(key, checkerror); return false; } return true; } function CorrectList(type,typelist) { let error_messages = []; if (!JSPLib.validate.validateIDList(typelist[type])) { error_messages.push([`Corrupted data on ${type} list!`]); let oldlist = JSPLib.utility.dataCopy(typelist[type]); typelist[type] = (Array.isArray(typelist[type]) ? typelist[type].filter((id) => JSPLib.validate.validateID(id)) : []); JSPLib.debug.debugExecute(()=>{ let validation_error = (Array.isArray(oldlist) ? JSPLib.utility.arrayDifference(oldlist, typelist[type]) : typelist[type]); error_messages.push(["Validation error:", validation_error]); }); } if (error_messages.length) { error_messages.forEach((error)=>{this.debug('log',...error);}); return true; } return false; } //Library functions ////NONE //Helper functions async function SetRecentDanbooruID(type,qualifier) { let type_addon = TYPEDICT[type].addons || {}; let url_addons = JSPLib.utility.joinArgs(type_addon, {only: ID_FIELD, limit: 1}); let jsonitem = await JSPLib.danbooru.submitRequest(TYPEDICT[type].controller, url_addons, {default_val: []}); if (jsonitem.length) { SaveLastID(type, JSPLib.danbooru.getNextPageID(jsonitem, true), qualifier); } else if (TYPEDICT[type].useritem) { SaveLastID(type, 1, qualifier); } } function AnyRenderedEvents() { return Object.keys(EL.renderedlist).some((type) => (EL.renderedlist[type].length > 0)); } function IsEventEnabled(type,event_type) { return EL.user_settings[event_type].includes(type); } function IsAnyEventEnabled(event_list,event_type) { return JSPLib.utility.arrayHasIntersection(event_list, EL.user_settings[event_type]); } function AreAllEventsEnabled(event_list,event_type) { return JSPLib.utility.isSubArray(EL.user_settings[event_type], event_list); } function IsCategorySubscribed(type) { return EL.user_settings.subscribed_mod_actions.includes(type); } function GetTypeQuery(type) { return EL.user_settings[type + '_query']; } function GetTableType(container) { return $('.striped tbody tr', container).attr('id').replace(/-\d+$/, ''); } function HideDmailNotice() { if (EL.dmail_notice.length) { EL.dmail_notice.hide(); let dmail_id = EL.dmail_notice.data('id'); JSPLib.utility.createCookie('hide_dmail_notice', dmail_id); } } //Data storage functions function GetList(type) { if (!IsEventEnabled(type, 'subscribe_events_enabled')) { return new Set(); } if (EL.subscribeset[type]) { return EL.subscribeset[type]; } EL.subscribeset[type] = JSPLib.storage.getStorageData(`el-${type}list`, localStorage, []); if (CorrectList(type, EL.subscribeset)) { setTimeout(()=>{ JSPLib.storage.setStorageData(`el-${type}list`, EL.subscribeset[type], localStorage); }, NONSYNCHRONOUS_DELAY); } EL.subscribeset[type] = new Set(EL.subscribeset[type]); return EL.subscribeset[type]; } function SetList(type,remove_item,itemid) { if (!IsEventEnabled(type, 'subscribe_events_enabled')) { return; } let typeset = GetList(type); if (remove_item) { typeset.delete(itemid); } else { typeset.add(itemid); } JSPLib.storage.setStorageData(`el-${type}list`, [...typeset], localStorage); EL.channel.postMessage({type: 'subscribe', eventtype: type, was_subscribed: remove_item, itemid: itemid, eventset: typeset}); EL.subscribeset[type] = typeset; } function GetUserList(type) { if (!IsEventEnabled(type, 'user_events_enabled')) { return new Set(); } if (EL.userset[type]) { return EL.userset[type]; } EL.userset[type] = JSPLib.storage.getStorageData(`el-us-${type}list`, localStorage, []); if (CorrectList(type, EL.userset)) { setTimeout(()=>{ JSPLib.storage.setStorageData(`el-us-${type}list`, EL.userset, localStorage); }, NONSYNCHRONOUS_DELAY); } EL.userset[type] = new Set(EL.userset[type]); return EL.userset[type]; } function SetUserList(type,remove_item,userid) { if (!IsEventEnabled(type, 'user_events_enabled')) { return; } let typeset = GetUserList(type); if (remove_item) { typeset.delete(userid); } else { typeset.add(userid); } JSPLib.storage.setStorageData(`el-us-${type}list`, [...typeset], localStorage); EL.channel.postMessage({type: 'subscribe_user', eventtype: type, was_subscribed: remove_item, userid: userid, eventset: typeset}); EL.userset[type] = typeset; } //Quicker way to check list existence; avoids unnecessarily parsing very long lists function CheckList(type) { if (!JSPLib.menu.isSettingEnabled('subscribe_events_enabled', type)) { return false; } let typelist = localStorage.getItem(`el-${type}list`); return Boolean(typelist) && typelist !== '[]'; } function CheckUserList(type) { if (!JSPLib.menu.isSettingEnabled('user_events_enabled', type)) { return false; } let typelist = localStorage.getItem(`el-us-${type}list`); return Boolean(typelist) && typelist !== '[]'; } //Auxiliary functions function FilterData(array,subscribe_set,user_set) { return array.filter((val) => IsShownData.call(this, val, subscribe_set,user_set)); } function IsShownData(val,subscribe_set,user_set) { if ((EL.user_settings.filter_user_events && this.user && (val[this.user] === EL.userid)) || EL.user_settings.filter_users.includes(val[this.user])) { return false; } if (user_set && this.user && user_set.has(val[this.user])) { return true; } if (subscribe_set && this.item) { let is_creator_event = EL.user_settings.show_creator_events && this.creator && JSPLib.utility.getNestedAttribute(val, this.creator) === EL.userid; if (!is_creator_event && !subscribe_set.has(val[this.item])) { return false; } } if (this.other_filter && !this.other_filter(val)) { return false; } return true; } function IsShownCommentary(val) { if (!EL.user_settings.filter_untranslated_commentary) { return true; } return (Boolean(val.translated_title) || Boolean(val.translated_description)); } function IsShownPostEdit(val) { if (EL.user_settings.filter_BUR_edits && val.updater_id === SERVER_USER_ID) { return false; } if (EL.user_settings.filter_post_edits === "") { return true; } let changed_tags = new Set(JSPLib.utility.concat(val.added_tags, val.removed_tags)); return !JSPLib.utility.setHasIntersection(changed_tags, EL.post_filter_tags); } function IsShownFeedback(val) { if (!EL.user_settings.filter_autofeedback) { return true; } return (val.body.match(/^Banned forever:/) === null) && (val.body.match(/^Banned \d+ (days?|weeks|months?|years?):/) === null) && (val.body.match(/^You have been (promoted|demoted) to a \S+ level account from \S+\./) === null) && (val.body.match(/\bYou (gained|lost) the ability to (approve posts|upload posts without limit|give user feedback|flag posts)\./) === null); } function IsShownBan(val) { if (!EL.user_settings.filter_autobans) { return true; } return val.banner_id !== SERVER_USER_ID; } function PostCustomQuery(query) { let parameters = {search: {}}; let taglist = query.trim().split(/\s+/); let tagchanges = taglist.filter((tag) => !tag.match(/^[+~-]/)); if (tagchanges.length) { parameters.search.all_changed_tags = tagchanges.join(' '); } let tagadds = taglist.filter((tag) => tag.startsWith('+')).map((tag) => tag.slice(1)); if (tagadds.length) { parameters.search.added_tags_include_any = tagadds.join(' '); } let tagremoves = taglist.filter((tag) => tag.startsWith('-')).map((tag) => tag.slice(1)); if (tagremoves.length) { parameters.search.removed_tags_include_any = tagremoves.join(' '); } let tagoptional = taglist.filter((tag) => tag.startsWith('~')).map((tag) => tag.slice(1)); if (tagoptional.length) { parameters.search.any_changed_tags = tagoptional.join(' '); } return (Object.keys(parameters.search).length > 0 ? parameters : {}); } function InsertPostPreview($container, post_id, query_string) { let $thumb_copy = $(EL.thumbs[post_id]).clone(); let $thumb_copy_link = $thumb_copy.find('a'); let thumb_url = $thumb_copy_link.attr('href') + query_string; $thumb_copy_link.attr('href', thumb_url); $container.append($thumb_copy); } function SaveLastID(type,lastid,qualifier='') { if (!JSPLib.validate.validateID(lastid)) { this.debug('log',"Last ID for", type, "is not valid!", lastid); return; } qualifier += (qualifier.length > 0 ? '-' : ''); let key = `el-${qualifier}${type}lastid`; let previousid = JSPLib.storage.checkStorageData(key, ValidateProgramData, localStorage, 1); lastid = Math.max(previousid, lastid); JSPLib.storage.setStorageData(key, lastid, localStorage); this.debug('log',`Set last ${qualifier}${type} ID:`, lastid); } function WasOverflow() { return JSPLib.storage.checkStorageData('el-overflow', ValidateProgramData, localStorage, false); } function SetLastSeenTime() { JSPLib.storage.setStorageData('el-last-seen', Date.now(), localStorage); } function CalculateOverflow(recalculate=false) { if (EL.any_overflow === undefined || recalculate) { EL.all_overflows = {}; EL.any_overflow = false; let enabled_events = JSPLib.utility.arrayIntersection(ALL_SUBSCRIBES, EL.all_subscribe_events); enabled_events.forEach((type)=>{ EL.all_overflows[type] = (EL.all_overflows[type] === undefined ? JSPLib.storage.checkStorageData(`el-${type}overflow`, ValidateProgramData, localStorage, false): EL.all_overflows[type]); EL.any_overflow = EL.any_overflow || EL.all_overflows[type]; }); } } function CheckOverflow(inputtype) { if (!ALL_SUBSCRIBES.includes(inputtype)) { return false; } return EL.all_overflows[inputtype]; } function ProcessEvent(inputtype, optype) { if ((optype !== 'all_subscribe_events' && !JSPLib.menu.isSettingEnabled(optype, inputtype)) || (optype === 'all_subscribe_events' && !EL.all_subscribe_events.includes(inputtype))) { this.debug('log',"Hard disable:", inputtype, optype); return false; } if (optype === 'all_subscribe_events' && !(JSPLib.menu.isSettingEnabled('subscribe_events_enabled', inputtype) && (EL.user_settings.show_creator_events || CheckList(inputtype))) && !(JSPLib.menu.isSettingEnabled('user_events_enabled', inputtype) && CheckUserList(inputtype))) { this.debug('log',"Soft disable:", inputtype, optype); return false; } JSPLib.debug.debugExecute(()=>{ this.debug('log',inputtype, optype, CheckOverflow(inputtype), !EL.any_overflow); }); if ((optype === 'all_subscribe_events') && CheckOverflow(inputtype)) { return CheckSubscribeType(inputtype); } else if (!EL.any_overflow) { switch(optype) { case 'post_query_events_enabled': return CheckPostQueryType(inputtype); case 'all_subscribe_events': return CheckSubscribeType(inputtype); case 'other_events_enabled': return CheckOtherType(inputtype); } } return false; } function CheckAbsence() { let last_seen = JSPLib.storage.getStorageData('el-last-seen', localStorage, 0); let time_absent = Date.now() - last_seen; if (last_seen === 0 || (time_absent < JSPLib.utility.one_day)) { return true; } EL.days_absent = JSPLib.utility.setPrecision(time_absent / JSPLib.utility.one_day, 2); return false; } //Table row functions //Get single instance of various types and insert into table row async function AddForumPost(forumid,rowelement) { let forum_page = await JSPLib.network.getNotify(`/forum_posts/${forumid}`); if (!forum_page) { return; } let $forum_page = $.parseHTML(forum_page); let $forum_post = $(`#forum_post_${forumid}`, $forum_page); let $outerblock = $.parseHTML(RenderOpenItemContainer('forum', forumid, 4)); $('td', $outerblock).append($forum_post); let $rowelement = $(rowelement); $rowelement.after($outerblock); if (EL.user_settings.mark_read_topics) { let topic_id = $rowelement.data('topic-id'); if (!EL.marked_topic.includes(topic_id)) { ReadForumTopic(topic_id); EL.marked_topic.push(topic_id); } } } function AddRenderedNote(noteid,rowelement) { let notehtml = $('.body-column', rowelement).html(); notehtml = notehtml && $.parseHTML(notehtml.trim())[0].textContent; let $outerblock = $.parseHTML(RenderOpenItemContainer('note', noteid, 7)); $('td', $outerblock).append(notehtml); $(rowelement).after($outerblock); } async function AddDmail(dmailid,rowelement) { let dmail = await JSPLib.network.getNotify(`/dmails/${dmailid}`); if (!dmail) { return; } let $dmail = $.parseHTML(dmail); $('.dmail h1:first-of-type', $dmail).hide(); let $outerblock = $.parseHTML(RenderOpenItemContainer('dmail', dmailid, 5)); $('td', $outerblock).append($('.dmail', $dmail)); $(rowelement).after($outerblock); } async function AddWiki(wikiverid,rowelement) { let $rowelement = $(rowelement); let wikiid = $rowelement.data('wiki-page-id'); let url_addons = {search: {wiki_page_id: wikiid}, page: `b${wikiverid}`, only: ID_FIELD, limit: 1}; let prev_wiki = await JSPLib.danbooru.submitRequest('wiki_page_versions', url_addons, {default_val: []}); if (prev_wiki.length) { let wiki_diff_page = await JSPLib.network.getNotify('/wiki_page_versions/diff', {url_addons: {otherpage: wikiverid, thispage: prev_wiki[0].id}}); if (!wiki_diff_page) { return; } let $wiki_diff_page = $.parseHTML(wiki_diff_page); let $outerblock = $.parseHTML(RenderOpenItemContainer('wiki', wikiverid, 4)); $('td', $outerblock).append($('#a-diff #content', $wiki_diff_page)); $rowelement.after($outerblock); } else { JSPLib.notice.error("Wiki creations have no diff!"); } } async function AddPoolDiff(poolverid,rowelement) { let pool_diff = await JSPLib.network.getNotify(`/pool_versions/${poolverid}/diff`); let $pool_diff = $.parseHTML(pool_diff); $('#a-diff > h1', $pool_diff).hide(); let $outerblock = $.parseHTML(RenderOpenItemContainer('pooldiff', poolverid, 7)); $('td', $outerblock).append($('#a-diff', $pool_diff)); $(rowelement).after($outerblock); } async function AddPoolPosts(poolverid,rowelement) { let $post_count = $('.post-count-column', rowelement); let add_posts = String($post_count.data('add-posts') || "").split(',').sort().reverse(); let rem_posts = String($post_count.data('rem-posts') || "").split(',').sort().reverse(); let total_posts = JSPLib.utility.concat(add_posts, rem_posts); let missing_posts = JSPLib.utility.arrayDifference(total_posts, Object.keys(EL.thumbs)); if (missing_posts.length) { let thumbnails = await JSPLib.network.getNotify(`/posts`, {url_addons: {tags: 'id:' + missing_posts.join(',') + ' status:any'}}); let $thumbnails = $.parseHTML(thumbnails); $('.post-preview', $thumbnails).each((i,thumb)=>{InitializeThumb(thumb);}); } let $outerblock = $.parseHTML(RenderOpenItemContainer('poolposts', poolverid, 7)); $('td', $outerblock).append(``); if (add_posts.length) { let $container = $('.el-add-pool-posts', $outerblock).show(); let query_string = '?q=id%3A' + add_posts.join('%2C'); add_posts.forEach((post_id)=>{InsertPostPreview($container, post_id, query_string);}); } if (rem_posts.length) { let $container = $('.el-rem-pool-posts', $outerblock).show(); let query_string = '?q=id%3A' + rem_posts.join('%2C'); rem_posts.forEach((post_id)=>{InsertPostPreview($container, post_id, query_string);}); } $(rowelement).after($outerblock); } //Update links function UpdateMultiLink(typelist,subscribed,itemid) { let typeset = new Set(typelist); let current_subscribed = new Set($('#el-subscribe-events .el-subscribed').map((i, entry) => entry.dataset.type.split(','))); let new_subscribed = (subscribed ? JSPLib.utility.setDifference(current_subscribed, typeset) : JSPLib.utility.setUnion(current_subscribed, typeset)); $(`#el-subscribe-events[data-id="${itemid}"] span:not(.el-event-hidden) > .el-multi-link`).each((i, entry)=>{ let entry_typelist = new Set(entry.dataset.type.split(',')); if (JSPLib.utility.isSuperSet(entry_typelist, new_subscribed)) { $(entry).removeClass('el-unsubscribed').addClass('el-subscribed'); $('a', entry).attr('title', 'subscribed'); } else { $(entry).removeClass('el-subscribed').addClass('el-unsubscribed'); $('a', entry).attr('title', 'unsubscribed'); } }); } function UpdateDualLink(type,subscribed,itemid) { let show = (subscribed ? 'subscribe' : 'unsubscribe'); let hide = (subscribed ? 'unsubscribe' : 'subscribe'); JSPLib.utility.fullHide(`.el-subscribe-dual-links[data-type="${type}"][data-id="${itemid}"] .el-${hide}`); JSPLib.utility.clearHide(`.el-subscribe-dual-links[data-type="${type}"][data-id="${itemid}"] .el-${show}`); } function ToggleSubscribeLinks() { SUBSCRIBE_EVENTS.forEach((type)=>{ if (IsEventEnabled(type, 'subscribe_events_enabled')) { $(`.el-subscribe-${type}-container`).removeClass('el-event-hidden'); } else { $(`.el-subscribe-${type}-container`).addClass('el-event-hidden'); } }); if (EL.controller === 'posts' && EL.action === 'show') { if (AreAllEventsEnabled(ALL_TRANSLATE_EVENTS, 'subscribe_events_enabled')) { $('.el-subscribe-translated-container').removeClass('el-event-hidden'); } else { $('.el-subscribe-translated-container').addClass('el-event-hidden'); } if (IsAnyEventEnabled(ALL_POST_EVENTS, 'subscribe_events_enabled')) { $('#el-subscribe-events').show(); let enabled_post_events = JSPLib.utility.arrayIntersection(ALL_POST_EVENTS, EL.user_settings.subscribe_events_enabled); $('#el-all-link').attr('data-type', enabled_post_events); } else { $('#el-subscribe-events').hide(); } } } function ToggleUserLinks() { USER_EVENTS.forEach((type)=>{ if (IsEventEnabled(type, 'user_events_enabled')) { $(`.el-user-${type}-container`).removeClass('el-event-hidden'); } else { $(`.el-user-${type}-container`).addClass('el-event-hidden'); } }); if (EL.controller === 'users' && EL.action === 'show') { if (AreAllEventsEnabled(ALL_TRANSLATE_EVENTS, 'user_events_enabled')) { $('.el-user-translated-container').removeClass('el-event-hidden'); } else { $('.el-user-translated-container').addClass('el-event-hidden'); } if (IsAnyEventEnabled(USER_EVENTS, 'user_events_enabled')) { $('#el-subscribe-events').show(); let enabled_user_events = JSPLib.utility.arrayIntersection(USER_EVENTS, EL.user_settings.user_events_enabled); $('#el-all-link').attr('data-type', enabled_user_events); } else { $('#el-subscribe-events').hide(); } } } //Insert and process HTML onto page for various types function InsertEvents($event_page,type) { let $table = $('.striped', $event_page); if (TYPEDICT[type].multiinsert) { AdjustColumnWidths($table[0]); } InitializeTypeDiv(type, $table); } function InsertDmails($dmail_page,type) { DecodeProtectedEmail($dmail_page); let $dmail_table = $('.striped', $dmail_page); $('tr[data-is-read="false"]', $dmail_table).css('font-weight', 'bold'); let $dmail_div = InitializeTypeDiv(type, $dmail_table); InitializeOpenDmailLinks($dmail_div[0]); } function InsertComments($comment_page) { DecodeProtectedEmail($comment_page); let $comment_section = $('.list-of-comments', $comment_page); $comment_section.find('> form').remove(); let $comment_div = InitializeTypeDiv('comment', $comment_section); InitializeCommentIndexLinks($comment_div); } function InsertForums($forum_page) { DecodeProtectedEmail($forum_page); let $forum_table = $('.striped', $forum_page); let $forum_div = InitializeTypeDiv('forum', $forum_table); InitializeTopicIndexLinks($forum_div[0]); InitializeOpenForumLinks($forum_div[0]); } function InsertNotes($note_page) { DecodeProtectedEmail($note_page); let $note_table = $('.striped', $note_page); $('th:first-of-type, td:first-of-type', $note_table[0]).remove(); AdjustColumnWidths($note_table[0]); let $note_div = InitializeTypeDiv('note', $note_table); AddThumbnailStubs($note_div[0]); InitializePostNoteIndexLinks('note', $note_div[0]); InitializeOpenNoteLinks($note_div[0]); } function InsertPosts($post_page) { let $post_table = $('.striped', $post_page); $('.post-version-select-column', $post_table[0]).remove(); $('tbody tr', $post_table[0]).each((i,row)=>{ let post_id = $(row).data('post-id'); let $preview = $('td.post-column .post-preview', row).detach(); if ($preview.length) { InitializeThumb($preview[0]); } $('td.post-column', row).html(`post #${post_id}`); }); AdjustColumnWidths($post_table[0]); let $post_div = InitializeTypeDiv('post', $post_table); AddThumbnailStubs($post_div[0]); InitializePostNoteIndexLinks('post', $post_div[0]); } function InsertWikis($wiki_page) { DecodeProtectedEmail($wiki_page); let $wiki_table = $('.striped', $wiki_page); let $wiki_div = InitializeTypeDiv('wiki', $wiki_table); InitializeWikiIndexLinks($wiki_div[0]); InitializeOpenWikiLinks($wiki_div[0]); } function InsertPools($pool_page) { DecodeProtectedEmail($pool_page); let $pool_table = $('.striped', $pool_page); $('.pool-category-collection, .pool-category-series', $pool_table[0]).each((i,entry)=>{ let short_pool_title = JSPLib.utility.maxLengthString(entry.innerText, 50); $(entry).attr('title', entry.innerText); entry.innerText = short_pool_title; }); let $pool_div = InitializeTypeDiv('pool', $pool_table); InitializePoolIndexLinks($pool_div[0]); InitializeOpenPoolLinks($pool_div[0]); } function InitializeTypeDiv(type,$type_page) { let $type_table = $(`#el-${type}-table`); if ($('>div', $type_table[0]).length) { $('thead', $type_page[0]).hide(); } let $type_div = $('').append($type_page); $('.post-preview', $type_div).addClass('blacklisted'); $type_table.append($type_div); return $type_div; } function InitializeThumb(thumb,query_string="") { let $thumb = $(thumb); $thumb.addClass('blacklisted'); $thumb.find('.post-preview-score').remove(); let postid = String($thumb.data('id')); let $link = $('a', thumb); let post_url = $link.attr('href').split('?')[0]; $link.attr('href', post_url + query_string); let $comment = $('.comment', thumb); if ($comment.length) { $comment.hide(); $('.el-subscribe-comment-container ', thumb).hide(); } thumb.style.setProperty('display', 'block', 'important'); thumb.style.setProperty('text-align', 'center', 'important'); EL.thumbs[postid] = thumb; } //Misc functions function ReadForumTopic(topicid) { $.ajax({ type: 'HEAD', url: '/forum_topics/' + topicid, headers: { Accept: 'text/html', } }); } function DecodeProtectedEmail(obj) { $('[data-cfemail]', obj).each((i,entry)=>{ let encoded_email = $(entry).data('cfemail'); let percent_decode = ""; let xorkey = '0x' + encoded_email.substr(0, 2) | 0; for(let n = 2; encoded_email.length - n; n += 2) { percent_decode+= '%' + ( '0' + ('0x' + encoded_email.substr(n, 2) ^ xorkey).toString(16)).slice(-2); } entry.outerHTML = decodeURIComponent(percent_decode); }); } function AddThumbnailStubs(dompage) { $('.striped thead tr', dompage).prepend('${link_html}
`); }); OpenItemClick('note', AddRenderedNote, AdjustRowspan); } function InitializeOpenDmailLinks(table) { $('.striped tbody tr', table).each((i,row)=>{ let dmailid = $(row).data('id'); let link_html = RenderOpenItemLinks('dmail', dmailid); $('.subject-column', row).prepend(link_html + ' | '); }); OpenItemClick('dmail', AddDmail); } function InitializeOpenWikiLinks(table) { $('.striped thead .diff-column').attr('width', '5%'); $('.striped tbody tr', table).each((i,row)=>{ let $column = $('.diff-column', row); let $diff_link = $('a', $column[0]); if ($diff_link.length) { let wikiverid = $(row).data('id'); let link_html = RenderOpenItemLinks('wiki', wikiverid, "Show diff", "Hide diff"); $diff_link.replaceWith(`${link_html}`); } else { $column.html('  No diff'); } }); OpenItemClick('wiki', AddWiki); } function InitializeOpenPoolLinks(table) { $('.striped tbody tr', table).each((i,row)=>{ let poolverid = $(row).data('id'); let $post_changes = $('.post-changes-column', row); let add_posts = $('.diff-list ins a[href^="/posts"]', $post_changes[0]).map((i,entry)=> entry.innerText).toArray(); let rem_posts = $('.diff-list del a[href^="/posts"]', $post_changes[0]).map((i,entry) => entry.innerText).toArray(); let $post_count = $('.post-count-column', row); if (add_posts.length || rem_posts.length) { let link_html = RenderOpenItemLinks('poolposts', poolverid, 'Show posts', 'Hide posts'); $post_count.prepend(link_html, ' | '); $post_count.attr('data-add-posts', add_posts); $post_count.attr('data-rem-posts', rem_posts); } else { $post_count.prepend(' No posts | '); } let $desc_changed_link = $('.diff-column a[href$="/diff"]', row); if ($desc_changed_link.length !== 0) { let link_html = RenderOpenItemLinks('pooldiff', poolverid, 'Show diff', 'Hide diff'); $desc_changed_link.replaceWith(link_html); } else { $('.diff-column', row).html(' No diff'); } }); OpenItemClick('pooldiff', AddPoolDiff); OpenItemClick('poolposts', AddPoolPosts); } function AdjustColumnWidths(table) { let width_dict = Object.assign({}, ...$("thead th", table).map((i,entry)=>{ let classname = JSPLib.utility.findAll(entry.className, /\S+column/g)[0]; let width = $(entry).attr('width'); return {[classname]: width}; })); $('tbody td', table).each((i,entry)=>{ let classname = JSPLib.utility.findAll(entry.className, /\S+column/g)[0]; if (!classname || !(classname in width_dict)) { return; } $(entry).css('width', width_dict[classname]); }); } //#C-USERS #A-SHOW function InitializeUserShowMenu() { let userid = $(document.body).data('user-id'); let $menu_obj = $.parseHTML(RenderMultilinkMenu(userid, USER_EVENTS, 'user_events_enabled')); USER_EVENTS.forEach((type)=>{ let linkhtml = RenderSubscribeMultiLinks(TYPEDICT[type].display, [type], userid, 'user_events_enabled'); let shownclass = (IsEventEnabled(type, 'user_events_enabled') ? "" : 'el-event-hidden'); $('#el-add-links', $menu_obj).append(`${linkhtml} | `); }); let shownclass = (AreAllEventsEnabled(ALL_TRANSLATE_EVENTS, 'user_events_enabled') ? "" : ' el-event-hidden'); let linkhtml = RenderSubscribeMultiLinks("Translations", ALL_TRANSLATE_EVENTS, userid, 'user_events_enabled'); $('#el-add-links', $menu_obj).append(`${linkhtml} | `); //The All link is always shown when the outer menu is shown, so no need to individually hide it let enabled_user_events = JSPLib.utility.arrayIntersection(USER_EVENTS, EL.user_settings.user_events_enabled); linkhtml = RenderSubscribeMultiLinks("All", enabled_user_events, userid, 'user_events_enabled'); $('#el-add-links', $menu_obj).append(`${linkhtml}`); $('#nav').append($menu_obj); } //#C-POSTS #A-SHOW function InitializePostShowMenu() { let postid = $('.image-container').data('id'); let $menu_obj = $.parseHTML(RenderMultilinkMenu(postid, ALL_POST_EVENTS, 'subscribe_events_enabled')); ALL_POST_EVENTS.forEach((type)=>{ let linkhtml = RenderSubscribeMultiLinks(TYPEDICT[type].display, [type], postid, 'subscribe_events_enabled'); let shownclass = (IsEventEnabled(type, 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $('#el-add-links', $menu_obj).append(`${linkhtml} | `); }); let shownclass = (AreAllEventsEnabled(ALL_TRANSLATE_EVENTS, 'subscribe_events_enabled') ? "" : ' el-event-hidden'); let linkhtml = RenderSubscribeMultiLinks("Translations", ALL_TRANSLATE_EVENTS, postid, 'subscribe_events_enabled'); $('#el-add-links', $menu_obj).append(`${linkhtml} | `); //The All link is always shown when the outer menu is shown, so no need to individually hide it let enabled_post_events = JSPLib.utility.arrayIntersection(ALL_POST_EVENTS, EL.user_settings.subscribe_events_enabled); linkhtml = RenderSubscribeMultiLinks("All", enabled_post_events, postid, 'subscribe_events_enabled'); $('#el-add-links', $menu_obj).append(`${linkhtml}`); $('#nav').append($menu_obj); } //#C-FORUM-TOPICS #A-SHOW function InitializeTopicShowMenu() { let topicid = $('body').data('forum-topic-id'); let $menu_obj = $.parseHTML(RenderMultilinkMenu(topicid, ['forum'], 'subscribe_events_enabled')); let linkhtml = RenderSubscribeMultiLinks("Topic", ['forum'], topicid, 'subscribe_events_enabled'); let shownclass = (IsEventEnabled('forum', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $('#el-add-links', $menu_obj).append(`${linkhtml}`); $('#nav').append($menu_obj); } //#C-FORUM-TOPICS #A-INDEX / #C-FORUM-POSTS #A-INDEX / EVENT-NOTICE function InitializeTopicIndexLinks(container,render=true) { let type = GetTableType(container); let typeset = GetList('forum'); $('.striped tbody tr', container).each((i,row)=>{ let data_selector = (type === 'forum-topic' ? 'id' : 'topic-id'); let topicid = $(row).data(data_selector); if (render) { let linkhtml = RenderSubscribeDualLinks('forum', topicid, 'span', "", "", true); let shownclass = (IsEventEnabled('forum', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $(".title-column, .topic-column", row).prepend(`${linkhtml} | `); } else { let subscribed = !typeset.has(topicid); UpdateDualLink('forum', subscribed, topicid); } }); } //#C-WIKI-PAGES #A-SHOW / #C-WIKI-PAGE-VERSIONS #A-SHOW function InitializeWikiShowMenu() { let data_selector = (EL.controller === 'wiki-pages' ? 'wiki-page-id' : 'wiki-page-version-wiki-page-id'); let wikiid = $('body').data(data_selector); let $menu_obj = $.parseHTML(RenderMultilinkMenu(wikiid, ['wiki'], 'subscribe_events_enabled')); let linkhtml = RenderSubscribeMultiLinks("Wiki", ['wiki'], wikiid, 'subscribe_events_enabled'); let shownclass = (IsEventEnabled('wiki', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $('#el-add-links', $menu_obj).append(`${linkhtml}`); $('#nav').append($menu_obj); } //#C-WIKI-PAGES #A-INDEX / #C-WIKI-PAGE-VERSIONS #A-INDEX / EVENT-NOTICE function InitializeWikiIndexLinks(container,render=true) { let type = GetTableType(container); let typeset = GetList('wiki'); $('.striped tbody tr', container).each((i,row)=>{ let data_selector = (type === 'wiki-page' ? 'id' : 'wiki-page-id'); let wikiid = $(row).data(data_selector); if (render) { let linkhtml = RenderSubscribeDualLinks('wiki', wikiid, 'span', "", "", true); let shownclass = (IsEventEnabled('wiki', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $(' .title-column', row).prepend(`${linkhtml} | `); } else { let subscribed = !typeset.has(wikiid); UpdateDualLink('wiki', subscribed, wikiid); } }); } //#C-POOLS #A-SHOW function InitializePoolShowMenu() { let poolid = $('body').data('pool-id'); let $menu_obj = $.parseHTML(RenderMultilinkMenu(poolid, ['pool'], 'subscribe_events_enabled')); let linkhtml = RenderSubscribeMultiLinks("Pool", ['pool'], poolid, 'subscribe_events_enabled'); let shownclass = (IsEventEnabled('pool', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $('#el-add-links', $menu_obj).append(`${linkhtml}`); $('#nav').append($menu_obj); } //#C-POOLS #A-INDEX / #C-POOL-VERSIONS #A-INDEX / EVENT-NOTICE function InitializePoolIndexLinks(container,render=true) { let type = GetTableType(container); let typeset = GetList('pool'); $('.striped tbody tr', container).each((i,row)=>{ let data_selector = (type === 'pool' ? 'id' : 'pool-id'); let poolid = $(row).data(data_selector); if (render) { let linkhtml = RenderSubscribeDualLinks('pool', poolid, 'span', "", "", true); let shownclass = (IsEventEnabled('pool', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $('.name-column, .pool-column', row).prepend(`${linkhtml} | `); } else { let subscribed = !typeset.has(poolid); UpdateDualLink('pool', subscribed, poolid); } }); } //#C-POOLS #A-GALLERY function InitializePoolGalleryLinks() { $('#c-pools #a-gallery .post-preview > a').each((i,entry)=>{ let match = entry.href.match(POOLS_REGEX); if (!match) { return; } let poolid = parseInt(match[1]); let linkhtml = RenderSubscribeDualLinks('pool', poolid, 'div', " ", 'pool'); let shownclass = (IsEventEnabled('pool', 'subscribe_events_enabled') ? "" : 'el-event-hidden'); $(entry).before(`