diff --git a/README.md b/README.md index 2a8903f..058196c 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,60 @@ Better Better Booru =================== Various tweaks to make Danbooru better. - + --- Links ----- * [Homepage](https://github.com/pseudonymous/better-better-booru) * [Greasy Fork](https://greasyfork.org/scripts/3575-better-better-booru) -* [Installation](#installation) * [Changelog](https://github.com/pseudonymous/better-better-booru/blob/master/changelog.md) - + --- -Features +Features -------- * Allows the viewing of hidden/censored tags and automatic control of the number of thumbnails per a page with a basic account or no account. -* Improved image resizing that works on flash and webm content and also allows resizing by width, height, or both. -* Improved blacklist functionality that includes support of wildcards (*), the "or/any" operator (~), and additional metatags (ex: score:<0, etc.). -* The ability to easily modify status borders to your liking and create your own secondary custom borders that match images based on your criteria. -* Multiple options for altering post viewing: +* Endless pages support for various thumbnail listings with several customization options. +* Multiple options and features for improved blacklist functionality: + * Post display - Change how blacklisted posts are hidden. + * Thumbnail marking - Mark blacklisted thumbnails that have been unhidden so that they are easier to distinguish from other thumbnails. + * Thumbnail controls - Allow control over the blacklist from a blacklisted thumbnail and allow for individual control of each blacklisted thumbnail. + * Smart view - When viewing a blacklisted post, automatically display it if its thumbnail was already unhidden. + * Added support for wildcards (*), the "or/any" operator (~), nesting/grouping, and additional metatags (ex: score:<0, etc.). +* Multiple options and features for altering post viewing: * Alternate image swap - Swap between the sample and original image by clicking the image. * Resize image mode - Set the initial automatic resizing of an image to be by width, height, or both. - * Image drag scrolling - Use click and drag on a post image or webm video to reposition it. + * Image drag scrolling - Use click and drag on post content to reposition it. * Auto-scroll image - Automatically scroll the post content into view upon browsing a post. -* Multiple options for altering the sidebar: - * Search add - Add links to the sidebar tags that allow the easy inclusion or exclusion of additional search tags. + * Disable embedded notes - Force notes to display with the original style when viewing a post. + * Improved image resizing that works on flash content and also allows resizing by width, height, or both. + * Improved ugoira functionality that includes note viewing on sample video versions, an additional "view sample" link for swapping from the original version, and support for swapping between the sample and original with Danbooru's hotkey. +* Multiple options for altering the layout: * Remove tag headers - Remove the headers from the post sidebar tag lists and combine them into a single list sorted by type. - * Tag list scrollbars - Limit the length of the post sidebar tag list(s) and use scrollbars when the list(s) exceed that limit. + * Tag scrollbars - Limit the length of the sidebar tag list(s) and use scrollbars when the list(s) exceed that limit. * Auto-hide sidebar - Hide the sidebar on the window's left side and display it when it gains focus or the mouse gets near it. + * Fixed sidebar - Fix/stick the sidebar to the left side of the page and prevent it from vertically scrolling out of view. + * Collapsible sidebar - Allow sections of the sidebar to be minimized and expanded. + * Fixed paginator - Fix/stick the paginator to the bottom of the window and prevent it from scrolling out of view. + * Move save search - Move the "save this search" button into the sidebar. + * Various options for easily modifying status borders to your liking and creating your own secondary custom borders that match images based on your criteria. + * Various options for customizing the majority of notices by either altering or hiding them. * Several other miscellaneous options: * Direct downloads - Let download managers download all the images currently displayed as thumbnails. * Track new posts - Create a specialized link focused on tracking and browsing new images. * Clean links - Remove extra information from post links in order to reduce history clutter/problems. * Arrow navigation - Allow the use of the arrow keys to navigate pages. * Post tag titles - Give posts a page title consisting of all of their tags. -* Improved accessibility for logged out users via several features and options: - * Options menu - Adds back the missing sidebar section with all the options accessible to logged out users. - * Resize image - Automatically resize the image to your window. - * Load sample first - Automatically load the sample or original image first. - * Blacklist tags - Create a blacklist for hiding unwanted posts. -* Various options for customizing the majority of notices by either altering or hiding them. - + * Search add - Add/remove links to/from the sidebar tags that allow the easy inclusion or exclusion of additional search tags. + * Page counter - Add an indicator near the top of listings that displays the page number information and allows for easy jumping to specific pages. + * Comment scores - Make the score rating for comments visible. + * Quick search - Filter the current thumbnails for specific posts that match your search. +* Improved accessibility for logged out users via several options and features: + * Control over the blacklist, post sample setting, and post resizing setting. + * Adds back the missing sidebar options section with all the options accessible to logged out users. + --- -Installation ---------------- +Installation +------------ 1. Installing a userscript manager is strongly recommended and sometimes required. Suggestions: * [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) (Firefox) * [Tampermonkey](http://tampermonkey.net/) (Chrome, Opera) @@ -54,5 +66,5 @@ Installation 4. Choose to allow/install after reviewing your options. 5. The script should now be installed and ready for use on Danbooru. 6. To access script features, use the "BBB Settings" link in the Danbooru navigation bar. - + ** Note: If installing from a local file on your computer, your browser may require additional steps. diff --git a/better-better-booru.user.js b/better-better-booru.user.js index c97cabf..411d1b6 100644 --- a/better-better-booru.user.js +++ b/better-better-booru.user.js @@ -3,7 +3,7 @@ // @namespace https://greasyfork.org/scripts/3575-better-better-booru // @author otani, modified by Jawertae, A Pseudonymous Coder & Moebius Strip. // @description Several changes to make Danbooru much better. Including the viewing of hidden/censored images on non-upgraded accounts and more. -// @version 6.3.1 +// @version 7.0 // @updateURL https://greasyfork.org/scripts/3575-better-better-booru/code/better_better_booru.meta.js // @downloadURL https://greasyfork.org/scripts/3575-better-better-booru/code/better_better_booru.user.js // @match http://*.donmai.us/* @@ -26,18 +26,127 @@ function bbbScript() { // This is needed to make this script work in Chrome. if (typeof(Danbooru) === "undefined") return; + /* Helper Prototypes */ + // Don't get hoisted so they should be declared at the top to simplify things. + String.prototype.bbbSpacePad = function() { + // Add a leading and trailing space. + return (this.length ? " " + this + " " : ""); + }; + + String.prototype.bbbSpaceClean = function() { + // Remove leading, trailing, and multiple spaces. + return this.replace(/\s+/g, " ").replace(/^\s|\s$/g, ""); + }; + + String.prototype.bbbTagClean = function() { + // Remove extra commas along with leading, trailing, and multiple spaces. + return this.replace(/[\s,]*(%\))\s*|\s*([~-]*\(%)[\s,]*/g, " $& ").replace(/[\s,]*,[\s,]*/g, ", ").replace(/[\s,]+$|^[\s,]+/g, "").replace(/\s+/g, " "); + }; + + String.prototype.bbbHash = function() { + // Turn a string into a hash using the current Danbooru hash method. + var hash = 5381; + var i = this.length; + + while(i) + hash = (hash * 33) ^ this.charCodeAt(--i); + + return hash >>> 0; + }; + + Element.prototype.bbbGetPadding = function() { + // Get all the padding measurements of an element including the total width and height. + if (window.getComputedStyle) { + var computed = window.getComputedStyle(this, null); + var paddingLeft = parseFloat(computed.paddingLeft); + var paddingRight = parseFloat(computed.paddingRight); + var paddingTop = parseFloat(computed.paddingTop); + var paddingBottom = parseFloat(computed.paddingBottom); + var paddingHeight = paddingTop + paddingBottom; + var paddingWidth = paddingLeft + paddingRight; + return {width: paddingWidth, height: paddingHeight, top: paddingTop, bottom: paddingBottom, left: paddingLeft, right: paddingRight}; + } + }; + + Element.prototype.bbbHasClass = function() { + // Test an element for one or more collections of classes. + var classList = this.classList; + + for (var i = 0, il = arguments.length; i < il; i++) { + var classes = arguments[i].bbbSpaceClean(); + + if (!classes) + continue; + + var classArray = classes.split(" "); + var hasClass = true; + + for (var j = 0, jl = classArray.length; j < jl; j++) { + if (!classList.contains(classArray[j])) { + hasClass = false; + break; + } + } + + if (hasClass) + return true; + } + + return false; + }; + + Element.prototype.bbbAddClass = function(classString) { + // Add one or more classes to an element. + var classes = classString.bbbSpaceClean(); + + if (!classes) + return; + + var classList = this.classList; + var classArray = classes.split(" "); + + for (var i = 0, il = classArray.length; i < il; i++) + classList.add(classArray[i]); + }; + + Element.prototype.bbbRemoveClass = function(classString) { + // Remove one or more classes from an element. + var classes = classString.bbbSpaceClean(); + + if (!classes) + return; + + var classList = this.classList; + var classArray = classes.split(" "); + + for (var i = 0, il = classArray.length; i < il; i++) + classList.remove(classArray[i]); + }; + + Element.prototype.bbbWatchNodes = function(func) { + // Watch for new nodes. + var observer = window.MutationObserver || window.WebKitMutationObserver; + + if (observer) { + observer = new observer(func); + observer.observe(this, {childList: true, subtree: true}); + } + else + this.addEventListener("DOMNodeInserted", func, false); + }; + /* Global Variables */ var bbb = { // Container for script info. blacklist: { entries: [], - match_list: {} + match_list: {}, + smart_view_target: undefined }, cache: { // Thumbnail info cache. current: { history: [], names: {} }, - hidden_imgs: [], save_enabled: false, stored: {} }, @@ -45,93 +154,180 @@ function bbbScript() { // This is needed to make this script work in Chrome. searches: [], style_list: {} }, - dragscroll: { - moved: false, + drag_scroll: { lastX: undefined, lastY: undefined, + moved: false, target: undefined }, el: { // Script elements. menu: {} // Menu elements. }, + endless: { + append_page: false, + enabled: false, + fill_first_page: false, + last_paginator: undefined, + new_paginator: undefined, + no_thumb_count: 0, + pages: [], + paused: false, + posts: {} + }, + fixed_paginator_space: 0, + fixed_sidebar: { + content: undefined, + left: undefined, + sidebar: undefined, + top: undefined + }, + hotkeys: { + other: { // Hotkeys for misc locations. + 66: {func: openMenu}, // B + 69: {func: endlessToggle}, // E + 70: {func: quickSearchOpen}, // F + s70: {func: quickSearchReset} // SHIFT + F + }, + post: { // Post hotkeys. + 49: {func: resizeHotkey, custom_handler: true}, // 1 + 50: {func: resizeHotkey, custom_handler: true}, // 2 + 51: {func: resizeHotkey, custom_handler: true}, // 3 + 52: {func: resizeHotkey, custom_handler: true}, // 4 + 66: {func: openMenu}, // B + 78: {func: function(event) { // N + Danbooru.Note.TranslationMode.toggle(event); + translationModeToggle(); + }}, + 86: {func: swapPost} // V + } + }, post: { // Post content info and status. info: {}, // Post information object. resize: { mode: "none", ratio: 1 }, - translationMode: false + swapped: false, // Whether the post content has been changed between the original and sample versions. + translation_mode: false }, options: { // Setting options and data. - bbb_version: "6.3.1", - alternate_image_swap: new Option("checkbox", false, "Alternate Image Swap", "Switch between the sample and original image by clicking the image. Notes can be toggled by using the link in the sidebar options section."), - arrow_nav: new Option("checkbox", false, "Arrow Navigation", "Allow the use of the left and right arrow keys to navigate pages. Has no effect on individual posts."), - autohide_sidebar: new Option("dropdown", "none", "Auto-hide Sidebar", "Hide the sidebar for individual posts and/or searches until the mouse comes close to the left side of the window or the sidebar gains focus.

Tips
By using Danbooru's keyboard shortcut for the letter \"Q\" to place focus on the search box, you can unhide the sidebar.

Use the thumbnail count option to get the most out of this feature on search listings.", {txtOptions:["Disabled:none", "Searches:search", "Posts:post", "Searches & Posts:post search"]}), - autoscroll_image: new Option("checkbox", false, "Auto-scroll Image", "Position the image as close as possible to the left and top edges of the window viewport when initially loading an individiual post."), - border_spacing: new Option("dropdown", 0, "Border Spacing", "Set the amount of blank space between a border and image and between a custom tag border and status border.

Note
Even when set to 0, status borders and custom tag borders will always have a minimum value of 1 between them.", {txtOptions:["0 (Default):0", "1:1", "2:2", "3:3"]}), - border_width: new Option("dropdown", 2, "Border Width", "Set the width of thumbnail borders.", {txtOptions:["1:1", "2 (Default):2", "3:3", "4:4", "5:5"]}), - bypass_api: new Option("checkbox", false, "Automatic API Bypass", "When logged out and API only features are enabled, do not warn about needing to be logged in. Instead, automatically bypass those features."), - clean_links: new Option("checkbox", false, "Clean Links", "Remove the extra information after the post ID in thumbnail links.

Note
Enabling this option will disable Danbooru's search navigation and active pool detection for individual posts."), - custom_status_borders: new Option("checkbox", false, "Custom Status Borders", "Override Danbooru's thumbnail borders for deleted, flagged, pending, parent, and child images."), - custom_tag_borders: new Option("checkbox", true, "Custom Tag Borders", "Add thumbnail borders to images with specific tags."), - direct_downloads: new Option("checkbox", false, "Direct Downloads", "Allow download managers to download the images displayed in the search, pool, and popular listings."), - enable_status_message: new Option("checkbox", true, "Enable Status Message", "When requesting information from Danbooru, display the request status in the lower right corner."), - hide_ban_notice: new Option("checkbox", false, "Hide Ban Notice", "Hide the Danbooru ban notice."), - hide_comment_notice: new Option("checkbox", false, "Hide Comment Guide Notice", "Hide the Danbooru comment guide notice."), - hide_pool_notice: new Option("checkbox", false, "Hide Pool Guide Notice", "Hide the Danbooru pool guide notice."), - hide_sign_up_notice: new Option("checkbox", false, "Hide Sign Up Notice", "Hide the Danbooru account sign up notice."), - hide_tag_notice: new Option("checkbox", false, "Hide Tag Guide Notice", "Hide the Danbooru tag guide notice."), - hide_tos_notice: new Option("checkbox", false, "Hide TOS Notice", "Hide the Danbooru terms of service agreement notice."), - hide_upgrade_notice: new Option("checkbox", false, "Hide Upgrade Notice", "Hide the Danbooru upgrade account notice."), - hide_upload_notice: new Option("checkbox", false, "Hide Upload Guide Notice", "Hide the Danbooru upload guide notice."), - image_drag_scroll: new Option("checkbox", false, "Image Drag Scrolling", "While holding down left click on a post image/webm video, mouse movement can be used to scroll the whole page and reposition the image/webm video.

Note
This option is automatically disabled when translation mode is active."), - image_resize: new Option("checkbox", true, "Resize Image", "Shrink large images to fit the browser window when initially loading an individual post.

Note
When logged in, the account's \"Fit images to window\" setting will override this option."), - image_resize_mode: new Option("dropdown", "width", "Resize Image Mode", "Choose how to shrink large images to fit the browser window when initially loading an individual post.", {txtOptions:["Width (Default):width", "Height:height", "Width & Height:all"]}), - load_sample_first: new Option("checkbox", true, "Load Sample First", "Load sample images first when viewing an individual post.

Note
When logged in, the account's \"Default image width\" setting will override this option."), - manage_cookies: new Option("checkbox", false, "Manage Notice Cookies", "When using the options to hide the upgrade, sign up, and/or TOS notice, also create cookies to disable these notices at the server level.

Tip
Use this feature if the notices keep flashing on your screen before being removed."), - minimize_status_notices: new Option("checkbox", false, "Minimize Status Notices", "Hide the Danbooru deleted, banned, flagged, appealed, and pending notices. When you want to see a hidden notice, you can click the appropriate status link in the information section of the sidebar."), - override_account: new Option("checkbox", false, "Override Account Settings", "Allow logged out settings to override account settings when logged in."), - post_tag_titles: new Option("checkbox", false, "Post Tag Titles", "Change the page titles for individual posts to a full list of the post tags."), - remove_tag_headers: new Option("checkbox", false, "Remove Tag Headers", "Remove the \"copyrights\", \"characters\", and \"artist\" headers from the sidebar tag list."), - script_blacklisted_tags: new Option("text", "", "Blacklisted Tags", "Hide images and posts that match the specified tag(s).

Guidelines
Matches can consist of a single tag or multiple tags. Each match must be separated by a comma and each tag in a match must be separated by a space. While using this script, the rules in the \"Thumbnail Matching Rules\" section under the help tab can be used in the blacklist.

Example
To filter posts tagged with spoilers and posts tagged with blood AND death, the blacklist would normally look like the following case:
spoilers, blood death

Note
When logged in, the account's \"Blacklisted tags\" list will override this option.", {isTagInput: true}), - search_add: new Option("checkbox", true, "Search Add", "Add + and - links to the sidebar tag list that modify the current search by adding or excluding additional search terms."), - show_banned: new Option("checkbox", false, "Show Banned", "Display all banned images in the search, pool, popular, and notes listings."), - show_deleted: new Option("checkbox", false, "Show Deleted", "Display all deleted images in the search, pool, popular, and notes listings."), - show_loli: new Option("checkbox", false, "Show Loli", "Display loli images in the search, pool, popular, comments, and notes listings."), - show_resized_notice: new Option("dropdown", "all", "Show Resized Notice", "Set which image type(s) the purple notice bar about image resizing is allowed to display on.

Tip
When a sample and original image are available for a post, a new option for swapping between the sample and original image becomes available in the sidebar options menu. Even if you disable the resized notice bar, you will always have access to its main function.", {txtOptions:["None (Disabled):none", "Original:original", "Sample:sample", "Original & Sample:all"]}), - show_shota: new Option("checkbox", false, "Show Shota", "Display shota images in the search, pool, popular, comments, and notes listings."), - show_toddlercon: new Option("checkbox", false, "Show Toddlercon", "Display toddlercon images in the search, pool, popular, comments, and notes listings."), - single_color_borders: new Option("checkbox", false, "Single Color Borders", "Only use one color for each thumbnail border."), - thumbnail_count: new Option("dropdown", 0, "Thumbnail Count", "Change the number of thumbnails that display in the search and notes listings.", {txtOptions:["Disabled:0"], numRange:[1,200]}), - track_new: new Option("checkbox", false, "Track New Posts", "Add a menu option titled \"New\" to the posts section submenu (between \"Listing\" and \"Upload\") that links to a customized search focused on keeping track of new posts.

Note
While browsing the new posts, the current page of images is also tracked. If the new post listing is left, clicking the \"New\" link later on will attempt to pull up the images where browsing was left off at.

Tip
If you would like to bookmark the new post listing, drag and drop the link to your bookmarks or right click it and bookmark/copy the location from the context menu."), + bbb_version: "7.0", + alternate_image_swap: newOption("checkbox", false, "Alternate Image Swap", "Switch between the sample and original image by clicking the image. NoteNotes can be toggled by using the link in the sidebar options section."), + arrow_nav: newOption("checkbox", false, "Arrow Navigation", "Allow the use of the left and right arrow keys to navigate pages. NoteThis option has no effect on individual posts."), + autohide_sidebar: newOption("dropdown", "none", "Auto-hide Sidebar", "Hide the sidebar for posts, favorites listings, and/or searches until the mouse comes close to the left side of the window or the sidebar gains focus.TipsBy using Danbooru's hotkey for the letter \"Q\" to place focus on the search box, you can unhide the sidebar.

Use the thumbnail count option to get the most out of this feature on search listings.", {txtOptions:["Disabled:none", "Favorites:favorites", "Posts:post", "Searches:search", "Favorites & Posts:favorites post", "Favorites & Searches:favorites search", "Posts & Searches:post search", "All:favorites post search"]}), + autoscroll_post: newOption("dropdown", "none", "Auto-scroll Post", "Automatically scroll a post to a particular point. Below Header: Scroll the window down until the header is no longer visible or scrolling is no longer possible. Post Content: Position the post content as close as possible to the left and top edges of the window viewport when initially loading a post. Using this option will also scroll past any notices above the content.", {txtOptions:["Disabled:none", "Below Header:header", "Post Content:post"]}), + blacklist_highlight_color: newOption("text", "#CCCCCC", "Highlight Color", "When using highlighting for \"thumbnail marking\", you may set the color here. NotesLeaving this field blank will result in the default color being used.

For easy color selection, use one of the many free tools on the internet like this one. Hex RGB color codes (#000000, #FFFFFF, etc.) are the recommended values."), + blacklist_thumb_controls: newOption("checkbox", false, "Thumbnail Controls", "Allow control over individual blacklisted thumbnails and access to blacklist toggle links from blacklisted thumbnails. DirectionsFor blacklisted thumbnails that have been revealed, hovering over them will reveal a clickable \"x\" icon that can hide them again.

If using the \"hidden\" or \"replaced\" post display options, clicking on the area of a blacklisted thumbnail will pop up a menu that displays what blacklist entries it matches. Clicking the thumbnail area a second time while that menu is open will reveal that single thumbnail.

The menu that pops up on the first click also allows for toggling any listed blacklist entry for the entire page and navigating to the post without revealing its thumbnail. NoteToggling blacklist entries will have no effect on posts that have been changed via their individual controls."), + blacklist_post_display: newOption("dropdown", "removed", "Post Display", "Set how the display of blacklisted posts in thumbnail listings and the comments section is handled. Removed: The default Danbooru behavior where the posts and the space they take up are completely removed. Hidden: Post space is preserved, but thumbnails are hidden. Replaced: Thumbnails are replaced by \"blacklisted\" thumbnail placeholders.", {txtOptions:["Removed (Default):removed", "Hidden:hidden", "Replaced:replaced"]}), + blacklist_smart_view: newOption("checkbox", false, "Smart View", "When navigating to a blacklisted post by using its thumbnail, if the thumbnail has already been revealed, the post content will temporarily be exempt from any blacklist checks for 1 minute and be immediately visible. NoteThumbnails in the parent/child notices of posts with exempt content will still be affected by the blacklist."), + blacklist_thumb_mark: newOption("dropdown", "none", "Thumbnail Marking", "Mark the thumbnails of blacklisted posts that have been revealed to make them easier to distinguish from other thumbnails. Highlight: Change the background color of blacklisted thumbnails. Icon Overlay: Add an icon to the lower right corner of blacklisted thumbnails.", {txtOptions:["Disabled:none", "Highlight:highlight", "Icon Overlay:icon"]}), + border_spacing: newOption("dropdown", 0, "Border Spacing", "Set the amount of blank space between a border and thumbnail and between a custom tag border and status border. NoteEven when set to 0, status borders and custom tag borders will always have a minimum value of 1 between them. TipUse this option if you often have trouble distinguishing a border from the thumbnail image.", {txtOptions:["0 (Default):0", "1:1", "2:2", "3:3"]}), + border_width: newOption("dropdown", 2, "Border Width", "Set the width of thumbnail borders.", {txtOptions:["1:1", "2 (Default):2", "3:3", "4:4", "5:5"]}), + bypass_api: newOption("checkbox", false, "Automatic API Bypass", "When logged out and API only features are enabled, do not warn about needing to be logged in. Instead, automatically bypass those features."), + clean_links: newOption("checkbox", false, "Clean Links", "Remove the extra information after the post ID in thumbnail links.NoteEnabling this option will disable Danbooru's search navigation and active pool/favorite group detection for posts."), + collapse_sidebar: newOption("checkbox", false, "Collapsible Sidebar", "Allow sections in the sidebar to be expanded and collapsed via clicking their header titles.NoteSections can be set to default to expanded or collapsed by right clicking their titles."), + comment_score: newOption("checkbox", false, "Comment Scores", "Make comment scores visible by adding them as direct links to their respective comments."), + custom_status_borders: newOption("checkbox", false, "Custom Status Borders", "Override Danbooru's thumbnail borders for deleted, flagged, pending, parent, and child images."), + custom_tag_borders: newOption("checkbox", true, "Custom Tag Borders", "Add thumbnail borders to posts with specific tags."), + direct_downloads: newOption("checkbox", false, "Direct Downloads", "Allow download managers to download the posts displayed in the favorites, search, pool, popular, and favorite group listings."), + disable_embedded_notes: newOption("checkbox", false, "Disable Embedded Notes", "Force posts with embedded notes to display with the original note styling. NotesWhile notes will display with the original styling, the actual post settings will still have embedded notes set to enabled.

Due to the actual settings, users that may wish to edit notes will have to edit the notes with the embedded note styling so that nothing ends up breaking in unexpected ways. When toggling translation mode or opening the edit note dialog box, the notes will automatically revert back to the original embedded notes until the page is reloaded.

Note resizing and moving will be allowed without the reversion to embedded notes since this ability is sometimes necessary for badly positioned notes. Any note resizing or moving done as a part of intended note editing should be done after triggering the embedded note reversion since any changes before it will be lost."), + enable_status_message: newOption("checkbox", true, "Enable Status Message", "When requesting information from Danbooru, display the request status in the lower right corner."), + endless_default: newOption("dropdown", "disabled", "Default", "Enable endless pages on the favorites, search, pool, notes, and favorite group listings. Off: Start up with all features off. On: Start up with all features on.Paused: Start up with all features on, but do not append new pages until the \"load more\" button is clicked.NoteWhen not set to disabled, endless pages can be toggled between off and on/paused by using the \"E\" hotkey or the \"endless\" link next to the \"listing\" link in the page submenu.", {txtOptions:["Disabled:disabled", "Off:off", "On:on", "Paused:paused"]}), + endless_fill: newOption("checkbox", false, "Fill Pages", "When appending pages with missing thumbnails caused by hidden posts or removed duplicate posts, retrieve thumbnails from the following pages and add them to the new page until the desired number of thumbnails is reached. NoteIf using page separators, the displayed page number for appended pages composed of thumbnails from multiple Danbooru pages will be replaced by a range consisting of the first and last pages from which thumbnails were retrieved."), + endless_pause_interval: newOption("dropdown", 0, "Pause Interval", "Pause endless pages each time the number of pages reaches a multiple of the selected amount.", {txtOptions:["Disabled:0"], numRange:[1,100]}), + endless_preload: newOption("checkbox", false, "Preload Next Page", "Start loading the next page as soon as possible.NoteA preloaded page will not be appended until the scroll limit is reached."), + endless_remove_dup: newOption("checkbox", false, "Remove Duplicates", "When appending new pages, remove posts that already exist in the listing from the new page.NoteDuplicate posts are caused by the addition of new posts to the beginning of a listing or changes to the order of the posts."), + endless_scroll_limit: newOption("dropdown", 500, "Scroll Limit", "Set the minimum amount of pixels that the window can have left to vertically scroll before it starts appending the next page.", {numList:[0,50,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000,1050,1100,1150,1200,1250,1300,1350,1400,1450,1500]}), + endless_separator: newOption("dropdown", "divider", "Page Separator", "Distinguish pages from each other by marking them with a separator.Marker: Place a thumbnail sized marker before the first thumbnail of each page.Divider: Completely separate pages by placing a horizontal line between them.", {txtOptions:["None:none", "Marker:marker", "Divider:divider"]}), + endless_session_toggle: newOption("checkbox", false, "Session Toggle", "When toggling endless pages on and off, the mode it's toggled to will override the default and persist across other pages in the same browsing session for that tab until it ends."), + fixed_paginator: newOption("dropdown", "disabled", "Fixed Paginator", "Make the paginator always visible for the favorites, search, pool, notes, and favorite group listings by fixing it to the bottom of the window when it would normally start scrolling out of view. NoteOptions labeled with \"minimal\" will also make the fixed paginator smaller by removing most of the blank space within it.", {txtOptions:["Disabled:disabled", "Endless:endless", "Endless (Minimal):endless minimal", "Normal:normal", "Normal (Minimal):normal minimal", "Always:endless normal", "Always (Minimal):endless normal minimal"]}), + fixed_sidebar: newOption("dropdown", "none", "Fixed Sidebar", "Make the sidebar never completely vertically scroll out of view for posts, favorites listings, and/or searches by fixing it to the top or bottom of the window when it would normally start scrolling out of view. NoteThe \"auto-hide sidebar\" option will override this option if both try to modify the same page. TipDepending on the available height in the browser window and the Danbooru location being modified, the \"tag scrollbars\", \"collapsible sidebar\", and/or \"remove tag headers\" options may be needed for best results.", {txtOptions:["Disabled:none", "Favorites:favorites", "Posts:post", "Searches:search", "Favorites & Posts:favorites post", "Favorites & Searches:favorites search", "Posts & Searches:post search", "All:favorites post search"]}), + hide_ban_notice: newOption("checkbox", false, "Hide Ban Notice", "Hide the Danbooru ban notice."), + hide_comment_notice: newOption("checkbox", false, "Hide Comment Guide Notice", "Hide the Danbooru comment guide notice."), + hide_pool_notice: newOption("checkbox", false, "Hide Pool Guide Notice", "Hide the Danbooru pool guide notice."), + hide_sign_up_notice: newOption("checkbox", false, "Hide Sign Up Notice", "Hide the Danbooru account sign up notice."), + hide_tag_notice: newOption("checkbox", false, "Hide Tag Guide Notice", "Hide the Danbooru tag guide notice."), + hide_tos_notice: newOption("checkbox", false, "Hide TOS Notice", "Hide the Danbooru terms of service agreement notice."), + hide_upgrade_notice: newOption("checkbox", false, "Hide Upgrade Notice", "Hide the Danbooru upgrade account notice."), + hide_upload_notice: newOption("checkbox", false, "Hide Upload Guide Notice", "Hide the Danbooru upload guide notice."), + image_swap_mode: newOption("dropdown", "load", "Image Swap Mode", "Set how swapping between the sample and original image is done.Load First: Display the image being swapped in after it has finished downloading. View While Loading: Immediately display the image being swapped in while it is downloading.", {txtOptions:["Load First (Default):load", "View While Loading:view"]}), + search_tag_scrollbars: newOption("dropdown", 0, "Search Tag Scrollbars", "Limit the length of the sidebar tag list for the search listing by restricting it to a set height in pixels. When the list exceeds the set height, a scrollbar will be added to allow the rest of the list to be viewed.", {txtOptions:["Disabled:0"], numList:[50,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000,1050,1100,1150,1200,1250,1300,1350,1400,1450,1500]}), + load_sample_first: newOption("checkbox", true, "Load Sample First", "Load sample images first when viewing a post.NoteWhen logged in, the account's \"default image width\" setting will override this option. This behavior can be changed with the \"override sample setting\" option under the preferences tab."), + manage_cookies: newOption("checkbox", false, "Manage Notice Cookies", "When using the options to hide the upgrade, sign up, and/or TOS notice, also create cookies to disable these notices at the server level.TipUse this feature if the notices keep flashing on your screen before being removed."), + minimize_status_notices: newOption("checkbox", false, "Minimize Status Notices", "Hide the Danbooru deleted, banned, flagged, appealed, and pending notices. When you want to see a hidden notice, you can click the appropriate status link in the information section of the sidebar."), + move_save_search: newOption("checkbox", false, "Move Save Search", "Move the \"save this search\" button into the related section in the sidebar."), + override_blacklist: newOption("dropdown", "logged_out", "Override Blacklist", "Allow the \"blacklist\" setting to override the default blacklist for logged out users and/or account blacklist for logged in users. Logged out: Override the default blacklist for logged out users. Always: Override the default blacklist for logged out users and account blacklist for logged in users.", {txtOptions:["Disabled:disabled", "Logged out:logged_out", "Always:always"]}), + override_resize: newOption("checkbox", false, "Override Resize Setting", "Allow the \"resize post\" setting to override the account \"fit images to window\" setting when logged in."), + override_sample: newOption("checkbox", false, "Override Sample Setting", "Allow the \"load sample first\" setting to override the account \"default image width\" setting when logged in. NoteWhen using this option, your Danbooru account settings should have \"default image width\" set to the corresponding value of the \"load sample first\" script setting. Not doing so will cause your browser to always download both the sample and original image. If you often change the \"load sample first\" setting, leaving your account to always load the sample/850px image first is your best option."), + page_counter: newOption("checkbox", false, "Page Counter", "Add a page counter and \"go to page #\" input field near the top of listing pages. NoteThe total number of pages will not be displayed if the pages are using the \"previous & next\" paging system or the total number of pages exceeds the maximum amount allowed by your user account level."), + post_drag_scroll: newOption("checkbox", false, "Post Drag Scrolling", "While holding down left click on a post's content, mouse movement can be used to scroll the whole page and reposition the content.NoteThis option is automatically disabled when translation mode is active."), + post_link_new_window: newOption("dropdown", "none", "New Tab/Window", "Force post links in the search, pool, popular, favorites, notes, and favorite group listings to open in a new tab/window during normal and/or endless page browsing. NotesWhen this option is active, holding down the control and shift keys while clicking a post link will open the post in the current tab/window.

Whether the post opens in a new tab or a new window depends upon your browser configuration. TipThis option can be useful as a safeguard to keep accidental left clicks from disrupting endless page browsing.", {txtOptions:["Disabled:disabled", "Endless:endless", "Normal:normal", "Always:endless normal"]}), + post_resize: newOption("checkbox", true, "Resize Post", "Shrink large post content to fit the browser window when initially loading a post.NoteWhen logged in, the account's \"fit images to window\" setting will override this option. This behavior can be changed with the \"override resize setting\" option under the preferences tab."), + post_resize_mode: newOption("dropdown", "width", "Resize Mode", "Choose how to shrink large post content to fit the browser window when initially loading a post.", {txtOptions:["Width (Default):width", "Height:height", "Width & Height:all"]}), + post_tag_scrollbars: newOption("dropdown", 0, "Post Tag Scrollbars", "Limit the length of the sidebar tag lists for posts by restricting them to a set height in pixels. For lists that exceed the set height, a scrollbar will be added to allow the rest of the list to be viewed.NoteWhen using \"remove tag headers\", this option will limit the overall length of the combined list.", {txtOptions:["Disabled:0"], numList:[50,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000,1050,1100,1150,1200,1250,1300,1350,1400,1450,1500]}), + post_tag_titles: newOption("checkbox", false, "Post Tag Titles", "Change the page titles for posts to a full list of the post tags."), + quick_search: newOption("dropdown", "disabled", "Quick Search", "Add a new search box to the upper right corner of the window viewport that allows searching through the current thumbnails for specific posts. Fade: Fade all posts that don't match in the thumbnail listing. Remove: Remove all posts that don't match from the thumbnail listing. DirectionsPlease read the \"Thumbnail Matching Rules\" section under the help tab for information about creating searches.

The search starts minimized in the upper right corner. Left clicking the main icon will open and close the search. Right clicking the main icon will completely reset the search. Holding down shift while left clicking the main icon will toggle an active search's pinned status.

While open, the search can be entered/updated in the search box and the pinned status can be toggled by clicking the pushpin icon. If no changes are made to an active search, submitting it a second time will reset the quick search. NotesOptions labeled with \"pinned\" will make searches default to being pinned.

A pinned search will persist across other pages in the same browsing session for that tab until it ends or the search is unpinned.

When not set to disabled, the quick search can be opened by using the \"F\" hotkey. Additionally, an active search can be reset by using \"Shift + F\". Pressing \"escape\" while the quick search is open will close it.", {txtOptions:["Disabled:disabled", "Fade:fade", "Fade (Pinned):fade pinned", "Remove:remove", "Remove (Pinned):remove pinned"]}), + remove_tag_headers: newOption("checkbox", false, "Remove Tag Headers", "Remove the \"copyrights\", \"characters\", and \"artist\" headers from the sidebar tag list."), + resize_link_style: newOption("dropdown", "full", "Resize Link Style", "Set how the resize links in the post sidebar options section will display. Full: Show the \"resize to window\", \"resize to window width\", and \"resize to window height\" links on separate lines. Minimal: Show the \"resize to window\" (W&H), \"resize to window width\" (W), and \"resize to window height\" (H) links on one line.", {txtOptions:["Full:full", "Minimal:minimal"]}), + script_blacklisted_tags: "", + search_add: newOption("dropdown", "disabled", "Search Add", "Modify the sidebar tag list by adding, removing, or replacing links in the sidebar tag list that modify the current search's tags. Remove: Remove any preexisting \"+\" and \"–\" links. Link: Add \"+\" and \"–\" links to modified versions of the current search that include or exclude their respective tags. Toggle: Add toggle links that modify the search box with their respective tags. Clicking a toggle link will switch between a tag being included (+), excluded (–), potentially included among other tags (~), and removed (»). Right clicking a toggle link will immediately remove its tag. If a tag already exists in the search box or gets entered/removed through alternative means, the toggle link will automatically update to reflect the tag's current status. NoteThe remove option is intended for users above the basic user level that want to remove the links. For users that can't normally see the links and do not wish to see them, this setting should be set to disabled.", {txtOptions:["Disabled:disabled", "Remove:remove", "Link:link", "Toggle:toggle"]}), + show_banned: newOption("checkbox", false, "Show Banned", "Display all banned posts in the search, pool, popular, favorites, comments, notes, and favorite group listings."), + show_deleted: newOption("checkbox", false, "Show Deleted", "Display all deleted posts in the search, pool, popular, favorites, notes, and favorite group listings. NoteWhen using this option, your Danbooru account settings should have \"deleted post filter\" set to no and \"show deleted children\" set to yes in order to function properly and minimize connections to Danbooru."), + show_loli: newOption("checkbox", false, "Show Loli", "Display loli posts in the search, pool, popular, favorites, comments, notes, and favorite group listings."), + show_resized_notice: newOption("dropdown", "all", "Show Resized Notice", "Set which image type(s) the purple notice bar about image resizing is allowed to display on. TipWhen a sample and original image are available for a post, a new option for swapping between the sample and original image becomes available in the sidebar options menu. Even if you disable the resized notice bar, you will always have access to its main function.", {txtOptions:["None (Disabled):none", "Original:original", "Sample:sample", "Original & Sample:all"]}), + show_shota: newOption("checkbox", false, "Show Shota", "Display shota posts in the search, pool, popular, favorites, comments, notes, and favorite group listings."), + show_toddlercon: newOption("checkbox", false, "Show Toddlercon", "Display toddlercon posts in the search, pool, popular, favorites, comments, notes, and favorite group listings."), + single_color_borders: newOption("checkbox", false, "Single Color Borders", "Only use one color for each thumbnail border."), + thumb_info: newOption("dropdown", "disabled", "Thumbnail Info", "Display the score(★), favorite count(♥), and rating (S, Q, or E) for a post with its thumbnails. Below: Display the extra information below thumbnails. Hover: Display the extra information upon hovering over a thumbnail's area. NoteExtra information will not be added to the thumbnails in the comments listing since the score and rating are already visible there. Instead, the number of favorites will be added next to the existing score display.", {txtOptions:["Disabled:disabled", "Below:below", "Hover:hover"]}), + thumbnail_count: newOption("dropdown", 0, "Thumbnail Count", "Change the number of thumbnails that display in the search, favorites, and notes listings.", {txtOptions:["Disabled:0"], numRange:[1,200]}), + track_new: newOption("checkbox", false, "Track New Posts", "Add a menu option titled \"New\" to the posts section submenu (between \"Listing\" and \"Upload\") that links to a customized search focused on keeping track of new posts.NoteWhile browsing the new posts, the current page of posts is also tracked. If the new post listing is left, clicking the \"new\" link later on will attempt to pull up the posts where browsing was left off at.TipIf you would like to bookmark the new post listing, drag and drop the link to your bookmarks or right click it and bookmark/copy the location from the context menu."), status_borders: borderSet(["deleted", true, "#000000", "solid", "post-status-deleted"], ["flagged", true, "#FF0000", "solid", "post-status-flagged"], ["pending", true, "#0000FF", "solid", "post-status-pending"], ["child", true, "#CCCC00", "solid", "post-status-has-parent"], ["parent", true, "#00FF00", "solid", "post-status-has-children"]), tag_borders: borderSet(["loli", true, "#FFC0CB", "solid"], ["shota", true, "#66CCFF", "solid"], ["toddlercon", true, "#9370DB", "solid"], ["status:banned", true, "#000000", "solid"]), - tag_scrollbars: new Option("dropdown", 0, "Tag List Scrollbars", "Limit the length of the sidebar tag lists for individual posts by restricting them to a set height in pixels. For lists that exceed the set height, a scrollbar will be added to allow the rest of the list to be viewed.

Note
When using \"Remove Tag Headers\", this option will limit the overall length of the combined list.", {txtOptions:["Disabled:0"], numList:[50,100,150,200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000,1050,1100,1150,1200,1250,1300,1350,1400,1450,1500]}), - thumb_cache_limit: new Option("dropdown", 5000, "Thumbnail Info Cache Limit", "Limit the number of thumbnail information entries cached in the browser.

Note
No actual thumbnails are cached. Only filename information used to speed up the display of hidden thumbnails is stored. Every 1000 entries is approximately equal to 0.1 megabytes of space.", {txtOptions:["Disabled:0"], numList:[1000,2000,3000,4000,5000,6000,7000,8000,9000,10000,11000,12000,13000,14000,15000,16000,17000,18000,19000,20000,21000,22000,23000,24000,25000,26000,27000,28000,29000,30000]}), - track_new_data: {viewed:0, viewing:1} + thumb_cache_limit: newOption("dropdown", 5000, "Thumbnail Info Cache Limit", "Limit the number of thumbnail information entries cached in the browser.NoteNo actual thumbnails are cached. Only filename information used to speed up the display of hidden thumbnails is stored. Every 1000 entries is approximately equal to 0.1 megabytes of space.", {txtOptions:["Disabled:0"], numList:[1000,2000,3000,4000,5000,6000,7000,8000,9000,10000,11000,12000,13000,14000,15000,16000,17000,18000,19000,20000,21000,22000,23000,24000,25000,26000,27000,28000,29000,30000]}), + collapse_sidebar_data: {post: {}, thumb: {}}, + track_new_data: {viewed: 0, viewing: 1} + }, + quick_search: "", + search_add: { + active_links: {}, + links: {}, + old: "" }, sections: { // Setting sections and ordering. - browse: new Section("general", ["show_loli", "show_shota", "show_toddlercon", "show_banned", "show_deleted", "thumbnail_count"], "Image Browsing"), - notices: new Section("general", ["show_resized_notice", "minimize_status_notices", "hide_sign_up_notice", "hide_upgrade_notice", "hide_tos_notice", "hide_comment_notice", "hide_tag_notice", "hide_upload_notice", "hide_pool_notice", "hide_ban_notice"], "Notices"), - sidebar: new Section("general", ["search_add", "remove_tag_headers", "tag_scrollbars", "autohide_sidebar"], "Tag Sidebar"), - image_control: new Section("general", ["alternate_image_swap", "image_resize_mode", "image_drag_scroll", "autoscroll_image"], "Image Control"), - logged_out: new Section("general", ["image_resize", "load_sample_first", "script_blacklisted_tags"], "Logged Out Settings"), - misc: new Section("general", ["direct_downloads", "track_new", "clean_links", "arrow_nav", "post_tag_titles"], "Misc."), - script_settings: new Section("general", ["bypass_api", "manage_cookies", "enable_status_message", "override_account", "thumb_cache_limit"], "Script Settings"), - border_options: new Section("general", ["custom_tag_borders", "custom_status_borders", "single_color_borders", "border_width", "border_spacing"], "Options"), - status_borders: new Section("border", "status_borders", "Custom Status Borders", "When using custom status borders, the borders can be edited here. For easy color selection, use one of the many free tools on the internet like this one."), - tag_borders: new Section("border", "tag_borders", "Custom Tag Borders", "When using custom tag borders, the borders can be edited here. For easy color selection, use one of the many free tools on the internet like this one.") + blacklist_options: newSection("general", ["blacklist_post_display", "blacklist_thumb_mark", "blacklist_highlight_color", "blacklist_thumb_controls", "blacklist_smart_view"], "Options"), + border_options: newSection("general", ["custom_tag_borders", "custom_status_borders", "single_color_borders", "border_width", "border_spacing"], "Options"), + browse: newSection("general", ["show_loli", "show_shota", "show_toddlercon", "show_banned", "show_deleted", "thumbnail_count", "thumb_info", "post_link_new_window"], "Post Browsing"), + control: newSection("general", ["load_sample_first", "alternate_image_swap", "image_swap_mode", "post_resize", "post_resize_mode", "post_drag_scroll", "autoscroll_post", "disable_embedded_notes"], "Post Control"), + endless: newSection("general", ["endless_default", "endless_session_toggle", "endless_separator", "endless_scroll_limit", "endless_remove_dup", "endless_pause_interval", "endless_fill", "endless_preload"], "Endless Pages"), + notices: newSection("general", ["show_resized_notice", "minimize_status_notices", "hide_sign_up_notice", "hide_upgrade_notice", "hide_tos_notice", "hide_comment_notice", "hide_tag_notice", "hide_upload_notice", "hide_pool_notice", "hide_ban_notice"], "Notices"), + sidebar: newSection("general", ["remove_tag_headers", "post_tag_scrollbars", "search_tag_scrollbars", "autohide_sidebar", "fixed_sidebar", "collapse_sidebar"], "Tag Sidebar"), + misc: newSection("general", ["direct_downloads", "track_new", "clean_links", "arrow_nav", "post_tag_titles", "search_add", "page_counter", "comment_score", "quick_search"], "Misc."), + misc_layout: newSection("general", ["fixed_paginator", "move_save_search"], "Misc."), + script_settings: newSection("general", ["bypass_api", "manage_cookies", "enable_status_message", "resize_link_style", "override_blacklist", "override_resize", "override_sample", "thumb_cache_limit"], "Script Settings"), + status_borders: newSection("border", "status_borders", "Custom Status Borders", "When using custom status borders, the borders can be edited here. For easy color selection, use one of the many free tools on the internet like this one."), + tag_borders: newSection("border", "tag_borders", "Custom Tag Borders", "When using custom tag borders, the borders can be edited here. For easy color selection, use one of the many free tools on the internet like this one.") + }, + settings: { + changed: {} }, - user: {} // User settings. + timers: {}, + user: {}, // User settings. + xml: { // Active xml requests. False when successfully completed or not yet used. True when incomplete due to being in progress or an error. + endless: false, + hidden: false, + paginator: false, + thumbs: false + } }; loadSettings(); // Load user settings. + // Provide a session ID in order to detect XML requests carrying over from other pages. + window.bbbSession = new Date().getTime(); + // Location variables. - var gUrl = location.href.split("#", 1)[0]; // URL without the anchor - var gUrlPath = location.pathname; // URL path only - var gUrlQuery = location.search; // URL query string only - var gLoc = currentLoc(); // Current location (post = single post, search = posts index, notes = notes index, popular = popular index, pool = single pool, comments = comments page, intro = introduction page) + var gLoc = danbLoc(); // Current location + var gLocRegex = new RegExp("\\b" + gLoc + "\\b"); // Script variables. // Global @@ -141,6 +337,13 @@ function bbbScript() { // This is needed to make this script work in Chrome. var show_banned = bbb.user.show_banned; var show_deleted = bbb.user.show_deleted; var direct_downloads = bbb.user.direct_downloads; + var post_link_new_window = bbb.user.post_link_new_window; + + var blacklist_post_display = bbb.user.blacklist_post_display; + var blacklist_thumb_mark = bbb.user.blacklist_thumb_mark; + var blacklist_highlight_color = bbb.user.blacklist_highlight_color; + var blacklist_thumb_controls = bbb.user.blacklist_thumb_controls; + var blacklist_smart_view = bbb.user.blacklist_smart_view; var custom_tag_borders = bbb.user.custom_tag_borders; var custom_status_borders = bbb.user.custom_status_borders; @@ -148,12 +351,23 @@ function bbbScript() { // This is needed to make this script work in Chrome. var border_spacing = bbb.user.border_spacing; var border_width = bbb.user.border_width; var clean_links = bbb.user.clean_links; - var autohide_sidebar = bbb.user.autohide_sidebar; + var comment_score = bbb.user.comment_score; + var thumb_info = bbb.user.thumb_info; + var autohide_sidebar = gLocRegex.test(bbb.user.autohide_sidebar); + var fixed_sidebar = gLocRegex.test(bbb.user.fixed_sidebar); + var fixed_paginator = bbb.user.fixed_paginator; + var collapse_sidebar = bbb.user.collapse_sidebar; + var move_save_search = bbb.user.move_save_search; + var page_counter = bbb.user.page_counter; + var quick_search = bbb.user.quick_search; var bypass_api = bbb.user.bypass_api; var manage_cookies = bbb.user.manage_cookies; var enable_status_message = bbb.user.enable_status_message; - var override_account = bbb.user.override_account; + var resize_link_style = bbb.user.resize_link_style; + var override_blacklist = bbb.user.override_blacklist; + var override_resize = bbb.user.override_resize; + var override_sample = bbb.user.override_sample; var track_new = bbb.user.track_new; var show_resized_notice = bbb.user.show_resized_notice; @@ -170,507 +384,304 @@ function bbbScript() { // This is needed to make this script work in Chrome. // Search var arrow_nav = bbb.user.arrow_nav; var search_add = bbb.user.search_add; + var search_tag_scrollbars = bbb.user.search_tag_scrollbars; var thumbnail_count = bbb.user.thumbnail_count; var thumbnail_count_default = 20; // Number of thumbnails BBB should expect Danbooru to return by default. var thumb_cache_limit = bbb.user.thumb_cache_limit; // Post var alternate_image_swap = bbb.user.alternate_image_swap; - var image_resize = bbb.user.image_resize; - var image_resize_mode = bbb.user.image_resize_mode; - var image_drag_scroll = bbb.user.image_drag_scroll; - var load_sample_first = bbb.user.load_sample_first; + var post_resize = accountSettingCheck("post_resize"); + var post_resize_mode = bbb.user.post_resize_mode; + var post_drag_scroll = bbb.user.post_drag_scroll; + var load_sample_first = accountSettingCheck("load_sample_first"); var remove_tag_headers = bbb.user.remove_tag_headers; - var tag_scrollbars = bbb.user.tag_scrollbars; + var post_tag_scrollbars = bbb.user.post_tag_scrollbars; var post_tag_titles = bbb.user.post_tag_titles; - var autoscroll_image = bbb.user.autoscroll_image; + var autoscroll_post = bbb.user.autoscroll_post; + var image_swap_mode = bbb.user.image_swap_mode; + var disable_embedded_notes = bbb.user.disable_embedded_notes; + + // Endless + var endless_default = bbb.user.endless_default; + var endless_fill = bbb.user.endless_fill; + var endless_pause_interval = bbb.user.endless_pause_interval; + var endless_preload = bbb.user.endless_preload; + var endless_remove_dup = bbb.user.endless_remove_dup; + var endless_scroll_limit = bbb.user.endless_scroll_limit; + var endless_separator = bbb.user.endless_separator; + var endless_session_toggle = bbb.user.endless_session_toggle; // Stored data var status_borders = bbb.user.status_borders; var tag_borders = bbb.user.tag_borders; + var collapse_sidebar_data = bbb.user.collapse_sidebar_data; var track_new_data = bbb.user.track_new_data; var script_blacklisted_tags = bbb.user.script_blacklisted_tags; // Other data var bbbHiddenImg = "data:image/png;base64,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"; + var bbbBlacklistImg = "data:image/png;base64,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"; + var bbbBlacklistIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAkFBMVEUAAAD////////////////////////////////////////////////////////////////p6en////////////////////////////////////////////////////////+/v7///////////////////////////////////////////////////////////97JICZAAAAL3RSTlMACAQBoUSi5QcnMfmnsMW9VQno+vjsxrwstPyzMhOpSxS65vX06czqQf7NvqbzQKZY7GsAAADASURBVHhefZDnDoJAEITn8Kh3FAEFQbH3Mu//doaNLSFxfn7J7hT80cgJlAqc0S8bu55P+p47/rJQ8yUdflhCHq/WpjmZvKjSPPOyBFbZlNRKPFySzTaOANQJ6fZujsf9YUcjNMvpOQACn+kyLmjaObBI6QcAFGkRxYblLAIsqd4Q0ayUDzeBcr4A5q2h6U5Vfy5GeQbIh8l6I0YSKal72k3uhUSS8OQ0WwGPddFQq2/NPLW22rxrDgcZTvd35KGevk8VfmeGhUQAAAAASUVORK5CYII="; /* "INIT" */ - customCSS(); // Contains the portions related to ads and notices. + customCSS(); // Contains the portions related to notices. - if (custom_tag_borders) - delayMe(formatThumbnails); + delayMe(formatThumbnails); // Delayed to allow Danbooru to run first. - if (autohide_sidebar.indexOf(gLoc) > -1) - autohideSidebar(); + delayMe(blacklistInit); // Delayed to allow Danbooru to run first. - delayMe(blacklistInit); + thumbInfo(); - if (search_add) - searchAdd(); + removeTagHeaders(); - if (remove_tag_headers) - removeTagHeaders(); + searchAdd(); - if (minimize_status_notices) - minimizeStatusNotices(); + minimizeStatusNotices(); - if (post_tag_titles) - postTagTitles(); + postTagTitles(); - if (track_new) - trackNew(); + trackNew(); injectSettings(); - if (enable_status_message) - bbbStatusInit(); + modifyPage(); - if (!noXML()) { - if (useAPI()) // API only features. - searchJSON(gLoc); - else // Alternate mode for features. - modifyPage(gLoc); - } + autohideSidebar(); - if (clean_links) - cleanLinks(); + delayMe(fixedSidebar); // Delayed to allow Danbooru layout to finalize. - if (direct_downloads) - postDDL(); + delayMe(collapseSidebar); // Delayed to allow Danbooru layout to finalize. - if (arrow_nav) - arrowNav(); + delayMe(fixedPaginator); // Delayed to allow Danbooru layout to finalize. - if (thumbnail_count) - limitFix(); + moveSaveSearch(); + + pageCounter(); + + quickSearch(); + + postLinkNewWindow(); + + commentScoreInit(); + + cleanLinks(); + + postDDL(); + + arrowNav(); + + fixLimit(); bbbHotkeys(); + endlessInit(); + /* Functions */ - /* Functions for creating a url and retrieving info from it */ + /* Functions for XML API info */ function searchJSON(mode, optArg) { - var numThumbs = getPosts().length; + // Figure out the desired URL for a JSON API request, trigger any necessary xml flag, and update the status message. + var url = location.href.split("#", 1)[0]; + var idCache; + var idList; + var idSearch; + var page; + + if (mode === "search" || mode === "notes" || mode === "favorites") { + if (potentialHiddenPosts(mode)) { + url = (allowUserLimit() ? updateURLQuery(url, {limit: thumbnail_count}) : url); + bbb.xml.thumbs = true; - if (mode === "search" || mode === "notes") { - var limitUrl = getVar("limit"); - var limitSearch = getLimitSearch(); - var numDesired = 0; - var limit = ""; - var numExpected; + if (mode === "search") + fetchJSON(url.replace(/\/?(?:posts)?\/?(?:\?|$)/, "/posts.json?"), "search"); + else if (mode === "notes") + fetchJSON(url.replace(/\/notes\/?(?:\?|$)/, "/notes.json?"), "notes"); + else if (mode === "favorites") + fetchJSON(url.replace(/\/favorites\/?(?:\?|$)/, "/favorites.json?"), "favorites"); - if (limitUrl !== undefined) - numExpected = limitUrl; - else if (limitSearch !== undefined) - numExpected = limitSearch; - else - numExpected = thumbnail_count_default; + bbbStatus("posts", "new"); + } + } + else if (mode === "popular" || mode === "popular_view") { + if (potentialHiddenPosts(mode)) { + bbb.xml.thumbs = true; - if (allowUserLimit()) { - numDesired = thumbnail_count; - limit = "&limit=" + thumbnail_count; + fetchJSON(url.replace(/\/(popular_view|popular)\/?/, "/$1.json"), mode); + bbbStatus("posts", "new"); } - else - numDesired = numExpected; + } + else if (mode === "pool" || mode === "favorite_group") { + if (potentialHiddenPosts(mode)) { + idCache = getIdCache(); + bbb.xml.thumbs = true; - if (numThumbs !== numDesired || numThumbs < numExpected) { - if (mode === "search") - fetchJSON(gUrl.replace(/\/?(?:posts)?\/?(?:\?|$)/, "/posts.json?") + limit, "search"); - else - fetchJSON(gUrl.replace(/\/notes\/?(?:\?|$)/, "/notes.json?") + limit, "notes"); + if (idCache) + searchJSON(mode + "_search", {post_ids: idCache}); + else // Get a new cache. + fetchJSON(url.replace(/\/(pools|favorite_groups)\/(\d+)/, "/$1/$2.json"), mode + "_cache", mode + "_search"); + + bbbStatus("posts", "new"); } } - else if (mode === "post") - delayMe(parsePost); // Delay is needed to force the script to pause and allow Danbooru to do whatever. It essentially mimics the async nature of the API call. - else if (mode === "popular") { - if (numThumbs !== thumbnail_count_default) - fetchJSON(gUrl.replace(/\/popular\/?/, "/popular.json"), "popular"); + else if (mode === "pool_search" || mode === "favorite_group_search") { + page = Number(getVar("page")) || 1; + idList = optArg.post_ids.split(" "); + idSearch = idList.slice((page - 1) * thumbnail_count_default, page * thumbnail_count_default); + + fetchJSON("/posts.json?tags=status:any+id:" + idSearch.join(","), mode, idSearch); } - else if (mode === "pool") { - if (numThumbs !== thumbnail_count_default) - fetchJSON(gUrl.replace(/\/pools\/(\d+)/, "/pools/$1.json"), "pool"); + else if (mode === "endless") { + bbb.xml.endless = true; + + if (gLoc === "pool" || gLoc === "favorite_group") { + idCache = getIdCache(); + + if (idCache) + searchJSON("endless_" + gLoc + "_search", {post_ids: idCache}); + else // Get a new cache. + fetchJSON(url.replace(/\/(pools|favorite_groups)\/(\d+)/, "/$1/$2.json"), gLoc + "_cache", "endless_" + gLoc + "_search"); + } + else { + url = endlessNexURL(); + + fetchJSON(url.replace(/(\?)|$/, ".json$1"), "endless"); + } + + bbbStatus("posts", "new"); } - else if (mode === "poolsearch") { - var poolIds = optArg.post_ids.split(" "); - var page = getVar("page") || 1; - var postIds = poolIds.slice((page - 1) * thumbnail_count_default, page * thumbnail_count_default); + else if (mode === "endless_pool_search" || mode === "endless_favorite_group_search") { + idList = optArg.post_ids.split(" "); + page = Number(getVar("page", endlessNexURL())); // If a pool gets over 1000 pages, I have no idea what happens for regular users. Biggest pool is currently around 400 pages so we won't worry about that for the time being. + idSearch = idList.slice((page - 1) * thumbnail_count_default, page * thumbnail_count_default); - fetchJSON("/posts.json?tags=status:any+id:" + postIds.join(","), "poolsearch", postIds); + fetchJSON("/posts.json?tags=status:any+id:" + idSearch.join(","), "endless", idSearch); } else if (mode === "comments") { - if (numThumbs !== 5) - fetchJSON(gUrl.replace(/\/comments\/?/, "/comments.json"), "comments"); + if (potentialHiddenPosts(mode)) { + fetchJSON(url.replace(/\/comments\/?/, "/comments.json"), "comments"); + bbbStatus("posts", "new"); + } } else if (mode === "parent" || mode === "child") { var parentUrl = "/posts.json?limit=200&tags=status:any+parent:" + optArg; fetchJSON(parentUrl, mode, optArg); + bbbStatus("posts", "new"); + } + else if (mode === "ugoira") { + fetchJSON(url.replace(/\/posts\/(\d+)/, "/posts/$1.json"), "ugoira"); + bbbStatus("posts", "new"); } } - function fetchJSON(url, mode, optArg) { + function fetchJSON(url, mode, optArg, session, retries) { // Retrieve JSON. var xmlhttp = new XMLHttpRequest(); + var xmlRetries = retries || 0; + var xmlSession = session || window.bbbSession; if (xmlhttp !== null) { xmlhttp.onreadystatechange = function() { - if (xmlhttp.readyState === 4) { // 4 = "loaded" + if (xmlSession !== window.bbbSession) // If we end up receiving an xml response from a different page, reject it. + xmlhttp.abort(); + else if (xmlhttp.readyState === 4) { // 4 = "loaded" if (xmlhttp.status === 200) { // 200 = "OK" var xml = JSON.parse(xmlhttp.responseText); - if (mode === "search" || mode === "popular" || mode === "notes") - parseListing(xml); + // Update status message. + if (mode === "search" || mode === "popular" || mode === "popular_view" || mode === "notes" || mode === "favorites" || mode === "pool_search" || mode === "favorite_group_search") { + bbb.xml.thumbs = false; + + parseListing(xml, optArg); + } else if (mode === "post") parsePost(xml); - else if (mode === "pool") - searchJSON("poolsearch", xml); - else if (mode === "poolsearch") - parseListing(xml, optArg); + else if (mode === "pool_cache" || mode === "favorite_group_cache") { + var collId = /\/(?:pools|favorite_groups)\/(\d+)/.exec(location.href)[1]; + + sessionStorage["bbb_" + mode + "_" + collId] = new Date().getTime() + " " + xml.post_ids; + searchJSON(optArg, xml); + } + else if (mode === "endless") { + bbb.xml.endless = false; + + endlessXMLJSONHandler(xml, optArg); + } else if (mode === "comments") parseComments(xml); else if (mode === "parent" || mode === "child") parseRelations(xml, mode, optArg); + else if (mode === "ugoira") + fixHiddenUgoira(xml); + + if (mode !== "pool_cache" && mode !== "favorite_group_cache") + bbbStatus("posts", "done"); } else { if (xmlhttp.status === 403 || xmlhttp.status === 401) { - danbNotice('Better Better Booru: Error retrieving information. Access denied. You must be logged in to a Danbooru account to access the API for hidden image information and direct downloads.
(Do not warn me again and automatically bypass API features in the future.)', "error"); + bbbNotice('Error retrieving post information. Access denied. You must be logged in to a Danbooru account to access the API for hidden image information and direct downloads.
(Do not warn me again and automatically bypass API features in the future.)', -1); document.getElementById("bbb-bypass-api-link").addEventListener("click", function(event) { updateSettings("bypass_api", true); this.parentNode.innerHTML = "Settings updated. You may change this setting under the preferences tab in the settings panel."; event.preventDefault(); }, false); + bbbStatus("posts", "error"); } - else if (xmlhttp.status === 421) - danbNotice("Better Better Booru: Error retrieving information. Your Danbooru API access is currently throttled. Please try again later.", "error"); - else if (xmlhttp.status !== 0) - danbNotice("Better Better Booru: Error retrieving information. (Code: " + xmlhttp.status + " " + xmlhttp.statusText + ")", "error"); - - // Update status message. - bbbStatus("error"); - } - } - }; - xmlhttp.open("GET", url, true); - xmlhttp.send(null); - - // Loading status message. - if (mode === "search" || mode === "popular" || mode === "notes" || mode === "post" || mode === "pool" || mode === "parent" || mode === "child") - bbbStatus("image"); - else if (mode === "comments") - bbbStatus("comment"); - } - } - - function modifyPage(mode) { - // Let other functions that don't require the API run. (Alternative to searchJSON) - if (mode === "post") - delayMe(parsePost); // Delay is needed to force the script to pause and allow Danbooru to do whatever. It essentially mimics the async nature of the API call. - else if (mode === "search" || mode === "notes") { - if (allowUserLimit()) { - var url = gUrl; - - if (url.indexOf("?") > -1) - url += "&limit=" + thumbnail_count; - else - url += "?limit=" + thumbnail_count; - - fetchPages(url, "thumbnails"); - } - } - } - - function scrapePost(pageEl) { - // Retrieve info from the current document or a supplied element containing the html with it. - var target = pageEl || document; - var imgContainer = getId("image-container", target, "section"); - - if (!imgContainer) - return {}; - - var img = getId("image", target, "img"); - var object = imgContainer.getElementsByTagName("object")[0]; - var webmVid = imgContainer.getElementsByTagName("video")[0]; - var dataInfo = [imgContainer.getAttribute("data-file-url"), imgContainer.getAttribute("data-md5"), imgContainer.getAttribute("data-file-ext")]; - var directLink = getId("image-resize-link", target, "a") || document.evaluate('.//section[@id="post-information"]/ul/li/a[starts-with(@href, "/data/")]', target, null, 9, null).singleNodeValue; - var twitterInfo = fetchMeta("twitter:image:src", target); - var previewInfo = fetchMeta("og:image", target); - var imgHeight = Number(imgContainer.getAttribute("data-height")); - var imgWidth = Number(imgContainer.getAttribute("data-width")); - var md5 = ""; - var ext = ""; - var infoValues; - var imgInfo = { - md5: "", - file_ext: "", - file_url: "", - large_file_url: "", - preview_file_url: "", - has_large: undefined, - id: Number(imgContainer.getAttribute("data-id")), - fav_count: Number(imgContainer.getAttribute("data-fav-count")), - has_children: (imgContainer.getAttribute("data-has-children") === "true" ? true : false), - has_active_children: (img ? (img.getAttribute("data-has-active-children") === "true" ? true : false) : !!target.getElementsByClassName("notice-parent").length), - parent_id: (imgContainer.getAttribute("data-parent-id") ? Number(imgContainer.getAttribute("data-parent-id")) : null), - rating: imgContainer.getAttribute("data-rating"), - score: Number(imgContainer.getAttribute("data-score")), - tag_string: imgContainer.getAttribute("data-tags"), - pool_string: imgContainer.getAttribute("data-pools"), - uploader_name: imgContainer.getAttribute("data-uploader"), - is_deleted: (fetchMeta("post-is-deleted", target) === "false" ? false : true), - is_flagged: (fetchMeta("post-is-flagged", target) === "false" ? false : true), - is_pending: (getId("pending-approval-notice", target, "div") ? true : false), - is_banned: (imgContainer.getAttribute("data-flags").indexOf("banned") < 0 ? false : true), - image_height: imgHeight || null, - image_width: imgWidth || null, - is_hidden: (img || object || webmVid ? false : true) - }; - - // Try to extract the file's name and extension. - if (dataInfo[1]) - infoValues = dataInfo; - else if (directLink) - infoValues = /data\/(\w+)\.(\w+)/.exec(directLink.href); - else if (twitterInfo) - infoValues = (twitterInfo.indexOf("sample") > -1 ? /data\/sample\/sample-(\w+)\.\w/.exec(twitterInfo) : /data\/(\w+)\.(\w+)/.exec(twitterInfo)); - else if (previewInfo) - infoValues = /data\/preview\/(\w+?)\.\w/.exec(previewInfo); - - if (infoValues) { - md5 = infoValues[1]; - ext = infoValues[2]; - - // Test for the original image file extension if it is unknown. - if (!ext && imgWidth) { - var testExt = ["jpg", "png", "gif", "jpeg", "webm"]; - - for (var i = 0, il = testExt.length; i < il; i++) { - if (isThere("/data/" + md5 + "." + testExt[i])) { - ext = testExt[i]; - break; - } - } - } - - imgInfo.has_large = (imgWidth > 850 && ext !== "swf" && ext !== "webm" ? true : false); - imgInfo.md5 = md5; - imgInfo.file_ext = ext; - imgInfo.file_url = "/data/" + md5 + "." + ext; - imgInfo.large_file_url = (imgInfo.has_large ? "/data/sample/sample-" + md5 + ".jpg" : "/data/" + md5 + "." + ext); - imgInfo.preview_file_url = (!imgHeight || ext === "swf" ? "/images/download-preview.png" : "/data/preview/" + md5 + ".jpg"); - } - else if (previewInfo === "/images/download-preview.png") - imgInfo.preview_file_url = "/images/download-preview.png"; - - return imgInfo; - } - - function fetchPages(url, mode, optArg) { - // Retrieve an actual page for certain pieces of information. - var xmlhttp = new XMLHttpRequest(); - - if (xmlhttp !== null) { - xmlhttp.onreadystatechange = function() { - if (xmlhttp.readyState === 4) { // 4 = "loaded" - if (xmlhttp.status === 200) { // 200 = "OK" - var docEl = document.createElement("html"); - var newContent; - var target; - var post; - var previewImg; - - docEl.innerHTML = xmlhttp.responseText; - - if (mode === "paginator") { // Fetch updated paginator for the first page of searches. - target = document.getElementsByClassName("paginator")[0]; - newContent = docEl.getElementsByClassName("paginator")[0]; - - if (newContent) - target.parentNode.replaceChild(newContent, target); + else if (xmlhttp.status === 421) { + bbbNotice("Error retrieving post information. Your Danbooru API access is currently throttled. Please try again later.", -1); + bbbStatus("posts", "error"); } - else if (mode === "comments") { // Fetch post to get comments. - var commentDiv = optArg.post; - var postId = optArg.post_id; - var commentSection = docEl.getElementsByClassName("comments-for-post")[0]; - var comments = commentSection.getElementsByClassName("comment"); - var numComments = comments.length; - var toShow = 6; // Number of comments to display. - post = scrapePost(docEl); - previewImg = commentDiv.getElementsByTagName("img")[0]; - target = commentDiv.getElementsByClassName("comments-for-post")[0]; - newContent = document.createDocumentFragment(); - - // Fix the image. - if (post.preview_file_url) { - if (post.file_url) { - commentDiv.setAttribute("data-md5", post.md5); - commentDiv.setAttribute("data-file-ext", post.file_ext); - commentDiv.setAttribute("data-file-url", post.file_url); - commentDiv.setAttribute("data-large-file-url", post.large_file_url); - } - - previewImg.src = post.preview_file_url; - previewImg.alt = /([^\/]+)\.\w+$/.exec(post.preview_file_url)[1]; - commentDiv.setAttribute("data-preview-file-url", post.preview_file_url); - } - - // Fix the comments. - if (numComments > toShow) { - for (var i = 0, toHide = numComments - toShow; i < toHide; i++) - comments[i].style.display = "none"; - - commentSection.getElementsByClassName("row notices")[0].innerHTML = ' Show all comments '; + else if (xmlhttp.status !== 0) { + if (xmlRetries < 1) { + xmlRetries++; + fetchJSON(url, mode, optArg, xmlSession, xmlRetries); } + else { + var linkId = uniqueIdNum(); // Create a unique ID. + var noticeMsg = bbbNotice('Error retrieving post information (JSON Code: ' + xmlhttp.status + ' ' + xmlhttp.statusText + '). (Retry)', -1); - // Add it all in and get it ready. - while (commentSection.children[0]) - newContent.appendChild(commentSection.children[0]); - - target.appendChild(newContent); - - Danbooru.Comment.initialize_all(); - - // Update status message. - bbbStatus("loaded"); - } - else if (mode === "thumbnails") { // Fetch the thumbnails and paginator from the page of a search and replace the existing ones. - var divId = (gLoc === "search" ? "posts" : "a-index"); - var paginator = docEl.getElementsByClassName("paginator")[0]; - target = document.getElementById(divId); - newContent = (paginator ? paginator.parentNode : null); - - if (newContent) - target.parentNode.replaceChild(newContent, target); - - // Thumbnail classes and titles - formatThumbnails(); - - // Blacklist - blacklistUpdate(); - - // Clean links - if (clean_links) - cleanLinks(); + bbbStatus("posts", "error"); - // Direct downloads. - if (direct_downloads) - postDDL(); - - // Update status message. - bbbStatus("loaded"); - } - else if (mode === "hidden") { // Fetch the hidden image information from a post for thumbnails. - var hiddenImgs = bbb.cache.hidden_imgs; - var hiddenId = hiddenImgs.shift(); - var bcc = bbb.cache.current; - var article = document.getElementById("post_" + hiddenId); - post = scrapePost(docEl); - previewImg = document.getElementById("bbb-img-" + hiddenId); - - // Update the thumbnail with the correct information. - if (post.preview_file_url) { - if (post.file_url) { - article.setAttribute("data-md5", post.md5); - article.setAttribute("data-file-ext", post.file_ext); - article.setAttribute("data-file-url", post.file_url); - article.setAttribute("data-large-file-url", post.large_file_url); - - if (direct_downloads && (gLoc === "search" || gLoc === "pool" || gLoc === "popular")) - document.getElementById("bbb-ddl-" + hiddenId).href = post.file_url; - } - - previewImg.src = post.preview_file_url; - article.setAttribute("data-preview-file-url", post.preview_file_url); - - bcc.history.push(hiddenId); - bcc.names[hiddenId] = /[^\/]+$/.exec(post.file_url || post.preview_file_url)[0]; - - // Continue to the next image or finish by updating the cache. - if (hiddenImgs.length) - fetchPages("/posts/" + hiddenImgs[0], "hidden"); - else { - updateThumbCache(); - bbbStatus("loaded"); - } - } - else { // The image information couldn't be found. - updateThumbCache(); - danbNotice("Better Better Booru: Error retrieving thumbnail information.", "error"); - bbbStatus("error"); + document.getElementById(linkId).addEventListener("click", function(event) { + closeBbbNoticeMsg(noticeMsg); + searchJSON(mode, optArg); + event.preventDefault(); + }, false); } } } - else if (xmlhttp.status !== 0) { - danbNotice("Better Better Booru: Error retrieving information. (Code: " + xmlhttp.status + " " + xmlhttp.statusText + ")", "error"); - - // Update status message. - bbbStatus("error"); - } } }; xmlhttp.open("GET", url, true); xmlhttp.send(null); - - // Loading status message. - if (mode === "thumbnails") - bbbStatus("image"); - else if (mode === "comments") - bbbStatus("comment"); } } - /* Functions for creating content from retrieved info */ function parseListing(xml, optArg) { // Use JSON results for thumbnail listings. var posts = xml; - var query = ""; - var target; - var replacement; - var before; - var child; - var childIndex = 0; - var newThumbs; - var orderedIds; - var paginator = document.getElementsByClassName("paginator")[0]; + var thumbContainer = getThumbContainer(gLoc); + var orderedIds = (gLoc === "pool" || gLoc === "favorite_group" ? optArg : undefined); + var before = getThumbSibling(gLoc); - // If no posts, do nothing. - if (!posts.length) { - bbbStatus("loaded"); + if (!posts[0]) return; - } - // Determine where the thumbnails are. - if (gLoc === "search") { - target = document.getElementById("posts"); - target = (target ? target.getElementsByTagName("div")[0] : undefined); - query = (gUrlQuery.indexOf("tags=") > -1 && !clean_links ? "?tags=" + getVar("tags") : ""); - } - else if (gLoc === "popular") - target = document.getElementById("a-index"); - else if (gLoc === "pool") { - target = document.getElementById("a-show"); - target = (target ? target.getElementsByTagName("section")[0] : undefined); - query = (!clean_links ? "?pool_id=" + /\/pools\/(\d+)/.exec(gUrlPath)[1] : ""); - before = paginator; - orderedIds = optArg; - } - else if (gLoc === "notes") { - target = document.getElementById("a-index"); - before = paginator; - } - - if (!target) { - danbNotice("Better Better Booru: Thumbnail section could not be located.", "error"); + if (!thumbContainer) { + bbbNotice("Thumbnail section could not be located.", -1); return; } // Thumb preparation. - newThumbs = createThumbListing(posts, query, orderedIds); + var newThumbs = createThumbListing(posts, orderedIds); // New thumbnail container preparation. - replacement = target.cloneNode(false); + var replacement = thumbContainer.cloneNode(false); + var childIndex = 0; - while (target.children[childIndex]) { - child = target.children[childIndex]; + while (thumbContainer.children[childIndex]) { + var child = thumbContainer.children[childIndex]; if (child.tagName !== "ARTICLE") replacement.appendChild(child); @@ -683,157 +694,95 @@ function bbbScript() { // This is needed to make this script work in Chrome. else replacement.insertBefore(newThumbs, before); - // Fix the paginator; - if (paginator && (gLoc === "search" || gLoc === "notes")) { - var noPages = paginator.textContent.indexOf("Go back") > -1; - var pageUrl = gUrl; + // Prepare thumbnails. + prepThumbnails(replacement); - if (allowUserLimit()) { - // Fix existing paginator with user's custom limit. - var pageLinks = paginator.getElementsByTagName("a"); + // Replace results with new results. + thumbContainer.parentNode.replaceChild(replacement, thumbContainer); - for (var i = 0, il = pageLinks.length; i < il; i++) - pageLinks[i].href = pageLinks[i].href + "&limit=" + thumbnail_count; - - // Attempt to fix the paginator by retrieving it from an actual page. Might not work if connections are going slowly. - if (pageUrl.indexOf("?") > -1) - pageUrl += "&limit=" + thumbnail_count; - else - pageUrl += "?limit=" + thumbnail_count; - - fetchPages(pageUrl, "paginator"); - } - else if (noPages) // Fix the paginator if the post xml and existing page are out of sync. - fetchPages(pageUrl, "paginator"); - } - - // Thumbnail classes and titles. - formatThumbnails(replacement); - - // Blacklist. - blacklistUpdate(replacement); - - // Direct downloads. - if (direct_downloads) - postDDL(replacement); - - // Replace results with new results. - target.parentNode.replaceChild(replacement, target); + // Fix the paginator. The paginator isn't always in the replacement, so run this on the whole page after the replacement is inserted. + fixPaginator(); // Fix hidden thumbnails. - fixHiddenImgs(); + fixHiddenThumbs(); - // Update status message. - bbbStatus("loaded"); + // Update the URL with the limit value. + if (allowUserLimit()) + history.replaceState({}, "", updateURLQuery(location.search, {limit: thumbnail_count})); } function parsePost(postInfo) { + // Take a post's info and alter its page. var post = bbb.post.info = formatInfo(postInfo || scrapePost()); var imgContainer = document.getElementById("image-container"); if (!imgContainer) { - danbNotice("Better Better Booru: Post content could not be located.", "error"); + bbbNotice("Post content could not be located.", -1); return; } if (!post || !post.file_url) { - danbNotice("Better Better Booru: Due to a lack of provided information, this post cannot be viewed.", "error"); + bbbNotice("Due to a lack of provided information, this post cannot be viewed.", -1); return; } - var ratio = (post.image_width > 850 ? 850 / post.image_width : 1); - var sampHeight = Math.round(post.image_height * ratio); - var sampWidth = Math.round(post.image_width * ratio); - var useSample = checkSetting("default-image-size", "large", load_sample_first); - var newWidth = 0; - var newHeight = 0; - var newUrl = ""; - var altTxt = ""; + // Stop if we're on Safebooru and the image isn't safe. + if (safebPostTest(post)) + return; // Enable the "Resize to window", "Toggle Notes", "Random Post", and "Find similar" options for logged out users. - if (!isLoggedIn()) { - var infoSection = document.getElementById("post-information"); - var options = document.createElement("section"); - options.id = "post-options"; - options.innerHTML = '

Options

'; - infoSection.parentNode.insertBefore(options, infoSection.nextElementSibling); - } + createOptionsSection(); - // Replace the "resize to window" link with new resize and swap links. - var resizeListItem = document.getElementById("image-resize-to-window-link").parentNode; - var optionsFrag = document.createDocumentFragment(); - - if (post.has_large) { - var swapList = document.createElement("li"); - optionsFrag.appendChild(swapList); - - var swapLink = bbb.el.swapLink = document.createElement("a"); - - if (useSample) { - swapLink.href = post.file_url; - swapLink.innerHTML = "View original"; - } - else { - swapLink.href = post.large_file_url; - swapLink.innerHTML = "View sample"; - } + // Fix the direct post links in the information and options sections for hidden posts. + fixPostDownloadLinks(); - swapLink.addEventListener("click", function(event) { - swapImage(); - event.preventDefault(); - }, false); - swapList.appendChild(swapLink); - } - - var resizeListAll = document.createElement("li"); - optionsFrag.appendChild(resizeListAll); + // Replace the "resize to window" link with new resize links. + modifyResizeLink(); - var resizeLinkAll = bbb.el.resizeLinkAll = document.createElement("a"); - resizeLinkAll.href = "#"; - resizeLinkAll.innerHTML = "Resize to window"; - resizeLinkAll.addEventListener("click", function(event) { - resizeImage("all"); - event.preventDefault(); - }, false); - resizeListAll.appendChild(resizeLinkAll); + // Create content. + if (post.file_ext === "swf") // Create flash object. + imgContainer.innerHTML = '

Save this flash (right click and save)

'; + else if (post.file_ext === "webm") // Create webm video + imgContainer.innerHTML = '

Save this video (right click and save)

'; + else if (post.file_ext === "zip" && /(?:^|\s)ugoira(?:$|\s)/.test(post.tag_string)) { // Create ugoira + var useUgoiraOrig = getVar("original"); - var resizeListWidth = document.createElement("li"); - optionsFrag.appendChild(resizeListWidth); + // Get rid of all the old events handlers. + if (Danbooru.Ugoira && Danbooru.Ugoira.player) + $(Danbooru.Ugoira.player).unbind(); - var resizeLinkWidth = bbb.el.resizeLinkWidth = document.createElement("a"); - resizeLinkWidth.href = "#"; - resizeLinkWidth.innerHTML = "Resize to window width"; - resizeLinkWidth.addEventListener("click", function(event) { - resizeImage("width"); - event.preventDefault(); - }, false); - resizeListWidth.appendChild(resizeLinkWidth); + if ((load_sample_first && useUgoiraOrig !== "1") || useUgoiraOrig === "0") { // Load sample webm version. + imgContainer.innerHTML = '

Save this video (right click and save) | View original | Toggle notes

'; - var resizeListHeight = document.createElement("li"); - optionsFrag.appendChild(resizeListHeight); + // Prep the "toggle notes" link. + noteToggleLinkInit(); + } + else { // Load original ugoira version. + imgContainer.innerHTML = '

Loaded 0%

'; - var resizeLinkHeight = bbb.el.resizeLinkHeight = document.createElement("a"); - resizeLinkHeight.href = "#"; - resizeLinkHeight.innerHTML = "Resize to window height"; - resizeLinkHeight.addEventListener("click", function(event) { - resizeImage("height"); - event.preventDefault(); - }, false); - resizeListHeight.appendChild(resizeLinkHeight); + // Make notes toggle when clicking the ugoira animation. + noteToggleInit(); - resizeListItem.parentNode.replaceChild(optionsFrag, resizeListItem); + // Prep the "toggle notes" link. The "toggle notes" link is added here just for consistency's sake. + noteToggleLinkInit(); - // Create content. - if (post.file_ext === "swf") // Create flash object. - imgContainer.innerHTML = '

Save this flash (right click and save)

'; - else if (post.file_ext === "webm") // Create webm video - imgContainer.innerHTML = '

Save this video (right click and save)

'; + if (post.pixiv_ugoira_frame_data.data) // Set up the post. + ugoiraInit(); + else // Fix hidden posts. + searchJSON("ugoira"); + } + } else if (!post.image_height) // Create manual download. imgContainer.innerHTML = '

Save this file (right click and save)

'; else { // Create image - if (useSample && post.has_large) { - newWidth = sampWidth; - newHeight = sampHeight; + var newWidth = 0; + var newHeight = 0; + var newUrl = ""; + var altTxt = ""; + + if (load_sample_first && post.has_large) { + newWidth = post.sample_width; + newHeight = post.sample_height; newUrl = post.large_file_url; altTxt = "Sample"; } @@ -844,212 +793,37 @@ function bbbScript() { // This is needed to make this script work in Chrome. altTxt = post.md5; } - imgContainer.innerHTML = '
' + altTxt + ' '; - - var img = bbb.el.img = document.getElementById("image"); - var bbbLoader = bbb.el.bbbLoader = document.getElementById("bbb-loader"); - - // Enable image swapping between the original and sample image. - if (post.has_large) { - // Remove the original notice (it's not always there) and replace it with our own. - var resizeNotice = document.getElementById("image-resize-notice"); - - if (resizeNotice) - resizeNotice.parentNode.removeChild(resizeNotice); - - var bbbResizeNotice = bbb.el.resizeNotice = document.createElement("div"); - bbbResizeNotice.id = "image-resize-notice"; - bbbResizeNotice.className = "ui-corner-all ui-state-highlight notice notice-resized"; - bbbResizeNotice.style.position = "relative"; - bbbResizeNotice.style.display = "none"; - bbbResizeNotice.innerHTML = ' ()'; - imgContainer.parentNode.insertBefore(bbbResizeNotice, imgContainer); - - var swapInit = true; - var resizeStatus = bbb.el.resizeStatus = document.getElementById("bbb-resize-status"); - var resizeLink = bbb.el.resizeLink = document.getElementById("bbb-resize-link"); - var closeResizeNotice = bbb.el.closeResizeNotice = document.getElementById("close-resize-notice"); - - if (useSample) { - resizeStatus.innerHTML = "Resized to " + Math.floor(ratio * 100) + "% of original"; - resizeLink.innerHTML = "view original"; - resizeLink.href = post.file_url; - - if (show_resized_notice === "sample" || show_resized_notice === "all") - bbbResizeNotice.style.display = "block"; - } - else { - resizeStatus.innerHTML = "Viewing original"; - resizeLink.innerHTML = "view sample"; - resizeLink.href = post.large_file_url; - - if (show_resized_notice === "original" || show_resized_notice === "all") - bbbResizeNotice.style.display = "block"; - } - - resizeLink.addEventListener("click", function(event) { - if (event.button === 0) { - swapImage(); - event.preventDefault(); - } - }, false); - bbbLoader.addEventListener("load", function() { - if (swapInit) - swapInit = false; - - if (bbbLoader.src !== "about:blank") { - img.src = bbbLoader.src; - bbbLoader.src = "about:blank"; - } - }, false); - bbbLoader.addEventListener("error", function(event) { - if (bbbLoader.src !== "about:blank") { - resizeStatus.innerHTML = (bbbLoader.src.indexOf("/sample/") < 0 ? "Original" : "Sample") + " image loading failed!"; - resizeLink.innerHTML = "retry"; - bbbLoader.src = "about:blank"; - } - - event.preventDefault(); - }, false); - img.addEventListener("load", function() { - if (bbbLoader.src === "about:blank") { - var showResNot = bbb.user.show_resized_notice; - - if (img.src.indexOf("/sample/") < 0) { // Original image loaded. - resizeStatus.innerHTML = "Viewing original"; - resizeLink.innerHTML = "view sample"; - resizeLink.href = post.large_file_url; - swapLink.innerHTML = "View sample"; - swapLink.href = post.large_file_url; - img.alt = post.md5; - img.setAttribute("height", post.image_height); - img.setAttribute("width", post.image_width); - bbbResizeNotice.style.display = (showResNot === "original" || showResNot === "all" ? "block" : "none"); - } - else { // Sample image loaded. - resizeStatus.innerHTML = "Resized to " + Math.floor(ratio * 100) + "% of original"; - resizeLink.innerHTML = "view original"; - resizeLink.href = post.file_url; - swapLink.innerHTML = "View original"; - swapLink.href = post.file_url; - img.alt = "Sample"; - img.setAttribute("height", sampHeight); - img.setAttribute("width", sampWidth); - bbbResizeNotice.style.display = (showResNot === "sample" || showResNot === "all" ? "block" : "none"); - } - } - - if (!swapInit) - resizeImage("swap"); - }, false); - closeResizeNotice.addEventListener("click", function() { - var showResNot = bbb.user.show_resized_notice; - - bbbResizeNotice.style.display = "none"; - - if (img.src.indexOf("/sample/") < 0) { // Original image. - if (showResNot === "original") - showResNot = "none"; - else if (showResNot === "all") - showResNot = "sample"; - - danbNotice("Better Better Booru: Settings updated. The resized notice will now be hidden when viewing original images. You may change this setting under \"Notices\" in the settings panel."); - } - else { // Sample image. - if (showResNot === "sample") - showResNot = "none"; - else if (showResNot === "all") - showResNot = "original"; - - danbNotice("Better Better Booru: Settings updated. The resized notice will now be hidden when viewing sample images. You may change this setting under \"Notices\" in the settings panel."); - } - - updateSettings("show_resized_notice", showResNot); - }, false); - } - - if (!alternate_image_swap) { // Make notes toggle when clicking the image. - document.addEventListener("click", function(event) { - if (event.target.id === "image" && event.button === 0 && !bbb.post.translationMode) { - if (!bbb.dragscroll.moved) - Danbooru.Note.Box.toggle_all(); - - event.stopPropagation(); - } - }, true); - } - else { // Make sample/original images swap when clicking the image. - // Make a "Toggle Notes" link in the options bar. - if (!document.getElementById("listnotetoggle")) { // For logged in users. - var translateOption = document.getElementById("add-notes-list"); - var listNoteToggle = document.createElement("li"); - - if (translateOption) { - listNoteToggle.innerHTML = 'Toggle notes'; - translateOption.parentNode.insertBefore(listNoteToggle, translateOption); - } - } + imgContainer.innerHTML = '
' + altTxt + ' '; - document.getElementById("listnotetoggle").addEventListener("click", function(event) { - Danbooru.Note.Box.toggle_all(); - event.preventDefault(); - }, false); + bbb.el.bbbLoader = document.getElementById("bbb-loader"); - // Make clicking the image swap between the original and sample image when available. - if (post.has_large) { - document.addEventListener("click", function(event) { - if (event.target.id === "image" && event.button === 0 && !bbb.post.translationMode) { - if (!bbb.dragscroll.moved) - swapImage(); + // Create/replace the elements related to image swapping and set them up. + swapImageInit(); - event.stopPropagation(); - } - }, true); - } - } + if (alternate_image_swap) // Make sample/original images swap when clicking the image. + alternateImageSwap(); + else // Make notes toggle when clicking the image. + noteToggleInit(); } // Enable drag scrolling. - if (image_drag_scroll) - dragScrollInit(); - - // Make translation mode work. - if (!document.getElementById("note-locked-notice")) { - var translateLink = document.getElementById("translate"); - - // Make the normal toggling work for hidden posts. - if (post.is_hidden) { - if (translateLink) - translateLink.addEventListener("click", Danbooru.Note.TranslationMode.toggle, false); - - document.addEventListener("keydown", function(event) { - if (event.keyCode === 78 && document.activeElement.type !== "text" && document.activeElement.type !== "textarea") - Danbooru.Note.TranslationMode.toggle(event); - }, false); - } + dragScrollInit(); - // Script translation mode events and tracking used to resolve timing issues. - bbb.post.translationMode = Danbooru.Note.TranslationMode.active; - - if (translateLink) - translateLink.addEventListener("click", translationModeToggle, false); + // Resize the content if desired. + if (post_resize) + resizePost(post_resize_mode); - document.addEventListener("keydown", function(event) { - if (event.keyCode === 78 && document.activeElement.type !== "text" && document.activeElement.type !== "textarea") - translationModeToggle(); - }, false); - } + // Enable translation mode. + translationModeInit(); - // Resize the content if desired. - if (checkSetting("always-resize-images", "true", image_resize)) - resizeImage(image_resize_mode); + // Disable embedded notes. + disableEmbeddedNotes(); // Load/reload notes. Danbooru.Note.load_all(); // Auto position the content if desired. - if (autoscroll_image) - autoscrollImage(); + autoscrollPost(); // Blacklist. blacklistUpdate(); @@ -1059,6 +833,7 @@ function bbbScript() { // This is needed to make this script work in Chrome. } function parseComments(xml) { + // Fix missing comments by inserting them into their appropriate position. var posts = xml; var numPosts = posts.length; var expectedPosts = numPosts; @@ -1069,10 +844,10 @@ function bbbScript() { // This is needed to make this script work in Chrome. var post = formatInfo(posts[i]); var existingPost = existingPosts[eci]; - if (!existingPost || post.id !== Number(existingPost.getAttribute("data-id"))) { - if (!/\b(?:loli|shota|toddlercon)\b/.test(post.tag_string) && !post.is_banned) // API post isn't hidden and doesn't exist on the page so the API has different information. Skip it and try to find where the page's info matches up. + if (!existingPost || String(post.id) !== existingPost.getAttribute("data-id")) { + if (!/(?:^|\s)(?:loli|shota|toddlercon)(?:$|\s)/.test(post.tag_string) && !post.is_banned) // API post isn't hidden and doesn't exist on the page. Skip it and try to find where the page's info matches up. continue; - else if ((!show_loli && /\bloli\b/.test(post.tag_string)) || (!show_shota && /\bshota\b/.test(post.tag_string)) || (!show_toddlercon && /\btoddlercon\b/.test(post.tag_string)) || (!show_banned && post.is_banned)) { // Skip hidden posts if the user has selected to do so. + else if ((!show_loli && /(?:^|\s)loli(?:$|\s)/.test(post.tag_string)) || (!show_shota && /(?:^|\s)shota(?:$|\s)/.test(post.tag_string)) || (!show_toddlercon && /(?:^|\s)toddlercon(?:$|\s)/.test(post.tag_string)) || (!show_banned && post.is_banned) || safebPostTest(post)) { // Skip hidden posts if the user has selected to do so. expectedPosts--; continue; } @@ -1112,31 +887,24 @@ function bbbScript() { // This is needed to make this script work in Chrome. childSpan.innerHTML = '
' + post.md5 + '
Date User ' + post.uploader_name + ' Rating ' + post.rating + ' Score ' + post.score + '
Tags' + tagLinks + '
'; + // Prepare thumbnails. + prepThumbnails(childSpan); + if (!existingPost) // There isn't a next post so append the new post to the end before the paginator. - document.getElementById("a-index").insertBefore(childSpan.firstChild, document.getElementsByClassName("paginator")[0]); + document.getElementById("a-index").insertBefore(childSpan.firstElementChild, getPaginator()); else // Insert new post before the post that should follow it. - existingPost.parentNode.insertBefore(childSpan.firstChild, existingPost); + existingPost.parentNode.insertBefore(childSpan.firstElementChild, existingPost); // Get the comments and image info. - fetchPages("/posts/" + post.id, "comments", {post: existingPosts[eci], post_id: post.id}); + searchPages("post_comments", post.id); } eci++; } - // If we don't have the expected number of posts, the API info and page are too out of sync. - if (existingPosts.length !== expectedPosts) { - danbNotice("Better Better Booru: Loading of hidden loli/shota post(s) failed. Please refresh.", "error"); - bbbStatus("error"); - } - else - bbbStatus("loaded"); - - // Thumbnail classes and titles. - formatThumbnails(); - - // Blacklist. - blacklistUpdate(); + // If we don't have the expected number of posts, the API info and page are too out of sync. (Message disabled to work around deleted comments until an accurate method is worked out.) + // if (existingPosts.length !== expectedPosts) + // bbbNotice("Loading of hidden post(s) failed. Please refresh.", -1); } function parseRelations(xml, mode, parentId) { @@ -1149,20 +917,17 @@ function bbbScript() { // This is needed to make this script work in Chrome. var showPreview = (relationCookie === undefined || relationCookie === "1" ? true : false); var childSpan = document.createElement("span"); var target; - var newNotice; - var previewLink; var previewLinkId; var previewLinkTxt; var previewId; var classes; var msg; var query = "?tags=parent:" + parentId + (show_deleted ? "+status:any" : "") + (thumbnail_count ? "&limit=" + thumbnail_count : ""); - var thumbDiv; - var thumb; var thumbs = ""; var displayStyle; var forceShowDeleted = activePost.is_deleted; // If the parent is deleted or the active post is deleted, all deleted posts are shown. - var parentDeleted; + var parentDeleted = false; + var isSafebooru = (location.host.indexOf("safebooru") > -1 ? true : false); var i; // Loop variable. // Figure out if the parent is deleted. @@ -1175,7 +940,7 @@ function bbbScript() { // This is needed to make this script work in Chrome. } } - // Setup the notice variables. + // Set up the notice variables. if (showPreview) { previewLinkTxt = "« hide"; displayStyle = "block"; @@ -1191,13 +956,33 @@ function bbbScript() { // This is needed to make this script work in Chrome. previewId = "has-parent-relationship-preview"; classes = "notice-child"; - if (numPosts) - msg = 'This post belongs to a parent' + (parentDeleted ? " (deleted)" : "" ); + if (!isSafebooru) { + if (numPosts) + msg = 'This post belongs to a parent' + (parentDeleted ? " (deleted)" : ""); + + if (numPosts === 3) + msg += ' and has a sibling'; + else if (numPosts > 3) + msg += ' and has ' + (numPosts - 2) + ' siblings'; + } + else { + var parentNotSafe = true; + + for (i = 0; i < numPosts; i++) { + if (posts[i].id === parentId) + parentNotSafe = false; + } - if (numPosts === 3) - msg += ' and has a sibling'; - else if (numPosts > 3) - msg += ' and has ' + (numPosts - 2) + ' siblings'; + var siblingLimit = (parentNotSafe ? 2 : 3); + + if (numPosts) + msg = 'This post belongs to a parent' + (parentNotSafe ? " (not safe)" : (parentDeleted ? " (deleted)" : "")); + + if (numPosts === siblingLimit) + msg += ' and has a sibling'; + else if (numPosts > siblingLimit) + msg += ' and has ' + (numPosts - 2) + ' siblings'; + } } else if (mode === "parent") { target = document.getElementsByClassName("notice-parent")[0]; @@ -1205,27 +990,39 @@ function bbbScript() { // This is needed to make this script work in Chrome. previewId = "has-children-relationship-preview"; classes = "notice-parent"; - if (numPosts === 2) - msg = 'This post has a child'; - else if (numPosts > 2) - msg = 'This post has ' + (numPosts - 1) + ' children'; + if (!isSafebooru) { + if (numPosts === 2) + msg = 'This post has a child'; + else if (numPosts > 2) + msg = 'This post has ' + (numPosts - 1) + ' children'; + } + else { + if (numPosts === 1) + msg = 'This post has no safe children'; + else if (numPosts === 2) + msg = 'This post has a child'; + else if (numPosts > 2) + msg = 'This post has ' + (numPosts - 1) + ' children'; + } } // Create the main notice element. childSpan.innerHTML = '
' + msg + ' (learn more) ' + previewLinkTxt + '
'; - newNotice = childSpan.firstChild; - thumbDiv = getId(previewId, newNotice, "div"); - previewLink = getId(previewLinkId, newNotice, "a"); + + var newNotice = childSpan.firstElementChild; + var thumbDiv = getId(previewId, newNotice); + var previewLink = getId(previewLinkId, newNotice); // Create the thumbnails. for (i = numPosts - 1; i >= 0; i--) { post = formatInfo(posts[i]); - if ((!show_loli && /\bloli\b/.test(post.tag_string)) || (!show_shota && /\bshota\b/.test(post.tag_string)) || (!show_toddlercon && /\btoddlercon\b/.test(post.tag_string)) || (!show_deleted && post.is_deleted && !forceShowDeleted) || (!show_banned && post.is_banned)) + if ((!show_loli && /(?:^|\s)loli(?:$|\s)/.test(post.tag_string)) || (!show_shota && /(?:^|\s)shota(?:$|\s)/.test(post.tag_string)) || (!show_toddlercon && /(?:^|\s)toddlercon(?:$|\s)/.test(post.tag_string)) || (!show_deleted && post.is_deleted && !forceShowDeleted) || (!show_banned && post.is_banned) || safebPostTest(post)) continue; - checkHiddenImg(post); - thumb = createThumbHTML(post, (clean_links ? "" : query)) + " "; + checkHiddenThumbs(post); + + var thumb = createThumbHTML(post, (clean_links ? "" : query)) + " "; if (post.id === parentId) thumbs = thumb + thumbs; @@ -1235,8 +1032,11 @@ function bbbScript() { // This is needed to make this script work in Chrome. thumbDiv.innerHTML = thumbs; - // Highlight the post we're one. - getId("post_" + activePost.id, thumbDiv, "article").className += " current-post"; + // Highlight the post we're on. + var activeThumb = getId("post_" + activePost.id, thumbDiv); + + if (activeThumb) + activeThumb.bbbAddClass("current-post"); // Make the show/hide links work. previewLink.addEventListener("click", function(event) { @@ -1254,11 +1054,8 @@ function bbbScript() { // This is needed to make this script work in Chrome. event.preventDefault(); }, false); - // Thumbnail classes and titles. - formatThumbnails(newNotice); - - // Blacklist. - blacklistUpdate(newNotice); + // Prepare thumbnails. + prepThumbnails(newNotice); // Replace/add the notice. if (target) @@ -1273,2997 +1070,7489 @@ function bbbScript() { // This is needed to make this script work in Chrome. } // Fix hidden thumbnails. - fixHiddenImgs(); - - // Update status message: - bbbStatus("loaded"); + fixHiddenThumbs(); } - function createThumbHTML(post, query) { - // Create a thumbnail HTML string. - return '
' + post.tag_string + '
'; - } + function fixHiddenUgoira(xml) { + // Use xml info to fix the missing info for hidden ugoira posts. + var post = bbb.post.info; + post.pixiv_ugoira_frame_data = xml.pixiv_ugoira_frame_data; - function createThumb(post, query) { - // Create a thumbnail element. (lazy method <_<) - var childSpan = document.createElement("span"); - childSpan.innerHTML = createThumbHTML(post, query); + var imgContainer = document.getElementById("image-container"); + var ugoira = (imgContainer ? imgContainer.getElementsByTagName("canvas")[0] : undefined); - return childSpan.firstChild; + if (ugoira) { + // Fix the missing data attributes. + ugoira.setAttribute("data-ugoira-content-type", post.pixiv_ugoira_frame_data.content_type); + ugoira.setAttribute("data-ugoira-frames", JSON.stringify(post.pixiv_ugoira_frame_data.data)); + + // Append the necessary script. + var mainScript = document.createElement("script"); + mainScript.src = "/assets/ugoira_player.js"; + mainScript.addEventListener("load", ugoiraInit, true); // Wait for this script to load before running the JavaScript that requires it. + document.head.appendChild(mainScript); + } } - function createThumbListing(posts, query, orderedIds) { - // Create a listing of thumbnails. - var thumb; - var thumbs = document.createDocumentFragment(); - var postHolder = {}; - var i, il; // Loop variables; + function endlessXMLJSONHandler(xml, optArg) { + // Create a thumbnail listing from JSON results and pass it to the queue. + var orderedIds = optArg; + var posts = createThumbListing(xml, orderedIds); + var newPage = document.createElement("div"); + newPage.className = "bbb-endless-page"; - // Generate thumbnails. - for (i = 0, il = posts.length; i < il; i++) { - var post = formatInfo(posts[i]); + newPage.appendChild(posts); + endlessQueuePage(newPage); + } - // Don't display loli/shota/toddlercon/deleted/banned if the user has opted so and skip to the next image. - if ((!show_loli && /\bloli\b/.test(post.tag_string)) || (!show_shota && /\bshota\b/.test(post.tag_string)) || (!show_toddlercon && /\btoddlercon\b/.test(post.tag_string)) || (!show_deleted && post.is_deleted) || (!show_banned && post.is_banned)) - continue; + /* Functions for XML page info */ + function searchPages(mode, optArg) { + // Let other functions that don't require the API run (alternative to searchJSON) and retrieve various pages for info. + var url; - // Check if the post is hidden. - checkHiddenImg(post); + if (mode === "search" || mode === "notes" || mode === "favorites" || mode === "thumbnails") { + if (allowUserLimit()) { + url = updateURLQuery(location.href, {limit: thumbnail_count}); + bbb.xml.thumbs = true; - // eek, not so huge line. - thumb = createThumb(post, query); + fetchPages(url, "thumbnails"); + bbbStatus("posts", "new"); + } + } + else if (mode === "endless") { + url = endlessNexURL(); + bbb.xml.endless = true; - // Generate output. - if (!orderedIds) - thumbs.appendChild(thumb); - else - postHolder[post.id] = thumb; + fetchPages(url, "endless"); + bbbStatus("posts", "new"); } + else if (mode === "paginator") { + url = (allowUserLimit() ? updateURLQuery(location.href, {limit: thumbnail_count}) : location.href); + bbb.xml.paginator = true; - // Place thumbnails in the correct order for pools. - if (orderedIds) { - for (i = 0, il = orderedIds.length; i < il; i++) { - thumb = postHolder[orderedIds[i]]; + fetchPages(url, "paginator"); + } + else if (mode === "post_comments") { + url = "/posts/" + optArg; - if (thumb) - thumbs.appendChild(thumb); - } + fetchPages(url, "post_comments", optArg); + bbbStatus("post_comments", "new"); } + else if (mode === "hidden") { + url = "/posts/" + optArg; + bbb.xml.hidden = true; - return thumbs; + fetchPages(url, "hidden"); + bbbStatus("hidden", "new"); + } } - /* Functions for the settings panel */ - function injectSettings() { - var menu = document.getElementById("top"); - menu = (menu ? menu.getElementsByTagName("menu")[0] : undefined); + function fetchPages(url, mode, optArg, session, retries) { + // Retrieve an actual page for certain pieces of information. + var xmlhttp = new XMLHttpRequest(); + var xmlRetries = retries || 0; + var xmlSession = session || window.bbbSession; - if (!menu) { - danbNotice("Better Better Booru: The settings panel link could not be created.", "error"); - return; - } + if (xmlhttp !== null) { + xmlhttp.onreadystatechange = function() { + if (xmlSession !== window.bbbSession) // If we end up receiving an xml response form a different page, reject it. + xmlhttp.abort(); + else if (xmlhttp.readyState === 4) { // 4 = "loaded" + if (xmlhttp.status === 200) { // 200 = "OK" + var docEl = document.createElement("html"); - var link = document.createElement("a"); - link.href = "#"; - link.innerHTML = "BBB Settings"; - link.addEventListener("click", function(event) { - if (!bbb.el.menu.window) { - loadSettings(); - createMenu(); - } + docEl.innerHTML = xmlhttp.responseText; - event.preventDefault(); - }, false); + if (mode === "paginator") { + bbb.xml.paginator = false; - var item = document.createElement("li"); - item.appendChild(link); + replacePaginator(docEl); + } + else if (mode === "post_comments") { + replaceComments(docEl, optArg); + bbbStatus("post_comments", "done"); + } + else if (mode === "thumbnails") { + bbb.xml.thumbs = false; - var menuItems = menu.getElementsByTagName("li"); - menu.insertBefore(item, menuItems[menuItems.length - 1]); + replaceThumbnails(docEl); + bbbStatus("posts", "done"); + } + else if (mode === "hidden") { + bbb.xml.hidden = false; - window.addEventListener("resize", adjustMenuTimer, false); - } + replaceHidden(docEl); + bbbStatus("hidden", "done"); + } + else if (mode === "endless") { + bbb.xml.endless = false; - function createMenu() { - var menu = bbb.el.menu.window = document.createElement("div"); - menu.id = "bbb_menu"; - menu.style.visibility = "hidden"; + endlessXMLPageHandler(docEl); + bbbStatus("posts", "done"); + } + } + else if (xmlhttp.status !== 0) { + if (xmlRetries < 1) { + xmlRetries++; + fetchPages(url, mode, optArg, xmlSession, xmlRetries); + } + else { + var linkId = uniqueIdNum(); // Create a unique ID. + var msg; - var header = document.createElement("h1"); - header.innerHTML = "Better Better Booru Settings"; - header.style.textAlign = "center"; - menu.appendChild(header); + if (mode === "hidden") { + msg = "Error retrieving hidden thumbnails"; + bbbStatus("hidden", "error"); + } + else if (mode === "thumbnails" || mode === "endless") { + msg = "Error retrieving post information"; + bbbStatus("posts", "error"); + } + else if (mode === "post_comments") { + msg = "Error retrieving comment information"; + bbbStatus("post_comments", "error"); + } + else if (mode === "paginator") + msg = "Error updating paginator"; - var tabBar = document.createElement("div"); - tabBar.style.padding = "0px 15px"; - tabBar.addEventListener("click", function(event) { - var target = event.target; + var noticeMsg = bbbNotice(msg + ' (HTML Code: ' + xmlhttp.status + ' ' + xmlhttp.statusText + '). (Retry)', -1); - if (target.href) - changeTab(target); + document.getElementById(linkId).addEventListener("click", function(event) { + closeBbbNoticeMsg(noticeMsg); + searchPages(mode, optArg); + event.preventDefault(); + }, false); + } + } + } + }; + xmlhttp.open("GET", url, true); + xmlhttp.send(null); + } + } - event.preventDefault(); - }, false); - menu.appendChild(tabBar); + function replacePaginator(el) { + // Replace the contents inside the paginator div so as to preserve the original div and any event listeners attached to it. + var oldPag = getPaginator(); + var newPag = getPaginator(el); - var generalTab = bbb.el.menu.generalTab = document.createElement("a"); - generalTab.name = "general"; - generalTab.href = "#"; - generalTab.innerHTML = "General"; - generalTab.className = "bbb-tab bbb-active-tab"; - tabBar.appendChild(generalTab); - - var borderTab = bbb.el.menu.borderTab = document.createElement("a"); - borderTab.name = "borders"; - borderTab.href = "#"; - borderTab.innerHTML = "Borders"; - borderTab.className = "bbb-tab"; - tabBar.appendChild(borderTab); + if (oldPag && newPag) + oldPag.innerHTML = newPag.innerHTML; + } - var prefTab = bbb.el.menu.prefTab = document.createElement("a"); - prefTab.name = "pref"; - prefTab.href = "#"; - prefTab.innerHTML = "Preferences"; - prefTab.className = "bbb-tab"; - tabBar.appendChild(prefTab); + function replaceComments(docEl, postId) { + // Fix hidden comments with information from a post. + var divId = "post_" + postId; + var commentDiv = document.getElementById(divId); + var commentSection = docEl.getElementsByClassName("comments-for-post")[0]; + var comments = commentSection.getElementsByClassName("comment"); + var numComments = comments.length; + var toShow = 6; // Number of comments to display. + var post = scrapePost(docEl); + var previewImg = commentDiv.getElementsByTagName("img")[0]; + var target = commentDiv.getElementsByClassName("comments-for-post")[0]; + var newContent = document.createDocumentFragment(); + + // Fix the image. + if (post.preview_file_url) { + if (post.file_url) { + commentDiv.setAttribute("data-md5", post.md5); + commentDiv.setAttribute("data-file-ext", post.file_ext); + commentDiv.setAttribute("data-file-url", post.file_url); + commentDiv.setAttribute("data-large-file-url", post.large_file_url); + } - var helpTab = bbb.el.menu.helpTab = document.createElement("a"); - helpTab.name = "help"; - helpTab.href = "#"; - helpTab.innerHTML = "Help"; - helpTab.className = "bbb-tab"; - tabBar.appendChild(helpTab); + previewImg.src = post.preview_file_url; + previewImg.alt = /([^\/]+)\.\w+$/.exec(post.preview_file_url)[1]; + commentDiv.setAttribute("data-preview-file-url", post.preview_file_url); + } - var scrollDiv = bbb.el.menu.scrollDiv = document.createElement("div"); - scrollDiv.className = "bbb-scroll-div"; - menu.appendChild(scrollDiv); - scrollDiv.scrollTop = 0; + // Fix the comments. + if (numComments > toShow) { + for (var i = 0, toHide = numComments - toShow; i < toHide; i++) + comments[i].style.display = "none"; - var generalPage = bbb.el.menu.generalPage = document.createElement("div"); - generalPage.className = "bbb-page"; - generalPage.style.display = "block"; - scrollDiv.appendChild(generalPage); + commentSection.getElementsByClassName("row notices")[0].innerHTML = ' Show all comments '; + } - generalPage.bbbSection(bbb.sections.browse); - generalPage.bbbSection(bbb.sections.image_control); - generalPage.bbbSection(bbb.sections.sidebar); - generalPage.bbbSection(bbb.sections.misc); - generalPage.bbbSection(bbb.sections.notices); - generalPage.bbbSection(bbb.sections.logged_out); + // Add it all in and get it ready. + while (commentSection.firstElementChild) + newContent.appendChild(commentSection.firstElementChild); - var bordersPage = bbb.el.menu.bordersPage = document.createElement("div"); - bordersPage.className = "bbb-page"; - scrollDiv.appendChild(bordersPage); + target.appendChild(newContent); - bordersPage.bbbSection(bbb.sections.border_options); - bordersPage.bbbSection(bbb.sections.status_borders); - bordersPage.bbbSection(bbb.sections.tag_borders); + Danbooru.Comment.initialize_all(); + $("#" + divId + " .simple_form .dtext-preview").hide(); + $("#" + divId + " .simple_form input[value=Preview]").click(Danbooru.Dtext.click_button); + } - var prefPage = bbb.el.menu.prefPage = document.createElement("div"); - prefPage.className = "bbb-page"; - scrollDiv.appendChild(prefPage); + function replaceThumbnails(docEl) { + // Replace the thumbnails and paginator with new ones. + var thumbContainer = getThumbContainer(gLoc); + var before = getThumbSibling(gLoc); - prefPage.bbbSection(bbb.sections.script_settings); - prefPage.bbbBackupSection(); + if (!thumbContainer) { + bbbNotice("Thumbnail section could not be located.", -1); + return; + } - var helpPage = bbb.el.menu.helpPage = document.createElement("div"); - helpPage.className = "bbb-page"; - scrollDiv.appendChild(helpPage); + // Thumb preparation. + var newThumbs = document.createDocumentFragment(); + var newPosts = getPosts(docEl); - helpPage.bbbTextSection('What You Need to Know', 'When using this script and depending on how you use it, there are a few things that need to be taken into consideration in order to get the best results.

Anonymous/Logged out users:
Logged in users:'); - helpPage.bbbTextSection('Thumbnail Matching Rules', 'For creating thumbnail matching rules, please consult the following examples:
Wildcards can be used with any of the above methods:
Multiple match rules can be applied by using commas:
The following metatags are supported:
The id, score, favcount, width, and height metatags can also use number ranges for matching:'); - helpPage.bbbTextSection('Questions, Suggestions, or Bugs?', 'If you have any questions, please use the Greasy Fork feedback forums located here. If you\'d like to report a bug or make a suggestion, please create an issue on GitHub here.'); - helpPage.bbbTocSection(); + while (newPosts[0]) + newThumbs.appendChild(newPosts[0]); - var close = document.createElement("a"); - close.innerHTML = "Save & Close"; - close.href = "#"; - close.className = "bbb-button"; - close.style.marginRight = "15px"; - close.addEventListener("click", function(event) { - removeMenu(); - saveSettings(); - event.preventDefault(); - }, false); + // New thumbnail container preparation. + var replacement = thumbContainer.cloneNode(false); + var childIndex = 0; - var cancel = document.createElement("a"); - cancel.innerHTML = "Cancel"; - cancel.href = "#"; - cancel.className = "bbb-button"; - cancel.addEventListener("click", function(event) { - removeMenu(); - loadSettings(); - event.preventDefault(); - }, false); + while (thumbContainer.children[childIndex]) { + var child = thumbContainer.children[childIndex]; - var reset = document.createElement("a"); - reset.innerHTML = "Reset to Defaults"; - reset.href = "#"; - reset.className = "bbb-button"; - reset.style.cssFloat = "right"; - reset.style.color = "#ff1100"; - reset.addEventListener("click", function(event) { - removeMenu(); - loadDefaults(); - createMenu(); - event.preventDefault(); - }, false); + if (child.tagName !== "ARTICLE") + replacement.appendChild(child); + else + childIndex++; + } - menu.appendChild(close); - menu.appendChild(cancel); - menu.appendChild(reset); + if (!before) + replacement.appendChild(newThumbs); + else + replacement.insertBefore(newThumbs, before); - var tip = bbb.el.menu.tip = document.createElement("div"); - tip.className = "bbb-expl"; - menu.appendChild(tip); + // Prepare thumbnails. + prepThumbnails(replacement); - var tagEditBlocker = bbb.el.menu.tagEditBlocker = document.createElement("div"); - tagEditBlocker.className = "bbb-edit-blocker"; - menu.appendChild(tagEditBlocker); + // Replace results with new results. + thumbContainer.parentNode.replaceChild(replacement, thumbContainer); - var tagEditBox = document.createElement("div"); - tagEditBox.className = "bbb-edit-box"; - tagEditBlocker.appendChild(tagEditBox); + // Replace paginator with new paginator. + replacePaginator(docEl); - var tagEditHeader = document.createElement("h2"); - tagEditHeader.innerHTML = "Tag Editor"; - tagEditHeader.className = "bbb-header"; - tagEditBox.appendChild(tagEditHeader); + // Update the URL with the limit value. + if (allowUserLimit()) + history.replaceState({}, "", updateURLQuery(location.search, {limit: thumbnail_count})); + } - var tagEditText = document.createElement("div"); - tagEditText.className = "bbb-edit-text"; - tagEditText.innerHTML = "Note: While using this window, separate matching rules/tag combinations can be separated by commas or separate lines. Blank lines are ignored."; - tagEditBox.appendChild(tagEditText); + function replaceHidden(docEl) { + // Fix the hidden image placeholders with information from a post. + var hiddenImgs = document.getElementsByClassName("bbb-hidden-thumb"); + var article = hiddenImgs[0]; - var tagEditArea = bbb.el.menu.tagEditArea = document.createElement("textarea"); - tagEditArea.className = "bbb-edit-area"; - tagEditBox.appendChild(tagEditArea); + // Hidden thumbnails no longer exist in the page so stop. + if (!article) + return; - var tagEditOk = document.createElement("a"); - tagEditOk.innerHTML = "OK"; - tagEditOk.href = "#"; - tagEditOk.className = "bbb-button"; - tagEditOk.addEventListener("click", function(event) { - var tags = tagEditArea.value.replace(/[\r\n]+/g, ",").bbbTagClean(); - var args = bbb.tagEdit; + var previewImg = article.getElementsByTagName("img")[0]; + var hiddenId = article.getAttribute("data-id"); + var bcc = bbb.cache.current; + var post = scrapePost(docEl); + + if (String(post.id) !== hiddenId) // Out of sync. Reset. + searchPages("hidden", hiddenId); + else if (post.preview_file_url) { // Update the thumbnail with the correct information. + if (post.file_url) { + article.setAttribute("data-md5", post.md5); + article.setAttribute("data-file-ext", post.file_ext); + article.setAttribute("data-file-url", post.file_url); + article.setAttribute("data-large-file-url", post.large_file_url); + + // Fix ddl. + postDDL(article); + } - tagEditBlocker.style.display = "none"; - args.input.value = tags; - args.object[args.prop] = tags; - event.preventDefault(); - }, false); - tagEditBox.appendChild(tagEditOk); + previewImg.src = post.preview_file_url; + article.setAttribute("data-preview-file-url", post.preview_file_url); - var tagEditCancel = document.createElement("a"); - tagEditCancel.innerHTML = "Cancel"; - tagEditCancel.href = "#"; - tagEditCancel.className = "bbb-button"; - tagEditCancel.style.cssFloat = "right"; - tagEditCancel.addEventListener("click", function(event) { - tagEditBlocker.style.display = "none"; - event.preventDefault(); - }, false); - tagEditBox.appendChild(tagEditCancel); + bcc.history.push(hiddenId); + bcc.names[hiddenId] = /[^\/]+$/.exec(post.file_url || post.preview_file_url)[0]; - // Add menu to the DOM and manipulate the dimensions. - document.body.appendChild(menu); + article.bbbRemoveClass("bbb-hidden-thumb"); - var viewHeight = window.innerHeight; - var barWidth = scrollbarWidth(); - var scrollDivDiff = menu.offsetHeight - scrollDiv.clientHeight; + // Continue to the next image or finish by updating the cache. + if (hiddenImgs[0]) { + hiddenId = hiddenImgs[0].getAttribute("data-id"); + searchPages("hidden", hiddenId); + } + else + updateThumbCache(); + } + else { // The image information couldn't be found. + bbb.xml.hidden = true; // Flag the XML as active to signal a problem and disable further attempts. - scrollDiv.style.maxHeight = viewHeight - scrollDiv.bbbGetPadding().height - scrollDivDiff - 50 + "px"; // Subtract 50 for margins (25 each). - scrollDiv.style.minWidth = 901 + barWidth + 3 + "px"; // Should keep the potential scrollbar from intruding on the original drawn layout if I'm thinking about this correctly. Seems to work in practice anyway. - scrollDiv.style.paddingLeft = barWidth + 3 + "px"; + updateThumbCache(); + bbbNotice("Error retrieving thumbnail information.", -1); + bbbStatus("hidden", "error"); + } + } - var menuWidth = menu.offsetWidth; + function endlessXMLPageHandler(docEl) { + // Take thumbnails from a page and pass them to the queue or retrieve hidden posts as necessary. + bbb.endless.new_paginator = getPaginator(docEl); - menu.style.marginLeft = -menuWidth / 2 + "px"; - menu.style.visibility = "visible"; - } + if (potentialHiddenPosts(gLoc, docEl) && useAPI()) + searchJSON("endless"); + else { + var posts = getPosts(docEl); + var newPage = document.createElement("div"); + newPage.className = "bbb-endless-page"; - function createSection(section) { - var sectionFrag = document.createDocumentFragment(); - var i, il; // Loop variables. + while (posts[0]) + newPage.appendChild(posts[0]); - if (section.header) { - var sectionHeader = document.createElement("h2"); - sectionHeader.innerHTML = section.header; - sectionHeader.className = "bbb-header"; - sectionFrag.appendChild(sectionHeader); + endlessQueuePage(newPage); } + } - if (section.text) { - var sectionText = document.createElement("div"); - sectionText.innerHTML = section.text; - sectionText.className = "bbb-section-text"; - sectionFrag.appendChild(sectionText); + function isThere(url) { + // Checks if file exists. Thanks to some random forum! + var req = new XMLHttpRequest(); // XMLHttpRequest object. + try { + req.open("HEAD", url, false); + req.send(null); + return (req.status === 200 ? true : false); + } catch(er) { + return false; } + } - var sectionDiv = document.createElement("div"); - sectionDiv.className = "bbb-section-options"; - sectionFrag.appendChild(sectionDiv); - - if (section.type === "general") { - var settingList = section.settings; - var sll = settingList.length; - var halfway = (sll > 1 ? Math.ceil(sll / 2) : 0); + /* Functions for retrieving page info */ + function scrapePost(pageEl) { + // Retrieve info from the current document or a supplied element containing the html with it. + var target = pageEl || document; + var postContent = getPostContent(target); + var imgContainer = postContent.container; - var leftSide = document.createElement("div"); - leftSide.className = "bbb-section-options-left"; - sectionDiv.appendChild(leftSide); + if (!imgContainer) + return {}; - var rightSide = document.createElement("div"); - rightSide.className = "bbb-section-options-right"; - sectionDiv.appendChild(rightSide); + var postEl = postContent.el; + var postTag = (postEl ? postEl.tagName : undefined); + var dataInfo = [imgContainer.getAttribute("data-file-url"), imgContainer.getAttribute("data-md5"), imgContainer.getAttribute("data-file-ext")]; + var directLink = getId("image-resize-link", target) || document.evaluate('.//section[@id="post-information"]/ul/li/a[starts-with(@href, "/data/")]', target, null, 9, null).singleNodeValue; + var twitterInfo = getMeta("twitter:image:src", target); + var previewInfo = getMeta("og:image", target); + var imgHeight = Number(imgContainer.getAttribute("data-height")); + var imgWidth = Number(imgContainer.getAttribute("data-width")); + var md5 = ""; + var ext = ""; + var infoValues; + var imgInfo = { + md5: "", + file_ext: "", + file_url: "", + large_file_url: "", + preview_file_url: "", + has_large: undefined, + id: Number(imgContainer.getAttribute("data-id")), + fav_count: Number(imgContainer.getAttribute("data-fav-count")), + has_children: (imgContainer.getAttribute("data-has-children") === "true" ? true : false), + has_active_children: (postTag === "IMG" || postTag === "CANVAS" ? postEl.getAttribute("data-has-active-children") === "true" : !!target.getElementsByClassName("notice-parent")[0]), + parent_id: (imgContainer.getAttribute("data-parent-id") ? Number(imgContainer.getAttribute("data-parent-id")) : null), + rating: imgContainer.getAttribute("data-rating"), + score: Number(imgContainer.getAttribute("data-score")), + tag_string: imgContainer.getAttribute("data-tags"), + pool_string: imgContainer.getAttribute("data-pools"), + uploader_name: imgContainer.getAttribute("data-uploader"), + is_deleted: (getMeta("post-is-deleted", target) === "false" ? false : true), + is_flagged: (getMeta("post-is-flagged", target) === "false" ? false : true), + is_pending: (getId("pending-approval-notice", target) ? true : false), + is_banned: (imgContainer.getAttribute("data-flags").indexOf("banned") < 0 ? false : true), + image_height: imgHeight || null, + image_width: imgWidth || null, + is_hidden: !postEl + }; - var optionTarget = leftSide; + // Try to extract the file's name and extension. + if (dataInfo[1]) + infoValues = dataInfo; + else if (directLink) + infoValues = /data\/(\w+)\.(\w+)/.exec(directLink.href); + else if (twitterInfo) + infoValues = (twitterInfo.indexOf("sample") > -1 ? /data\/sample\/sample-(\w+)\.\w/.exec(twitterInfo) : /data\/(\w+)\.(\w+)/.exec(twitterInfo)); + else if (previewInfo) + infoValues = /data\/preview\/(\w+?)\.\w/.exec(previewInfo); - for (i = 0; i < sll; i++) { - var settingName = settingList[i]; + if (infoValues) { + md5 = infoValues[1]; + ext = infoValues[2]; - if (halfway && i >= halfway) - optionTarget = rightSide; + // Test for the original image file extension if it is unknown. + if (!ext && imgWidth) { + var testExt = ["jpg", "png", "gif", "jpeg", "webm"]; - var newOption = createOption(settingName); - optionTarget.appendChild(newOption); + for (var i = 0, il = testExt.length; i < il; i++) { + if (isThere("/data/" + md5 + "." + testExt[i])) { + ext = testExt[i]; + break; + } + } } - } - else if (section.type === "border") { - var borderSettings = bbb.user[section.settings]; - for (i = 0, il = borderSettings.length; i < il; i++) { - var newBorderOption = createBorderOption(borderSettings, i); - sectionDiv.appendChild(newBorderOption); + var isUgoira = (postTag === "CANVAS" || (ext === "zip" && /(?:^|\s)ugoira(?:$|\s)/.test(imgInfo.tag_string))); + var isAnimatedImg = /(?:^|\s)animated_(?:gif|png)(?:$|\s)/.test(imgInfo.tag_string); + + if (isUgoira) { + if (postTag === "CANVAS") { + imgInfo.pixiv_ugoira_frame_data = { + id: undefined, // Don't have this value. + post_id: imgInfo.id, + data: JSON.parse(postEl.getAttribute("data-ugoira-frames")), + content_type: postEl.getAttribute("data-ugoira-content-type").replace(/"/gi, "") + }; + } + else { + imgInfo.pixiv_ugoira_frame_data = { + id: "", // Don't have this value. + post_id: imgInfo.id, + data: "", + content_type: "" + }; + } } - var indexWrapper = document.createElement("div"); - indexWrapper.setAttribute("data-bbb-index", i); - sectionDiv.appendChild(indexWrapper); + imgInfo.has_large = (!isAnimatedImg && ((imgWidth > 850 && ext !== "swf" && ext !== "webm") || isUgoira) ? true : false); + imgInfo.md5 = md5; + imgInfo.file_ext = ext; + imgInfo.file_url = "/data/" + md5 + "." + ext; + imgInfo.preview_file_url = (!imgHeight || ext === "swf" ? "/images/download-preview.png" : "/data/preview/" + md5 + ".jpg"); - var borderDivider = document.createElement("div"); - borderDivider.className = "bbb-border-divider"; - indexWrapper.appendChild(borderDivider); + if (isUgoira) + imgInfo.large_file_url = "/data/sample/sample-" + md5 + ".webm"; + else if (imgInfo.has_large) + imgInfo.large_file_url = "/data/sample/sample-" + md5 + ".jpg"; + else + imgInfo.large_file_url = "/data/" + md5 + "." + ext; } + else if (previewInfo === "/images/download-preview.png") + imgInfo.preview_file_url = "/images/download-preview.png"; - return sectionFrag; + return imgInfo; } - Element.prototype.bbbSection = function(section) { - this.appendChild(createSection(section)); - }; + function getId(elId, target) { + // Retrieve an element by ID from either the current document or an element containing it. + if (!target || target === document) + return document.getElementById(elId); + else if (target.id === elId) + return target; + else + return target.querySelector("#" + elId); - function createOption(settingName) { - var optionObject = bbb.options[settingName]; - var userSetting = bbb.user[settingName]; - var i, il; // Loop variables. + return null; + } - var label = document.createElement("label"); - label.className = "bbb-general-label"; + function getPostContent(pageEl) { + // Retrieve the post content related elements. + var target = pageEl || document; + var imgContainer = getId("image-container", target); - var textSpan = document.createElement("span"); - textSpan.className = "bbb-general-text"; - textSpan.innerHTML = optionObject.label; - label.appendChild(textSpan); + if (!imgContainer) + return {}; - var inputSpan = document.createElement("span"); - inputSpan.className = "bbb-general-input"; - label.appendChild(inputSpan); + var img = getId("image", target); + var swfObj = imgContainer.getElementsByTagName("object")[0]; + var swfEmb = (swfObj ? swfObj.getElementsByTagName("embed")[0] : undefined); + var webmVid = imgContainer.getElementsByTagName("video")[0]; + var ugoira = imgContainer.getElementsByTagName("canvas")[0]; + var other = document.evaluate('.//a[starts-with(@href, "/data/")]', imgContainer, null, 9, null).singleNodeValue; + var el = swfEmb || webmVid || ugoira || img || other; + var secondaryEl = swfObj; // Other elements related to the main element. Only applies to flash for now. - var item; - var itemFrag = document.createDocumentFragment(); + return {container: imgContainer, el: el, secEl: secondaryEl}; + } - switch (optionObject.type) { - case "dropdown": - var txtOptions = optionObject.txtOptions; - var numRange = optionObject.numRange; - var numList = optionObject.numList; - var selectOption; + function getPosts(target) { + // Return a list of posts depending from the document or a specific element. + if (!target || target === document) // All posts in the document. + return document.getElementsByClassName("post-preview"); + else if (!target.bbbHasClass("post-preview")) // All posts in a specific element. + return target.getElementsByClassName("post-preview"); + else // Single specific post. + return [target]; + } - item = document.createElement("select"); - item.name = settingName; + function getPaginator(target) { + // Return the paginator of the document or a specific element. + if (!target || target === document) // Paginator in the document. + return document.getElementsByClassName("paginator")[0]; + else if (!target.bbbHasClass("paginator")) // Paginator in a specific element. + return target.getElementsByClassName("paginator")[0]; + else // Single specific paginator. + return target; + } - if (txtOptions) { - for (i = 0, il = txtOptions.length; i < il; i++) { - var txtOption = txtOptions[i].split(":"); + function getThumbContainer(mode, pageEl) { + // Retrieve the element that contains the thumbnails. + var target = pageEl || document; + var container; - selectOption = document.createElement("option"); - selectOption.innerHTML = txtOption[0]; - selectOption.value = txtOption[1]; + if (mode === "search") { + container = getId("posts", target); + container = (container ? container.getElementsByTagName("div")[0] : undefined); + } + else if (mode === "popular" || mode === "notes" || mode === "popular_view") + container = getId("a-index", target); + else if (mode === "pool" || mode === "favorite_group") { + container = getId("a-show", target); + container = (container ? container.getElementsByTagName("section")[0] : undefined); + } + else if (mode === "favorites") + container = getId("posts", target); - if (selectOption.value === String(userSetting)) - selectOption.selected = true; + // Can't always depend on the first post so it's used as a fallback. + if (!container) { + var posts = getPosts(target); + var firstPost = posts[0]; - item.appendChild(selectOption); - } - } + if (firstPost) + container = firstPost.parentNode; + } - if (numList) { - for (i = 0, il = numList.length; i < il; i++) { - selectOption = document.createElement("option"); - selectOption.innerHTML = numList[i]; - selectOption.value = numList[i]; + return container; + } - if (selectOption.value === String(userSetting)) - selectOption.selected = true; + function getThumbSibling(mode, pageEl) { + // If it exists, retrieve the element that thumbnails should be added before. + var target = pageEl || document; + var sibling; - item.appendChild(selectOption); - } + var posts = getPosts(target); + var numPosts = posts.length; + var lastPost = (numPosts ? posts[numPosts - 1] : undefined); + var lastPostParent = (lastPost ? lastPost.parentNode : undefined); + var thumbContainer = getThumbContainer(mode, target); + var lastPostEl = (lastPostParent && lastPostParent !== thumbContainer && lastPostParent.parentNode === thumbContainer ? lastPostParent : lastPost); + + if (lastPostEl) { + var contChildren = thumbContainer.children; + + for (var i = contChildren.length - 1; i >= 0; i--) { + if (contChildren[i] === lastPostEl) { + sibling = contChildren[i + 1]; + break; } + } + } + else if (mode === "pool" || mode === "notes" || mode === "favorites" || mode === "favorite_group") { + var paginator = getPaginator(target); + var endlessDiv = getId("bbb-endless-button-div", target); - if (numRange) { - var end = numRange[1]; + sibling = endlessDiv || paginator; + } - for (i = numRange[0]; i <= end; i++) { - selectOption = document.createElement("option"); - selectOption.innerHTML = i; - selectOption.value = i; + return sibling; + } - if (selectOption.value === String(userSetting)) - selectOption.selected = true; + function getPaginatorNextURL(target) { + // Retrieve the next page's URL from the paginator. + var paginator = getPaginator(target); - item.appendChild(selectOption); - } - } + if (paginator) { + var paginatorLinks = paginator.getElementsByTagName("a"); - item.addEventListener("change", function() { - var selected = this.value; - bbb.user[settingName] = (bbbIsNum(selected) ? Number(selected) : selected); - }, false); - itemFrag.appendChild(item); - break; - case "checkbox": - item = document.createElement("input"); - item.name = settingName; - item.type = "checkbox"; - item.checked = userSetting; - item.addEventListener("click", function() { bbb.user[settingName] = this.checked; }, false); - itemFrag.appendChild(item); - break; - case "text": - item = document.createElement("input"); - item.name = settingName; - item.type = "text"; - item.value = userSetting; - item.addEventListener("change", function() { bbb.user[settingName] = (optionObject.isTagInput ? this.value.bbbTagClean() : this.value.bbbSpaceClean()); }, false); - itemFrag.appendChild(item); + for (var i = paginatorLinks.length - 1; i >= 0; i--) { + var paginatorLink = paginatorLinks[i]; - if (optionObject.isTagInput) { - var tagExpand = document.createElement("a"); - tagExpand.href = "#"; - tagExpand.className = "bbb-edit-link"; - tagExpand.innerHTML = "»"; - tagExpand.addEventListener("click", function(event) { - tagEditWindow(item, bbb.user, settingName); - event.preventDefault(); - }, false); - itemFrag.appendChild(tagExpand); + if (paginatorLink.rel.toLowerCase() === "next" && paginatorLink.href) { + return paginatorLink.href; } - break; - case "number": - item = document.createElement("input"); - item.name = settingName; - item.type = "text"; - item.value = userSetting; - item.addEventListener("change", function() { bbb.user[settingName] = Number(this.value); }, false); - itemFrag.appendChild(item); - break; - default: - console.log("Better Better Booru Error: Unexpected object type. Type: " + optionObject.type); - break; + } } - inputSpan.appendChild(itemFrag); - - var explLink = document.createElement("a"); - explLink.innerHTML = "?"; - explLink.href = "#"; - explLink.className = "bbb-expl-link"; - explLink.addEventListener("click", function(event) { event.preventDefault(); }, false); - explLink.bbbSetTip(bbb.options[settingName].expl); - inputSpan.appendChild(explLink); - return label; + return undefined; } - function createBorderOption(borderSettings, index) { - var borderItem = borderSettings[index]; - var isStatus = (borderItem.class_name ? true : false); + function getMeta(meta, pageEl) { + // Get a value from an HTML meta tag. + var target = pageEl || document; + var metaTags = target.getElementsByTagName("meta"); - var borderSpacer = document.createElement("span"); - borderSpacer.className = "bbb-border-spacer"; + for (var i = 0, il = metaTags.length; i < il; i++) { + var tag = metaTags[i]; - var indexWrapper = document.createElement("div"); - indexWrapper.setAttribute("data-bbb-index", index); + if (tag.name === meta || tag.getAttribute("property") === meta) { + if (tag.hasAttribute("content")) + return tag.content; + else + return undefined; + } + } - var borderDivider = document.createElement("div"); - borderDivider.className = "bbb-border-divider"; - indexWrapper.appendChild(borderDivider); + return undefined; + } - var borderDiv = document.createElement("div"); - borderDiv.className = "bbb-border-div"; - indexWrapper.appendChild(borderDiv); + function getVar(urlVar, targetUrl) { + // Retrieve a value from a specified/current URL's query string. + // Undefined refers to a param that isn't even declared. Null refers to a declared param that hasn't been defined with a value (&test&). An empty string ("") refers to a param that has been defined with nothing (&test=&). + var url = targetUrl; - var borderBarDiv = document.createElement("div"); - borderBarDiv.className = "bbb-border-bar"; - borderDiv.appendChild(borderBarDiv); + if (!url) + url = location.search; - var enableLabel = document.createElement("label"); - enableLabel.innerHTML = "Enabled:"; - borderBarDiv.appendChild(enableLabel); + var result = url.split(new RegExp("[&\?]" + urlVar))[1]; - var enableBox = document.createElement("input"); - enableBox.type = "checkbox"; - enableBox.checked = borderItem.is_enabled; - enableBox.addEventListener("click", function() { borderItem.is_enabled = this.checked; }, false); - enableLabel.appendChild(enableBox); + if (result === undefined) + return undefined; - var editSpan = document.createElement("span"); - editSpan.style.cssFloat = "right"; - borderBarDiv.appendChild(editSpan); + result = result.split(/[#&]/, 1)[0].split("=", 2)[1]; - var moveButton = document.createElement("a"); - moveButton.href = "#"; - moveButton.innerHTML = "Move"; - moveButton.className = "bbb-border-button"; - moveButton.addEventListener("click", function(event) { - moveBorder(borderSettings, indexWrapper); - event.preventDefault(); - }, false); - moveButton.bbbSetTip("Click the blue highlighted area that indicates where you would like to move this border."); - editSpan.appendChild(moveButton); + if (result === undefined) + return null; + else + return result; + } - var previewButton = document.createElement("a"); - previewButton.href = "#"; - previewButton.innerHTML = "Preview"; - previewButton.className = "bbb-border-button"; - previewButton.addEventListener("click", function(event) { event.preventDefault(); }, false); - previewButton.bbbBorderPreview(borderItem); - editSpan.appendChild(previewButton); + function getTagVar(urlVar, url) { + // Retrieve a metatag's value from the tag portion of a specified/current URL's query string. + if (!url) + url = location.search; - if (!isStatus) { - var deleteButton = document.createElement("a"); - deleteButton.href = "#"; - deleteButton.innerHTML = "Delete"; - deleteButton.className = "bbb-border-button"; - deleteButton.addEventListener("click", function(event) { - deleteBorder(borderSettings, indexWrapper); - event.preventDefault(); - }, false); - editSpan.appendChild(deleteButton); + var tags = getVar("tags", url); + var tag; + var result; - var newButton = document.createElement("a"); - newButton.href = "#"; - newButton.innerHTML = "New"; - newButton.className = "bbb-border-button"; - newButton.addEventListener("click", function(event) { - newBorder(borderSettings, indexWrapper); - event.preventDefault(); - }, false); - newButton.bbbSetTip("Click the blue highlighted area that indicates where you would like to create a border."); - editSpan.appendChild(newButton); - } + // If the tags parameter isn't provided or has no value, the metatag is undefined. + if (tags === null || tags === undefined) + return undefined; - editSpan.appendChild(borderSpacer.cloneNode(false)); + tags = tags.split(/\+|%20/g); - var helpButton = document.createElement("a"); - helpButton.href = "#"; - helpButton.innerHTML = "Help"; - helpButton.className = "bbb-border-button"; - helpButton.addEventListener("click", function(event) { event.preventDefault(); }, false); - helpButton.bbbSetTip("Enabled: When checked, the border will be applied. When unchecked, it won't be applied.

Status/Tags: Describes the posts that the border should be applied to. For custom tag borders, you may specify the rules the post must match for the border to be applied. Please read the \"Thumbnail Matching Rules\" section under the help tab for information about creating rules.

Color: Set the color of the border. Hex RGB color codes (#000000, #FFFFFF, etc.) are the recommended values.

Style: Set how the border looks. Please note that double only works with a border width of 3 or higher.

Move: Move the border to a new position. Higher borders have higher priority. In the event of a post matching more than 4 borders, the first 4 borders get applied and the rest are ignored. If single color borders are enabled, only the first matching border is applied.

Preview: Display a preview of the border's current settings.

Delete: Remove the border and its settings.

New: Create a new border."); - editSpan.appendChild(helpButton); + for (var i = 0, il = tags.length; i < il; i++) { + tag = decodeURIComponent(tags[i]); - var borderSettingsDiv = document.createElement("div"); - borderSettingsDiv.className = "bbb-border-settings"; - borderDiv.appendChild(borderSettingsDiv); - - var nameLabel = document.createElement("label"); - nameLabel.className = "bbb-border-name"; - borderSettingsDiv.appendChild(nameLabel); - - if (isStatus) - nameLabel.innerHTML = "Status:" + borderItem.tags; - else { - nameLabel.innerHTML = "Tags:"; - - var nameInput = document.createElement("input"); - nameInput.type = "text"; - nameInput.value = borderItem.tags; - nameInput.addEventListener("change", function() { borderItem.tags = this.value.bbbTagClean(); }, false); - nameLabel.appendChild(nameInput); - - var nameExpand = document.createElement("a"); - nameExpand.href = "#"; - nameExpand.className = "bbb-edit-link"; - nameExpand.innerHTML = "»"; - nameExpand.addEventListener("click", function(event) { - tagEditWindow(nameInput, borderItem, "tags"); - event.preventDefault(); - }, false); - nameLabel.appendChild(nameExpand); + if (tag.indexOf(urlVar + ":") === 0) + result = encodeURIComponent(tag.split(":")[1]); // Let the calling function decide whether it wants the decoded tag or not. } - var colorLabel = document.createElement("label"); - colorLabel.innerHTML = "Color:"; - colorLabel.className = "bbb-border-color"; - borderSettingsDiv.appendChild(colorLabel); - - var colorInput = document.createElement("input"); - colorInput.type = "text"; - colorInput.value = borderItem.border_color; - colorInput.addEventListener("change", function() { borderItem.border_color = this.value.bbbSpaceClean(); }, false); - colorLabel.appendChild(colorInput); - - var styleLabel = document.createElement("label"); - styleLabel.innerHTML = "Style:"; - styleLabel.className = "bbb-border-style"; - borderSettingsDiv.appendChild(styleLabel); - - var styleDrop = document.createElement("select"); - styleDrop.addEventListener("change", function() { borderItem.border_style = this.value; }, false); - styleLabel.appendChild(styleDrop); - - var solidOption = document.createElement("option"); - solidOption.innerHTML = "solid"; - solidOption.value = "solid"; - styleDrop.appendChild(solidOption); + return result; + } - var dashedOption = document.createElement("option"); - dashedOption.innerHTML = "dashed"; - dashedOption.value = "dashed"; - styleDrop.appendChild(dashedOption); + /* Functions for the settings panel */ + function injectSettings() { + var menu = document.getElementById("top"); + menu = (menu ? menu.getElementsByTagName("menu")[0] : undefined); - var dottedOption = document.createElement("option"); - dottedOption.innerHTML = "dotted"; - dottedOption.value = "dotted"; - styleDrop.appendChild(dottedOption); + if (!menu) + return; - var doubleOption = document.createElement("option"); - doubleOption.innerHTML = "double"; - doubleOption.value = "double"; - styleDrop.appendChild(doubleOption); + var menuItems = menu.getElementsByTagName("li"); + var numMenuItems = menu.getElementsByTagName("li").length; + var moreItem = menuItems[numMenuItems - 1]; - var styleOptions = styleDrop.getElementsByTagName("option"); + for (var i = numMenuItems - 1; i >= 0; i--) { + var menuLink = menuItems[i]; - for (var i = 0; i < 4; i++) { - if (styleOptions[i].value === borderItem.border_style) { - styleOptions[i].selected = true; + if (menuLink.textContent.indexOf("More") > -1) { + moreItem = menuLink; break; } } - return indexWrapper; - } + var link = document.createElement("a"); + link.href = "#"; + link.innerHTML = "BBB Settings"; + link.addEventListener("click", function(event) { + openMenu(); - function createTextSection(header, text) { - var sectionFrag = document.createDocumentFragment(); + event.preventDefault(); + }, false); - if (header) { - var sectionHeader = document.createElement("h2"); - sectionHeader.innerHTML = header; - sectionHeader.className = "bbb-header"; - sectionFrag.appendChild(sectionHeader); - } + var item = document.createElement("li"); + item.appendChild(link); - if (text) { - var desc = document.createElement("div"); - desc.innerHTML = text; - desc.className = "bbb-section-text"; - sectionFrag.appendChild(desc); - } + if (moreItem) + menu.insertBefore(item, moreItem); + else + menu.appendChild(item); - return sectionFrag; + window.addEventListener("resize", adjustMenuTimer, false); } - Element.prototype.bbbTextSection = function(header, text) { - this.appendChild(createTextSection(header, text)); - }; + function openMenu() { + if (bbb.el.menu.window) + return; - function createBackupSection() { - var sectionFrag = document.createDocumentFragment(); + loadSettings(); + createMenu(); + } - var sectionHeader = document.createElement("h2"); - sectionHeader.innerHTML = "Backup/Restore Settings"; - sectionHeader.className = "bbb-header"; - sectionFrag.appendChild(sectionHeader); + function reloadMenu() { + removeMenu(); + createMenu(); + } - var sectionText = document.createElement("div"); - sectionText.innerHTML = "When creating a backup, there are two options. Creating a text backup will provide a plain text format backup in the area provided that can be copied and saved where desired. Creating a backup page will open a new page that can be saved with the browser's \"save page\" or bookmark options. To restore a backup, copy and paste the desired backup into the provided area and click \"Restore Backup\"."; - sectionText.className = "bbb-section-text"; - sectionFrag.appendChild(sectionText); + function createMenu() { + var menu = bbb.el.menu.window = document.createElement("div"); + menu.id = "bbb_menu"; + menu.style.visibility = "hidden"; - var sectionDiv = document.createElement("div"); - sectionDiv.className = "bbb-section-options"; - sectionDiv.innerHTML = "Backup Text:
"; - sectionFrag.appendChild(sectionDiv); + var tip = bbb.el.menu.tip = document.createElement("div"); + tip.id = "bbb-expl"; + menu.appendChild(tip); - var backupTextarea = bbb.el.menu.backupTextarea = document.createElement("textarea"); - backupTextarea.className = "bbb-backup-area"; - sectionDiv.appendChild(backupTextarea); + var header = document.createElement("h1"); + header.innerHTML = "Better Better Booru Settings"; + header.style.textAlign = "center"; + menu.appendChild(header); - var buttonDiv = document.createElement("div"); - buttonDiv.className = "bbb-section-options"; - sectionFrag.appendChild(buttonDiv); + var tabBar = document.createElement("div"); + tabBar.style.padding = "0px 15px"; + tabBar.addEventListener("click", function(event) { + var target = event.target; - var textBackup = document.createElement("a"); - textBackup.innerHTML = "Create Backup Text"; - textBackup.href = "#"; - textBackup.className = "bbb-button"; - textBackup.style.marginRight = "15px"; - textBackup.addEventListener("click", function(event) { - createBackupText(); - event.preventDefault(); - }, false); - buttonDiv.appendChild(textBackup); + if (target.href) + changeTab(target); - var pageBackup = document.createElement("a"); - pageBackup.innerHTML = "Create Backup Page"; - pageBackup.href = "#"; - pageBackup.className = "bbb-button"; - pageBackup.style.marginRight = "15px"; - pageBackup.addEventListener("click", function(event) { - createBackupPage(); event.preventDefault(); }, false); - buttonDiv.appendChild(pageBackup); + menu.appendChild(tabBar); - var restoreBackup = document.createElement("a"); - restoreBackup.innerHTML = "Restore Backup"; - restoreBackup.style.cssFloat = "right"; - restoreBackup.href = "#"; - restoreBackup.className = "bbb-button"; - restoreBackup.addEventListener("click", function(event) { - restoreBackupText(); - event.preventDefault(); - }, false); - buttonDiv.appendChild(restoreBackup); + var generalTab = bbb.el.menu.generalTab = document.createElement("a"); + generalTab.name = "general"; + generalTab.href = "#"; + generalTab.innerHTML = "General"; + generalTab.className = "bbb-tab bbb-active-tab"; + tabBar.appendChild(generalTab); - return sectionFrag; - } + var blacklistTab = bbb.el.menu.blacklistTab = document.createElement("a"); + blacklistTab.name = "blacklist"; + blacklistTab.href = "#"; + blacklistTab.innerHTML = "Blacklist"; + blacklistTab.className = "bbb-tab"; + tabBar.appendChild(blacklistTab); - Element.prototype.bbbBackupSection = function() { - this.appendChild(createBackupSection()); - }; + var borderTab = bbb.el.menu.borderTab = document.createElement("a"); + borderTab.name = "borders"; + borderTab.href = "#"; + borderTab.innerHTML = "Borders"; + borderTab.className = "bbb-tab"; + tabBar.appendChild(borderTab); - function createTocSection(page) { - // Generate a Table of Contents based on the page's current section headers. - var sectionFrag = document.createDocumentFragment(); - var pageSections = page.getElementsByTagName("h2"); + var layoutTab = bbb.el.menu.layoutTab = document.createElement("a"); + layoutTab.name = "layout"; + layoutTab.href = "#"; + layoutTab.innerHTML = "Layout"; + layoutTab.className = "bbb-tab"; + tabBar.appendChild(layoutTab); - var sectionHeader = document.createElement("h2"); - sectionHeader.innerHTML = "Table of Contents"; - sectionHeader.className = "bbb-header"; - sectionFrag.appendChild(sectionHeader); + var prefTab = bbb.el.menu.prefTab = document.createElement("a"); + prefTab.name = "pref"; + prefTab.href = "#"; + prefTab.innerHTML = "Preferences"; + prefTab.className = "bbb-tab"; + tabBar.appendChild(prefTab); - var sectionText = document.createElement("div"); - sectionText.className = "bbb-section-text"; - sectionFrag.appendChild(sectionText); + var helpTab = bbb.el.menu.helpTab = document.createElement("a"); + helpTab.name = "help"; + helpTab.href = "#"; + helpTab.innerHTML = "Help"; + helpTab.className = "bbb-tab"; + tabBar.appendChild(helpTab); - var tocList = document.createElement("ol"); - tocList.className = "bbb-toc"; - sectionText.appendChild(tocList); + var scrollDiv = bbb.el.menu.scrollDiv = document.createElement("div"); + scrollDiv.className = "bbb-scroll-div"; + menu.appendChild(scrollDiv); + scrollDiv.scrollTop = 0; - for (var i = 0, il = pageSections.length; i < il;) { - var listItem = document.createElement("li"); - tocList.appendChild(listItem); + var generalPage = bbb.el.menu.generalPage = document.createElement("div"); + generalPage.className = "bbb-page"; + generalPage.style.display = "block"; + scrollDiv.appendChild(generalPage); - var linkItem = document.createElement("a"); - linkItem.textContent = pageSections[i].textContent; - linkItem.href = "#" + (++i); - listItem.appendChild(linkItem); - } + generalPage.bbbSection(bbb.sections.browse); + generalPage.bbbSection(bbb.sections.control); + generalPage.bbbSection(bbb.sections.endless); + generalPage.bbbSection(bbb.sections.misc); - tocList.addEventListener("click", function (event) { - var target = event.target; - var targetValue = target.href; + var blacklistPage = bbb.el.menu.blacklistPage = document.createElement("div"); + blacklistPage.className = "bbb-page"; + scrollDiv.appendChild(blacklistPage); - if (targetValue) { - var sectionTop = pageSections[targetValue.split("#")[1]].offsetTop; - bbb.el.menu.scrollDiv.scrollTop = sectionTop; - event.preventDefault(); - } - }, false); + blacklistPage.bbbSection(bbb.sections.blacklist_options); + blacklistPage.bbbBlacklistSection(); - return sectionFrag; - } + var layoutPage = bbb.el.menu.layoutPage = document.createElement("div"); + layoutPage.className = "bbb-page"; + scrollDiv.appendChild(layoutPage); - Element.prototype.bbbTocSection = function() { - var page = this; - page.insertBefore(createTocSection(page), page.firstChild); - }; + layoutPage.bbbSection(bbb.sections.sidebar); + layoutPage.bbbSection(bbb.sections.notices); + layoutPage.bbbSection(bbb.sections.misc_layout); - function Option(type, def, lbl, expl, optPropObject) { - /* - * Option type notes - * ================= - * By specifying a unique type, you can create a specialized menu option. - * - * Checkbox, text, and number do not require any extra properties. - * - * Dropdown requires either txtOptions, numRange, or numList. - * txtOptions = Array containing a list of options and their values separated by a colon. (ex: ["option1:value1", "option2:value2"]) - * numRange = Array containing the starting and ending numbers of the number range. - * numList = Array containing a list of the desired numbers. - * If more than one of these is provided, they are added to the list in this order: txtOptions, numList, numRange - */ + var bordersPage = bbb.el.menu.bordersPage = document.createElement("div"); + bordersPage.className = "bbb-page"; + scrollDiv.appendChild(bordersPage); - this.type = type; - this.def = def; // Default. - this.label = lbl; - this.expl = expl; // Explanation. + bordersPage.bbbSection(bbb.sections.border_options); + bordersPage.bbbSection(bbb.sections.status_borders); + bordersPage.bbbSection(bbb.sections.tag_borders); - if (optPropObject) { // Additional properties provided in the form of an object. - for (var i in optPropObject) { - if (optPropObject.hasOwnProperty(i)) - this[i] = optPropObject[i]; - } - } - } + var prefPage = bbb.el.menu.prefPage = document.createElement("div"); + prefPage.className = "bbb-page"; + scrollDiv.appendChild(prefPage); - function Section(type, settingList, header, text) { - /* - * Section type notes - * ================== - * Current section types are general and border. - * - * The setting list for general sections are provided in the form of an array containing the setting names as strings. - * The setting list for border sections is the setting name containing the borders as a string. - */ + prefPage.bbbSection(bbb.sections.script_settings); + prefPage.bbbBackupSection(); - this.type = type; - this.settings = settingList; - this.header = header; - this.text = text; - } + var helpPage = bbb.el.menu.helpPage = document.createElement("div"); + helpPage.className = "bbb-page"; + scrollDiv.appendChild(helpPage); - function Border(tags, isEnabled, color, style, className) { - this.tags = tags; - this.is_enabled = isEnabled; - this.border_color = color; - this.border_style = style; - this.class_name = className; - } + helpPage.bbbTextSection('Thumbnail Matching Rules', 'For creating thumbnail matching rules, please consult the following examples:
Wildcards can be used with any of the above methods:
Multiple match rules can be specified by using commas or separate lines when possible:
Tags can be nested/grouped together by using parentheses coupled with percent signs:
The following metatags are supported:
The id, score, favcount, width, and height metatags can also use number ranges for matching:'); + helpPage.bbbTextSection('Hotkeys', 'Posts
Note: Numbers refer to the main typing keypad and not the numeric keypad.

General'); + helpPage.bbbTextSection('Questions, Suggestions, or Bugs?', 'If you have any questions, please use the Greasy Fork feedback forums located here. If you\'d like to report a bug or make a suggestion, please create an issue on GitHub here.'); + helpPage.bbbTocSection(); - function borderSet() { - var formatted = []; + var close = document.createElement("a"); + close.innerHTML = "Save & Close"; + close.href = "#"; + close.className = "bbb-button"; + close.style.marginRight = "15px"; + close.addEventListener("click", function(event) { + removeMenu(); + saveSettings(); + event.preventDefault(); + }, false); - for (var i = 0, il = arguments.length; i < il; i++) { - var border = arguments[i]; + var cancel = document.createElement("a"); + cancel.innerHTML = "Cancel"; + cancel.href = "#"; + cancel.className = "bbb-button"; + cancel.addEventListener("click", function(event) { + removeMenu(); + loadSettings(); + event.preventDefault(); + }, false); - formatted.push(new Border(border[0], border[1], border[2], border[3], border[4])); - } + var reset = document.createElement("a"); + reset.innerHTML = "Reset to Defaults"; + reset.href = "#"; + reset.className = "bbb-button"; + reset.style.cssFloat = "right"; + reset.style.color = "#ff1100"; + reset.addEventListener("click", function(event) { + loadDefaults(); + reloadMenu(); + event.preventDefault(); + }, false); - return formatted; - } + menu.appendChild(close); + menu.appendChild(cancel); + menu.appendChild(reset); - function resetBorderElements(section) { - // Reset the list of border items after moving or creating a new border. - var borderElements = section.children; + var tagEditBlocker = bbb.el.menu.tagEditBlocker = document.createElement("div"); + tagEditBlocker.className = "bbb-edit-blocker"; + menu.appendChild(tagEditBlocker); - for (var i = 0, il = borderElements.length; i < il; i++) { - var borderElement = borderElements[i]; + var tagEditBox = document.createElement("div"); + tagEditBox.className = "bbb-edit-box"; + tagEditBlocker.appendChild(tagEditBox); - borderElement.className = borderElement.className.replace(/\s?bbb-no-highlight/gi, ""); - borderElement.setAttribute("data-bbb-index", i); - } - } + var tagEditHeader = document.createElement("h2"); + tagEditHeader.innerHTML = "Tag Editor"; + tagEditHeader.className = "bbb-header"; + tagEditBox.appendChild(tagEditHeader); - function deleteBorder(borderSettings, borderElement) { - // Remove a border and if it's the last border, create a blank disabled one. - var section = borderElement.parentNode; - var index = Number(borderElement.getAttribute("data-bbb-index")); + var tagEditArea = bbb.el.menu.tagEditArea = document.createElement("textarea"); + tagEditArea.className = "bbb-edit-area"; + tagEditBox.appendChild(tagEditArea); - section.removeChild(borderElement); - borderSettings.splice(index,1); + var tagEditOk = document.createElement("a"); + tagEditOk.innerHTML = "OK"; + tagEditOk.href = "#"; + tagEditOk.className = "bbb-button"; + tagEditOk.addEventListener("click", function(event) { + var tags = searchMultiToSingle(tagEditArea.value); + var args = bbb.tagEdit; - if (borderSettings.length === 0) { - // If no borders are left, add a new blank border. - var newBorderItem = new Border("", false, "#000000", "solid"); - borderSettings.push(newBorderItem); + tagEditBlocker.style.display = "none"; + args.input.value = tags; + args.object[args.prop] = tags; + event.preventDefault(); + }, false); + tagEditBox.appendChild(tagEditOk); - var newBorderElement = createBorderOption(borderSettings, 0); - section.insertBefore(newBorderElement, section.children[0]); - } + var tagEditCancel = document.createElement("a"); + tagEditCancel.innerHTML = "Cancel"; + tagEditCancel.href = "#"; + tagEditCancel.className = "bbb-button"; + tagEditCancel.style.cssFloat = "right"; + tagEditCancel.addEventListener("click", function(event) { + tagEditBlocker.style.display = "none"; + event.preventDefault(); + }, false); + tagEditBox.appendChild(tagEditCancel); - resetBorderElements(section); - } + // Add menu to the DOM and manipulate the dimensions. + document.body.appendChild(menu); - function moveBorder(borderSettings, borderElement) { - // Prepare to move a border and wait for the user to click where it'll go. - var section = borderElement.parentNode; - var index = Number(borderElement.getAttribute("data-bbb-index")); + var viewHeight = document.documentElement.clientHeight; + var barWidth = scrollbarWidth(); + var scrollDivDiff = menu.offsetHeight - scrollDiv.clientHeight; - borderElement.className += " bbb-no-highlight"; - borderElement.nextSibling.className += " bbb-no-highlight"; - bbb.borderEdit = {mode: "move", settings: borderSettings, section: section, index: index, element: borderElement}; - section.className += " bbb-insert-highlight"; - bbb.el.menu.window.addEventListener("click", insertBorder, true); - } + scrollDiv.style.maxHeight = viewHeight - scrollDiv.bbbGetPadding().height - scrollDivDiff - 50 + "px"; // Subtract 50 for margins (25 each). + scrollDiv.style.minWidth = 901 + barWidth + 3 + "px"; // Should keep the potential scrollbar from intruding on the original drawn layout if I'm thinking about this correctly. Seems to work in practice anyway. + scrollDiv.style.paddingLeft = barWidth + 3 + "px"; - function newBorder(borderSettings, borderElement) { - // Prepare to create a border and wait for the user to click where it'll go. - var section = borderElement.parentNode; + var menuWidth = menu.offsetWidth; - bbb.borderEdit = {mode: "new", settings: borderSettings, section: section}; - section.className += " bbb-insert-highlight"; - bbb.el.menu.window.addEventListener("click", insertBorder, true); + menu.style.marginLeft = -menuWidth / 2 + "px"; + menu.style.visibility = "visible"; } - function insertBorder (event) { - // Place either a new or moved border where indicated. - var target = event.target; - var section = bbb.borderEdit.section; + function createSection(section) { + var sectionFrag = document.createDocumentFragment(); + var i, il; // Loop variables. - if (target.className === "bbb-border-divider") { - var newIndex = Number(target.parentNode.getAttribute("data-bbb-index")); - var borderSettings = bbb.borderEdit.settings; + if (section.header) { + var sectionHeader = document.createElement("h2"); + sectionHeader.innerHTML = section.header; + sectionHeader.className = "bbb-header"; + sectionFrag.appendChild(sectionHeader); + } - if (bbb.borderEdit.mode === "new") { // Make a new border. - var newBorderItem = new Border("", false, "#000000", "solid"); - borderSettings.splice(newIndex, 0, newBorderItem); + if (section.text) { + var sectionText = document.createElement("div"); + sectionText.innerHTML = section.text; + sectionText.className = "bbb-section-text"; + sectionFrag.appendChild(sectionText); + } - var newBorderElement = createBorderOption(borderSettings, newIndex); + var sectionDiv = document.createElement("div"); + sectionDiv.className = "bbb-section-options"; + sectionFrag.appendChild(sectionDiv); - section.insertBefore(newBorderElement, section.children[newIndex]); + if (section.type === "general") { + var settingList = section.settings; + var sll = settingList.length; + var halfway = (sll > 1 ? Math.ceil(sll / 2) : 0); - } - else if (bbb.borderEdit.mode === "move") { // Move the border. - var oldIndex = bbb.borderEdit.index; + var leftSide = document.createElement("div"); + leftSide.className = "bbb-section-options-left"; + sectionDiv.appendChild(leftSide); - if (newIndex !== oldIndex) { - var borderItem = borderSettings.splice(oldIndex, 1)[0]; - var borderElement = bbb.borderEdit.element; + var rightSide = document.createElement("div"); + rightSide.className = "bbb-section-options-right"; + sectionDiv.appendChild(rightSide); - if (newIndex < oldIndex) - borderSettings.splice(newIndex, 0, borderItem); - else if (newIndex > oldIndex) - borderSettings.splice(newIndex - 1, 0, borderItem); + var optionTarget = leftSide; - section.insertBefore(borderElement, section.children[newIndex]); - } + for (i = 0; i < sll; i++) { + var settingName = settingList[i]; + + if (halfway && i >= halfway) + optionTarget = rightSide; + + var newOption = createOption(settingName); + optionTarget.appendChild(newOption); } } + else if (section.type === "border") { + var borderSettings = bbb.user[section.settings]; - resetBorderElements(section); - section.className = section.className.replace(/\s?bbb-insert-highlight/gi, ""); - bbb.el.menu.window.removeEventListener("click", insertBorder, true); - } + for (i = 0, il = borderSettings.length; i < il; i++) { + var newBorderOption = createBorderOption(borderSettings, i); + sectionDiv.appendChild(newBorderOption); + } - function showTip(event, text, styleString) { - var x = event.clientX; - var y = event.clientY; - var tip = bbb.el.menu.tip; - var topOffset = 0; + var indexWrapper = document.createElement("div"); + indexWrapper.setAttribute("data-bbb-index", i); + sectionDiv.appendChild(indexWrapper); - if (styleString) - tip.setAttribute("style", styleString); + var borderDivider = document.createElement("div"); + borderDivider.className = "bbb-border-divider"; + indexWrapper.appendChild(borderDivider); + } - tip.innerHTML = text; - tip.style.visibility = "hidden"; - tip.style.display = "block"; + return sectionFrag; + } - // Resize the tip to minimize blank space. - var origHeight = tip.clientHeight; - var padding = tip.bbbGetPadding(); - var paddingWidth = padding.width; + Element.prototype.bbbSection = function(section) { + this.appendChild(createSection(section)); + }; - while (origHeight >= tip.clientHeight && tip.clientWidth > 15) - tip.style.width = tip.clientWidth - paddingWidth - 2 + "px"; + function createOption(settingName) { + var optionObject = bbb.options[settingName]; + var userSetting = bbb.user[settingName]; + var i, il; // Loop variables. - tip.style.width = tip.clientWidth - paddingWidth + 2 + "px"; + var label = document.createElement("label"); + label.className = "bbb-general-label"; - if (tip.scrollWidth > tip.clientWidth) - tip.style.width = "auto"; + var textSpan = document.createElement("span"); + textSpan.className = "bbb-general-text"; + textSpan.innerHTML = optionObject.label; + label.appendChild(textSpan); - // Don't allow the tip to go above the top of the window. - if (y - tip.offsetHeight - 2 < 5) - topOffset = y - tip.offsetHeight - 7; + var inputSpan = document.createElement("span"); + inputSpan.className = "bbb-general-input"; + label.appendChild(inputSpan); + + var item; + var itemFrag = document.createDocumentFragment(); + + switch (optionObject.type) { + case "dropdown": + var txtOptions = optionObject.txtOptions; + var numRange = optionObject.numRange; + var numList = optionObject.numList; + var selectOption; + + item = document.createElement("select"); + item.name = settingName; + + if (txtOptions) { + for (i = 0, il = txtOptions.length; i < il; i++) { + var txtOption = txtOptions[i].split(":"); + + selectOption = document.createElement("option"); + selectOption.innerHTML = txtOption[0]; + selectOption.value = txtOption[1]; + + if (selectOption.value === String(userSetting)) + selectOption.selected = true; + + item.appendChild(selectOption); + } + } + + if (numList) { + for (i = 0, il = numList.length; i < il; i++) { + selectOption = document.createElement("option"); + selectOption.innerHTML = numList[i]; + selectOption.value = numList[i]; + + if (selectOption.value === String(userSetting)) + selectOption.selected = true; + + item.appendChild(selectOption); + } + } + + if (numRange) { + var end = numRange[1]; + + for (i = numRange[0]; i <= end; i++) { + selectOption = document.createElement("option"); + selectOption.innerHTML = i; + selectOption.value = i; + + if (selectOption.value === String(userSetting)) + selectOption.selected = true; + + item.appendChild(selectOption); + } + } + + item.addEventListener("change", function() { + var selected = this.value; + bbb.user[settingName] = (bbbIsNum(selected) ? Number(selected) : selected); + bbb.settings.changed[settingName] = true; + }, false); + itemFrag.appendChild(item); + break; + case "checkbox": + item = document.createElement("input"); + item.name = settingName; + item.type = "checkbox"; + item.checked = userSetting; + item.addEventListener("click", function() { + bbb.user[settingName] = this.checked; + bbb.settings.changed[settingName] = true; + }, false); + itemFrag.appendChild(item); + break; + case "text": + item = document.createElement("input"); + item.name = settingName; + item.type = "text"; + item.value = userSetting; + item.addEventListener("change", function() { + bbb.user[settingName] = (optionObject.isTagInput ? this.value.bbbTagClean() : this.value.bbbSpaceClean()); + bbb.settings.changed[settingName] = true; + }, false); + itemFrag.appendChild(item); + + if (optionObject.isTagInput) { + var tagExpand = document.createElement("a"); + tagExpand.href = "#"; + tagExpand.className = "bbb-edit-link"; + tagExpand.innerHTML = "»"; + tagExpand.addEventListener("click", function(event) { + tagEditWindow(item, bbb.user, settingName); + event.preventDefault(); + }, false); + itemFrag.appendChild(tagExpand); + } + break; + case "number": + item = document.createElement("input"); + item.name = settingName; + item.type = "text"; + item.value = userSetting; + item.addEventListener("change", function() { + bbb.user[settingName] = Number(this.value); + bbb.settings.changed[settingName] = true; + }, false); + itemFrag.appendChild(item); + break; + default: + bbbNotice('Unexpected menu object type for "' + optionObject.label + '". (Type: ' + optionObject.type + ')', -1); + return label; + } + inputSpan.appendChild(itemFrag); + + var explLink = document.createElement("a"); + explLink.innerHTML = "?"; + explLink.href = "#"; + explLink.className = "bbb-expl-link"; + explLink.bbbSetTip(bbb.options[settingName].expl); + inputSpan.appendChild(explLink); + + return label; + } + + function createBorderOption(borderSettings, index) { + var borderItem = borderSettings[index]; + var isStatus = (borderItem.class_name ? true : false); + + var borderSpacer = document.createElement("span"); + borderSpacer.className = "bbb-border-spacer"; + + var indexWrapper = document.createElement("div"); + indexWrapper.setAttribute("data-bbb-index", index); + + var borderDivider = document.createElement("div"); + borderDivider.className = "bbb-border-divider"; + indexWrapper.appendChild(borderDivider); + + var borderDiv = document.createElement("div"); + borderDiv.className = "bbb-border-div"; + indexWrapper.appendChild(borderDiv); + + var borderBarDiv = document.createElement("div"); + borderBarDiv.className = "bbb-border-bar"; + borderDiv.appendChild(borderBarDiv); + + var enableLabel = document.createElement("label"); + enableLabel.innerHTML = "Enabled:"; + borderBarDiv.appendChild(enableLabel); + + var enableBox = document.createElement("input"); + enableBox.type = "checkbox"; + enableBox.checked = borderItem.is_enabled; + enableBox.addEventListener("click", function() { borderItem.is_enabled = this.checked; }, false); + enableLabel.appendChild(enableBox); + + var editSpan = document.createElement("span"); + editSpan.style.cssFloat = "right"; + borderBarDiv.appendChild(editSpan); + + var moveButton = document.createElement("a"); + moveButton.href = "#"; + moveButton.innerHTML = "Move"; + moveButton.className = "bbb-border-button"; + moveButton.addEventListener("click", function(event) { + moveBorder(borderSettings, indexWrapper); + event.preventDefault(); + }, false); + moveButton.bbbSetTip("Click the blue highlighted area that indicates where you would like to move this border."); + editSpan.appendChild(moveButton); + + var previewButton = document.createElement("a"); + previewButton.href = "#"; + previewButton.innerHTML = "Preview"; + previewButton.className = "bbb-border-button"; + previewButton.addEventListener("click", function(event) { event.preventDefault(); }, false); + previewButton.bbbBorderPreview(borderItem); + editSpan.appendChild(previewButton); + + if (!isStatus) { + var deleteButton = document.createElement("a"); + deleteButton.href = "#"; + deleteButton.innerHTML = "Delete"; + deleteButton.className = "bbb-border-button"; + deleteButton.addEventListener("click", function(event) { + deleteBorder(borderSettings, indexWrapper); + event.preventDefault(); + }, false); + editSpan.appendChild(deleteButton); + + var newButton = document.createElement("a"); + newButton.href = "#"; + newButton.innerHTML = "New"; + newButton.className = "bbb-border-button"; + newButton.addEventListener("click", function(event) { + createBorder(borderSettings, indexWrapper); + event.preventDefault(); + }, false); + newButton.bbbSetTip("Click the blue highlighted area that indicates where you would like to create a border."); + editSpan.appendChild(newButton); + } + + editSpan.appendChild(borderSpacer.cloneNode(false)); + + var helpButton = document.createElement("a"); + helpButton.href = "#"; + helpButton.innerHTML = "Help"; + helpButton.className = "bbb-border-button"; + helpButton.bbbSetTip("Enabled: When checked, the border will be applied. When unchecked, it won't be applied.Status/Tags: Describes the posts that the border should be applied to. For custom tag borders, you may specify the rules the post must match for the border to be applied. Please read the \"Thumbnail Matching Rules\" section under the help tab for information about creating rules.Color: Set the color of the border. Hex RGB color codes (#000000, #FFFFFF, etc.) are the recommended values.Style: Set how the border looks. Please note that double only works with a border width of 3 or higher.Move: Move the border to a new position. Higher borders have higher priority. In the event of a post matching more than 4 borders, the first 4 borders get applied and the rest are ignored. If single color borders are enabled, only the first matching border is applied.Preview: Display a preview of the border's current settings.Delete: Remove the border and its settings.New: Create a new border."); + editSpan.appendChild(helpButton); + + var borderSettingsDiv = document.createElement("div"); + borderSettingsDiv.className = "bbb-border-settings"; + borderDiv.appendChild(borderSettingsDiv); + + var nameLabel = document.createElement("label"); + nameLabel.className = "bbb-border-name"; + borderSettingsDiv.appendChild(nameLabel); + + if (isStatus) + nameLabel.innerHTML = "Status:" + borderItem.tags; + else { + nameLabel.innerHTML = "Tags:"; + + var nameInput = document.createElement("input"); + nameInput.type = "text"; + nameInput.value = borderItem.tags; + nameInput.addEventListener("change", function() { borderItem.tags = this.value.bbbTagClean(); }, false); + nameLabel.appendChild(nameInput); + + var nameExpand = document.createElement("a"); + nameExpand.href = "#"; + nameExpand.className = "bbb-edit-link"; + nameExpand.innerHTML = "»"; + nameExpand.addEventListener("click", function(event) { + tagEditWindow(nameInput, borderItem, "tags"); + event.preventDefault(); + }, false); + nameLabel.appendChild(nameExpand); + } + + var colorLabel = document.createElement("label"); + colorLabel.innerHTML = "Color:"; + colorLabel.className = "bbb-border-color"; + borderSettingsDiv.appendChild(colorLabel); + + var colorInput = document.createElement("input"); + colorInput.type = "text"; + colorInput.value = borderItem.border_color; + colorInput.addEventListener("change", function() { borderItem.border_color = this.value.bbbSpaceClean(); }, false); + colorLabel.appendChild(colorInput); + + var styleLabel = document.createElement("label"); + styleLabel.innerHTML = "Style:"; + styleLabel.className = "bbb-border-style"; + borderSettingsDiv.appendChild(styleLabel); + + var styleDrop = document.createElement("select"); + styleDrop.addEventListener("change", function() { borderItem.border_style = this.value; }, false); + styleLabel.appendChild(styleDrop); + + var solidOption = document.createElement("option"); + solidOption.innerHTML = "solid"; + solidOption.value = "solid"; + styleDrop.appendChild(solidOption); + + var dashedOption = document.createElement("option"); + dashedOption.innerHTML = "dashed"; + dashedOption.value = "dashed"; + styleDrop.appendChild(dashedOption); + + var dottedOption = document.createElement("option"); + dottedOption.innerHTML = "dotted"; + dottedOption.value = "dotted"; + styleDrop.appendChild(dottedOption); + + var doubleOption = document.createElement("option"); + doubleOption.innerHTML = "double"; + doubleOption.value = "double"; + styleDrop.appendChild(doubleOption); + + var styleOptions = styleDrop.getElementsByTagName("option"); + + for (var i = 0; i < 4; i++) { + if (styleOptions[i].value === borderItem.border_style) { + styleOptions[i].selected = true; + break; + } + } + + return indexWrapper; + } + + function createTextSection(header, text) { + var sectionFrag = document.createDocumentFragment(); + + if (header) { + var sectionHeader = document.createElement("h2"); + sectionHeader.innerHTML = header; + sectionHeader.className = "bbb-header"; + sectionFrag.appendChild(sectionHeader); + } + + if (text) { + var desc = document.createElement("div"); + desc.innerHTML = text; + desc.className = "bbb-section-text"; + sectionFrag.appendChild(desc); + } + + return sectionFrag; + } + + Element.prototype.bbbTextSection = function(header, text) { + this.appendChild(createTextSection(header, text)); + }; + + function createBackupSection() { + var sectionFrag = document.createDocumentFragment(); + + var sectionHeader = document.createElement("h2"); + sectionHeader.innerHTML = "Backup/Restore Settings"; + sectionHeader.className = "bbb-header"; + sectionFrag.appendChild(sectionHeader); + + var sectionDiv = document.createElement("div"); + sectionDiv.className = "bbb-section-options"; + sectionFrag.appendChild(sectionDiv); + + var backupTextarea = bbb.el.menu.backupTextarea = document.createElement("textarea"); + backupTextarea.className = "bbb-backup-area"; + sectionDiv.appendChild(backupTextarea); + + var buttonDiv = document.createElement("div"); + buttonDiv.className = "bbb-section-options"; + sectionFrag.appendChild(buttonDiv); + + var textBackup = document.createElement("a"); + textBackup.innerHTML = "Create Backup Text"; + textBackup.href = "#"; + textBackup.className = "bbb-button"; + textBackup.style.marginRight = "15px"; + textBackup.addEventListener("click", function(event) { + createBackupText(); + event.preventDefault(); + }, false); + buttonDiv.appendChild(textBackup); + + var pageBackup = document.createElement("a"); + pageBackup.innerHTML = "Create Backup Page"; + pageBackup.href = "#"; + pageBackup.className = "bbb-button"; + pageBackup.style.marginRight = "15px"; + pageBackup.addEventListener("click", function(event) { + createBackupPage(); + event.preventDefault(); + }, false); + buttonDiv.appendChild(pageBackup); + + var rightButtons = document.createElement("span"); + rightButtons.style.cssFloat = "right"; + buttonDiv.appendChild(rightButtons); + + var restoreBackup = document.createElement("a"); + restoreBackup.innerHTML = "Restore Backup"; + restoreBackup.style.marginRight = "15px"; + restoreBackup.href = "#"; + restoreBackup.className = "bbb-button"; + restoreBackup.addEventListener("click", function(event) { + restoreBackupText(); + event.preventDefault(); + }, false); + rightButtons.appendChild(restoreBackup); + + var helpButton = document.createElement("a"); + helpButton.innerHTML = "Help"; + helpButton.href = "#"; + helpButton.className = "bbb-button"; + helpButton.bbbSetTip("Create copies of your settings that can be used for recovering lost/corrupted settings or transferring settings.DirectionsThere are two options for creating a backup. Creating a text backup will provide a plain text format backup in the area provided that can be copied and saved where desired. Creating a backup page will open a new page that can be saved with the browser's \"save page\" or bookmark options.

To restore a backup, copy and paste the desired backup into the provided area and click \"Restore Backup\"."); + rightButtons.appendChild(helpButton); + + return sectionFrag; + } + + Element.prototype.bbbBackupSection = function() { + this.appendChild(createBackupSection()); + }; + + function createBlacklistSection() { + var sectionFrag = document.createDocumentFragment(); + + var sectionHeader = document.createElement("h2"); + sectionHeader.innerHTML = "Blacklist"; + sectionHeader.className = "bbb-header"; + sectionFrag.appendChild(sectionHeader); + + var sectionText = document.createElement("div"); + sectionText.innerHTML = "Hide posts that match the specified tag(s)."; + sectionText.className = "bbb-section-text"; + //sectionFrag.appendChild(sectionText); + + var sectionDiv = document.createElement("div"); + sectionDiv.className = "bbb-section-options"; + sectionFrag.appendChild(sectionDiv); + + var blacklistTextarea = bbb.el.menu.blacklistTextarea = document.createElement("textarea"); + blacklistTextarea.className = "bbb-blacklist-area"; + blacklistTextarea.value = searchSingleToMulti(bbb.user.script_blacklisted_tags); + blacklistTextarea.addEventListener("change", function() { bbb.user.script_blacklisted_tags = searchMultiToSingle(blacklistTextarea.value); }, false); + sectionDiv.appendChild(blacklistTextarea); + + var buttonDiv = document.createElement("div"); + buttonDiv.className = "bbb-section-options"; + sectionFrag.appendChild(buttonDiv); + + var formatButton = document.createElement("a"); + formatButton.innerHTML = "Format"; + formatButton.href = "#"; + formatButton.className = "bbb-button"; + formatButton.addEventListener("click", function(event) { + var textareaString = searchMultiToSingle(blacklistTextarea.value); + + blacklistTextarea.value = searchSingleToMulti(textareaString); + event.preventDefault(); + }, false); + buttonDiv.appendChild(formatButton); + + var helpButton = document.createElement("a"); + helpButton.innerHTML = "Help"; + helpButton.href = "#"; + helpButton.className = "bbb-button"; + helpButton.style.cssFloat = "right"; + helpButton.bbbSetTip("Hide posts that match the specified tag(s).DirectionsPlease read the \"Thumbnail Matching Rules\" section under the help tab for information about creating matching rules for posts you wish to blacklist. Blank lines will be ignored and are only used for improved readability.

All commas will be converted to new lines and all extra spaces and extra blank lines will be removed the next time the settings are opened. By using the \"Format\" button, you can manually perform this action on the blacklist rules. NoteWhen logged in, the account's \"Blacklisted tags\" list will override this option. This behavior can be changed with the \"override blacklist\" option under the preferences tab."); + buttonDiv.appendChild(helpButton); + + return sectionFrag; + } + + Element.prototype.bbbBlacklistSection = function() { + this.appendChild(createBlacklistSection()); + }; + + function createTocSection(page) { + // Generate a Table of Contents based on the page's current section headers. + var sectionFrag = document.createDocumentFragment(); + var pageSections = page.getElementsByTagName("h2"); + + var sectionHeader = document.createElement("h2"); + sectionHeader.innerHTML = "Table of Contents"; + sectionHeader.className = "bbb-header"; + sectionFrag.appendChild(sectionHeader); + + var sectionText = document.createElement("div"); + sectionText.className = "bbb-section-text"; + sectionFrag.appendChild(sectionText); + + var tocList = document.createElement("ol"); + tocList.className = "bbb-toc"; + sectionText.appendChild(tocList); + + for (var i = 0, il = pageSections.length; i < il;) { + var listItem = document.createElement("li"); + tocList.appendChild(listItem); + + var linkItem = document.createElement("a"); + linkItem.textContent = pageSections[i].textContent; + linkItem.href = "#" + (++i); + listItem.appendChild(linkItem); + } + + tocList.addEventListener("click", function (event) { + var target = event.target; + var targetValue = target.href; + + if (targetValue) { + var sectionTop = pageSections[targetValue.split("#")[1]].offsetTop; + bbb.el.menu.scrollDiv.scrollTop = sectionTop; + event.preventDefault(); + } + }, false); + + return sectionFrag; + } + + Element.prototype.bbbTocSection = function() { + var page = this; + page.insertBefore(createTocSection(page), page.firstElementChild); + }; + + function newOption(type, def, lbl, expl, optPropObject) { + /* + * Option type notes + * ================= + * By specifying a unique type, you can create a specialized menu option. + * + * Checkbox, text, and number do not require any extra properties. + * + * Dropdown requires either txtOptions, numRange, or numList. + * txtOptions = Array containing a list of options and their values separated by a colon. (ex: ["option1:value1", "option2:value2"]) + * numRange = Array containing the starting and ending numbers of the number range. + * numList = Array containing a list of the desired numbers. + * If more than one of these is provided, they are added to the list in this order: txtOptions, numList, numRange + */ + + var option = { + type: type, + def: def, // Default. + label: lbl, + expl: expl // Explanation. + }; + + if (optPropObject) { // Additional properties provided in the form of an object. + for (var i in optPropObject) { + if (optPropObject.hasOwnProperty(i)) + option[i] = optPropObject[i]; + } + } + + return option; + } + + function newSection(type, settingList, header, text) { + /* + * Section type notes + * ================== + * Current section types are general and border. + * + * The setting list for general sections are provided in the form of an array containing the setting names as strings. + * The setting list for border sections is the setting name containing the borders as a string. + */ + return { + type: type, + settings: settingList, + header: header, + text: text + }; + } + + function newBorder(tags, isEnabled, color, style, className) { + return { + tags: tags, + is_enabled: isEnabled, + border_color: color, + border_style: style, + class_name: className + }; + } + + function borderSet() { + var formatted = []; + + for (var i = 0, il = arguments.length; i < il; i++) { + var border = arguments[i]; + + formatted.push(newBorder(border[0], border[1], border[2], border[3], border[4])); + } + + return formatted; + } + + function resetBorderElements(section) { + // Reset the list of border items after moving or creating a new border. + var borderElements = section.children; + + for (var i = 0, il = borderElements.length; i < il; i++) { + var borderElement = borderElements[i]; + + borderElement.bbbRemoveClass("bbb-no-highlight"); + borderElement.setAttribute("data-bbb-index", i); + } + } + + function deleteBorder(borderSettings, borderElement) { + // Remove a border and if it's the last border, create a blank disabled one. + var section = borderElement.parentNode; + var index = Number(borderElement.getAttribute("data-bbb-index")); + + section.removeChild(borderElement); + borderSettings.splice(index,1); + + if (!borderSettings[0]) { + // If no borders are left, add a new blank border. + var newBorderItem = newBorder("", false, "#000000", "solid"); + borderSettings.push(newBorderItem); + + var newBorderElement = createBorderOption(borderSettings, 0); + section.insertBefore(newBorderElement, section.firstElementChild); + } + + resetBorderElements(section); + } + + function moveBorder(borderSettings, borderElement) { + // Prepare to move a border and wait for the user to click where it'll go. + var section = borderElement.parentNode; + var index = Number(borderElement.getAttribute("data-bbb-index")); + + borderElement.bbbAddClass("bbb-no-highlight"); + borderElement.nextSibling.bbbAddClass("bbb-no-highlight"); + bbb.borderEdit = {mode: "move", settings: borderSettings, section: section, index: index, el: borderElement}; + section.bbbAddClass("bbb-insert-highlight"); + bbb.el.menu.window.addEventListener("click", insertBorder, true); + } + + function createBorder(borderSettings, borderElement) { + // Prepare to create a border and wait for the user to click where it'll go. + var section = borderElement.parentNode; + + bbb.borderEdit = {mode: "new", settings: borderSettings, section: section}; + section.bbbAddClass("bbb-insert-highlight"); + bbb.el.menu.window.addEventListener("click", insertBorder, true); + } + + function insertBorder(event) { + // Place either a new or moved border where indicated. + var target = event.target; + var section = bbb.borderEdit.section; + + if (target.className === "bbb-border-divider") { + var newIndex = Number(target.parentNode.getAttribute("data-bbb-index")); + var borderSettings = bbb.borderEdit.settings; + + if (bbb.borderEdit.mode === "new") { // Make a new border. + var newBorderItem = newBorder("", false, "#000000", "solid"); + borderSettings.splice(newIndex, 0, newBorderItem); + + var newBorderElement = createBorderOption(borderSettings, newIndex); + + section.insertBefore(newBorderElement, section.children[newIndex]); + + } + else if (bbb.borderEdit.mode === "move") { // Move the border. + var oldIndex = bbb.borderEdit.index; + + if (newIndex !== oldIndex) { + var borderItem = borderSettings.splice(oldIndex, 1)[0]; + var borderElement = bbb.borderEdit.el; + + if (newIndex < oldIndex) + borderSettings.splice(newIndex, 0, borderItem); + else if (newIndex > oldIndex) + borderSettings.splice(newIndex - 1, 0, borderItem); + + section.insertBefore(borderElement, section.children[newIndex]); + } + } + } + + resetBorderElements(section); + section.bbbRemoveClass("bbb-insert-highlight"); + bbb.el.menu.window.removeEventListener("click", insertBorder, true); + } + + function showTip(event, content, styleString) { + var x = event.clientX; + var y = event.clientY; + var tip = bbb.el.menu.tip; + + if (styleString) + tip.setAttribute("style", styleString); + + formatTip(event, tip, content, x, y); + } + + function hideTip() { + bbb.el.menu.tip.removeAttribute("style"); + } + + Element.prototype.bbbBorderPreview = function(borderItem) { + this.addEventListener("click", function(event) { showTip(event, "\"IMAGE\"", "background-color: #FFFFFF;"); }, false); + this.addEventListener("mouseout", hideTip, false); + }; + + Element.prototype.bbbSetTip = function(text) { + var tip = bbb.el.menu.tip; + + this.addEventListener("click", function(event) { + showTip(event, text, false); + event.preventDefault(); + }, false); + this.addEventListener("mouseout", function() { bbb.timers.hideTip = window.setTimeout(hideTip, 100); }, false); + tip.addEventListener("mouseover", function() { window.clearTimeout(bbb.timers.hideTip); }, false); + tip.addEventListener("mouseleave", hideTip, false); + }; + + function changeTab(tab) { + var activeTab = document.getElementsByClassName("bbb-active-tab")[0]; + + if (tab === activeTab) + return; + + activeTab.bbbRemoveClass("bbb-active-tab"); + bbb.el.menu[activeTab.name + "Page"].style.display = "none"; + bbb.el.menu.scrollDiv.scrollTop = 0; + tab.bbbAddClass("bbb-active-tab"); + bbb.el.menu[tab.name + "Page"].style.display = "block"; + } + + function tagEditWindow(input, object, prop) { + bbb.el.menu.tagEditBlocker.style.display = "block"; + bbb.el.menu.tagEditArea.value = searchSingleToMulti(input.value); + bbb.el.menu.tagEditArea.focus(); + bbb.tagEdit = {input: input, object: object, prop: prop}; + } + + function adjustMenuHeight() { + var menu = bbb.el.menu.window; + var scrollDiv = bbb.el.menu.scrollDiv; + var viewHeight = document.documentElement.clientHeight; + var scrollDivDiff = menu.offsetHeight - scrollDiv.clientHeight; + + scrollDiv.style.maxHeight = viewHeight - scrollDiv.bbbGetPadding().height - scrollDivDiff - 50 + "px"; // Subtract 50 for margins (25 each). + bbb.timers.adjustMenu = 0; + } + + function adjustMenuTimer() { + if (!bbb.timers.adjustMenu && bbb.el.menu.window) + bbb.timers.adjustMenu = window.setTimeout(adjustMenuHeight, 50); + } + + function removeMenu() { + // Destroy the menu so that it gets rebuilt. + var menu = bbb.el.menu.window; + + if (!menu) + return; + + menu.parentNode.removeChild(menu); + bbb.el.menu = {}; + } + + function loadSettings() { + // Load stored settings. + if (typeof(localStorage.bbb_settings) === "undefined") + loadDefaults(); + else { + bbb.user = JSON.parse(localStorage.bbb_settings); + checkUser(bbb.user, bbb.options); + + if (bbb.user.bbb_version !== bbb.options.bbb_version) { + convertSettings("load"); + saveSettings(); + } + } + } + + function loadDefaults() { + // Load the default settings. + bbb.user = {}; + + for (var i in bbb.options) { + if (bbb.options.hasOwnProperty(i)) { + if (typeof(bbb.options[i].def) !== "undefined") + bbb.user[i] = bbb.options[i].def; + else + bbb.user[i] = bbb.options[i]; + } + } + } + + function checkUser(user, options) { + // Verify the user has all the base settings and add them with their default values if they don't. + for (var i in options) { + if (options.hasOwnProperty(i)) { + if (typeof(user[i]) === "undefined") { + if (typeof(options[i].def) !== "undefined") + user[i] = options[i].def; + else + user[i] = options[i]; + } + else if (typeof(user[i]) === "object" && !(user[i] instanceof Array)) + checkUser(user[i], options[i]); + } + } + } + + function saveSettings() { + // Save the user settings to localStorage after making any necessary checks/adjustments. + if (bbb.settings.changed.track_new && !bbb.user.track_new && bbb.user.track_new_data.viewed) // Reset new post tracking if it has been disabled. + bbb.user.track_new_data = bbb.options.track_new_data.def; + + if (bbb.settings.changed.thumb_cache_limit && thumb_cache_limit !== bbb.user.thumb_cache_limit) // Trim down the thumb cache as necessary if the limit has changed. + adjustThumbCache(); + + if (bbb.settings.changed.thumbnail_count) // Update the link limit values if the user has changed the value. + fixLimit(bbb.user.thumbnail_count); + + if (bbb.settings.changed.blacklist_highlight_color && bbb.user.blacklist_highlight_color === "") // Use the default highlight color if the field is left blank. + bbb.user.blacklist_highlight_color = "#CCCCCC"; + + bbb.settings.changed = {}; + localStorage.bbb_settings = JSON.stringify(bbb.user); + } + + function updateSettings() { + // Change & save the settings without the panel. Accepts a comma delimited list of alternating settings and values: setting1, value1, setting2, value2 + loadSettings(); + + for (var i = 0, il = arguments.length; i < il; i += 2) { + var setting = arguments[i].split("."); + var value = arguments[i + 1]; + var settingPath = bbb.user; + + for (var j = 0, jl = setting.length - 1; j < jl; j++) + settingPath = settingPath[setting[j]]; + + settingPath[setting[j]] = value; + bbb.settings.changed[setting[j]] = true; + } + + saveSettings(); + } + + function convertSettings(reason) { + // If the user settings are from an old version, attempt to convert some settings and update the version number. Settings will start conversion at the appropriate case and be allowed to run through every case after it until the end. + var userVer = bbb.user.bbb_version; + var scriptVer = bbb.options.bbb_version; + + if (isOldVersion(userVer)) { + switch (userVer) { + case "6.0.2": + // Temporary special tests for users that used the test version. + if (/500$/.test(bbb.user.thumb_cache_limit)) + bbb.user.thumb_cache_limit = bbb.options.thumb_cache_limit.def; + + if (!/\.(jpg|gif|png)/.test(localStorage.bbb_thumb_cache)) { + localStorage.removeItem("bbb_thumb_cache"); + loadThumbCache(); + } + + if (bbb.user.tag_scrollbars === "false") + bbb.user.tag_scrollbars = 0; + + case "6.1": + case "6.2": + case "6.2.1": + case "6.2.2": + // Reset the thumb cache to deal with "download-preview" and incorrect extension entries. + if (localStorage.bbb_thumb_cache) { + localStorage.removeItem("bbb_thumb_cache"); + loadThumbCache(); + } + + // Convert the old hide_original_notice setting to the new show_resized_notice setting that replaces it. + if (bbb.user.hide_original_notice) + bbb.user.show_resized_notice = "sample"; + + // Set the new show_banned setting to true if show_deleted is true. + if (bbb.user.show_deleted) + bbb.user.show_banned = true; + + // Add a custom border for banned posts to match the other hidden post borders. + if (!/(?:^|\s)status:banned(?:$|\s)/i.test(JSON.stringify(bbb.user.tag_borders))) + bbb.user.tag_borders.push(newBorder("status:banned", false, "#000000", "solid")); + + // Warn about uninstalling old version from Userscripts.org + if (reason !== "backup") + bbbNotice("You have just been updated from a version of this script that was hosted on Userscripts.org. Before continuing any further, please open your userscript manager and remove any versions of this script older than version 6.3 that may be there.", 0); + + case "6.3": + case "6.3.1": + case "6.3.2": + case "6.4": + case "6.5": + case "6.5.1": + case "6.5.2": + case "6.5.3": + case "6.5.4": + // Copy over settings to their new names. + if (bbb.user.image_drag_scroll) + bbb.user.post_drag_scroll = bbb.user.image_drag_scroll; + + if (bbb.user.image_resize) + bbb.user.post_resize = bbb.user.image_resize; + + if (bbb.user.image_resize_mode) + bbb.user.post_resize_mode = bbb.user.image_resize_mode; + + if (bbb.user.tag_scrollbars) + bbb.user.post_tag_scrollbars = bbb.user.tag_scrollbars; + + // Convert old settings. + if (bbb.user.autoscroll_image) + bbb.user.autoscroll_post = "post"; + + if (bbb.user.search_add) + bbb.user.search_add = "link"; + + if (bbb.user.override_account) { + bbb.user.override_blacklist = "always"; + bbb.user.override_resize = true; + bbb.user.override_sample = true; + } + + break; + } + + cleanUser(); + bbb.user.bbb_version = scriptVer; + } + else if (userVer !== scriptVer) // Revert the version number for downgrades so that conversion can properly work on the settings again for a future upgrade. + bbb.user.bbb_version = scriptVer; + } + + function cleanUser() { + // Verify the user doesn't have any settings that aren't in the base settings and delete them if they do. + var user = bbb.user; + + for (var i in user) { + if (user.hasOwnProperty(i)) { + if (typeof(bbb.options[i]) === "undefined") + delete user[i]; + } + } + } + + function createBackupText() { + // Create a plain text version of the settings. + var textarea = bbb.el.menu.backupTextarea; + textarea.value = "Better Better Booru v" + bbb.user.bbb_version + " Backup (" + timestamp() + "):\r\n\r\n" + JSON.stringify(bbb.user) + "\r\n"; + textarea.focus(); + textarea.setSelectionRange(0,0); + } + + function createBackupPage() { + // Open a new tab/window and place the setting text in it. + window.open(('data:text/html,Better Better Booru v' + bbb.user.bbb_version + ' Backup (' + timestamp() + ')' + JSON.stringify(bbb.user) + '').replace(/#/g, encodeURIComponent("#"))); + } + + function restoreBackupText() { + // Load the backup text provided into the script. + var textarea = bbb.el.menu.backupTextarea; + var backupString = textarea.value.replace(/\r?\n/g, "").match(/\{.+\}/); + + if (backupString) { + try { + bbb.user = JSON.parse(backupString); // This is where we expect an error. + checkUser(bbb.user, bbb.options); + convertSettings("backup"); + reloadMenu(); + alert("Backup settings loaded successfully. After reviewing the settings to ensure they are correct, please click \"Save & Close\" to finalize the restore."); + } + catch (error) { + if (error instanceof SyntaxError) + alert("The backup does not appear to be formatted correctly. Please make sure everything was pasted correctly/completely and that only one backup is provided."); + else + alert("Unexpected error: " + error.message); + } + } + else + alert("A backup could not be detected in the text provided. Please make sure everything was pasted correctly/completely."); + } + + /* Post functions */ + function swapImageInit() { + // Create the custom elements for swapping between the sample and original images and set them up. + createSwapElements(); + + if (image_swap_mode === "load") + swapImageLoad(); + else if (image_swap_mode === "view") + swapImageView(); + } + + function createSwapElements() { + // Create the elements for swapping between the original and sample image. + var post = bbb.post.info; + + if (!post.has_large) + return; + + // Remove the original notice (it's not always there) and replace it with our own. + var img = document.getElementById("image"); + var imgContainer = document.getElementById("image-container"); + var resizeNotice = document.getElementById("image-resize-notice"); + + if (resizeNotice) + resizeNotice.parentNode.removeChild(resizeNotice); + + var bbbResizeNotice = bbb.el.resizeNotice = document.createElement("div"); + bbbResizeNotice.id = "image-resize-notice"; + bbbResizeNotice.className = "ui-corner-all ui-state-highlight notice notice-resized"; + bbbResizeNotice.style.position = "relative"; + bbbResizeNotice.style.display = "none"; + bbbResizeNotice.innerHTML = ' ()'; + + var resizeStatus = bbb.el.resizeStatus = getId("bbb-resize-status", bbbResizeNotice); + var resizeLink = bbb.el.resizeLink = getId("bbb-resize-link", bbbResizeNotice); + var closeResizeNotice = bbb.el.closeResizeNotice = getId("close-resize-notice", bbbResizeNotice); + + closeResizeNotice.addEventListener("click", function() { + var showResNot = bbb.user.show_resized_notice; + + bbbResizeNotice.style.display = "none"; + + if (img.src.indexOf("/sample/") < 0) { // Original image. + if (showResNot === "original") + showResNot = "none"; + else if (showResNot === "all") + showResNot = "sample"; + + bbbNotice("Settings updated. The resized notice will now be hidden when viewing original images. You may change this setting under \"Notices\" in the settings panel.", 10); + } + else { // Sample image. + if (showResNot === "sample") + showResNot = "none"; + else if (showResNot === "all") + showResNot = "original"; + + bbbNotice("Settings updated. The resized notice will now be hidden when viewing sample images. You may change this setting under \"Notices\" in the settings panel.", 10); + } + + updateSettings("show_resized_notice", showResNot); + }, false); + + // Create a swap image link in the sidebar options section. + var optionsSectionList = document.getElementById("post-options"); + optionsSectionList = (optionsSectionList ? optionsSectionList.getElementsByTagName("ul")[0] : undefined); + + var firstOption = (optionsSectionList ? optionsSectionList.getElementsByTagName("li")[0] : undefined); + + var swapListItem = document.createElement("li"); + + var swapLink = bbb.el.swapLink = document.createElement("a"); + swapListItem.appendChild(swapLink); + + swapLink.addEventListener("click", function(event) { + swapPost(); + event.preventDefault(); + }, false); + + // Prepare the element text, etc. + swapImageUpdate((load_sample_first ? "sample" : "original")); + + // Add the elements to the document. + imgContainer.parentNode.insertBefore(bbbResizeNotice, imgContainer); + + if (optionsSectionList && firstOption) + optionsSectionList.insertBefore(swapListItem, firstOption); + } + + function swapImageLoad() { + // Set up the post to load the content before displaying it. + var post = bbb.post.info; + + if (!post.has_large) + return; + + var img = document.getElementById("image"); + var bbbLoader = bbb.el.bbbLoader; + var resizeStatus = bbb.el.resizeStatus; + var resizeLink = bbb.el.resizeLink; + var swapLink = bbb.el.swapLink; + + resizeLink.addEventListener("click", function(event) { + if (event.button === 0) { + swapPost(); + event.preventDefault(); + } + }, false); + bbbLoader.addEventListener("load", function() { // Change the image to the successfully loaded sample/original image. + if (!bbb.post.swapped) + bbb.post.swapped = true; + + if (bbbLoader.src !== "about:blank") { + img.src = bbbLoader.src; + bbbLoader.src = "about:blank"; + } + }, false); + bbbLoader.addEventListener("error", function(event) { // State the image has failed loading and provide a retry link. + if (bbbLoader.src !== "about:blank") { + var currentImg = (bbbLoader.src.indexOf("/sample/") < 0 ? "Original" : "Sample"); + + resizeStatus.innerHTML = currentImg + " image loading failed!"; + resizeLink.innerHTML = "retry"; + swapLink.innerHTML = "View " + currentImg.toLowerCase(); + bbbLoader.src = "about:blank"; + } + + event.preventDefault(); + }, false); + img.addEventListener("load", function() { // Update the swap image elements. + if (bbbLoader.src === "about:blank") { + if (img.src.indexOf("/sample/") < 0) // Original image loaded. + swapImageUpdate("original"); + else // Sample image loaded. + swapImageUpdate("sample"); + } + + if (bbb.post.swapped) + resizePost("swap"); + }, false); + } + + function swapImageView() { + // Set up the post to display the content as it loads. + var post = bbb.post.info; + + if (!post.has_large) + return; + + var img = document.getElementById("image"); + var resizeStatus = bbb.el.resizeStatus; + var resizeLink = bbb.el.resizeLink; + var swapLink = bbb.el.swapLink; + + resizeLink.addEventListener("click", function(event) { + if (event.button === 0) { + swapPost(); + event.preventDefault(); + } + }, false); + img.addEventListener("error", function(event) { // State the image has failed loading and provide a link to the other image. + if (img.src !== "about:blank") { + var currentImg = (img.src.indexOf("/sample/") < 0 ? "Original" : "Sample"); + var otherImg = (currentImg === "Original" ? "sample" : "original"); + + resizeStatus.innerHTML = currentImg + " image loading failed!"; + resizeLink.innerHTML = "view " + otherImg; + swapLink.innerHTML = "View " + otherImg; + } + + event.preventDefault(); + }, false); + } + + function noteToggleInit() { + // Override Danbooru's image click handler for toggling notes with a custom one. + document.addEventListener("click", function(event) { + if (event.target.id === "image" && event.button === 0 && !bbb.post.translation_mode) { + if (!bbb.drag_scroll.moved) + Danbooru.Note.Box.toggle_all(); + + event.stopPropagation(); + } + }, true); + } + + function noteToggleLinkInit() { + // Make a "toggle notes" link in the sidebar options or prepare an existing link. + var toggleLink = document.getElementById("bbb-note-toggle"); + + if (!toggleLink) { + var before = document.getElementById((isLoggedIn() ? "add-notes-list" : "random-post")); + + if (before) { + var listNoteToggle = document.createElement("li"); + listNoteToggle.innerHTML = 'Toggle notes'; + before.parentNode.insertBefore(listNoteToggle, before); + toggleLink = document.getElementById("bbb-note-toggle"); + } + } + + if (toggleLink) { + document.getElementById("bbb-note-toggle").addEventListener("click", function(event) { + Danbooru.Note.Box.toggle_all(); + event.preventDefault(); + }, false); + } + } + + function translationModeInit() { + // Set up translation mode. + var post = bbb.post.info; + var postContent = getPostContent(); + var postEl = postContent.el; + var postTag = (postEl ? postEl.tagName : undefined); + var translateLink = document.getElementById("translate"); + var toggleFunction; + + if (post.file_ext !== "webm" && post.file_ext !== "swf") { // Don't allow translation functions on webm videos or flash. + if (postTag !== "VIDEO") { // Make translation mode work on non-video content. + // Allow the translation note functions if notes aren't locked. + if (document.getElementById("note-locked-notice")) + return; + + // Make the link toggling work for hidden posts. + if (post.is_hidden) { + if (translateLink) + translateLink.addEventListener("click", Danbooru.Note.TranslationMode.toggle, false); + } + + // Script translation mode events and tracking used to resolve timing issues. + bbb.post.translation_mode = Danbooru.Note.TranslationMode.active; + + if (translateLink) + translateLink.addEventListener("click", translationModeToggle, false); + } + else { // Allow note viewing on ugoira webm video samples, but don't allow editing. + toggleFunction = function(event) { + bbbNotice('Note editing is not allowed while using the ugoira video sample. Please use the original ugoira version for note editing.', -1); + event.preventDefault(); + }; + + Danbooru.Note.TranslationMode.toggle = toggleFunction; + Danbooru.Note.Edit.show = toggleFunction; + + if (translateLink) + translateLink.addEventListener("click", toggleFunction, false); + + // Override the hotkey for "N". + createHotkey("78", toggleFunction); + } + } + else if (translateLink) { // If the translate link exists on webm videos or flash, provide a warning. + toggleFunction = function(event) { + bbbNotice('Note editing is not allowed on flash/video content.', -1); + event.preventDefault(); + }; + + Danbooru.Note.TranslationMode.toggle = toggleFunction; + Danbooru.Note.Edit.show = toggleFunction; + translateLink.addEventListener("click", toggleFunction, false); + createHotkey("78", toggleFunction); // Override the hotkey for "N". + } + } + + function disableEmbeddedNotes() { + // Disable embedded notes for viewing and re-enable them for note editing. + var useEmbedded = (getMeta("post-has-embedded-notes") === "true"); + var noteContainer = document.getElementById("note-container"); + var notesSection = document.getElementById("notes"); + var notes = (notesSection ? notesSection.getElementsByTagName("article") : undefined); + + if (!disable_embedded_notes || !useEmbedded || !notes[0]) + return; + + Danbooru.Note.embed = false; + + var post = bbb.post.info; + var postContent = getPostContent(); + var postEl = postContent.el; + var postTag = (postEl ? postEl.tagName : undefined); + + // Stop here for content that doesn't allow note editing. + if (post.file_ext === "webm" || post.file_ext === "swf" || postTag === "VIDEO") + return; + + // Save the original note functions. + var origToggleFunction = bbb.hotkeys.post[78]; + var origEditFunction = Danbooru.Note.Edit.show; + + // Create override functions. + var toggleFunction = function(event) { + var translateLink = document.getElementById("translate"); + + if (event.type === "click" && event.target !== translateLink) + return; + + resetFunction(); + + Danbooru.Note.TranslationMode.toggle(event); + translationModeToggle(); + + event.preventDefault(); + event.stopPropagation(); + }; + + var editFunction = function(editTarget) { // This function is actually assigned under an anonymous function in Danbooru. The first argument is an element from the div. + resetFunction(); + origEditFunction(editTarget); + }; + + var resetFunction = function() { + // Remove all overrides/overwrites. + Danbooru.Note.Edit.show = origEditFunction; + document.removeEventListener("click", toggleFunction, true); + createHotkey("78", origToggleFunction); + + // Reset notes with embedded notes enabled. + Danbooru.Note.embed = true; + noteContainer.innerHTML = ""; + Danbooru.Note.load_all(); + }; + + document.addEventListener("click", toggleFunction, true); // Override all other click events for the translate link. + createHotkey("78", toggleFunction); // Override the hotkey for "N". + Danbooru.Note.Edit.show = editFunction; // Overwrite the note edit function. + } + + function alternateImageSwap() { + // Override Danbooru's image click handler for toggling notes with a custom one that swaps the image. + var post = bbb.post.info; + + if (post.has_large) { + document.addEventListener("click", function(event) { + if (event.target.id === "image" && event.button === 0 && !bbb.post.translation_mode) { + if (!bbb.drag_scroll.moved) + swapPost(); + + event.stopPropagation(); + } + }, true); + } + + // Set up the "toggle notes" link since the image won't be used for toggling. + noteToggleLinkInit(); + } + + function createOptionsSection() { + // Create the sidebar options section for logged out users. + if (isLoggedIn()) + return; + + var post = bbb.post.info; + var infoSection = document.getElementById("post-information"); + var options = document.createElement("section"); + options.id = "post-options"; + options.innerHTML = '

Options

'; + infoSection.parentNode.insertBefore(options, infoSection.nextElementSibling); + } + + function fixPostDownloadLinks() { + // Fix the disabled "size" and "download" links in the sidebar of hidden posts. + var post = bbb.post.info; + + if (isLoggedIn() && !post.is_hidden) + return; + var i, il; // Loop variables. + + // Fix the "size" link. + var infoSection = document.getElementById("post-information"); + + if (infoSection) { + var infoItems = infoSection.getElementsByTagName("li"); + var sizeRegex = /^(\s*Size:\s+)([\d\.]+\s+\S+)/i; + + for (i = 0, il = infoItems.length; i < il; i++) { + var infoItem = infoItems[i]; + + if (sizeRegex.test(infoItem.innerHTML)) { + infoItem.innerHTML = infoItem.innerHTML.replace(sizeRegex, '$1$2'); + break; + } + } + } + + // Fix the "download" link. + var optionsSection = document.getElementById("post-options"); + + if (optionsSection) { + var optionItems = optionsSection.getElementsByTagName("li"); + var downloadRegex = /^\s*Download\s*$/i; + var title = getMeta("og:title"); + var downloadName = (title ? title.replace(" - Danbooru", " - ") : ""); + + for (i = 0, il = optionItems.length; i < il; i++) { + var optionItem = optionItems[i]; + + if (downloadRegex.test(optionItem.innerHTML)) { + optionItem.innerHTML = 'Download'; + break; + } + } + } + } + + function modifyResizeLink() { + // Replace the single resize link with three custom resize links. + var resizeListLink = document.getElementById("image-resize-to-window-link"); + + if (!resizeListLink) + return; + + var resizeListItem = resizeListLink.parentNode; + var resizeListParent = resizeListItem.parentNode; + var optionsFrag = document.createDocumentFragment(); + + var resizeLinkAll = bbb.el.resizeLinkAll = document.createElement("a"); + resizeLinkAll.href = "#"; + resizeLinkAll.addEventListener("click", function(event) { + resizePost("all"); + event.preventDefault(); + }, false); + + var resizeLinkWidth = bbb.el.resizeLinkWidth = document.createElement("a"); + resizeLinkWidth.href = "#"; + resizeLinkWidth.addEventListener("click", function(event) { + resizePost("width"); + event.preventDefault(); + }, false); + + var resizeLinkHeight = bbb.el.resizeLinkHeight = document.createElement("a"); + resizeLinkHeight.href = "#"; + resizeLinkHeight.addEventListener("click", function(event) { + resizePost("height"); + event.preventDefault(); + }, false); + + if (resize_link_style === "full") { + var resizeListAll = document.createElement("li"); + optionsFrag.appendChild(resizeListAll); + + resizeLinkAll.innerHTML = "Resize to window"; + resizeListAll.appendChild(resizeLinkAll); + + var resizeListWidth = document.createElement("li"); + optionsFrag.appendChild(resizeListWidth); + + resizeLinkWidth.innerHTML = "Resize to window width"; + resizeListWidth.appendChild(resizeLinkWidth); + + var resizeListHeight = document.createElement("li"); + optionsFrag.appendChild(resizeListHeight); + + resizeLinkHeight.innerHTML = "Resize to window height"; + resizeListHeight.appendChild(resizeLinkHeight); + + resizeListParent.replaceChild(optionsFrag, resizeListItem); + } + else if (resize_link_style === "minimal") { + var resizeList = document.createElement("li"); + optionsFrag.appendChild(resizeList); + + var resizeLabelLink = document.createElement("a"); + resizeLabelLink.href = "#"; + resizeLabelLink.innerHTML = "Resize:"; + resizeLabelLink.style.marginRight = "2px"; + resizeLabelLink.addEventListener("click", function(event) { + if (bbb.post.resize.mode === "none") + resizePost("all"); + else + resizePost("none"); + + event.preventDefault(); + }, false); + resizeList.appendChild(resizeLabelLink); + + resizeLinkAll.innerHTML = "(W&H)"; + resizeLinkAll.className = "bbb-resize-link"; + resizeLinkAll.title = "Resize to Window Width & Height"; + resizeList.appendChild(resizeLinkAll); + + resizeLinkWidth.innerHTML = "(W)"; + resizeLinkWidth.className = "bbb-resize-link"; + resizeLinkWidth.title = "Resize to Window Width"; + resizeList.appendChild(resizeLinkWidth); + + resizeLinkHeight.innerHTML = "(H)"; + resizeLinkHeight.className = "bbb-resize-link"; + resizeLinkHeight.title = "Resize to Window Height"; + resizeList.appendChild(resizeLinkHeight); + + resizeList.style.height = "0px"; + resizeList.style.visibility = "hidden"; + resizeList.style.fontWeight = "bold"; + resizeList.style.position = "relative"; + + resizeListParent.insertBefore(resizeList, resizeListItem); + + var allWidth = resizeLinkAll.clientWidth; + var widthWidth = resizeLinkWidth.clientWidth; + var heightWidth = resizeLinkHeight.clientWidth; + + resizeLinkAll.style.width = allWidth + "px"; + resizeLinkWidth.style.width = widthWidth + "px"; + resizeLinkHeight.style.width = heightWidth + "px"; + + resizeList.style.height = "auto"; + resizeList.style.visibility = "visible"; + resizeList.style.fontWeight = "normal"; + + resizeListParent.removeChild(resizeListItem); + } + } + + function resizePost(mode) { + // Custom resize post script. + var postContent = getPostContent(); + var imgContainer = postContent.container; + var contentDiv = document.getElementById("content"); + var ugoiraPanel = document.getElementById("ugoira-control-panel"); + var ugoiraSlider = document.getElementById("seek-slider"); + var target = postContent.el; + var targetTag = (target ? target.tagName : undefined); + + if (!target || !imgContainer || !contentDiv || targetTag === "A") + return; + + var currentMode = bbb.post.resize.mode; + var currentRatio = bbb.post.resize.ratio; + var resizeLinkAll = bbb.el.resizeLinkAll; + var resizeLinkWidth = bbb.el.resizeLinkWidth; + var resizeLinkHeight = bbb.el.resizeLinkHeight; + var availableWidth = imgContainer.clientWidth || contentDiv.clientWidth - contentDiv.bbbGetPadding().width; + var availableHeight = document.documentElement.clientHeight - 40; + var targetCurrentWidth = target.clientWidth || parseFloat(target.style.width) || target.getAttribute("width"); + var targetCurrentHeight = target.clientHeight || parseFloat(target.style.height) || target.getAttribute("height"); + var useDataDim = targetTag === "EMBED" || targetTag === "VIDEO"; + var targetWidth = (useDataDim ? imgContainer.getAttribute("data-width") : target.getAttribute("width")); // Was NOT expecting target.width to return the current width (css style width) and not the width attribute's value here... + var targetHeight = (useDataDim ? imgContainer.getAttribute("data-height") : target.getAttribute("height")); + var tooWide = targetCurrentWidth > availableWidth; + var tooTall = targetCurrentHeight > availableHeight; + var widthRatio = availableWidth / targetWidth; + var heightRatio = availableHeight / targetHeight; + var imgMode = mode; + var switchMode = false; + var ratio = 1; + var linkWeight = {all: "normal", width: "normal", height: "normal"}; + + if (mode === "swap") { // The image is being swapped between the original and sample image so everything needs to be reset. Ignore the current mode. + switchMode = true; + imgMode = "none"; + } + else if (mode === currentMode || mode === "none" || (mode === "width" && widthRatio >= 1) || (mode === "height" && heightRatio >= 1) || (mode === "all" && widthRatio >= 1 && heightRatio >= 1)) { // Cases where resizing is being toggled off or isn't needed. + if (currentMode !== "none") { // No need to do anything if the content is already at the original dimensions. + switchMode = true; + imgMode = "none"; + } + } + else if (mode === "height" && (tooTall || currentMode !== "none")) { + switchMode = true; + ratio = heightRatio; + linkWeight.height = "bold"; + } + else if (mode === "width" && (tooWide || currentMode !== "none")) { + switchMode = true; + ratio = widthRatio; + linkWeight.width = "bold"; + } + else if (mode === "all" && (tooWide || tooTall || currentMode !== "none")) { + switchMode = true; + ratio = (widthRatio < heightRatio ? widthRatio : heightRatio); + linkWeight.all = "bold"; + } + + if (switchMode) { + if (currentRatio !== ratio || mode === "swap") { + if (targetTag === "IMG" || targetTag === "CANVAS") { + target.style.width = targetWidth * ratio + "px"; + target.style.height = targetHeight * ratio + "px"; + + if (ugoiraPanel && ugoiraSlider) { + ugoiraPanel.style.width = targetWidth * ratio + "px"; + ugoiraSlider.style.width = targetWidth * ratio - 81 + "px"; + } + + Danbooru.Note.Box.scale_all(); + } + else if (targetTag === "EMBED") { + var secondaryTarget = postContent.secEl; + + secondaryTarget.height = target.height = targetHeight * ratio; + secondaryTarget.width = target.width = targetWidth * ratio; + } + else if (targetTag === "VIDEO") { + target.height = targetHeight * ratio; + target.width = targetWidth * ratio; + } + } + + bbb.post.resize.mode = imgMode; + bbb.post.resize.ratio = ratio; + resizeLinkAll.style.fontWeight = linkWeight.all; + resizeLinkWidth.style.fontWeight = linkWeight.width; + resizeLinkHeight.style.fontWeight = linkWeight.height; + } + } + + function swapPost() { + // Initiate the swap between the sample and original post content. + var post = bbb.post.info; + var target = getPostContent().el; + var targetTag = (target ? target.tagName : undefined); + var bbbLoader = bbb.el.bbbLoader; + var resizeStatus = bbb.el.resizeStatus; + var resizeLink = bbb.el.resizeLink; + var swapLink = bbb.el.swapLink; + + if (!post.has_large) + return; + + if (post.file_ext === "zip" && /(?:^|\s)ugoira(?:$|\s)/.test(post.tag_string)) { + if (targetTag === "CANVAS") + location.href = updateURLQuery(location.href, {original: "0"}); + else if (targetTag === "VIDEO") + location.href = updateURLQuery(location.href, {original: "1"}); + } + else if (targetTag === "IMG") { + if (image_swap_mode === "load") { // Load image and then view mode. + if (bbbLoader.src !== "about:blank") { // Messages after cancelling. + if (target.src.indexOf("/sample/") < 0) + swapImageUpdate("original"); + else + swapImageUpdate("sample"); + + bbbLoader.src = "about:blank"; + } + else { // Messages during loading. + if (target.src.indexOf("/sample/") < 0) { + resizeStatus.innerHTML = "Loading sample image..."; + resizeLink.innerHTML = "cancel"; + swapLink.innerHTML = "View sample (cancel)"; + bbbLoader.src = post.large_file_url; + } + else { + resizeStatus.innerHTML = "Loading original image..."; + resizeLink.innerHTML = "cancel"; + swapLink.innerHTML = "View original (cancel)"; + bbbLoader.src = post.file_url; + } + } + } + else if (image_swap_mode === "view") { // View image while loading mode. + if (target.src.indexOf("/sample/") < 0) { // Load the sample image. + swapImageUpdate("sample"); + target.src = "about:blank"; + target.removeAttribute("src"); + delayMe(function() { target.src = post.large_file_url; }); + } + else { // Load the original image. + swapImageUpdate("original"); + target.src = "about:blank"; + target.removeAttribute("src"); + delayMe(function() { target.src = post.file_url; }); + } + + if (!bbb.post.swapped) + delayMe(function() { resizePost("swap"); }); + else + bbb.post.swapped = true; + } + } + } + + function swapImageUpdate(mode) { + // Update all the elements related to swapping images when the image URL is changed. + var post = bbb.post.info; + var img = document.getElementById("image"); + var bbbResizeNotice = bbb.el.resizeNotice; + var resizeStatus = bbb.el.resizeStatus; + var resizeLink = bbb.el.resizeLink; + var swapLink = bbb.el.swapLink; + var showResNot = bbb.user.show_resized_notice; + + if (mode === "original") { // When the image is changed to the original image. + resizeStatus.innerHTML = "Viewing original"; + resizeLink.innerHTML = "view sample"; + resizeLink.href = post.large_file_url; + swapLink.innerHTML = "View sample"; + swapLink.href = post.large_file_url; + img.alt = post.md5; + img.setAttribute("height", post.image_height); + img.setAttribute("width", post.image_width); + bbbResizeNotice.style.display = (showResNot === "original" || showResNot === "all" ? "block" : "none"); + } + else if (mode === "sample") { // When the image is changed to the sample image. + resizeStatus.innerHTML = "Resized to " + Math.floor(post.sample_ratio * 100) + "% of original"; + resizeLink.innerHTML = "view original"; + resizeLink.href = post.file_url; + swapLink.innerHTML = "View original"; + swapLink.href = post.file_url; + img.alt = "Sample"; + img.setAttribute("height", post.sample_height); + img.setAttribute("width", post.sample_width); + bbbResizeNotice.style.display = (showResNot === "sample" || showResNot === "all" ? "block" : "none"); + } + } + + function checkRelations() { + // Test whether the parent/child notice could have hidden posts. + var post = bbb.post.info; + var thumbCount; + var deletedCount; + var loggedIn = isLoggedIn(); + var fixParent = false; + var fixChild = false; + var relationCookie = getCookie()["show-relationship-previews"]; + var showPreview = (relationCookie === undefined || relationCookie === "1" ? true : false); + var parentLink = document.getElementById("has-children-relationship-preview-link"); + var childLink = document.getElementById("has-parent-relationship-preview-link"); + + if (post.has_children) { + var parentNotice = document.getElementsByClassName("notice-parent")[0]; + + if (parentNotice) { + var parentText = parentNotice.textContent.match(/has (\d+|a) child/); + var parentCount = (parentText ? Number(parentText[1]) || 1 : 0); + thumbCount = getPosts(parentNotice).length; + deletedCount = parentNotice.getElementsByClassName("post-status-deleted").length; + + if ((!loggedIn && show_deleted && !deletedCount) || (parentCount && parentCount + 1 !== thumbCount)) + fixParent = true; + } + else if (show_deleted) + fixParent = true; + } + + if (fixParent) { + if (showPreview || !parentLink) + searchJSON("parent", post.id); + else + parentLink.addEventListener("click", requestRelations, false); + } + + if (post.parent_id) { + var childNotice = document.getElementsByClassName("notice-child")[0]; + + if (childNotice) { + var childText = childNotice.textContent.match(/has (\d+|a) sibling/); + var childCount = (childText ? Number(childText[1]) || 1 : 0) + 1; + thumbCount = getPosts(childNotice).length; + deletedCount = childNotice.getElementsByClassName("post-status-deleted").length; + + if ((!loggedIn && show_deleted && !deletedCount) || (childCount && childCount + 1 !== thumbCount)) + fixChild = true; + } + } + + if (fixChild) { + if (showPreview || !childLink) + searchJSON("child", post.parent_id); + else + childLink.addEventListener("click", requestRelations, false); + } + } + + function requestRelations(event) { + // Start the parent/child notice JSON request when the user chooses to display the thumbs in a notice. + var post = bbb.post.info; + var target = event.target; + + if (target.id === "has-children-relationship-preview-link") + searchJSON("parent", post.id); + else if (target.id === "has-parent-relationship-preview-link") + searchJSON("child", post.parent_id); + + target.removeEventListener("click", requestRelations, false); + event.preventDefault(); + } + + function removeTagHeaders() { + // Remove the "copyright", "characters", and "artist" headers in the post sidebar. + var tagList = document.getElementById("tag-list"); + + if (!tagList || !remove_tag_headers || gLoc !== "post") + return; + + var tagHolder = document.createDocumentFragment(); + var childIndex = 0; + var mainList; + + while (tagList.children[childIndex]) { + var header = tagList.children[childIndex]; + var list = tagList.children[childIndex + 1]; + + if (header.tagName === "H2" && list && list.tagName === "UL") { + tagList.removeChild(header); + tagList.removeChild(list); + + while (list.firstElementChild) + tagHolder.appendChild(list.firstElementChild); + } + else if (header.tagName === "H1" && list && list.tagName === "UL") { + mainList = list; + childIndex += 2; + } + else + childIndex++; + } + + if (mainList) + mainList.insertBefore(tagHolder, mainList.firstElementChild); + else { + var newHeader = document.createElement("h1"); + newHeader.innerHTML = "Tags"; + tagList.appendChild(newHeader); + + var newList = document.createElement("ul"); + newList.appendChild(tagHolder); + tagList.appendChild(newList); + } + } + + function postTagTitles() { + // Replace the post title with the full set of tags. + if (post_tag_titles && gLoc === "post") + document.title = getMeta("tags").replace(/\s/g, ", ").replace(/_/g, " ") + " - Danbooru"; + } + + function minimizeStatusNotices() { + // Show status notices only when their respective status link is clicked in the sidebar. + if (!minimize_status_notices || gLoc !== "post") + return; + + var infoSection = document.getElementById("post-information"); + var infoListItems = (infoSection ? infoSection.getElementsByTagName("li") : null); + var statusListItem; + var newStatusContent; + var flaggedNotice = document.getElementsByClassName("notice-flagged")[0]; + var appealedNotice = document.getElementsByClassName("notice-appealed")[0]; + var pendingNotice = document.getElementsByClassName("notice-pending")[0]; + var deletedNotices = document.getElementsByClassName("notice-deleted"); + var deletedNotice; + var bannedNotice; + var i, il; // Loop variables. + + if (infoListItems) { + // Locate the status portion of the information section. + for (i = infoListItems.length - 1; i >= 0; i--) { + var infoListItem = infoListItems[i]; + + if (infoListItem.textContent.indexOf("Status:") > -1) { + statusListItem = infoListItem; + newStatusContent = statusListItem.textContent; + break; + } + } + + // Hide and alter the notices and create the appropriate status links. + if (statusListItem) { + if (flaggedNotice) { + flaggedNotice.style.display = "none"; + flaggedNotice.style.position = "absolute"; + flaggedNotice.style.zIndex = "2003"; + newStatusContent = newStatusContent.replace("Flagged", 'Flagged'); + } + + if (pendingNotice) { + pendingNotice.style.display = "none"; + pendingNotice.style.position = "absolute"; + pendingNotice.style.zIndex = "2003"; + newStatusContent = newStatusContent.replace("Pending", 'Pending'); + } + + for (i = 0, il = deletedNotices.length; i < il; i++) { + deletedNotices[i].style.display = "none"; + deletedNotices[i].style.position = "absolute"; + deletedNotices[i].style.zIndex = "2003"; + + if (deletedNotices[i].getElementsByTagName("li")[0]) { + deletedNotice = deletedNotices[i]; + newStatusContent = newStatusContent.replace("Deleted", 'Deleted'); + } + else { + bannedNotice = deletedNotices[i]; + newStatusContent = newStatusContent.replace("Banned", 'Banned'); + } + } + + if (appealedNotice) { + appealedNotice.style.display = "none"; + appealedNotice.style.position = "absolute"; + appealedNotice.style.zIndex = "2003"; + newStatusContent = newStatusContent + ' Appealed'; + } + + statusListItem.innerHTML = newStatusContent; + } + + // Prepare the links. + var flaggedLink = document.getElementById("bbb-flagged-link"); + var appealedLink = document.getElementById("bbb-appealed-link"); + var pendingLink = document.getElementById("bbb-pending-link"); + var deletedLink = document.getElementById("bbb-deleted-link"); + var bannedLink = document.getElementById("bbb-banned-link"); + + if (flaggedLink) + statusLinkEvents(flaggedLink, flaggedNotice); + if (appealedLink) + statusLinkEvents(appealedLink, appealedNotice); + if (pendingLink) + statusLinkEvents(pendingLink, pendingNotice); + if (deletedLink) + statusLinkEvents(deletedLink, deletedNotice); + if (bannedLink) + statusLinkEvents(bannedLink, bannedNotice); + } + } + + function statusLinkEvents(link, notice) { + // Attach events to the status links to enable a tooltip style notice. + link.addEventListener("click", function(event) { showStatusNotice(event, notice); }, false); + link.addEventListener("mouseout", function() { bbb.timers.minNotice = window.setTimeout(function() { notice.style.display = "none"; }, 200); }, false); + notice.addEventListener("mouseover", function() { window.clearTimeout(bbb.timers.minNotice); }, false); + notice.addEventListener("mouseleave", function() { notice.style.display = "none"; }, false); + } + + function showStatusNotice(event, noticeEl) { + // Display a minimized status notice upon a click event. + var x = event.pageX; + var y = event.pageY; + var notice = noticeEl; + var topOffset = 0; + + notice.style.maxWidth = document.documentElement.clientWidth * 0.66 + "px"; + notice.style.visibility = "hidden"; + notice.style.display = "block"; + + // Don't allow the notice to go above the top of the window. + if (event.clientY - notice.offsetHeight - 2 < 5) + topOffset = event.clientY - notice.offsetHeight - 7; + + notice.style.left = x + 2 + "px"; + notice.style.top = y - notice.offsetHeight - 2 - topOffset + "px"; + notice.style.visibility = "visible"; + + event.preventDefault(); + } + + function dragScrollInit() { + // Start up drag scroll. + if (!post_drag_scroll) + return; + + var target = getPostContent().el; + var targetTag = (target ? target.tagName : undefined); + + if (targetTag === "IMG" || targetTag === "VIDEO" || targetTag === "CANVAS") { + bbb.drag_scroll.target = target; + + if (!bbb.post.translation_mode) + dragScrollEnable(); + + // Disable click behavior when dragging the video around. + if (targetTag === "VIDEO") { + document.addEventListener("click", function(event) { + if (event.button === 0 && event.target.id === "image" && bbb.drag_scroll.moved) + event.preventDefault(); + }, true); + } + } + } + + function dragScrollToggle() { + // Enable drag scroll with translation mode is off and disable it when translation mode is on. + if (!post_drag_scroll || !bbb.drag_scroll.target) + return; + + if (bbb.post.translation_mode) + dragScrollDisable(); + else + dragScrollEnable(); + } + + function dragScrollEnable() { + // Add the drag scroll event listeners. + var target = bbb.drag_scroll.target; + + target.addEventListener("mousedown", dragScrollOn, false); + target.addEventListener("dragstart", disableEvent, false); + target.addEventListener("selectstart", disableEvent, false); + } + + function dragScrollDisable() { + // Remove the drag scroll event listeners. + var target = bbb.drag_scroll.target; + + target.removeEventListener("mousedown", dragScrollOn, false); + target.removeEventListener("dragstart", disableEvent, false); + target.removeEventListener("selectstart", disableEvent, false); + } + + function dragScrollOn(event) { + // Start monitoring mouse movement. + if (event.button === 0) { + bbb.drag_scroll.lastX = event.clientX; + bbb.drag_scroll.lastY = event.clientY; + bbb.drag_scroll.moved = false; + + document.addEventListener("mousemove", dragScrollMove, false); + document.addEventListener("mouseup", dragScrollOff, false); + } + } + + function dragScrollMove(event) { + // Move the page based on mouse movement. + var newX = event.clientX; + var newY = event.clientY; + var xDistance = bbb.drag_scroll.lastX - newX; + var yDistance = bbb.drag_scroll.lastY - newY; + + window.scrollBy(xDistance, yDistance); + + bbb.drag_scroll.lastX = newX; + bbb.drag_scroll.lastY = newY; + bbb.drag_scroll.moved = xDistance !== 0 || yDistance !== 0 || bbb.drag_scroll.moved; // Doing this since I'm not sure what Chrome's mousemove event is doing. It apparently fires even when the moved distance is equal to zero. + } + + function dragScrollOff() { + // Stop monitoring mouse movement. + document.removeEventListener("mousemove", dragScrollMove, false); + document.removeEventListener("mouseup", dragScrollOff, false); + } + + function disableEvent(event) { + // removeEventListener friendly function for stopping an event. + event.preventDefault(); + } + + function translationModeToggle() { + // Toggle the translation mode status and drag scrolling. + bbb.post.translation_mode = !bbb.post.translation_mode; + + dragScrollToggle(); + } + + function autoscrollPost() { + // Automatically scroll a post to the desired position. + var scrolled = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + + if (autoscroll_post === "none" || scrolled !== 0) // Don't scroll if the page is already srolled. + return; + + if (autoscroll_post === "post") { + var target = getPostContent().el; + + if (target) + target.scrollIntoView(); + } + else if (autoscroll_post === "header") { + var page = document.getElementById("page"); + + if (!page) + return; + + var pageTop = page.offsetTop; + + window.scroll(0, pageTop); + } + } + + function ugoiraInit() { + // Execute a static copy of Danbooru's embedded JavaScript for setting up the post. + var post = bbb.post.info; + + try { + Danbooru.Ugoira = {}; + + Danbooru.Ugoira.create_player = function() { + var meta_data = { + mime_type: post.pixiv_ugoira_frame_data.content_type, + frames: post.pixiv_ugoira_frame_data.data + }; + var options = { + canvas: document.getElementById("image"), + source: post.file_url, + metadata: meta_data, + chunkSize: 300000, + loop: true, + autoStart: true, + debug: false + }; + + this.player = new ZipImagePlayer(options); + }; + + Danbooru.Ugoira.player = null; + + $(function() { + Danbooru.Ugoira.create_player(); + $(Danbooru.Ugoira.player).on("loadProgress", function(event, progress) { + $("#ugoira-load-percentage").text(Math.floor(progress * 100)); + }); + $(Danbooru.Ugoira.player).on("loadingStateChanged", function(event, state) { + if (state === 2) { + $("#ugoira-load-progress").remove(); + $("#seek-slider").show(); + } + }); + + var player_manually_paused = false; + + $("#ugoira-play").click(function(event) { + Danbooru.Ugoira.player.play(); + $(this).hide(); + $("#ugoira-pause").show(); + player_manually_paused = false; + event.preventDefault(); + }); + $("#ugoira-pause").click(function(event) { + Danbooru.Ugoira.player.pause(); + $(this).hide(); + $("#ugoira-play").show(); + player_manually_paused = true; + event.preventDefault(); + }); + + $("#seek-slider").slider({ + min: 0, + max: Danbooru.Ugoira.player._frameCount-1, + start: function() { + // Need to pause while slider is being dragged or playback speed will bug out + Danbooru.Ugoira.player.pause(); + }, + slide: function(event, ui) { + Danbooru.Ugoira.player._frame = ui.value; + Danbooru.Ugoira.player._displayFrame(); + }, + stop: function() { + // Resume playback when dragging stops, but only if player was not paused by the user earlier + if (!(player_manually_paused)) { + Danbooru.Ugoira.player.play(); + } + } + }); + $(Danbooru.Ugoira.player).on("frame", function(frame, frame_number) { + $("#seek-slider").slider("option", "value", frame_number); + }); + }); + } + catch (error) { + bbbNotice("Unexpected error creating the ugoira post. (Error: " + error.message + ")", -1); + } + } + + /* Thumbnail functions */ + function formatThumbnails(target) { + // Create thumbnail titles and borders. + var posts = getPosts(target); + var i, il; // Loop variables. + + if (!posts[0]) + return; + + var searches = bbb.custom_tag.searches; + + // Create and cache border search objects. + if (custom_tag_borders && !searches[0]) { + for (i = 0, il = tag_borders.length; i < il; i++) + searches.push(createSearch(tag_borders[i].tags)); + } + + // Cycle through each post and apply titles and borders. + for (i = 0, il = posts.length; i < il; i++) { + var post = posts[i]; + var img = post.getElementsByTagName("img")[0]; + + if (!img) + continue; + + var link = img.parentNode; + var tags = post.getAttribute("data-tags"); + var tagsStr = (tags ? tags : ""); + var user = post.getAttribute("data-uploader"); + var userStr = (user ? " user:" + user : ""); + var rating = post.getAttribute("data-rating"); + var ratingStr = (rating ? " rating:" + rating : ""); + var score = post.getAttribute("data-score"); + var scoreStr = (score ? " score:" + score : ""); + var views = post.getAttribute("data-views"); + var viewsStr = (views ? " views:" + views : ""); + var title = tagsStr + userStr + ratingStr + scoreStr + viewsStr; + var id = post.getAttribute("data-id"); + var hasChildren = (post.getAttribute("data-has-children") === "true" ? true : false); + var secondary = []; + var secondaryLength = 0; + var borderStyle; + var styleList = bbb.custom_tag.style_list; + + // Skip thumbnails that have already been done. + if (link.bbbHasClass("bbb-thumb-link")) + continue; + + // Create title. + img.title = title; + + // Give the thumbnail link an identifying class. + link.bbbAddClass("bbb-thumb-link"); + + // Give the post container an ID class for resolving cases where the same post shows up on the page multiple times. + post.bbbAddClass("post_" + id); + + // Correct parent status borders on "no active children" posts for logged out users. + if (hasChildren && show_deleted) + post.bbbAddClass("post-status-has-children"); + + // Secondary custom tag borders. + if (custom_tag_borders) { + if (typeof(styleList[id]) === "undefined") { + for (var j = 0, jl = tag_borders.length; j < jl; j++) { + var tagBorderItem = tag_borders[j]; + + if (tagBorderItem.is_enabled && thumbSearchMatch(post, searches[j])) { + secondary.push([tagBorderItem.border_color, tagBorderItem.border_style]); + + if (secondary.length === 4) + break; + } + } + + secondaryLength = secondary.length; + + if (secondaryLength) { + link.bbbAddClass("bbb-custom-tag"); + + if (secondaryLength === 1 || (single_color_borders && secondaryLength > 1)) + borderStyle = "border: " + border_width + "px " + secondary[0][0] + " " + secondary[0][1] + " !important;"; + else if (secondaryLength === 2) + borderStyle = "border-color: " + secondary[0][0] + " " + secondary[1][0] + " " + secondary[1][0] + " " + secondary[0][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[1][1] + " " + secondary[1][1] + " " + secondary[0][1] + " !important;"; + else if (secondaryLength === 3) + borderStyle = "border-color: " + secondary[0][0] + " " + secondary[1][0] + " " + secondary[2][0] + " " + secondary[0][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[1][1] + " " + secondary[2][1] + " " + secondary[0][1] + " !important;"; + else if (secondaryLength === 4) + borderStyle = "border-color: " + secondary[0][0] + " " + secondary[2][0] + " " + secondary[3][0] + " " + secondary[1][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[2][1] + " " + secondary[3][1] + " " + secondary[1][1] + " !important;"; + + link.setAttribute("style", borderStyle); + styleList[id] = borderStyle; + } + else + styleList[id] = false; + } + else if (styleList[id] !== false && !post.bbbHasClass("bbb-custom-tag")) { // Post is already tested, but needs to be set up again. + link.bbbAddClass("bbb-custom-tag"); + link.setAttribute("style", styleList[id]); + } + } + } + } + + function prepThumbnails(target) { + // Take new thumbnails and apply the necessary functions for preparing them. + // Thumbnail classes and titles. + formatThumbnails(target); + + // Thumbnail info. + thumbInfo(target); + + // Clean links. + cleanLinks(target); + + // Blacklist. + blacklistUpdate(target); + + // Direct downloads. + postDDL(target); + + // Quick search. + quickSearchTest(target); + + // Fix the mode menu. + danbModeMenu(target); + } + + function checkHiddenThumbs(post) { + // Alter a hidden thumbnails with cache info or queue it for the cache. + if (!post.md5) { + if (!bbb.cache.stored.history) + loadThumbCache(); + + var cacheName = bbb.cache.stored.names[post.id]; + + if (cacheName) { // Load the thumbnail info from the cache. + if (cacheName === "download-preview.png") + post.preview_file_url = "/images/download-preview.png"; + else { + var cacheValues = cacheName.split("."); + var cacheMd5 = cacheValues[0]; + var cacheExt = cacheValues[1]; + + post.md5 = cacheMd5; + post.file_ext = cacheExt; + post.preview_file_url = (!post.image_height || cacheExt === "swf" ? "/images/download-preview.png" : "/data/preview/" + cacheMd5 + ".jpg"); + post.large_file_url = (post.has_large ? "/data/sample/sample-" + cacheMd5 + ".jpg" : "/data/" + cacheName); + post.file_url = "/data/" + cacheName; + } + } + else // Mark hidden img for fixing. + post.thumb_class += " bbb-hidden-thumb"; + } + } + + function fixHiddenThumbs() { + // Fix hidden thumbnails by fetching the info from a page. + if (bbb.xml.hidden) + return; + + var hiddenImgs = document.getElementsByClassName("bbb-hidden-thumb"); + + if (hiddenImgs[0]) { + if (!bbb.cache.save_enabled) { + window.addEventListener("beforeunload", updateThumbCache); + bbb.cache.save_enabled = true; + } + + searchPages("hidden", hiddenImgs[0].getAttribute("data-id")); + } + } + + function createThumbHTML(post, query) { + // Create a thumbnail HTML string. + return '
' + post.tag_string + '
'; + } + + function createThumb(post, query) { + // Create a thumbnail element. (lazy method <_<) + var childSpan = document.createElement("span"); + childSpan.innerHTML = createThumbHTML(post, query); + + return childSpan.firstElementChild; + } + + function createThumbListing(posts, orderedIds) { + // Create a listing of thumbnails. + var thumbs = document.createDocumentFragment(); + var postHolder = {}; + var query = getThumbQuery(); + var thumb; + var i, il; // Loop variables; + + // Generate thumbnails. + for (i = 0, il = posts.length; i < il; i++) { + var post = formatInfo(posts[i]); + + // Don't display loli/shota/toddlercon/deleted/banned if the user has opted so and skip to the next image. + if ((!show_loli && /(?:^|\s)loli(?:$|\s)/.test(post.tag_string)) || (!show_shota && /(?:^|\s)shota(?:$|\s)/.test(post.tag_string)) || (!show_toddlercon && /(?:^|\s)toddlercon(?:$|\s)/.test(post.tag_string)) || (!show_deleted && post.is_deleted) || (!show_banned && post.is_banned) || safebPostTest(post)) + continue; + + // Check if the post is hidden. + checkHiddenThumbs(post); + + // eek, not so huge line. + thumb = createThumb(post, query); + + // Generate output. + if (!orderedIds) + thumbs.appendChild(thumb); + else + postHolder[post.id] = thumb; + } + + // Place thumbnails in the correct order for pools. + if (orderedIds) { + for (i = 0, il = orderedIds.length; i < il; i++) { + thumb = postHolder[orderedIds[i]]; + + if (thumb) + thumbs.appendChild(thumb); + } + } + + return thumbs; + } + + function loadThumbCache() { + // Initialize or load up the thumbnail cache. + if (typeof(localStorage.bbb_thumb_cache) !== "undefined") + bbb.cache.stored = JSON.parse(localStorage.bbb_thumb_cache); + else { + bbb.cache.stored = {history: [], names: {}}; + localStorage.bbb_thumb_cache = JSON.stringify(bbb.cache.stored); + } + } + + function updateThumbCache() { + // Add the current new thumbnail info to the saved thumbnail information. + if (!bbb.cache.current.history[0] || !thumb_cache_limit) + return; + + loadThumbCache(); + + var bcc = bbb.cache.current; + var bcs = bbb.cache.stored; + var i, il; // Loop variables. + + // Make sure we don't have duplicates in the new info. + for (i = 0, il = bcc.history.length; i < il; i++) { + if (bcs.names[bcc.history[i]]) { + delete bcc.names[bcc.history[i]]; + bcc.history.splice(i, 1); + il--; + i--; + } + } + + // Add the new thumbnail info in. + for (i in bcc.names) { + if (bcc.names.hasOwnProperty(i)) { + bcs.names[i] = bcc.names[i]; + } + } + + bcs.history = bcs.history.concat(bcc.history); + + // Prune the cache if it's larger than the user limit. + if (bcs.history.length > thumb_cache_limit) { + var removedIds = bcs.history.splice(0, bcs.history.length - thumb_cache_limit); + + for (i = 0, il = removedIds.length; i < il; i++) + delete bcs.names[removedIds[i]]; + } + + localStorage.bbb_thumb_cache = JSON.stringify(bcs); + bbb.cache.current = {history: [], names: {}}; + } + + function adjustThumbCache() { + // Prune the cache if it's larger than the user limit. + loadThumbCache(); + + thumb_cache_limit = bbb.user.thumb_cache_limit; + + var bcs = bbb.cache.stored; + + if (bcs.history.length > thumb_cache_limit) { + var removedIds = bcs.history.splice(0, bcs.history.length - thumb_cache_limit); + + for (var i = 0, il = removedIds.length; i < il; i++) + delete bcs.names[removedIds[i]]; + } + + localStorage.bbb_thumb_cache = JSON.stringify(bcs); + } + + function getIdCache() { + // Retrieve the cached list of post IDs used for the pool/favorite group thumbnails. + var collId = /\/(?:pools|favorite_groups)\/(\d+)/.exec(location.href)[1]; + var idCache = sessionStorage["bbb_" + gLoc + "_cache_" + collId]; + var curTime = new Date().getTime(); + var cacheTime; + var timeDiff; + + if (idCache) { + idCache = idCache.split(" "); + cacheTime = idCache.shift(); + timeDiff = (curTime - cacheTime) / 1000; // Cache age in seconds. + } + + if (!idCache || (timeDiff && timeDiff > 600)) + return undefined; + else + return idCache.join(" "); + } + + function getThumbQuery() { + // Return the thumbnail URL query value. + var query = ""; + + if (gLoc === "search" || gLoc === "favorites") { + query = getCurTags(); + query = (query ? "?tags=" + query : ""); + } + else if (gLoc === "pool") + query = "?pool_id=" + /\/pools\/(\d+)/.exec(location.pathname)[1]; + else if (gLoc === "favorite_group") + query = "?favgroup_id=" + /\/favorite_groups\/(\d+)/.exec(location.pathname)[1]; + + return query; + } + + function getCurTags() { + // Retrieve the current search tags for URL use. + var tags; + + if (gLoc === "search") { + tags = getVar("tags") || ""; + } + else if (gLoc === "favorites") { + tags = document.getElementById("tags"); + tags = (tags ? tags.getAttribute("value").replace("fav:", "ordfav:").bbbSpaceClean() : ""); // Use getAttribute to avoid potential user changes to the input. + } + + return tags; + } + + function postDDL(target) { + // Add direct downloads to thumbnails. + if (!direct_downloads || (gLoc !== "search" && gLoc !== "pool" && gLoc !== "popular" && gLoc !== "popular_view" && gLoc !== "favorites" && gLoc !== "favorite_group")) + return; + + var posts = getPosts(target); + + for (var i = 0, il = posts.length; i < il; i++) { + var post = posts[i]; + var postOrigUrl = post.getAttribute("data-file-url") || ""; + var postSampUrl = post.getAttribute("data-large-file-url") || ""; + var postUrl = (postSampUrl.indexOf(".webm") > -1 ? postSampUrl : postOrigUrl); + var postId = post.getAttribute("data-id"); + var ddlLink = post.getElementsByClassName("bbb-ddl")[0]; + + if (!ddlLink) { // If the direct download doesn't already exist, create it. + ddlLink = document.createElement("a"); + ddlLink.innerHTML = "Direct Download"; + ddlLink.href = postUrl || "DDL unavailable for post " + postId + ".jpg"; + ddlLink.id = "bbb-ddl-" + postId; + ddlLink.className = "bbb-ddl"; + post.appendChild(ddlLink); + } + else if (ddlLink.href.indexOf("/data/") < 0) // Fix existing links for hidden thumbs. + ddlLink.href = postUrl || "DDL unavailable for post " + postId + ".jpg"; + } + } + + function cleanLinks(target) { + // Remove the query portion of thumbnail links. + if (!clean_links) + return; + + var targetContainer; + var links; + var link; + var linkParent; + + if (target) + targetContainer = target; + else if (gLoc === "post") + targetContainer = document.getElementById("content"); + else if (gLoc === "pool" || gLoc === "favorite_group") { + targetContainer = document.getElementById("a-show"); + targetContainer = (targetContainer ? targetContainer.getElementsByTagName("section")[0] : undefined); + } + else if (gLoc === "search" || gLoc === "favorites") + targetContainer = document.getElementById("posts"); + else if (gLoc === "intro") + targetContainer = document.getElementById("a-intro"); + + if (targetContainer) { + links = targetContainer.getElementsByTagName("a"); + + for (var i = 0, il = links.length; i < il; i++) { + link = links[i]; + linkParent = link.parentNode; + + if (linkParent.tagName === "ARTICLE" || linkParent.id.indexOf("nav-link-for-pool-") === 0) + link.href = link.href.split("?", 1)[0]; + } + } + } + + function danbModeMenu(target) { + // Add mode menu functionality to newly created thumbnails. + var modeSection = document.getElementById("mode-box"); + + if (!modeSection) + return; + + var links = (target || document).getElementsByClassName("bbb-thumb-link"); + var menuHandler = function(event) { + if (event.button === 0) + Danbooru.PostModeMenu.click(event); + }; + + for (var i = 0, il = links.length; i < il; i++) + links[i].addEventListener("click", menuHandler, false); + } + + function potentialHiddenPosts(mode, target) { + // Check a normal thumbnail listing for possible hidden posts. + var numPosts = getPosts(target).length; + var noResults = noResultsPage(target); + var limit = getLimit(); + + if (mode === "search" || mode === "notes" || mode === "favorites") { + var numDesired; + var numExpected; + + numExpected = (limit !== undefined ? limit : thumbnail_count_default); + numDesired = (allowUserLimit() ? thumbnail_count : numExpected); + + if (!noResults && (numPosts !== numDesired || numPosts < numExpected)) + return true; + } + else if (mode === "popular" || mode === "pool" || mode === "favorite_group" || mode === "popular_view") { + if (!noResults && numPosts !== limit) + return true; + } + else if (mode === "comments") { + if (numPosts !== limit) + return true; + } + + return false; + } + + /* Endless Page functions */ + function endlessToggle(event) { + // Toggle endless pages on and off. + if (endless_default === "disabled" || (gLoc !== "search" && gLoc !== "pool" && gLoc !== "notes" && gLoc !== "favorites" && gLoc !== "favorite_group")) + return; + + // Change the default for the duration of the session if necessary. + if (endless_session_toggle) { + var onValue = (bbb.user.endless_default !== "off" ? bbb.user.endless_default : "on"); + var newDefault = (bbb.endless.enabled ? "off" : onValue); + + sessionStorage.bbb_endless_default = endless_default = newDefault; + } + + if (bbb.endless.enabled) { + endlessDisable(); + + if (event && event.type !== "click") + bbbNotice("Endless pages disabled.", 2); + } + else { + endlessEnable(); + + if (event && event.type !== "click") + bbbNotice("Endless pages enabled.", 2); + } + } + + function endlessEnable() { + // Turn on endless pages. + if (endless_default === "disabled" || noXML()) + return; + + bbb.endless.enabled = true; + bbb.el.endlessEnableDiv.style.display = "none"; + bbb.el.endlessLoadDiv.style.display = "inline-block"; + bbb.el.endlessLink.style.fontWeight = "bold"; + + // Check on the next page status. + endlessCheck(); + + // Add the listeners for detecting the amount of scroll left. + window.addEventListener("scroll", endlessCheck, false); + window.addEventListener("resize", endlessCheck, false); + document.addEventListener("keyup", endlessCheck, false); + document.addEventListener("click", endlessCheck, false); + } + + function endlessDisable() { + // Turn off endless pages. + bbb.endless.enabled = false; + bbb.endless.append_page = false; + bbb.el.endlessEnableDiv.style.display = "inline-block"; + bbb.el.endlessLoadDiv.style.display = "none"; + bbb.el.endlessLink.style.fontWeight = "normal"; + + // Remove the listeners for detecting the amount of scroll left. + window.removeEventListener("scroll", endlessCheck, false); + window.removeEventListener("resize", endlessCheck, false); + document.removeEventListener("keyup", endlessCheck, false); + document.removeEventListener("click", endlessCheck, false); + } + + function endlessInit() { + // Set up and start endless pages. + if (endless_default === "disabled" || (gLoc !== "search" && gLoc !== "pool" && gLoc !== "notes" && gLoc !== "favorites" && gLoc !== "favorite_group")) + return; + + // Add the endless link to the menu. + var menu = document.getElementById("top"); + menu = (menu ? menu.getElementsByTagName("menu")[1] : undefined); + + if (menu) { + var menuItems = menu.getElementsByTagName("li"); + var numMenuItems = menu.getElementsByTagName("li").length; + var listingItemSibling = menuItems[1]; + + for (var i = 0; i < numMenuItems; i++) { + var menuLink = menuItems[i]; + var nextLink = menuItems[i + 1]; + + if (menuLink.textContent.indexOf("Listing") > -1) { + if (nextLink) + listingItemSibling = nextLink; + else + listingItemSibling = undefined; + + break; + } + } + + var link = bbb.el.endlessLink = document.createElement("a"); + link.href = "#"; + link.innerHTML = "Endless"; + link.addEventListener("click", function(event) { + endlessToggle(); + event.preventDefault(); + }, false); + + var item = document.createElement("li"); + item.style.textAlign = "center"; + item.style.display = "inline-block"; + item.appendChild(link); + + if (listingItemSibling) + menu.insertBefore(item, listingItemSibling); + else + menu.appendChild(item); + + link.style.fontWeight = "bold"; + item.style.width = item.clientWidth + "px"; + link.style.fontWeight = "normal"; + } + + var paginator = getPaginator(); + + if (paginator) { + var paginatorParent = paginator.parentNode; + + // Set up the load more button. + var buttonDiv = document.createElement("div"); + buttonDiv.id = "bbb-endless-button-div"; + + var loadButtonDiv = bbb.el.endlessLoadDiv = document.createElement("div"); + loadButtonDiv.id = "bbb-endless-load-div"; + buttonDiv.appendChild(loadButtonDiv); + + var loadButton = bbb.el.endlessLoadButton = document.createElement("a"); + loadButton.innerHTML = "Load More"; + loadButton.href = "#"; + loadButton.id = "bbb-endless-load-button"; + loadButton.style.display = "none"; + loadButton.addEventListener("click", function(event) { + loadButton.style.display = "none"; + loadButton.blur(); + bbb.endless.paused = false; + bbb.endless.append_page = true; + endlessCheck(); + event.preventDefault(); + }, false); + loadButtonDiv.appendChild(loadButton); + + // Set up the enable button. + var enableButtonDiv = bbb.el.endlessEnableDiv = document.createElement("div"); + enableButtonDiv.id = "bbb-endless-enable-div"; + buttonDiv.appendChild(enableButtonDiv); + + var enableButton = document.createElement("a"); + enableButton.innerHTML = "Endless"; + enableButton.href = "#"; + enableButton.id = "bbb-endless-enable-button"; + enableButton.addEventListener("click", function(event) { + enableButton.blur(); + endlessToggle(); + event.preventDefault(); + }, false); + enableButtonDiv.appendChild(enableButton); + + paginatorParent.insertBefore(buttonDiv, paginator); + } + + // Check the session default or original default value to see if endless pages should be enabled. + var sessionDefault = sessionStorage.bbb_endless_default; + + if (endless_session_toggle && sessionDefault) + endless_default = sessionDefault; + + if (endless_default !== "off") + endlessEnable(); + else + endlessDisable(); + } + + function endlessObjectInit() { + // Initialize the values for the first XML request. Runs separately from endlessInit since it requires the initial page being finalized. + var posts = getPosts(); + var numPosts = posts.length; + + // Prep the first paginator. + bbb.endless.last_paginator = getPaginator(); + + // If we're already on the last page, don't continue. + if (endlessLastPage()) + return; + + // Note the posts that already exist. + if (endless_remove_dup) { + for (var i = 0; i < numPosts; i++) { + var post = posts[i]; + + bbb.endless.posts[post.id] = post; + } + } + + // Create a special "page" for filling out the first page. + if (endless_fill) { + var limit = getLimit() || thumbnail_count_default; + + if (numPosts < limit) { + var newPageObject = { + page: document.createElement("span"), + page_num: [(getVar("page") || "1")], + paginator: bbb.endless.last_paginator, + ready: false + }; + + bbb.endless.fill_first_page = true; + bbb.endless.pages.push(newPageObject); + } + } + } + + function endlessCheck() { + // Check whether the current document is ready for a page to be appended. + if (!bbb.endless.enabled) + return; + + // Check whether endless pages needs to be paused. + endlessPauseCheck(); + + // Stop if the check is delayed. + if (bbb.timers.endlessDelay) + return; + + // Check whether a user is looking at the "posts tab" and not the "wiki tab" in the main search listing. + var postsDiv = (gLoc === "search" ? document.getElementById("posts") : undefined); + var postsVisible = (!postsDiv || postsDiv.style.display !== "none"); + + if (bbb.xml.thumbs || bbb.xml.paginator || !postsVisible) // Delay the check until the page is completely ready. + endlessDelay(100); + else { + if (!bbb.endless.last_paginator) + endlessObjectInit(); + + if (bbb.endless.append_page) + endlessQueueCheck(); + else { // Check the amount of space left to scroll and attempt to add a page if we're far enough down. + var scrolled = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + var viewHeight = document.documentElement.clientHeight; + var docHeight = document.documentElement.offsetHeight; + + if (docHeight <= viewHeight + scrolled + endless_scroll_limit) + bbb.endless.append_page = true; + + endlessQueueCheck(); + } + } + } + + function endlessQueueCheck() { + // Check the page queue and append or request a page. + if (!bbb.endless.enabled || bbb.endless.paused) + return; + + if (bbb.endless.append_page || bbb.endless.fill_first_page) { + if (endlessPageReady()) + endlessAppendPage(); + else + endlessRequestPage(); + } + else if (endless_preload && !endlessPageReady()) + endlessRequestPage(); + } + + function endlessRequestPage() { + // Start an XML request for a new page. + if (bbb.xml.endless || endlessLastPage()) // Retrieve pages one at a time for as long as they exist. + return; + + searchPages("endless"); + } + + function endlessQueuePage(newPage) { + // Take some thumbnails from a page and work them into the queue. + var limit = getLimit() || thumbnail_count_default; + var pageNum = getVar("page", endlessNexURL()); + var paginator = bbb.endless.last_paginator = bbb.endless.new_paginator; + var badPaginator = (paginator.textContent.indexOf("Go back") > -1); // Sometimes the paginator sends us to a page with no results. + var lastPage = endlessLastPage() || badPaginator; + var posts = getPosts(newPage); + + bbb.endless.new_paginator = undefined; + + // Remove duplicates. + if (endless_remove_dup) { + for (var i = 0; i < posts.length; i++) { + var post = posts[i]; + var postId = post.id; + + if (bbb.endless.posts[postId]) { + newPage.removeChild(post); + i--; + } + else + bbb.endless.posts[postId] = post; + } + } + + // Fill up existing page objects with thumbnails. + var lastPageObject = bbb.endless.pages[bbb.endless.pages.length - 1]; + + if (endless_fill && lastPageObject && !lastPageObject.ready) { + if (badPaginator) // Paginator isn't accurate. Ignore this page's number and paginator. + lastPageObject.ready = true; + else { + var lastQueuePage = lastPageObject.page; + var lastQueuePosts = getPosts(lastQueuePage); + var fillLimit = (bbb.endless.fill_first_page ? limit - getPosts().length : limit); + + while (!lastPageObject.ready && posts[0]) { + lastQueuePage.appendChild(posts[0]); + + if (lastQueuePosts.length === fillLimit) + lastPageObject.ready = true; + } + + // If there are no more posts and pages, mark the last page as ready. + if (!lastPageObject.ready && !posts[0] && lastPage) + lastPageObject.ready = true; + + // Make sure the displayed paginator is always the one from the last retrieved page to have all of it's thumbnails used so the user doesn't click to the next page and skip queued thumbnails that haven't been displayed yet. + if (!posts[0]) + lastPageObject.paginator = paginator; + + lastPageObject.page_num.push(pageNum); + } + } + + // Queue up a new page object. + var numNewPosts = posts.length; + + if (numNewPosts > 0 || (!endless_fill && !badPaginator) || (endless_fill && !bbb.endless.pages[0] && !lastPage)) { // Queue the page if: 1) There are thumbnails. 2) It's normal mode and not a "no results" page. 3) It's fill mode and there is no object to work with for future pages. + var newPageObject = { + page: newPage, + page_num: [pageNum], + paginator: paginator, + ready: (!endless_fill || numNewPosts === limit || lastPage ? true : false) + }; + + bbb.endless.pages.push(newPageObject); + + if (bbb.endless.no_thumb_count < 10) + bbb.endless.no_thumb_count = 0; + } + else if (!badPaginator) + bbb.endless.no_thumb_count++; + + // Get rid of the load more button for special circumstances where the paginator isn't accurate. + if (lastPage && !bbb.endless.pages[0]) + bbb.el.endlessLoadButton.style.display = "none"; + + // Warn the user if this is a listing full of hidden posts. + if (bbb.endless.no_thumb_count === 10) { + bbbNotice("There have been no or very few thumbnails detected in the last 10 retrieved pages. Using endless pages with fill mode on this search could potentially be very slow or stall out completely. If you would like to continue, you may click the \"Load More\" button near the bottom of the page.", -1); + endlessPause(); + } + else + endlessQueueCheck(); + } + + function endlessAppendPage() { + // Prep the first queued page object and add it to the document. + var firstPageObject = bbb.endless.pages.shift(); + var page = firstPageObject.page; + var thumbContainer = getThumbContainer(gLoc); + var before = getThumbSibling(gLoc); + + // Prepare thumbnails. + prepThumbnails(page); + + // Page separators. + var pageNum = firstPageObject.page_num; + var numPageNum = pageNum.length; + var firstNum = pageNum[0]; + var lastNum = (numPageNum > 1 ? pageNum[numPageNum - 1] : undefined); + + if (endless_separator === "divider") { + var divider = document.createElement("div"); + divider.className = "bbb-endless-divider"; + + var dividerLink = document.createElement("div"); + dividerLink.className = "bbb-endless-divider-link"; + divider.appendChild(dividerLink); + + dividerLink.innerHTML = 'Page ' + firstNum + '' + (lastNum ? ' ~ Page ' + lastNum + '' : ''); + + if (!bbb.endless.fill_first_page) + page.insertBefore(divider, page.firstElementChild); + else if (lastNum) // Only add the divider for filling the first page when there are actual posts added. + thumbContainer.insertBefore(divider, (getPosts()[0] || before)); + } + else if (endless_separator === "marker") { + var markerContainer = document.createElement("article"); + markerContainer.className = "bbb-endless-marker-article"; + + var marker = document.createElement("div"); + marker.className = "bbb-endless-marker"; + markerContainer.appendChild(marker); + + var markerLink = document.createElement("div"); + markerLink.className = "bbb-endless-marker-link"; + marker.appendChild(markerLink); + + markerLink.innerHTML = 'Page ' + firstNum + '' + (lastNum ? '
~
Page ' + lastNum + '' : ''); + + if (!bbb.endless.fill_first_page) + page.insertBefore(markerContainer, page.firstElementChild); + else if (lastNum) // Only add the marker for filling the first page when there are actual posts added. + thumbContainer.insertBefore(markerContainer, (getPosts()[0] || before)); + } + + // Add the new page. + if (!before) + thumbContainer.appendChild(page); + else + thumbContainer.insertBefore(page, before); + + // Replace the paginator. + replacePaginator(firstPageObject.paginator); + + // Fix hidden thumbnails. + fixHiddenThumbs(); + bbbStatus("hidden", "new"); // Update status message with new amount. + + if (!bbb.endless.fill_first_page) + bbb.endless.append_page = false; + else { + bbb.endless.fill_first_page = false; + endlessQueueCheck(); + } + + if (quick_search.indexOf("remove") > -1 && bbb.quick_search !== "") + endlessDelay(1100); + + endlessCheck(); + } + + function endlessNexURL() { + // Get the URL of the next new page. + return getPaginatorNextURL(bbb.endless.last_paginator); + } + + function endlessPageReady() { + // Check if the first queued page object is ready to be appended. + var firstPageObject = bbb.endless.pages[0]; + + return (firstPageObject && firstPageObject.ready); + } + + function endlessLastPage() { + // Check if there isn't a next page. + return (!endlessNexURL() || noResultsPage()); + } + + function endlessPauseCheck() { + // Check if loading needs to be paused due to the interval or default. + if (bbb.endless.append_page) + return; + + var numPages = document.getElementsByClassName("bbb-endless-page").length + 1; + + if (numPages % endless_pause_interval === 0 || (endless_default === "paused" && numPages === 1)) + endlessPause(); + } + + function endlessPause() { + // Pause endless pages so that it can't add any more pages. + if (bbb.endless.paused || (endlessLastPage() && !bbb.endless.pages[0])) + return; + + bbb.endless.paused = true; + bbb.endless.append_page = false; + bbb.el.endlessLoadButton.style.display = "inline-block"; + } + + function endlessDelay(ms) { + // Delay endless pages for the provided number of milliseconds. + bbb.timers.endlessDelay = window.setTimeout( function() { + bbb.timers.endlessDelay = 0; + + endlessCheck(); + }, ms); + } + + /* Blacklist Functions */ + function blacklistInit() { + // Reset the blacklist with the account settings when logged in or script settings when logged out/using the override. + var blacklistTags = accountSettingCheck("script_blacklisted_tags"); + var blacklistBox = document.getElementById("blacklist-box"); + var blacklistList = document.getElementById("blacklist-list"); + var enableLink = document.getElementById("re-enable-all-blacklists"); + var disableLink = document.getElementById("disable-all-blacklists"); + var blacklistedPosts = document.getElementsByClassName("blacklisted"); + var i, il; // Loop variables. + + // Reset sidebar blacklist. + if (blacklistBox && blacklistList) { + blacklistBox.style.display = "none"; + + var childIndex = 0; + + while (blacklistList.children[childIndex]) { + var child = blacklistList.children[childIndex]; + + if (child.getElementsByTagName("a")[0] && child !== enableLink && child !== disableLink) + blacklistList.removeChild(child); + else + childIndex++; + } + } + + // Reset any blacklist info. + if (bbb.blacklist.entries[0]) { + delete bbb.blacklist; + bbb.blacklist = {entries: [], match_list: {}, smart_view_target: undefined}; + } + + // Reset any blacklisted thumbnails. + while (blacklistedPosts[0]) + blacklistedPosts[0].bbbRemoveClass("blacklisted blacklisted-active"); + + // Check if there actually are any tags. + if (!blacklistTags || !/[^\s,]/.test(blacklistTags)) + return; + + // Preserve commas within nested/grouped tags. + var groupsObject = replaceSearchGroups(blacklistTags); + var groups = groupsObject.groups; + + blacklistTags = groupsObject.search.replace(/,/g, "%,%"); + blacklistTags = restoreSearchGroups(blacklistTags, groups); + blacklistTags = blacklistTags.split("%,%"); + + // Create the blacklist section. + var cookies = getCookie(); + var blacklistDisabled = (cookies["disable-all-blacklists"] === "1" && blacklistBox); + + for (i = 0, il = blacklistTags.length; i < il; i++) { + var blacklistTag = blacklistTags[i].bbbSpaceClean(); + var blacklistSearch = createSearch(blacklistTag); + + if (blacklistSearch[0]) { + var entryHash = blacklistTag.bbbHash(); + var entryDisabled = (blacklistDisabled || (cookies["bl:" + entryHash] === "1") ? true : false); + var newEntry = {active: !entryDisabled, tags:blacklistTag, search:blacklistSearch, matches: [], index: i, hash: entryHash}; + + bbb.blacklist.entries.push(newEntry); + + if (blacklistList) { + var blacklistItem = document.createElement("li"); + blacklistItem.title = blacklistTag; + blacklistItem.className = "bbb-blacklist-item-" + i; + blacklistItem.style.display = "none"; + + var blacklistLink = document.createElement("a"); + blacklistLink.innerHTML = (blacklistTag.length < 19 ? blacklistTag + " " : blacklistTag.substring(0, 18).bbbSpaceClean() + "... "); + blacklistLink.className = "bbb-blacklist-entry-" + i + (entryDisabled ? " blacklisted-active" : ""); + blacklistLink.setAttribute("data-bbb-blacklist-entry", i); + blacklistLink.addEventListener("click", blacklistEntryLinkToggle, false); + blacklistItem.appendChild(blacklistLink); + + var blacklistCount = document.createElement("span"); + blacklistCount.className = "count"; + blacklistCount.innerHTML = "0"; + blacklistItem.appendChild(blacklistCount); + + blacklistList.appendChild(blacklistItem); + } + } + } + + // Replace the disable/enable all blacklist links with our own. + if (enableLink && disableLink) { + var newEnableLink = bbb.el.blacklistEnableLink = enableLink.cloneNode(true); + var newDisableLink = bbb.el.blacklistDisableLink = disableLink.cloneNode(true); + + newEnableLink.addEventListener("click", blacklistLinkToggle, false); + newDisableLink.addEventListener("click", blacklistLinkToggle, false); + + if (blacklistDisabled) { + newEnableLink.style.display = "inline"; + newDisableLink.style.display = "none"; + } + else { + newEnableLink.style.display = "none"; + newDisableLink.style.display = "inline"; + } + + if (blacklistList) + blacklistList.style.marginBottom = "0.5em"; + + enableLink.parentNode.replaceChild(newEnableLink, enableLink); + disableLink.parentNode.replaceChild(newDisableLink, disableLink); + } + + // Test all posts on the page for a match and set up the initial blacklist. + blacklistUpdate(); + } + + function blacklistLinkToggle(event) { + // Event listener function for permanently toggling the entire blacklist. + if (event.button !== 0) + return; + + var blacklistDisabled = (getCookie()["disable-all-blacklists"] === "1"); + var entries = bbb.blacklist.entries; + + for (var i = 0, il = entries.length; i < il; i++) { + var entry = entries[i]; + + if (blacklistDisabled) { + if (!entry.active) + blacklistEntryToggle(i); + } + else { + if (entry.active) + blacklistEntryToggle(i); + } + } + + if (blacklistDisabled) { + bbb.el.blacklistEnableLink.style.display = "none"; + bbb.el.blacklistDisableLink.style.display = "inline"; + createCookie("disable-all-blacklists", 0, 365); + } + else { + bbb.el.blacklistEnableLink.style.display = "inline"; + bbb.el.blacklistDisableLink.style.display = "none"; + createCookie("disable-all-blacklists", 1, 365); + } + + event.preventDefault(); + } + + function blacklistEntryLinkToggle(event) { + // Event listener function for blacklist entry toggle links. + if (event.button !== 0) + return; + + var entryNumber = Number(event.target.getAttribute("data-bbb-blacklist-entry")); + + blacklistEntryToggle(entryNumber); + + event.preventDefault(); + } + + function blacklistEntryToggle(entryIndex) { + // Toggle a blacklist entry and adjust all of its related elements. + var entry = bbb.blacklist.entries[entryIndex]; + var matches = entry.matches; + var links = document.getElementsByClassName("bbb-blacklist-entry-" + entryIndex); + var id; + var els; + var matchList; + var i, il, j, jl; // Loop variables. + + if (entry.active) { + entry.active = false; + + createCookie("bl:" + entry.hash, 1); + + for (i = 0, il = links.length; i < il; i++) + links[i].bbbAddClass("blacklisted-active"); + + for (i = 0, il = matches.length; i < il; i++) { + id = matches[i]; + matchList = bbb.blacklist.match_list[id]; + + matchList.count--; + + if (!matchList.count && matchList.override !== false) { + if (id === "image-container") + document.getElementById("image-container").bbbRemoveClass("blacklisted-active"); + else { + els = document.getElementsByClassName(id); + + for (j = 0, jl = els.length; j < jl; j++) + els[j].bbbRemoveClass("blacklisted-active"); + } + } + } + } + else { + entry.active = true; + + createCookie("bl:" + entry.hash, 0, -1); + + for (i = 0, il = links.length; i < il; i++) + links[i].bbbRemoveClass("blacklisted-active"); + + for (i = 0, il = matches.length; i < il; i++) { + id = matches[i]; + matchList = bbb.blacklist.match_list[id]; + + matchList.count++; + + if (matchList.override !== true) { + if (id === "image-container") + document.getElementById("image-container").bbbAddClass("blacklisted-active"); + else { + els = document.getElementsByClassName(id); + + for (j = 0, jl = els.length; j < jl; j++) + els[j].bbbAddClass("blacklisted-active"); + } + } + } + } + } + + function blacklistUpdate(target) { + // Update the blacklists without resetting everything. + if (!bbb.blacklist.entries[0]) + return; + + // Retrieve the necessary elements from the target element or current document. + var blacklistBox = getId("blacklist-box", target) || document.getElementById("blacklist-box"); + var blacklistList = getId("blacklist-list", target) || document.getElementById("blacklist-list"); + var imgContainer = getId("image-container", target); + var posts = getPosts(target); + + var i, il; // Loop variables. + + // Test the image for a match when viewing a post. + if (imgContainer) { + var imgId = imgContainer.getAttribute("data-id"); + + if (!blacklistSmartViewCheck(imgId)) + blacklistTest(imgContainer); + } + + // Search the posts for matches. + for (i = 0, il = posts.length; i < il; i++) + blacklistTest(posts[i]); + + // Update the blacklist sidebar section match counts and display any blacklist items that have a match. + if (blacklistBox && blacklistList) { + for (i = 0, il = bbb.blacklist.entries.length; i < il; i++) { + var entryLength = bbb.blacklist.entries[i].matches.length; + var item = blacklistList.getElementsByClassName("bbb-blacklist-item-" + i)[0]; + + if (entryLength) { + blacklistBox.style.display = "block"; + item.style.display = ""; + item.getElementsByClassName("count")[0].innerHTML = entryLength; + } + } + } + } + + function blacklistTest(el) { + // Test a post/image for a blacklist match and use its ID to store its info. + var id = el.id; + var matchList = bbb.blacklist.match_list[id]; + + if (typeof(matchList) === "undefined") { // Post hasn't been tested yet. + matchList = bbb.blacklist.match_list[id] = {count: undefined, matches: [], override: undefined}; + + for (var i = 0, il = bbb.blacklist.entries.length; i < il; i++) { + var entry = bbb.blacklist.entries[i]; + + if (thumbSearchMatch(el, entry.search)) { + el.bbbAddClass("blacklisted"); + + if (entry.active) { + el.bbbAddClass("blacklisted-active"); + matchList.count = ++matchList.count || 1; + } + else + matchList.count = matchList.count || 0; + + matchList.matches.push(entry); + entry.matches.push(id); + } + } + + if (matchList.count === undefined) // No match. + matchList.count = false; + else if (el.id !== "image-container") { // Match found so prepare the thumbnail. + if (blacklist_thumb_controls) + blacklistPostControl(el, matchList); + + if (blacklist_smart_view) + blacklistSmartView(el); + } + + } + else if (matchList.count !== false && !el.bbbHasClass("blacklisted")) { // Post is already tested, but needs to be set up again. + if (matchList.count > 0 && matchList.override !== true) + el.bbbAddClass("blacklisted blacklisted-active"); + else + el.bbbAddClass("blacklisted"); + + if (el.id !== "image-container") { + if (blacklist_thumb_controls) + blacklistPostControl(el, matchList); + + if (blacklist_smart_view) + blacklistSmartView(el); + } + } + } + + function blacklistPostControl(el, matchList) { + // Add the blacklist post controls to a thumbnail. + var target = el.getElementsByClassName("preview")[0] || el; + var id = el.id; + var tip = bbb.el.blacklistTip; + + if (!tip) { // Create the tip if it doesn't exist. + tip = bbb.el.blacklistTip = document.createElement("div"); + tip.id = "bbb-blacklist-tip"; + document.body.appendChild(tip); + } + + if (target) { + // Set up the tip events listeners for hiding and displaying it. + target.addEventListener("click", function(event) { + if (event.button !== 0 || event.ctrlKey || event.shiftKey || event.altKey) + return; + + var target = event.target; + var blacklistTip = bbb.el.blacklistTip; + var i, il; // Loop variables. + + if (!el.bbbHasClass("blacklisted-active") || (target.tagName === "A" && !target.bbbHasClass("bbb-thumb-link"))) // If the thumb isn't currently hidden or a link that isn't the thumb link is clicked, allow the link click. + return; + + if (blacklistTip.style.display !== "block") { + var matchEntries = matchList.matches; + var tipContent = document.createDocumentFragment(); + + var header = document.createElement("b"); + header.innerHTML = "Blacklist Matches"; + tipContent.appendChild(header); + + var list = document.createElement("ul"); + tipContent.appendChild(list); + + for (i = 0, il = matchEntries.length; i < il; i++) { + var matchEntry = matchEntries[i]; + var entryIndex = matchEntry.index; + var blacklistTag = matchEntry.tags; + + var blacklistItem = document.createElement("li"); + blacklistItem.title = blacklistTag; + + var blacklistLink = document.createElement("a"); + blacklistLink.href = "#"; + blacklistLink.className = "bbb-blacklist-entry-" + entryIndex + (matchEntry.active ? "" : " blacklisted-active"); + blacklistLink.setAttribute("data-bbb-blacklist-entry", entryIndex); + blacklistLink.innerHTML = (blacklistTag.length < 51 ? blacklistTag + " " : blacklistTag.substring(0, 50).bbbSpaceClean() + "..."); + blacklistLink.addEventListener("click", blacklistEntryLinkToggle, false); + blacklistItem.appendChild(blacklistLink); + + list.appendChild(blacklistItem); + } + + var viewLinkDiv = document.createElement("div"); + viewLinkDiv.style.marginTop = "1em"; + viewLinkDiv.style.textAlign = "center"; + viewLinkDiv.innerHTML = 'View post'; + tipContent.appendChild(viewLinkDiv); + + if (blacklist_smart_view) { + var viewLink = getId("bbb-blacklist-view-link", viewLinkDiv); + + if (viewLink) { + viewLink.addEventListener("click", function(event) { + if (event.button === 0) + blacklistSmartViewUpdate(el); + }, false); + } + } + + blacklistShowTip(event, tipContent); + } + else { + var els = document.getElementsByClassName(id); + + for (i = 0, il = els.length; i < il; i++) + els[i].bbbRemoveClass("blacklisted-active"); + + blacklistHideTip(); + bbb.blacklist.match_list[id].override = true; + } + + event.preventDefault(); + event.stopPropagation(); + }, true); + target.addEventListener("mouseleave", function() { bbb.timers.blacklistTip = window.setTimeout(blacklistHideTip, 100); }, false); + tip.addEventListener("mouseover", function() { window.clearTimeout(bbb.timers.blacklistTip); }, false); + tip.addEventListener("mouseleave", blacklistHideTip, false); + + // Add the hide button. + var hide = document.createElement("span"); + hide.className = "bbb-close-circle"; + hide.addEventListener("click", function(event) { + if (event.button !== 0) + return; + + var els = document.getElementsByClassName(id); + + for (var i = 0, il = els.length; i < il; i++) + els[i].bbbAddClass("blacklisted-active"); + + bbb.blacklist.match_list[id].override = false; + }, false); + target.appendChild(hide); + } + } + + function blacklistShowTip(event, content) { + // Display the blacklist control tip. + var x = event.pageX; + var y = event.pageY; + var tip = bbb.el.blacklistTip; + + formatTip(event, tip, content, x, y); + } + + function blacklistHideTip() { + // Reset the blacklist control tip to hidden. + var tip = bbb.el.blacklistTip; + + if (tip) + tip.removeAttribute("style"); + } + + function blacklistSmartView(el) { + // Set up the smart view event listeners. + var img = el.getElementsByTagName("img")[0]; + var link = (img ? img.parentNode : undefined); + + if (!link) + return; + + // Normal left click support. + link.addEventListener("click", function(event) { + if (event.button === 0) + blacklistSmartViewUpdate(el); + }, false); + + // Right and middle button click support. + link.addEventListener("mousedown", function(event) { + if (event.button === 1) + bbb.blacklist.smart_view_target = link; + }, false); + link.addEventListener("mouseup", function(event) { + if (event.button === 1 && bbb.blacklist.smart_view_target === link) + blacklistSmartViewUpdate(el); + else if (event.button === 2) + blacklistSmartViewUpdate(el); + }, false); + } + + function blacklistSmartViewUpdate(el) { + // Update the blacklisted thumbnail info in the smart view object. + var time = new Date().getTime(); + var id = el.getAttribute("data-id"); + var smartView; + + if (typeof(localStorage.bbb_smart_view) === "undefined") // Initialize the object if it doesn't exist. + smartView = {last: time}; + else { + smartView = JSON.parse(localStorage.bbb_smart_view); + + if (time - smartView.last > 60000) // Reset the object if it hasn't been changed within a minute. + smartView = {last: time}; + else + smartView.last = time; // Adjust the object. + } + + if (!el.bbbHasClass("blacklisted-active")) + smartView[id] = time; + else + delete smartView[id]; + + localStorage.bbb_smart_view = JSON.stringify(smartView); + } + + function blacklistSmartViewCheck(id) { + // Check whether to display the post during the blacklist init. + if (!blacklist_smart_view || typeof(localStorage.bbb_smart_view) === "undefined") + return false; + else { + var smartView = JSON.parse(localStorage.bbb_smart_view); + var time = new Date().getTime(); + + if (time - smartView.last > 60000) { // Delete the ids if the object hasn't been changed within a minute. + localStorage.removeItem("bbb_smart_view"); + return false; + } + else if (!smartView[id]) // Return false if the id isn't found. + return false; + else if (time - smartView[id] > 60000) // Return false if the click is over a minute ago. + return false; + } + + return true; + } + + /* Other functions */ + function modifyPage() { + // Determine what function may be needed to fix/update content. + if (noXML()) + return; + + if (gLoc === "post") + delayMe(parsePost); // Delay is needed to force the script to pause and allow Danbooru to do whatever. It essentially mimics the async nature of the API call. + else if (gLoc === "comment_search" || gLoc === "comment") + delayMe(fixCommentSearch); + else if (useAPI()) // API only features. + searchJSON(gLoc); + else // Alternate mode for features. + searchPages(gLoc); + } + + function formatInfo(post) { + // Add information to/alter information in the post object. + if (!post) + return undefined; + + // Figure out the thumbnail classes. + var flags = ""; + var thumbClass = ""; + + if (post.is_deleted) { + flags += " deleted"; + thumbClass += " post-status-deleted"; + } + if (post.is_pending) { + flags += " pending"; + thumbClass += " post-status-pending"; + } + if (post.is_banned) + flags += " banned"; + if (post.is_flagged) { + flags += " flagged"; + thumbClass += " post-status-flagged"; + } + if (post.has_children && (post.has_active_children || show_deleted)) + thumbClass += " post-status-has-children"; + if (post.parent_id) + thumbClass += " post-status-has-parent"; + + // Figure out sample image dimensions and ratio. + post.sample_ratio = (post.image_width > 850 ? 850 / post.image_width : 1); + post.sample_height = Math.round(post.image_height * post.sample_ratio); + post.sample_width = Math.round(post.image_width * post.sample_ratio); + + // Hidden post fixes. + post.md5 = post.md5 || ""; + post.file_ext = post.file_ext || ""; + post.preview_file_url = post.preview_file_url || bbbHiddenImg; + post.large_file_url = post.large_file_url || ""; + post.file_url = post.file_url || ""; + + // Potential null value fixes. + post.approver_id = post.approver_id || ""; + post.parent_id = post.parent_id || ""; + post.pixiv_id = post.pixiv_id || ""; + + post.flags = flags.bbbSpaceClean(); + post.thumb_class = thumbClass; + + return post; + } + + function fixPaginator(target) { + // Determine whether the paginator needs to be updated and request one as needed. + var paginator = getPaginator(target); + + if (!paginator || gLoc === "pool" || gLoc === "favorite_group" || !allowUserLimit()) + return; + + if (/\d/.test(paginator.textContent)) { // Fix numbered paginators. + // Fix existing paginator with user's custom limit. + var pageLinks = paginator.getElementsByTagName("a"); + var pageLink; + + for (var i = 0, il = pageLinks.length; i < il; i++) { + pageLink = pageLinks[i]; + pageLink.href = updateURLQuery(pageLink.href, {limit: thumbnail_count}); + } + + searchPages("paginator"); + } + else { // Fix next/previous paginators. + paginator.innerHTML = "

Loading...

"; // Disable the paginator while fixing it. + + searchPages("paginator"); + } + } + + function fixCommentSearch() { + // Fix the thumbnails for hidden posts in the comment search. + var posts = getPosts(); + + for (var i = 0, il = posts.length; i < il; i++) { + var post = posts[i]; + var hasImg = post.getElementsByTagName("img")[0]; + var tags = post.getAttribute("data-tags"); + var previewUrl = post.getAttribute("data-preview-file-url"); + + // If the information for fixing the thumbnails is missing, stop checking. + if (!hasImg && !previewUrl) + return; + + // Skip posts with content the user doesn't want or that already have images. + if (hasImg || (!show_loli && /(?:^|\s)loli(?:$|\s)/.test(tags)) || (!show_shota && /(?:^|\s)shota(?:$|\s)/.test(tags)) || (!show_toddlercon && /(?:^|\s)toddlercon(?:$|\s)/.test(tags)) || (!show_banned && /(?:^|\s)banned(?:$|\s)/.test(post.getAttribute("data-flags"))) || safebPostTest(post)) + continue; + + var preview = post.getElementsByClassName("preview")[0]; + var before = preview.firstElementChild; + + var thumb = document.createElement("a"); + thumb.href = "/posts/" + post.getAttribute("data-id"); + thumb.innerHTML = '' + post.getAttribute('; + + if (before) + preview.insertBefore(thumb, before); + else + preview.appendChild(thumb); + + prepThumbnails(post); + } + } + + function bbbNotice(txt, noticeType) { + // Display the notice or add information to it if it already exists. + // A secondary number argument can be provided: -1 = error, 0 = permanent, >0 = temporary (disappears after X seconds where X equals the number provided), "no number" = temporary for 6 seconds + var notice = bbb.el.notice; + var noticeMsg = bbb.el.noticeMsg; + var type = (typeof(noticeType) !== "undefined" ? noticeType : 6); + + var msg = document.createElement("div"); + msg.className = "bbb-notice-msg-entry"; + msg.innerHTML = txt; + msg.style.color = (type === -1 ? "#FF0000" : "#000000"); + + if (!notice) { + var noticeContainer = document.createElement("span"); // Will contain the Danbooru notice and BBB notice so that they can "stack" and reposition as the other disappears. + noticeContainer.id = "bbb-notice-container"; + + var danbNotice = document.getElementById("notice"); + + // Override Danbooru notice styling to make it get along with the notice container. + if (danbNotice) { + danbNotice.style.marginBottom = "10px"; + danbNotice.style.width = "100%"; + danbNotice.style.position = "relative"; + danbNotice.style.top = "0px"; + danbNotice.style.left = "0px"; + noticeContainer.appendChild(danbNotice); + } + + notice = bbb.el.notice = document.createElement("div"); + notice.id = "bbb-notice"; + notice.innerHTML = '
BBB:
'; + noticeContainer.appendChild(notice); + + noticeMsg = bbb.el.noticeMsg = getId("bbb-notice-msg", notice); + + getId("bbb-notice-close", notice).addEventListener("click", function(event) { + closeBbbNotice(); + event.preventDefault(); + }, false); + + document.body.appendChild(noticeContainer); + } + + if (bbb.timers.keepBbbNotice) + window.clearTimeout(bbb.timers.keepBbbNotice); + + if (notice.style.display === "block" && /\S/.test(noticeMsg.textContent)) { // Insert new text at the top if the notice is already open and has an actual message. + noticeMsg.insertBefore(msg, noticeMsg.firstElementChild); + + // Don't allow the notice to be closed via clicking for half a second. Prevents accidental message closing. + bbb.timers.keepBbbNotice = window.setTimeout(function() { + bbb.timers.keepBbbNotice = 0; + }, 500); + } + else { // Make sure the notice is clear and put in the first message. + noticeMsg.innerHTML = ""; + noticeMsg.appendChild(msg); + } + + // Hide the notice after a certain number of seconds. + if (type > 0) { + window.setTimeout(function() { + closeBbbNoticeMsg(msg); + }, type * 1000); + } + + notice.style.display = "block"; + + return msg; + } + + function closeBbbNotice() { + // Click handler for closing the notice. + if (bbb.timers.keepBbbNotice) + return; + + bbb.el.notice.style.display = "none"; + } + + function closeBbbNoticeMsg(el) { + // Closes the provided notice message or the whole notice if there is only one message. + var notice = bbb.el.notice; + var target = el; + var targetParent = target.parentNode; + + if (notice.getElementsByClassName("bbb-notice-msg-entry").length < 2) + closeBbbNotice(); + else if (targetParent) + targetParent.removeChild(target); + } + + function bbbStatus(mode, xmlState) { + // Updates the BBB status message. + // xmlState: "new" = opening an XML request, "done" = closing an xml request, "error" = xml request failed. + if (!enable_status_message) + return; + + var status = bbb.el.status; + + // Set up the status message if it isn't ready. + if (!status) { + bbb.status = { // Status messages. + msg: { + post_comments: {txt: "Fixing hidden comments... ", count: 0}, + hidden: {txt: "Fixing hidden thumbnails... ", count: 0, queue: document.getElementsByClassName("bbb-hidden-thumb")}, // Hidden thumbnail message. + posts: {txt: "Loading post info... ", count: 0} // General message for XML requests for hidden posts. + }, + count: 0 + }; + + var msgList = bbb.status.msg; + + status = bbb.el.status = document.createElement("div"); + status.id = "bbb-status"; + + for (var i in msgList) { + if (msgList.hasOwnProperty(i)) { + var curMsg = msgList[i]; + + var msgDiv = curMsg.el = document.createElement("div"); + msgDiv.style.display = "none"; + msgDiv.innerHTML = curMsg.txt; + status.appendChild(msgDiv); + + curMsg.info = document.createElement("span"); + msgDiv.appendChild(curMsg.info); + } + } + + document.body.appendChild(status); + } + + var msg = bbb.status.msg[mode]; + var newCount = 0; + + if (msg.queue) { // If the xml requests are queued, use the queue length as the current remaining value. + newCount = (xmlState === "error" ? 0 : msg.queue.length); + bbb.status.count += newCount - msg.count; + msg.count = newCount; + msg.info.innerHTML = newCount; // Update the displayed number of requests remaining. + } + else { // For simultaneous xml requests, just increment/decrement. + if (xmlState === "new") + newCount = 1; + else if (xmlState === "done" || xmlState === "error") + newCount = -1; + + bbb.status.count += newCount; + msg.count += newCount; + } + + if (msg.count) + msg.el.style.display = "block"; + else + msg.el.style.display = "none"; + + if (bbb.status.count) // If requests are pending, display the notice. + status.style.display = "block"; + else // If requests are done, hide the notice. + status.style.display = "none"; + } + + function thumbSearchMatch(post, searchArray) { + // Take search objects and test them against a thumbnail's info. + if (!searchArray[0]) + return false; + + var postInfo; + + if (post instanceof Element) { + var tags = post.getAttribute("data-tags"); + var flags = post.getAttribute("data-flags") || "active"; + var rating = " rating:" + post.getAttribute("data-rating"); + var status = " status:" + (flags === "flagged" ? flags + " active" : flags).replace(/\s/g, " status:"); + var user = " user:" + post.getAttribute("data-uploader").replace(/\s/g, "_").toLowerCase(); + var poolData = " " + post.getAttribute("data-pools"); + var pools = (/pool:\d+/.test(poolData) && !/pool:(collection|series)/.test(poolData) ? poolData + " pool:inactive" : poolData); + var score = post.getAttribute("data-score"); + var favcount = post.getAttribute("data-fav-count"); + var id = post.getAttribute("data-id"); + var width = post.getAttribute("data-width"); + var height = post.getAttribute("data-height"); + var parentId = post.getAttribute("data-parent-id"); + var parent = (parentId ? " parent:" + parentId : ""); + var hasChildren = post.getAttribute("data-has-children"); + var child = (hasChildren === "true" ? " child:true" : ""); + + postInfo = { + tags: tags.bbbSpacePad(), + metatags:(rating + status + user + pools + parent + child).bbbSpacePad(), + score: Number(score), + favcount: Number(favcount), + id: Number(id), + width: Number(width), + height: Number(height) + }; + } + else + postInfo = post; + + var anyResult; + var allResult; + var searchTerm = ""; + var j, jl; // Loop variables. + + for (var i = 0, il = searchArray.length; i < il; i++) { + var searchObject = searchArray[i]; + var all = searchObject.all; + var any = searchObject.any; + + // Continue to the next matching rule if there are no tags to test. + if (!any.total && !all.total) + continue; + + if (any.total) { + anyResult = false; + + // Loop until one positive match is found. + for (j = 0, jl = any.includes.length; j < jl; j++) { + searchTerm = any.includes[j]; + + if (thumbTagMatch(postInfo, searchTerm)) { + anyResult = true; + break; + } + } + + // If we don't have a positive match yet, loop through the excludes. + if (!anyResult) { + for (j = 0, jl = any.excludes.length; j < jl; j++) { + searchTerm = any.excludes[j]; + + if (!thumbTagMatch(postInfo, searchTerm)) { + anyResult = true; + break; + } + } + } + + // Continue to the next matching rule if none of the "any" tags matched. + if (!anyResult) + continue; + } + + if (all.total) { + allResult = true; + + // Loop until a negative match is found. + for (j = 0, jl = all.includes.length; j < jl; j++) { + searchTerm = all.includes[j]; + + if (!thumbTagMatch(postInfo, searchTerm)) { + allResult = false; + break; + } + } + + // If we still have a positive match, loop through the excludes. + if (allResult) { + for (j = 0, jl = all.excludes.length; j < jl; j++) { + searchTerm = all.excludes[j]; + + if (thumbTagMatch(postInfo, searchTerm)) { + allResult = false; + break; + } + } + } + + // Continue to the next matching rule if one of the "all" tags didn't match. + if (!allResult) + continue; + } + + // Loop completed without a negative match so return true. + return true; + } + + // If we haven't managed a positive match for any rules, return false. + return false; + } + + function thumbTagMatch(postInfo, tag) { + // Test thumbnail info for a tag match. + var targetTags; + + if (typeof(tag) === "string") { // Check regular tags and metatags with string values. + targetTags = (isMetatag(tag) ? postInfo.metatags : postInfo.tags); + + if (targetTags.indexOf(tag) > -1) + return true; + else + return false; + } + else if (tag instanceof RegExp) { // Check wildcard tags. + targetTags = (isMetatag(tag.source) ? postInfo.metatags : postInfo.tags); + + return tag.test(targetTags); + } + else if (typeof(tag) === "object") { // Check numeric metatags. + if (tag instanceof Array) + return thumbSearchMatch(postInfo, tag); + else { + var tagsMetaValue = postInfo[tag.tagName]; + + if (tag.equals !== undefined) { + if (tagsMetaValue !== tag.equals) + return false; + } + else { + if (tag.greater !== undefined && tagsMetaValue <= tag.greater) + return false; + + if (tag.less !== undefined && tagsMetaValue >= tag.less) + return false; + } + + return true; + } + } + } + + function createSearch(search) { + // Take search strings, turn them into search objects, and pass back the objects in an array. + if (!/[^\s,]/.test(search)) + return []; + + var groupsObject = replaceSearchGroups(search); + var groups = groupsObject.groups; + var searchStrings = groupsObject.search.toLowerCase().replace(/\b(rating:[qes])\w+/g, "$1").split(","); + var searches = []; + + // Sort through each matching rule. + for (var i = 0, il = searchStrings.length; i < il; i++) { + var searchString = searchStrings[i].split(" "); + var searchObject = { + all: {includes: [], excludes: [], total: 0}, + any: {includes: [], excludes: [], total: 0} + }; + + // Divide the tags into any and all sets with excluded and included tags. + for (var j = 0, jl = searchString.length; j < jl; j++) { + var searchTerm = searchString[j]; + var mode; + var primaryMode = "all"; + var secondaryMode = "includes"; + + while (searchTerm.charAt(0) === "~" || searchTerm.charAt(0) === "-") { + switch (searchTerm.charAt(0)) { + case "~": + primaryMode = "any"; + break; + case "-": + secondaryMode = "excludes"; + break; + } + + searchTerm = searchTerm.slice(1); + } + + if (!searchTerm.length) // Stop if there is no actual tag. + continue; + + mode = searchObject[primaryMode][secondaryMode]; + + if (isNumMetatag(searchTerm)) { // Parse numeric metatags and turn them into objects. + var tagArray = searchTerm.split(":"); + var metaObject = { + tagName: tagArray[0], + equals: undefined, + greater: undefined, + less: undefined + }; + var numSearch = tagArray[1]; + var numArray; + var equals; + var greater; + var less; + + if (numSearch.indexOf("<=") === 0 || numSearch.indexOf("..") === 0) { // Less than or equal to. (tag:<=# & tag:..#) + less = parseInt(numSearch.slice(2), 10); + + if (!isNaN(less)) { + metaObject.less = less + 1; + mode.push(metaObject); + } + } + else if (numSearch.indexOf(">=") === 0) { // Greater than or equal to. (tag:>=#) + greater = parseInt(numSearch.slice(2), 10); + + if (!isNaN(greater)) { + metaObject.greater = greater - 1; + mode.push(metaObject); + } + } + else if (numSearch.length > 2 && numSearch.indexOf("..") === numSearch.length - 2) { // Greater than or equal to. (tag:#..) + greater = parseInt(numSearch.slice(0, -2), 10); + + if (!isNaN(greater)) { + metaObject.greater = greater - 1; + mode.push(metaObject); + } + } + else if (numSearch.charAt(0) === "<") { // Less than. (tag:<#) + less = parseInt(numSearch.slice(1), 10); + + if (!isNaN(less)) { + metaObject.less = less; + mode.push(metaObject); + } + } + else if (numSearch.charAt(0) === ">") { // Greater than. (tag:>#) + greater = parseInt(numSearch.slice(1), 10); + + if (!isNaN(greater)) { + metaObject.greater = greater; + mode.push(metaObject); + } + } + else if (numSearch.indexOf("..") > -1) { // Greater than or equal to and less than or equal to range. (tag:#..#) + numArray = numSearch.split(".."); + greater = parseInt(numArray[0], 10); + less = parseInt(numArray[1], 10); + + if (!isNaN(greater) && !isNaN(less)) { + metaObject.greater = greater - 1; + metaObject.less = less + 1; + mode.push(metaObject); + } + } + else { // Exact number. (tag:#) + equals = parseInt(numSearch, 10); + + if (!isNaN(equals)) { + metaObject.equals = equals; + mode.push(metaObject); + } + } + } + else if (searchTerm.indexOf("*") > -1) // Prepare wildcard tags as regular expressions. + mode.push(new RegExp(escapeRegEx(searchTerm).replace(/\*/g, "\S*").bbbSpacePad())); // Don't use "\\S*" here since escapeRegEx replaces * with \*. That escape carries over to the next replacement and makes us end up with "\\S*". + else if (/%\d+%/.test(searchTerm)) { + var groupIndex = Number(searchTerm.match(/\d+/)[0]); + + mode.push(createSearch(groups[groupIndex])); + } + else if (typeof(searchTerm) === "string") { // Add regular tags. + if (isMetatag(searchTerm)) { + var tagObject = searchTerm.split(/:(.+)/); + var tagName = tagObject[0]; + var tagValue = tagObject[1]; + + // Drop metatags with no value. + if (!tagValue) + continue; + + if (tagValue === "any" && (tagName === "pool" || tagName === "parent" || tagName === "child")) + mode.push(new RegExp((tagName + ":\\S*").bbbSpacePad())); + else if (tagValue === "none" && (tagName === "pool" || tagName === "parent" || tagName === "child")) { + secondaryMode = (secondaryMode === "includes" ? "excludes" : "includes"); // Flip the include/exclude mode. + mode = searchObject[primaryMode][secondaryMode]; + + mode.push(new RegExp((tagName + ":\\S*").bbbSpacePad())); + } + else if (tagValue === "active" && tagName === "pool") + mode.push(new RegExp((tagName + ":(collection|series)").bbbSpacePad())); + else // Allow all other values through (ex: parent:# & pool:series). + mode.push(searchTerm.bbbSpacePad()); + } + else + mode.push(searchTerm.bbbSpacePad()); + } + } + + searchObject.all.total = searchObject.all.includes.length + searchObject.all.excludes.length; + searchObject.any.total = searchObject.any.includes.length + searchObject.any.excludes.length; + + if (searchObject.all.total || searchObject.any.total) + searches.push(searchObject); + } + + return searches; + } + + function replaceSearchGroups(search) { + // Collect all the nested/grouped tags in a search and replace them with placeholders. + if (search.indexOf("%") < 0) + return {search: search, groups: []}; + + var searchString = search; + var parens = searchString.match(/\(%|%\)/g); + + // Remove unpaired opening parentheses near the end of the search. + while (parens[parens.length - 1] === "(%") { + searchString = searchString.replace(/^(.*\s)?[~-]*\(%/, "$1"); + parens.pop(); + } + + // Take the remaining parentheses and figure out how to pair them up. + var startCount = 0; + var endCount = 0; + var groupStartIndex = 0; + var groups = []; + + for (var i = 0, il = parens.length; i < il; i++) { + var paren = parens[i]; + var nextParen = parens[i + 1]; + + if (paren === "(%") + startCount++; + else + endCount++; + + if (endCount > startCount) { // Remove unpaired closing parentheses near the start of the string. + searchString = searchString.replace(/^(.*?)%\)/, "$1"); + endCount = 0; + groupStartIndex++; + } + else if (startCount === endCount || (!nextParen && endCount > 0 && startCount > endCount)) { // Replace evenly paired parentheses with a placeholder. + var groupRegex = new RegExp(parens.slice(groupStartIndex, i + 1).join(".*?").replace(/[\(\)]/g, "\\$&")); + var groupMatch = searchString.match(groupRegex)[0]; + + searchString = searchString.replace(groupMatch, "%" + groups.length + "%"); + startCount = 0; + endCount = 0; + groupStartIndex = i + 1; + groups.push(groupMatch.substring(2, groupMatch.length - 2)); + } + else if (!nextParen && startCount > 0 && endCount === 0 ) // Remove leftover unpaired opening parentheses. + searchString = searchString.replace(/^(.*\s)?[~-]*\(%/, "$1"); + } + + return {search: searchString, groups: groups}; + } + + function restoreSearchGroups(search, groups) { + // Replace all group placeholders with their corresponding group. + var restoredSearch = search; + + for (var i = 0, il = groups.length; i < il; i++) { + var groupPlaceholder = new RegExp("%" + i + "%"); + + restoredSearch = restoredSearch.replace(groupPlaceholder, "(%" + groups[i] + "%)"); + } + + return restoredSearch; + } + + function cleanSearchGroups(string) { + // Take a search string and clean up extra spaces, commas, and any parentheses that are missing their opening/closing parenthesis. + var groupObject = replaceSearchGroups(string); + var groups = groupObject.groups; + var searchString = groupObject.search; + + for (var i = 0, il = groups.length; i < il; i++) + groups[i] = cleanSearchGroups(groups[i]); + + searchString = restoreSearchGroups(searchString, groups).bbbTagClean(); + + return searchString; + } + + function searchSingleToMulti(string) { + // Take a single line search and format it into multiple lines for a textarea. + var groupsObject = replaceSearchGroups(cleanSearchGroups(string)); + var searchString = groupsObject.search; + var groups = groupsObject.groups; + var searchText = searchString.replace(/,\s*/g, "\r\n\r\n"); + + searchText = restoreSearchGroups(searchText, groups); + + return searchText; + } + + function searchMultiToSingle(multi) { + // Take a multiple line search from a textarea and format it into a single line. + var searchStrings = multi.split(/[\r\n]+/g); + + for (var i = 0, il = searchStrings.length; i < il; i++) + searchStrings[i] = cleanSearchGroups(searchStrings[i]); + + var searchString = searchStrings.join(", "); + + return searchString; + } + + function trackNew() { + // Set up the track new option and manage the search. + var header = document.getElementById("top"); + + if (!track_new || !header) + return; + + var activeMenu = header.getElementsByClassName("current")[0]; + var secondMenu = header.getElementsByTagName("menu")[1]; - tip.style.left = x - tip.offsetWidth - 2 + "px"; - tip.style.top = y - tip.offsetHeight - 2 - topOffset + "px"; - tip.style.visibility = "visible"; - } + // Insert new posts link. + if (activeMenu && activeMenu.textContent === "Posts" && secondMenu) { + var menuItems = secondMenu.getElementsByTagName("li"); + var numMenuItems = secondMenu.getElementsByTagName("li").length; + var listingItemSibling = menuItems[1]; - function hideTip() { - bbb.el.menu.tip.removeAttribute("style"); - } + for (var i = 0; i < numMenuItems; i++) { + var menuLink = menuItems[i]; + var nextLink = menuItems[i + 1]; - Element.prototype.bbbBorderPreview = function(borderItem) { - this.addEventListener("click", function(event) { showTip(event, "\"IMAGE\"", "background-color: #FFFFFF;"); }, false); - this.addEventListener("mouseout", hideTip, false); - }; + if (menuLink.textContent.indexOf("Listing") > -1) { + if (nextLink) + listingItemSibling = nextLink; + else + listingItemSibling = undefined; - Element.prototype.bbbSetTip = function(text) { - this.addEventListener("click", function(event) { showTip(event, text, false); }, false); - this.addEventListener("mouseout", hideTip, false); - }; + break; + } + } - function changeTab(tab) { - var activeTab = document.getElementsByClassName("bbb-active-tab")[0]; + var link = document.createElement("a"); + link.href = "/posts?new_posts=redirect&page=b1"; + link.innerHTML = "New"; + link.addEventListener("click", function(event) { + if (event.button === 0) { + trackNewLoad(); + event.preventDefault(); + } + }, false); - if (tab === activeTab) - return; + var item = document.createElement("li"); + item.appendChild(link); - activeTab.className = activeTab.className.replace(/\s?bbb-active-tab/g, ""); - bbb.el.menu[activeTab.name + "Page"].style.display = "none"; - bbb.el.menu.scrollDiv.scrollTop = 0; - tab.className += " bbb-active-tab"; - bbb.el.menu[tab.name + "Page"].style.display = "block"; - } + if (listingItemSibling) + secondMenu.insertBefore(item, listingItemSibling); + else + secondMenu.appendChild(item); + } - function tagEditWindow(input, object, prop) { - bbb.el.menu.tagEditBlocker.style.display = "block"; - bbb.el.menu.tagEditArea.value = input.value.bbbTagClean().replace(/,\s*/g, "\r\n\r\n"); - bbb.tagEdit = {input: input, object: object, prop: prop}; - } + if (gLoc === "search") { + var info = track_new_data; + var mode = getVar("new_posts"); + var postsDiv = document.getElementById("posts"); + var postSections = document.getElementById("post-sections"); + var posts = getPosts(); + var firstPost = posts[0]; - function adjustMenuHeight() { - var menu = bbb.el.menu.window; - var scrollDiv = bbb.el.menu.scrollDiv; - var viewHeight = window.innerHeight; - var scrollDivDiff = menu.offsetHeight - scrollDiv.clientHeight; + if (mode === "init" && !info.viewed && !getVar("tags") && !getVar("page")) { // Initialize. + if (firstPost) { + info.viewed = Number(firstPost.getAttribute("data-id")); + info.viewing = 1; + saveSettings(); + bbbNotice("New post tracking initialized. Tracking will start with new posts after the current last image.", 8); + } + } + else if (mode === "redirect") { // Bookmarkable redirect link. (http://danbooru.donmai.us/posts?new_posts=redirect&page=b1) + if (postsDiv) + postsDiv.innerHTML = "Redirecting..."; - scrollDiv.style.maxHeight = viewHeight - scrollDiv.bbbGetPadding().height - scrollDivDiff - 50 + "px"; // Subtract 50 for margins (25 each). + trackNewLoad(); + } + else if (mode === "list") { + var limitNum = getLimit() || thumbnail_count || thumbnail_count_default; + var currentPage = Number(getVar("page")) || 1; + var savedPage = Math.ceil((info.viewing - limitNum) / limitNum) + 1; + var currentViewed = Number(/id:>(\d+)/.exec(decodeURIComponent(location.search))[1]); + var paginator = getPaginator(); + + // Replace the chickens message on the first page with a more specific message. + if (!firstPost && currentPage < 2) { + if (postsDiv && postsDiv.firstElementChild) + postsDiv.firstElementChild.innerHTML = "No new posts."; + } + + // Update the saved page information. + if (savedPage !== currentPage && info.viewed === currentViewed) { + info.viewing = (currentPage - 1) * limitNum + 1; + saveSettings(); + } + + // Modify new post searches with a mark as viewed link. + if (postSections) { + var markSection = document.createElement("li"); + + var markLink = document.createElement("a"); + markLink.innerHTML = (currentPage > 1 ? "Mark pages 1-" + currentPage + " viewed" : "Mark page 1 viewed"); + markLink.href = "#"; + markSection.appendChild(markLink); + postSections.appendChild(markSection); + + markLink.addEventListener("click", function(event) { + trackNewMark(); + event.preventDefault(); + }, false); + + var resetSection = document.createElement("li"); + resetSection.style.cssFloat = "right"; + + var resetLink = document.createElement("a"); + resetLink.innerHTML = "Reset (Mark all viewed)"; + resetLink.href = "#"; + resetLink.style.color = "#FF1100"; + resetSection.appendChild(resetLink); + postSections.appendChild(resetSection); + + resetLink.addEventListener("click", function(event) { + trackNewReset(); + event.preventDefault(); + }, false); + + // Update the mark link if the paginator updates. + if (paginator) { + paginator.bbbWatchNodes(function() { + var activePage = paginator.getElementsByTagName("span")[0]; + + if (activePage) + markLink.innerHTML = "Mark pages 1-" + activePage.textContent.bbbSpaceClean() + " viewed"; + }); + } + } + } + } } - function adjustMenuTimer() { - if (!adjustMenuTimeout && bbb.el.menu.window) - var adjustMenuTimeout = window.setTimeout(function() { adjustMenuHeight(); }, 50); + function trackNewLoad() { + // Create the search URL and load it. + var info = bbb.user.track_new_data; + var limitNum = bbb.user.thumbnail_count || thumbnail_count_default; + var savedPage = Math.ceil((info.viewing - limitNum) / limitNum) + 1; + + if (info.viewed) + location.href = "/posts?new_posts=list&tags=order:id_asc+id:>" + info.viewed + "&page=" + savedPage + "&limit=" + limitNum; + else + location.href = "/posts?new_posts=init&limit=" + limitNum; } - function removeMenu() { - // Destroy the menu so that it gets rebuilt. - var menu = bbb.el.menu.window; + function trackNewReset() { + // Reinitialize settings/Mark all viewed. + loadSettings(); - if (!menu) - return; + var limitNum = bbb.user.thumbnail_count || thumbnail_count_default; - menu.parentNode.removeChild(menu); - bbb.el.menu = {}; + bbb.user.track_new_data = bbb.options.track_new_data.def; + saveSettings(); + + bbbNotice("Reinitializing new post tracking. Please wait.", 0); + location.href = "/posts?new_posts=init&limit=" + limitNum; } - function loadSettings() { - // Load stored settings. - if (typeof(localStorage.bbb_settings) === "undefined") - loadDefaults(); + function trackNewMark() { + // Mark the current images and older as viewed. + loadSettings(); + + var info = bbb.user.track_new_data; + var limitNum = getLimit() || bbb.user.thumbnail_count || thumbnail_count_default; + var posts = getPosts(); + var lastPost = posts[posts.length - 1]; + var lastId = (lastPost ? Number(lastPost.getAttribute("data-id")) : null ); + + if (!lastPost) + bbbNotice("Unable to mark as viewed. No posts detected.", -1); + else if (info.viewed >= lastId) + bbbNotice("Unable to mark as viewed. Posts have already been marked.", -1); else { - bbb.user = JSON.parse(localStorage.bbb_settings); - checkUser(bbb.user, bbb.options); + info.viewed = Number(lastPost.getAttribute("data-id")); + info.viewing = 1; + saveSettings(); - if (bbb.user.bbb_version !== bbb.options.bbb_version) { - convertSettings("load"); - saveSettings(); + bbbNotice("Posts marked as viewed. Please wait while the pages are updated.", 0); + location.href = "/posts?new_posts=list&tags=order:id_asc+id:>" + info.viewed + "&page=1&limit=" + limitNum; + } + } + + function customCSS() { + var i; // Loop variable. + var customStyles = document.createElement("style"); + customStyles.type = "text/css"; + + var styles = '#bbb_menu {background-color: #FFFFFF; border: 1px solid #CCCCCC; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5); padding: 15px; position: fixed; top: 25px; left: 50%; z-index: 9001;}' + + '#bbb_menu * {font-size: 14px; line-height: 16px; outline: 0px none; border: 0px none; margin: 0px; padding: 0px;}' + // Reset some base settings. + '#bbb_menu h1 {font-size: 24px; line-height: 42px;}' + + '#bbb_menu h2 {font-size: 16px; line-height: 25px;}' + + '#bbb_menu input, #bbb_menu select, #bbb_menu textarea {border: #CCCCCC 1px solid;}' + + '#bbb_menu input {height: 17px; padding: 1px 0px; margin-top: 4px; vertical-align: top;}' + + '#bbb_menu input[type="checkbox"] {margin: 0px; vertical-align: middle; position: relative; bottom: 2px;}' + + '#bbb_menu .bbb-general-input input[type="text"], #bbb_menu .bbb-general-input select {width: 175px;}' + + '#bbb_menu select {height: 21px; margin-top: 4px; vertical-align: top;}' + + '#bbb_menu option {padding: 0px 3px;}' + + '#bbb_menu textarea {padding: 2px; resize: none;}' + + '#bbb_menu ul, #bbb_menu ol {list-style: outside disc none; margin-top: 0px; margin-bottom: 0px; margin-left: 20px; display: block;}' + + '#bbb_menu .bbb-scroll-div {border: 1px solid #CCCCCC; margin: -1px 0px 5px 0px; padding: 5px 0px; overflow-y: auto;}' + + '#bbb_menu .bbb-page {position: relative; display: none;}' + + '#bbb_menu .bbb-button {border: 1px solid #CCCCCC; border-radius: 5px; display: inline-block; padding: 5px;}' + + '#bbb_menu .bbb-tab {border-top-left-radius: 5px; border-top-right-radius: 5px; display: inline-block; padding: 5px; border: 1px solid #CCCCCC; margin-right: -1px;}' + + '#bbb_menu .bbb-active-tab {background-color: #FFFFFF; border-bottom-width: 0px; padding-bottom: 6px;}' + + '#bbb_menu .bbb-header {border-bottom: 2px solid #CCCCCC; margin-bottom: 5px; width: 700px;}' + + '#bbb_menu .bbb-toc {list-style-type: upper-roman; margin-left: 30px;}' + + '#bbb_menu .bbb-section-options, #bbb_menu .bbb-section-text {margin-bottom: 5px; max-width: 902px;}' + + '#bbb_menu .bbb-section-options-left, #bbb_menu .bbb-section-options-right {display: inline-block; vertical-align: top; width: 435px;}' + + '#bbb_menu .bbb-section-options-left {border-right: 1px solid #CCCCCC; margin-right: 15px; padding-right: 15px;}' + + '#bbb_menu .bbb-general-label {display: block; height: 29px; padding: 0px 5px;}' + + '#bbb_menu .bbb-general-label:hover {background-color: #EEEEEE;}' + + '#bbb_menu .bbb-general-text {line-height: 29px;}' + + '#bbb_menu .bbb-general-input {float: right; line-height: 29px;}' + + '#bbb_menu .bbb-expl-link {font-size: 12px; font-weight: bold; margin-left: 5px; padding: 2px;}' + + '#bbb_menu .bbb-border-div {background-color: #EEEEEE; padding: 2px; margin: 0px 5px 0px 0px;}' + + '#bbb_menu .bbb-border-bar, #bbb_menu .bbb-border-settings {height: 29px; padding: 0px 2px; overflow: hidden;}' + + '#bbb_menu .bbb-border-settings {background-color: #FFFFFF;}' + + '#bbb_menu .bbb-border-div label, #bbb_menu .bbb-border-div span {display: inline-block; line-height: 29px;}' + + '#bbb_menu .bbb-border-name {text-align: left; width: 540px;}' + + '#bbb_menu .bbb-border-name input {width:460px;}' + + '#bbb_menu .bbb-border-color {text-align: center; width: 210px;}' + + '#bbb_menu .bbb-border-color input {width: 148px;}' + + '#bbb_menu .bbb-border-style {float: right; text-align: right; width: 130px;}' + + '#bbb_menu .bbb-border-divider {height: 4px;}' + + '#bbb_menu .bbb-insert-highlight .bbb-border-divider {background-color: blue; cursor: pointer;}' + + '#bbb_menu .bbb-no-highlight .bbb-border-divider {background-color: transparent; cursor: auto;}' + + '#bbb_menu .bbb-border-button {border: 1px solid #CCCCCC; border-radius: 5px; display: inline-block; padding: 2px; margin: 0px 2px;}' + + '#bbb_menu .bbb-border-spacer {display: inline-block; height: 12px; width: 0px; border-right: 1px solid #CCCCCC; margin: 0px 5px;}' + + '#bbb_menu .bbb-backup-area {height: 300px; width: 896px; margin-top: 2px;}' + + '#bbb_menu .bbb-blacklist-area {height: 300px; width: 896px; margin-top: 2px;}' + + '#bbb_menu .bbb-edit-blocker {display: none; height: 100%; width: 100%; background-color: rgba(0, 0, 0, 0.33); position: fixed; top: 0px; left: 0px;}' + + '#bbb_menu .bbb-edit-box {height: 500px; width: 800px; margin-left: -412px; margin-top: -262px; position: fixed; left: 50%; top: 50%; background-color: #FFFFFF; border: 2px solid #CCCCCC; padding: 10px; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5);}' + + '#bbb_menu .bbb-edit-text {margin-bottom: 5px;}' + + '#bbb_menu .bbb-edit-area {height: 429px; width: 794px; margin-bottom: 5px;}' + + '#bbb_menu .bbb-edit-link {background-color: #FFFFFF; border: 1px solid #CCCCCC; display: inline-block; height: 19px; line-height: 19px; margin-left: -1px; padding: 0px 2px; margin-top: 4px; text-align: center; vertical-align: top;}' + + '#bbb-expl {background-color: #CCCCCC; border: 1px solid #000000; display: none; font-size: 12px; padding: 5px; position: fixed; max-width: 488px; width: 488px; overflow: hidden; z-index: 9002; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5);}' + + '#bbb-expl * {font-size: 12px;}' + + '#bbb-expl tiphead {display: block; font-weight: bold; text-decoration: underline; font-size: 13px; margin-top: 12px;}' + + '#bbb-expl tipdesc {display: inline; font-weight: bold;}' + + '#bbb-expl tipdesc:before {content: "\\A0"; display: block; height: 12px; clear: both;}' + // Simulate a double line break. + '#bbb-status {background-color: rgba(255, 255, 255, 0.75); border: 1px solid rgba(204, 204, 204, 0.75); font-size: 12px; font-weight: bold; text-align: right; display: none; padding: 3px; position: fixed; bottom: 0px; right: 0px; z-index: 9002;}' + + '#bbb-notice-container {position: fixed; top: 0.5em; left: 25%; width: 50%;}' + + '#bbb-notice {padding: 3px; width: 100%; display: none; position: relative; z-index: 9002; border-radius: 2px; border: 1px solid #000000; background-color: #CCCCCC;}' + + '#bbb-notice-msg {margin: 0px 25px 0px 55px; max-height: 200px; overflow: auto;}' + + '#bbb-notice-msg .bbb-notice-msg-entry {border-bottom: solid 1px #000000; margin-bottom: 5px; padding-bottom: 5px;}' + + '#bbb-notice-msg .bbb-notice-msg-entry:last-child {border-bottom: none 0px; margin-bottom: 0px; padding-bottom: 0px;}'; + + // Provide a little extra space for listings that allow thumbnail_count. + if (thumbnail_count && (gLoc === "search" || gLoc === "notes" || gLoc === "favorites")) { + styles += 'div#page {margin: 0px 10px 0px 20px !important;}' + + 'section#content {padding: 0px !important;}'; + } + + // Calculate some dimensions. + var totalBorderWidth = (custom_tag_borders ? border_width * 2 + (border_spacing * 2 || 1) : border_width + border_spacing); + var thumbMaxWidth = 150 + totalBorderWidth * 2; + var thumbMaxHeight = thumbMaxWidth; + var listingExtraSpace = (14 - totalBorderWidth * 2 > 2 ? 14 - totalBorderWidth * 2 : 2); + var commentExtraSpace = 34 - totalBorderWidth * 2; + var customBorderSpacing = (border_spacing || 1); + + if (thumb_info === "below") + thumbMaxHeight += 18; // Add some extra height for the info. + + // Border setup. + var sbsl = status_borders.length; + var statusBorderItem; + + styles += 'article.post-preview a.bbb-thumb-link, .post-preview div.preview a.bbb-thumb-link {display: inline-block !important;}' + + 'article.post-preview {height: ' + thumbMaxHeight + 'px !important; width: ' + thumbMaxWidth + 'px !important; margin: 0px ' + listingExtraSpace + 'px ' + listingExtraSpace + 'px 0px !important;}' + + 'article.post-preview.pooled {height: ' + (thumbMaxHeight + 60) + 'px !important;}' + // Pool gallery view thumb height adjustment. + '#has-parent-relationship-preview article.post-preview, #has-children-relationship-preview article.post-preview {padding: 5px 5px 10px !important; width: auto !important; max-width: ' + thumbMaxWidth + 'px !important; margin: 0px !important;}' + + 'article.post-preview a.bbb-thumb-link {line-height: 0px !important;}' + + '.post-preview div.preview {height: ' + thumbMaxHeight + 'px !important; width: ' + thumbMaxWidth + 'px !important; margin-right: ' + commentExtraSpace + 'px !important;}' + + '.post-preview div.preview a.bbb-thumb-link {line-height: 0px !important;}' + + '.post-preview a.bbb-thumb-link img {border-width: ' + border_width + 'px !important; padding: ' + border_spacing + 'px !important;}' + + 'a.bbb-thumb-link.bbb-custom-tag {border-width: ' + border_width + 'px !important;}' + + 'article.post-preview:before, .post-preview div.preview:before {margin: ' + totalBorderWidth + 'px !important;}'; // Thumbnail icon overlay position adjustment. + + if (custom_status_borders) { + var activeStatusStyles = ""; + var statusBorderInfo = {}; + + for (i = 0; i < sbsl; i++) { + statusBorderItem = status_borders[i]; + statusBorderInfo[statusBorderItem.tags] = statusBorderItem; + } + + for (i = 0; i < sbsl; i++) { + statusBorderItem = status_borders[i]; + + if (single_color_borders) { + if (statusBorderItem.is_enabled) + activeStatusStyles = '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}' + activeStatusStyles; + else + styles += '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: transparent !important;}'; // Disable status border by resetting it to transparent. + } + else { + if (statusBorderItem.is_enabled) { + if (statusBorderItem.tags === "parent") { + styles += '.post-preview.post-status-has-children a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; // Parent only status border. + + if (statusBorderInfo.child.is_enabled) + styles += '.post-preview.post-status-has-children.post-status-has-parent a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' ' + statusBorderInfo.child.border_color + ' ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' ' + statusBorderInfo.child.border_style + ' ' + statusBorderItem.border_style + ' !important;}'; // Parent and child status border. + } + else if (statusBorderItem.tags === "child") + styles += '.post-preview.post-status-has-parent a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; // Child only status border. + else { + activeStatusStyles = '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged only status border. + + if (statusBorderInfo.parent.is_enabled) + activeStatusStyles = '.post-preview.post-status-has-children.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderInfo.parent.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.parent.border_color + ' !important; border-style: ' + statusBorderInfo.parent.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.parent.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged and parent status border. + + if (statusBorderInfo.child.is_enabled) + activeStatusStyles = '.post-preview.post-status-has-parent.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderInfo.child.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' !important; border-style: ' + statusBorderInfo.child.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged and child status border. + + if (statusBorderInfo.child.is_enabled && statusBorderInfo.parent.is_enabled) + activeStatusStyles = '.post-preview.post-status-has-children.post-status-has-parent.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderInfo.parent.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' !important; border-style: ' + statusBorderInfo.parent.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged, parent, and child status border. + } + } + else + styles += '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: transparent !important;}'; // Disable status border by resetting it to transparent. + } } + + styles += activeStatusStyles; } - } + else if (single_color_borders) { // Allow single color borders when not using custom status borders. Works off of the old border hierarchy: Deleted > Flagged > Pending > Child > Parent + var defaultStatusBorders = bbb.options.status_borders; - function loadDefaults() { - bbb.user = {}; + for (i = defaultStatusBorders.length - 1; i >= 0; i--) { + statusBorderItem = defaultStatusBorders[i]; - for (var i in bbb.options) { - if (bbb.options.hasOwnProperty(i)) { - if (typeof(bbb.options[i].def) !== "undefined") - bbb.user[i] = bbb.options[i].def; - else - bbb.user[i] = bbb.options[i]; + styles += '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; } } - } - function checkUser(user, options) { - // Verify the user has all the base settings and add them with their default values if they don't. - for (var i in options) { - if (options.hasOwnProperty(i)) { - if (typeof(user[i]) === "undefined") { - if (typeof(options[i].def) !== "undefined") - user[i] = options[i].def; - else - user[i] = options[i]; - } - else if (typeof(user[i]) === "object" && !(user[i] instanceof Array)) - checkUser(user[i], options[i]); + if (custom_tag_borders) { + styles += '.post-preview a.bbb-thumb-link.bbb-custom-tag img {border-width: 0px !important;}' + // Remove the transparent border for images that get custom tag borders. + 'article.post-preview a.bbb-thumb-link, .post-preview div.preview a.bbb-thumb-link {margin-top: ' + (border_width + customBorderSpacing) + 'px !important;}'; // Align one border images with two border images. + + for (i = 0; i < sbsl; i++) { + statusBorderItem = status_borders[i]; + + if (statusBorderItem.is_enabled) + styles += '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link.bbb-custom-tag {margin: 0px !important; padding: ' + customBorderSpacing + 'px !important;}' + // Remove margin alignment and add border padding for images that have status and custom tag borders. + '.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link.bbb-custom-tag img {border-width: ' + border_width + 'px !important;}'; // Override the removal of the transparent border for images that have status borders and custom tag borders. } } - } - function saveSettings() { - // Save the user settings to localStorage after making any necessary checks/adjustments. - if (!bbb.user.track_new && bbb.user.track_new_data.viewed) // Reset new post tracking if it's disabled. - bbb.user.track_new_data = bbb.options.track_new_data.def; - - if (thumb_cache_limit !== bbb.user.thumb_cache_limit) // Trim down the thumb cache as necessary if the limit has changed. - adjustThumbCache(); + // Thumbnail info. + var thumbInfoStyle = "height: 18px; font-size: 14px; line-height: 18px; text-align: center;"; - localStorage.bbb_settings = JSON.stringify(bbb.user); - } + if (thumb_info === "below") + styles += '.bbb-thumb-info-parent .bbb-thumb-info {display: block;' + thumbInfoStyle + '}'; + else if (thumb_info === "hover") { + styles += '.bbb-thumb-info-parent .bbb-thumb-info {display: none; position: relative; bottom: 18px; background-color: rgba(255, 255, 255, 0.9);' + thumbInfoStyle + '}' + + '.bbb-thumb-info-parent:hover .bbb-thumb-info {display: block;}' + + '#has-children-relationship-preview article.post-preview.bbb-thumb-info-parent, #has-parent-relationship-preview article.post-preview.bbb-thumb-info-parent {min-width: 130px !important;}' + // Give parent/child notice thumbs a minimum width to prevent element shifting upon hover. + '.bbb-thumb-info-parent:hover .bbb-thumb-info.bbb-thumb-info-short {bottom: 0px;}' + // Short thumbnails get no overlapping. + '.bbb-thumb-info-parent.blacklisted-active .bbb-thumb-info.bbb-thumb-info-short {bottom: 18px;}'; // Actively blacklisted short thumbnails get overlapping. + } - function updateSettings() { - // Change & save the settings without the panel. Accepts a comma delimited list of alternating settings and values: setting1, value1, setting2, value2 - loadSettings(); + // Endless + if (endless_default !== "disabled") { + styles += 'div.paginator {padding: 3em 0px 0px;}' + + '#bbb-endless-button-div {width: 100%; height: 0px; overflow: visible; clear: both; text-align: center;}' + + '#bbb-endless-load-div, #bbb-endless-enable-div {display: none; position: absolute;}' + + '#bbb-endless-load-button, #bbb-endless-enable-button {position: relative; left: -50%; border: 1px solid #EAEAEA; border-radius: 5px; display: inline-block; padding: 5px; margin-top: 3px;}'; - for (var i = 0, il = arguments.length; i < il; i += 2) { - var setting = arguments[i].split("."); - var value = arguments[i + 1]; - var settingPath = bbb.user; + if (endless_separator === "divider") { + styles += '.bbb-endless-page {display: block; clear: both;}' + + '.bbb-endless-divider {display: block; border: 1px solid #CCCCCC; height: 0px; margin: 15px 0px; width: 100%; float: left;}' + + '.bbb-endless-divider-link {position: relative; top: -16px; display: inline-block; height: 32px; margin-left: 5%; padding: 0px 5px; font-size: 14px; font-weight: bold; line-height: 32px; background-color: #FFFFFF; color: #CCCCCC;}'; + } + else if (endless_separator === "marker") { + styles += '.bbb-endless-page {display: inline;}' + + 'article.bbb-endless-marker-article {height: ' + thumbMaxHeight + 'px !important; width: ' + thumbMaxWidth + 'px !important; margin: 0px ' + listingExtraSpace + 'px ' + listingExtraSpace + 'px 0px !important; float: left; overflow: hidden; text-align: center; vertical-align: baseline; position: relative;}' + + '.bbb-endless-marker {display: inline-block; border: 1px solid #CCCCCC; height: 148px; width: 148px; line-height: 148px; text-align: center; margin-top: ' + totalBorderWidth + 'px;}' + + '.bbb-endless-marker-link {display: inline-block; font-size: 14px; font-weight: bold; line-height: 14px; vertical-align: middle; color: #CCCCCC;}'; + } + else if (endless_separator === "none") + styles += '.bbb-endless-page {display: inline;}'; + } - for (var j = 0, jl = setting.length - 1; j < jl; j++) - settingPath = settingPath[setting[j]]; + // Hide sidebar. + if (autohide_sidebar) { + styles += 'div#page {margin: 0px 10px 0px 20px !important;}' + + 'aside#sidebar {background-color: transparent !important; border-width: 0px !important; height: 100% !important; width: 250px !important; position: fixed !important; left: -285px !important; opacity: 0 !important; overflow: hidden !important; padding: 0px 25px !important; top: 0px !important; z-index: 2001 !important;}' + + 'aside#sidebar.bbb-sidebar-show, aside#sidebar:hover {background-color: #FFFFFF !important; border-right: 1px solid #CCCCCC !important; left: 0px !important; opacity: 1 !important; overflow-y: auto !important; padding: 0px 15px !important;}' + + 'section#content {margin-left: 0px !important;}' + + '.ui-autocomplete {z-index: 2002 !important;}'; + } - settingPath[setting[j]] = value; + if (collapse_sidebar) { + styles += '#sidebar ul.bbb-collapsed-sidebar, #sidebar form.bbb-collapsed-sidebar {display: block !important; height: 0px !important; margin: 0px !important; padding: 0px !important; overflow: hidden !important;}' + // Hide the element without changing the display to "none" since that interferes with some of Danbooru's JS. + '#sidebar h1, #sidebar h2 {display: inline-block !important;}'; // Inline-block is possible here due to not using display in the previous rule. } - saveSettings(); - } + // Blacklist thumbnail display; + if (blacklist_post_display === "removed") { + styles += 'div.post.post-preview.blacklisted {display: block !important;}' + // Comment listing override. + 'div.post.post-preview.blacklisted.blacklisted-active {display: none !important;}'; + } + else if (blacklist_post_display === "hidden") { + styles += 'article.post-preview.blacklisted.blacklisted-active {display: inline-block !important;}' + + 'div.post.post-preview.blacklisted {display: block !important;}' + // Comments. + 'article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link, div.post.post-preview.blacklisted.blacklisted-active div.preview {visibility: hidden !important;}'; + } + else if (blacklist_post_display === "replaced") { + styles += 'article.post-preview.blacklisted.blacklisted-active, div.post.post-preview.blacklisted.blacklisted-active {display: inline-block !important; background-position: ' + totalBorderWidth + 'px ' + totalBorderWidth + 'px !important; background-repeat: no-repeat !important; background-image: url(' + bbbBlacklistImg + ') !important;}' + + '#has-parent-relationship-preview article.post-preview.blacklisted.blacklisted-active, #has-children-relationship-preview article.post-preview.blacklisted.blacklisted-active {background-position: ' + (totalBorderWidth + 5) + 'px ' + (totalBorderWidth + 5) + 'px !important;}' + // Account for relation notice padding. + 'article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link img, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link img {opacity: 0.0 !important; height: 150px !important; width: 150px !important; border-width: 0px !important; padding: 0px !important;}' + // Remove all status border space. + 'article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link {padding: 0px !important; margin: ' + totalBorderWidth + 'px !important; margin-bottom: 0px !important;}' + // Align no border thumbs with custom/single border thumbs. + 'article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link.bbb-custom-tag, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link.bbb-custom-tag {padding: ' + border_spacing + 'px !important; margin: ' + (border_width + customBorderSpacing) + 'px !important; margin-bottom: 0px !important;}' + + 'div.post.post-preview.blacklisted {display: block !important;}' + + 'div.post.post-preview.blacklisted.blacklisted-active {display: block !important;}'; + } - function convertSettings(reason) { - // If the user settings are from an old version, attempt to convert some settings and update the version number. Settings will start conversion at the appropriate case and be allowed to run through every case after it until the end. - var userVer = bbb.user.bbb_version; - var scriptVer = bbb.options.bbb_version; + // Blacklist marking. + if (blacklist_thumb_mark === "icon") { + styles += 'article.post-preview:before, div.post.post-preview div.preview:before {content: none !important;}' + // Disable original Danbooru animated overlay. + 'article.post-preview.blacklisted a.bbb-thumb-link:after, div.post.post-preview.blacklisted div.preview a.bbb-thumb-link:after {content: "\\A0"; position: absolute; bottom: 0px; right: 0px; height: 20px; width: 20px; line-height: 20px; font-weight: bold; color: #FFFFFF; background: rgba(0, 0, 0, 0.5) url(\'' + bbbBlacklistIcon + '\');}' + // Create blacklist overlay. + 'article.post-preview[data-tags~="animated"] a.bbb-thumb-link:before, article.post-preview[data-file-ext="swf"] a.bbb-thumb-link:before, article.post-preview[data-file-ext="webm"] a.bbb-thumb-link:before, div.post.post-preview[data-tags~="animated"] div.preview a.bbb-thumb-link:before, div.post.post-preview[data-file-ext="swf"] div.preview a.bbb-thumb-link:before, div.post.post-preview[data-file-ext="webm"] div.preview a.bbb-thumb-link:before {content: "\\25BA"; position: absolute; width: 20px; height: 20px; color: #FFFFFF; background-color: rgba(0, 0, 0, 0.5); line-height: 20px; top: 0px; left: 0px;}' + // Recreate Danbooru animated overlay. + 'article.post-preview.blacklisted a.bbb-thumb-link:after, article.post-preview a.bbb-thumb-link:before, div.post.post-preview.blacklisted div.preview a.bbb-thumb-link:after, div.post.post-preview div.preview a.bbb-thumb-link:before {margin: ' + (border_width + border_spacing) + 'px;}' + // Margin applies to posts with no borders or only a status border. + 'article.post-preview.blacklisted a.bbb-thumb-link.bbb-custom-tag:after, article.post-preview a.bbb-thumb-link.bbb-custom-tag:before, div.post.post-preview.blacklisted div.preview a.bbb-thumb-link.bbb-custom-tag:after, div.post.post-preview div.preview a.bbb-thumb-link.bbb-custom-tag:before {margin: ' + border_spacing + 'px;}' + // Margin applies to posts with only a custom border. + 'article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link:after, article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link:before, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link:after, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link:before {content: none;}' + // Don't display when actively blacklisted. + 'article.post-preview a.bbb-thumb-link, div.post.post-preview div.preview a.bbb-thumb-link {position: relative;}'; // Allow the overlays to position relative to the link. - if (isOldVersion(userVer)) { - switch (userVer) { - case "6.0.2": - // Temporary special tests for users that used the test version. Affects version 6.0.2. - if (/500$/.test(bbb.user.thumb_cache_limit)) - bbb.user.thumb_cache_limit = bbb.options.thumb_cache_limit.def; + for (i = 0; i < sbsl; i++) { + statusBorderItem = status_borders[i]; - if (!/\.(jpg|gif|png)/.test(localStorage.bbb_thumb_cache)) { - localStorage.removeItem("bbb_thumb_cache"); - loadThumbCache(); - } + if (statusBorderItem.is_enabled) + styles += 'article.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link.bbb-custom-tag:after, article.post-preview.' + statusBorderItem.class_name + ' a.bbb-thumb-link.bbb-custom-tag:before, div.post.post-preview.' + statusBorderItem.class_name + ' div.preview a.bbb-thumb-link.bbb-custom-tag:after, div.post.post-preview.' + statusBorderItem.class_name + ' div.preview a.bbb-thumb-link.bbb-custom-tag:before {margin: ' + (border_width + border_spacing + customBorderSpacing) + 'px !important}'; // Margin applies to posts with a status and custom border. + } + } + else if (blacklist_thumb_mark === "highlight") { + styles += 'article.post-preview.blacklisted, div.post.post-preview.blacklisted div.preview {background-color: ' + blacklist_highlight_color + ' !important;}' + + 'article.post-preview.blacklisted.blacklisted-active, div.post.post-preview.blacklisted.blacklisted-active div.preview {background-color: transparent !important;}' + + 'article.post-preview.blacklisted.blacklisted-active.current-post {background-color: rgba(0, 0, 0, 0.1) !important}'; + } + + // Blacklist post controls. + if (blacklist_thumb_controls) { + styles += '#bbb-blacklist-tip {background-color: #FFFFFF; border: 1px solid #000000; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5); display: none; font-size: 12px; line-height: 14px; padding: 5px; position: absolute; max-width: 420px; width: 420px; overflow: hidden; z-index: 9002;}' + + '#bbb-blacklist-tip * {font-size: 12px; line-height: 14px;}' + + '#bbb-blacklist-tip .blacklisted-active {text-decoration: line-through; font-weight: normal;}' + + '#bbb-blacklist-tip ul {list-style: outside disc none; margin-top: 0px; margin-bottom: 0px; margin-left: 15px;}' + + 'article.post-preview.blacklisted.blacklisted-active, div.post.post-preview.blacklisted.blacklisted-active div.preview, article.post-preview.blacklisted.blacklisted-active a.bbb-thumb-link, div.post.post-preview.blacklisted.blacklisted-active div.preview a.bbb-thumb-link {cursor: help !important;}' + + 'article.post-preview.blacklisted.blacklisted-active a, div.post.post-preview.blacklisted.blacklisted-active div.preview a {cursor: pointer !important;}' + + 'article.post-preview.blacklisted, div.post.post-preview.blacklisted div.preview {position: relative !important;}' + + 'article.post-preview.blacklisted:hover .bbb-close-circle, div.post.post-preview.blacklisted:hover div.preview .bbb-close-circle {display: block; position: absolute; top: 0px; right: 0px; z-index: 9002 ; cursor: pointer; background-image: url(\'/images/ui-icons_222222_256x240.png\'); background-repeat: no-repeat; background-color: #FFFFFF; background-position: -32px -192px; width: 16px; height: 16px; border-radius: 8px; overflow: hidden;}' + + 'article.post-preview.blacklisted.blacklisted-active:hover .bbb-close-circle, div.post.post-preview.blacklisted.blacklisted-active:hover div.preview .bbb-close-circle {display: none;}' + + 'article.post-preview.blacklisted .bbb-close-circle, div.post.post-preview.blacklisted div.preview .bbb-close-circle {display: none;}'; + } + + if (move_save_search) { + styles += '.bbb-saved-search-item #saved-searches-nav, .bbb-saved-search-item #saved-searches-nav * {background-color: transparent; color: #0073FF; display: inline; font-family: Verdana,Helvetica,sans-serif; line-height: 1.25em; padding: 0px; margin: 0px; border: none;}' + + '.bbb-saved-search-item #saved-searches-nav input:hover, .bbb-saved-search-item #saved-searches-nav button:hover {color: #80b9ff;}' + + '.bbb-saved-search-item #saved-searches-nav input:focus, .bbb-saved-search-item #saved-searches-nav button:focus {outline: thin dotted;}'; + } + + if (quick_search !== "disabled") { + styles += '#bbb-quick-search {position: fixed; top: 0px; right: 0px; z-index: 2001; overflow: auto; padding: 2px; background-color: #FFFFFF; border-bottom: 1px solid #CCCCCC; border-left: 1px solid #CCCCCC; border-bottom-left-radius: 10px;}' + + '#bbb-quick-search-form {display: none;}' + + '.bbb-quick-search-show #bbb-quick-search-form {display: inline;}' + + '#bbb-quick-search-status, #bbb-quick-search-pin {border: none; width: 16px; height: 16px; background-color: transparent; background-repeat: no-repeat; background-color: transparent; background-image: url(\'/images/ui-icons_222222_256x240.png\');}' + + '#bbb-quick-search-status {background-position: -160px -112px;}' + // Magnifying glass. + '.bbb-quick-search-active #bbb-quick-search-status, .bbb-quick-search-show.bbb-quick-search-active.bbb-quick-search-pinned #bbb-quick-search-status {background-position: -128px -112px;}' + // Plus magnifying glass. + '#bbb-quick-search-pin {background-position: -128px -145px;}' + // Horizontal pin. + '.bbb-quick-search-pinned #bbb-quick-search-pin, .bbb-quick-search-active.bbb-quick-search-pinned #bbb-quick-search-status {background-position: -145px -145px;}' + // Vertical pin. + '#bbb-quick-search.bbb-quick-search-active {background-color: #DDDDDD;}' + + '#bbb-quick-search.bbb-quick-search-active.bbb-quick-search-show {background-color: #FFFFFF;}' + + '#bbb-quick-search-pin:focus, #bbb-quick-search-pin:hover {background-color: #CCCCCC;}' + + '#news-updates {padding-right: 25px !important;}'; + + if (quick_search.indexOf("remove") > -1) + styles += 'article.post-preview.bbb-quick-search-filtered, article.post.post-preview.blacklisted.bbb-quick-search-filtered, article.post-preview.blacklisted.blacklisted-active.bbb-quick-search-filtered {display: none !important;}'; + else if (quick_search.indexOf("fade") > -1) + styles += 'article.post-preview.bbb-quick-search-filtered {opacity: 0.1;}'; + } + + if (resize_link_style === "minimal") + styles += '.bbb-resize-link {display: inline-block; text-align: center; margin-right: 2px; font-size: 87.5%;}'; + + if (search_add === "remove") + styles += '.search-inc-tag, .search-exl-tag {display: none !important;}'; - if (bbb.user.tag_scrollbars === "false") - bbb.user.tag_scrollbars = 0; + if (direct_downloads) + styles += '.bbb-ddl {display: none !important;}'; - case "6.1": - case "6.2": - case "6.2.1": - case "6.2.2": - // Reset the thumb cache to deal with "download-preview" and incorrect extension entries. Affects versions 6.1 - 6.2.2. - if (localStorage.bbb_thumb_cache) { - localStorage.removeItem("bbb_thumb_cache"); - loadThumbCache(); - } + if (post_tag_scrollbars) + styles += '#tag-list ul {max-height: ' + post_tag_scrollbars + 'px !important; overflow-y: auto !important; font-size: 87.5% !important;}'; - // Convert the old hide_original_notice setting to the new show_resized_notice setting that replaces it. - if (bbb.user.hide_original_notice) - bbb.user.show_resized_notice = "sample"; + if (search_tag_scrollbars) + styles += '#tag-box ul {max-height: ' + search_tag_scrollbars + 'px !important; overflow-y: auto !important; font-size: 87.5% !important; margin-right: 2px !important;}'; - // Set the new show_banned setting to true if show_deleted is true. - if (bbb.user.show_deleted) - bbb.user.show_banned = true; + if (hide_tos_notice && document.getElementById("tos-notice")) { + styles += '#tos-notice {display: none !important;}'; - // Add a custom border for banned posts to match the other hidden post borders. - if (!/\bstatus:banned\b/i.test(JSON.stringify(bbb.user.tag_borders))) - bbb.user.tag_borders.push(new Border("status:banned", false, "#000000", "solid")); + if (manage_cookies) + createCookie("accepted_tos", 1, 365); + } - // Warn about uninstalling old version from Userscripts.org - if (reason !== "backup") - danbNotice("Better Better Booru: You have just been updated from a version of this script that was hosted on Userscripts.org. Before continuing any further, please open your userscript manager and remove any versions of this script older than version 6.3 that may be there.", "perm"); - case "6.3": - break; - } + if (hide_sign_up_notice && document.getElementById("sign-up-notice")) { + styles += '#sign-up-notice {display: none !important;}'; - cleanUser(); - bbb.user.bbb_version = scriptVer; + if (manage_cookies) + createCookie("hide_sign_up_notice", 1, 7); } - else if (userVer !== scriptVer) // Revert the version number for downgrades so that conversion can properly work on the settings again for a future upgrade. - bbb.user.bbb_version = scriptVer; - } - function cleanUser() { - // Verify the user doesn't have any settings that aren't in the base settings and delete them if they do. - var user = bbb.user; + if (hide_upgrade_notice && document.getElementById("upgrade-account-notice")) { + styles += '#upgrade-account-notice {display: none !important;}'; - for (var i in user) { - if (user.hasOwnProperty(i)) { - if (typeof(bbb.options[i]) === "undefined") - delete user[i]; - } + if (manage_cookies) + createCookie("hide_upgrade_account_notice", 1, 7); } - } - function createBackupText() { - // Create a plain text version of the settings. - var textarea = bbb.el.menu.backupTextarea; - textarea.value = "Better Better Booru v" + bbb.user.bbb_version + " Backup (" + timestamp("y-m-d hh:mm:ss") + "):\r\n\r\n" + JSON.stringify(bbb.user) + "\r\n"; - textarea.focus(); - textarea.setSelectionRange(0,0); - } + if (hide_ban_notice) + styles += '#ban-notice {display: none !important;}'; - function createBackupPage() { - // Open a new tab/window and place the setting text in it. - window.open(('data:text/html,Better Better Booru v' + bbb.user.bbb_version + ' Backup (' + timestamp("y-m-d hh:mm:ss") + ')' + JSON.stringify(bbb.user) + '').replace(/#/g, encodeURIComponent("#"))); - } + if (hide_comment_notice) { + var commentGuide; - function restoreBackupText() { - // Load the backup text provided into the script. - var textarea = bbb.el.menu.backupTextarea; - var backupString = textarea.value.replace(/\r?\n/g, "").match(/{.+}/); + if (gLoc === "post") { + commentGuide = document.evaluate('//section[@id="comments"]/h2/a[contains(@href,"/wiki_pages")]/..', document, null, 9, null).singleNodeValue; - if (backupString) { - try { - bbb.user = JSON.parse(backupString); // This is where we expect an error. - removeMenu(); - checkUser(bbb.user, bbb.options); - convertSettings("backup"); - createMenu(); - alert("Backup settings loaded successfully. After reviewing the settings to ensure they are correct, please click \"Save & Close\" to finalize the restore."); + if (commentGuide && commentGuide.textContent === "Before commenting, read the how to comment guide.") + commentGuide.style.display = "none"; } - catch (error) { - if (error instanceof SyntaxError) - alert("The backup does not appear to be formatted correctly. Please make sure everything was pasted correctly/completely and that only one backup is provided."); - else - alert("Unexpected error: " + error.message); + else if (gLoc === "comments") { + commentGuide = document.evaluate('//div[@id="a-index"]/div/h2/a[contains(@href,"/wiki_pages")]/..', document, null, 9, null).singleNodeValue; + + if (commentGuide && commentGuide.textContent === "Before commenting, read the how to comment guide.") + commentGuide.style.display = "none"; } } - else - alert("A backup could not be detected in the text provided. Please make sure everything was pasted correctly/completely."); - } - function blacklistInit() { - // Reset the blacklist with the account settings when logged in or script settings when logged out/using the override. - var blacklistTags = (useAccount() ? fetchMeta("blacklisted-tags") : script_blacklisted_tags); - var blacklistBox = document.getElementById("blacklist-box"); - var blacklistList = document.getElementById("blacklist-list"); - var blacklistedPosts = document.getElementsByClassName("blacklisted"); + if (hide_tag_notice && gLoc === "post") { + var tagGuide = document.evaluate('//section[@id="edit"]/div/p/a[contains(@href,"/howto:tag")]/..', document, null, 9, null).singleNodeValue; - // Reset sidebar blacklist. - if (blacklistBox && blacklistList) { - blacklistBox.style.display = "none"; - blacklistList.innerHTML = ""; + if (tagGuide && tagGuide.textContent === "Before editing, read the how to tag guide.") + tagGuide.style.display = "none"; } - // Reset any blacklist info. - if (bbb.blacklist.entries.length) { - delete bbb.blacklist; - bbb.blacklist = {entries: [], match_list: {}}; - } + if (hide_upload_notice && gLoc === "upload") + styles += '#upload-guide-notice {display: none !important;}'; - // Reset any blacklisted thumbnails. - while (blacklistedPosts[0]) { - var blacklistedPost = blacklistedPosts[0]; - blacklistedPost.className = blacklistedPost.className.replace(/\s?blacklisted(-active)?/ig, ""); + if (hide_pool_notice && gLoc === "new_pool") { + var poolGuide = document.evaluate('//div[@id="c-new"]/p/a[contains(@href,"/howto:pools")]/..', document, null, 9, null).singleNodeValue; + + if (poolGuide && poolGuide.textContent === "Before creating a pool, read the pool guidelines.") + poolGuide.style.display = "none"; } - // Check if there actually are any tags. - if (!/[^\s,]/.test(blacklistTags)) - return; - else - blacklistTags = blacklistTags.split(","); + customStyles.innerHTML = styles; + document.getElementsByTagName("head")[0].appendChild(customStyles); + } - // Create the the blacklist section. - for (var i = 0, il = blacklistTags.length; i < il; i++) { - var blacklistTag = blacklistTags[i]; - var blacklistSearch = createSearch(blacklistTag); + function pageCounter() { + // Set up the page counter. + var pageEl = document.getElementById("page"); + var paginator = getPaginator(); - if (blacklistSearch.length) { - var newEntry = {active: true, tags:blacklistTag, search:blacklistSearch, matches: []}; + if (!page_counter || !paginator || !pageEl) + return; - bbb.blacklist.entries.push(newEntry); + var numString = ""; + var lastNumString; + + // Provide page number info if available. + if (/\d/.test(paginator.textContent)) { + var activePage = paginator.getElementsByTagName("span")[0]; + var pageItems = paginator.getElementsByTagName("li"); + var numPageItems = pageItems.length; + var lastPageItem = pageItems[numPageItems - 1]; + var activeNum = activePage.textContent.bbbSpaceClean(); + var lastNum; + + if (activePage.parentNode === lastPageItem) // Last/only page case. + lastNum = activeNum; + else { // In all other cases, there should always be a next page button and at least two other page items (1-X). + lastNum = lastPageItem.previousElementSibling.textContent.bbbSpaceClean(); + + if (!bbbIsNum(lastNum)) // Too many pages for the current user to view. + lastNum = ""; + } - if (blacklistList) { - var blacklistItem = document.createElement("li"); - blacklistItem.title = blacklistTag; - blacklistItem.style.display = "none"; + lastNumString = (lastNum ? " of " + lastNum : ""); + numString = 'Page ' + activeNum + '' + lastNumString + ' | '; + } - var blacklistLink = document.createElement("a"); - blacklistLink.innerHTML = (blacklistTag.length < 21 ? blacklistTag + " " : blacklistTag.substring(0, 20).bbbSpaceClean() + "... "); - blacklistLink.addEventListener("click", function(entry) { - return function(event) { - if (event.button === 0) { - var matches = entry.matches; - var link = this; - var post; - var el; - var i, il; // Loop variables. - - if (entry.active) { - entry.active = false; - link.className = "blacklisted-active"; - - for (i = 0, il = matches.length; i < il; i++) { - post = matches[i]; - el = document.getElementById(post.elId); - bbb.blacklist.match_list[post.id]--; - - if (!bbb.blacklist.match_list[post.id]) - el.className = el.className.replace(/\s?blacklisted-active/ig, ""); - } - } - else { - entry.active = true; - link.className = ""; - - for (i = 0, il = matches.length; i < il; i++) { - post = matches[i]; - el = document.getElementById(post.elId); - bbb.blacklist.match_list[post.id]++; - el.className += " blacklisted-active"; - } - } - } - }; - }(newEntry), false); - blacklistItem.appendChild(blacklistLink); + var pageNav = bbb.el.pageCounter; + var pageInput = bbb.el.pageCounterInput; - var blacklistCount = document.createElement("span"); - blacklistCount.innerHTML = "0"; - blacklistItem.appendChild(blacklistCount); + if (!pageNav) { // Create the page nav. + var pageNavString = '
' + numString + '
'; - blacklistList.appendChild(blacklistItem); - } - } - } + pageNav = bbb.el.pageCounter = document.createElement("div"); + pageNav.innerHTML = pageNavString; - // Test all posts on the page for a match and set up the initial blacklist. - blacklistUpdate(); - } + pageInput = bbb.el.pageCounterInput = getId("bbb-page-counter-input", pageNav); - function blacklistUpdate(target) { - // Update the blacklists without resetting everything. - if (!bbb.blacklist.entries.length) - return; + getId("bbb-page-counter-form", pageNav).addEventListener("submit", function(event) { + var value = pageInput.value.bbbSpaceClean(); - var blacklistBox = document.getElementById("blacklist-box"); - var blacklistList = document.getElementById("blacklist-list"); - var imgContainer = getId("image-container", target); - var posts = getPosts(target); - var i, il; // Loop variables. + if (value !== "") + location.href = updateURLQuery(location.href, {page:value}); - // Test the image for a match when viewing a post. - if (imgContainer) - blacklistTest("imgContainer", imgContainer); + event.preventDefault(); + }, false); - // Search the posts for matches. - for (i = 0, il = posts.length; i < il; i++) { - var post = posts[i]; - var postId = post.getAttribute("data-id"); + if (numString) + paginator.bbbWatchNodes(pageCounter); - blacklistTest(postId, post); + pageEl.insertBefore(pageNav, pageEl.firstElementChild); } + else // Update the last page in the page nav. + document.getElementById("bbb-page-counter-last").innerHTML = lastNumString; + } - // Update the blacklist sidebar section match counts and display any blacklist items that have a match. - if (blacklistBox && blacklistList) { - for (i = 0, il = bbb.blacklist.entries.length; i < il; i++) { - var entryLength = bbb.blacklist.entries[i].matches.length; - var item = blacklistList.getElementsByTagName("li")[i]; + function quickSearch() { + // Set up quick search. + if (quick_search === "disabled" || (gLoc !== "search" && gLoc !== "notes" && gLoc !== "favorites" && gLoc !== "pool" && gLoc !== "popular" && gLoc !== "popular_view" && gLoc !== "favorite_group")) + return; - if (entryLength) { - blacklistBox.style.display = "block"; - item.style.display = ""; - item.getElementsByTagName("span")[0].innerHTML = entryLength; - } - } - } - } + var allowAutocomplete = (getMeta("enable-auto-complete") === "true"); - function blacklistTest(matchId, element) { - // Test a post/image for a blacklist match and use the provided ID to store its info. - var matchList = bbb.blacklist.match_list; + // Create the quick search. + var searchDiv = bbb.el.quickSearchDiv = document.createElement("div"); + searchDiv.id = "bbb-quick-search"; + searchDiv.innerHTML = '
'; - if (typeof(matchList[matchId]) === "undefined") { - for (var i = 0, il = bbb.blacklist.entries.length; i < il; i++) { - var entry = bbb.blacklist.entries[i]; + var searchForm = bbb.el.quickSearchForm = getId("bbb-quick-search-form", searchDiv); + var searchInput = bbb.el.quickSearchInput = getId("bbb-quick-search-input", searchDiv); + var searchPin = bbb.el.quickSearchPin = getId("bbb-quick-search-pin", searchDiv); + var searchSubmit = bbb.el.quickSearchSubmit = getId("bbb-quick-search-submit", searchDiv); + var searchStatus = bbb.el.quickSearchStatus = getId("bbb-quick-search-status", searchDiv); - if (thumbSearchMatch(element, entry.search)) { - if (element.className.indexOf("blacklisted") < 0) - element.className += " blacklisted"; + // Make the submit event search posts or reset the search. + searchForm.addEventListener("submit", function(event) { + var oldValue = bbb.quick_search.bbbSpaceClean(); + var curValue = searchInput.value.bbbSpaceClean(); - if (entry.active) { - if (element.className.indexOf("blacklisted-active") < 0) - element.className += " blacklisted-active"; + if (curValue === "" || curValue === oldValue) + quickSearchReset(); + else { + bbb.quick_search = bbb.el.quickSearchInput.value; - matchList[matchId] = ++matchList[matchId] || 1; - } - else - matchList[matchId] = matchList[matchId] || 0; + if (searchDiv.bbbHasClass("bbb-quick-search-pinned")) + sessionStorage.bbb_quick_search = bbb.quick_search; + else if (quick_search.indexOf("pinned") > -1) + quickSearchPinEnable(); - entry.matches.push({id:matchId, elId:element.id}); - } + quickSearchTest(); } - if (typeof(matchList[matchId]) === "undefined") - matchList[matchId] = false; - } - else if (matchList[matchId] !== false && element.className.indexOf("blacklisted") < 0) - element.className += (matchList[matchId] > 0 ? " blacklisted blacklisted-active" : " blacklisted"); - } + // Make autocomplete close without getting too tricky. + searchSubmit.focus(); + delayMe(function() { searchInput.focus(); }); // Delay this so the blur event has time to register properly. - function resizeImage(mode) { - // Custom resize post script. - var imgContainer = document.getElementById("image-container"); - var img = document.getElementById("image"); - var swfObj = (imgContainer ? imgContainer.getElementsByTagName("object")[0] : undefined); - var swfEmb = (swfObj ? swfObj.getElementsByTagName("embed")[0] : undefined); - var webmVid = (imgContainer ? imgContainer.getElementsByTagName("video")[0] : undefined); - var target = img || swfEmb || webmVid; + event.preventDefault(); + }, false); - if (!target || !imgContainer) - return; + // Hide the search div if the new focus isn't one of the inputs. + searchDiv.addEventListener("blur", function(event) { + var target = event.target; - var currentMode = bbb.post.resize.mode; - var currentRatio = bbb.post.resize.ratio; - var resizeLinkAll = bbb.el.resizeLinkAll; - var resizeLinkWidth = bbb.el.resizeLinkWidth; - var resizeLinkHeight = bbb.el.resizeLinkHeight; - var availableWidth = imgContainer.clientWidth; - var availableHeight = window.innerHeight - 40; - var targetCurrentWidth = target.clientWidth; - var targetCurrentHeight = target.clientHeight; - var targetWidth = (swfEmb || webmVid ? imgContainer.getAttribute("data-width") : img.getAttribute("width")); // Was NOT expecting target.width to return the current width (css style width) and not the width attribute's value here... - var targetHeight = (swfEmb || webmVid ? imgContainer.getAttribute("data-height") : img.getAttribute("height")); - var tooWide = targetCurrentWidth > availableWidth; - var tooTall = targetCurrentHeight > availableHeight; - var widthRatio = availableWidth / targetWidth; - var heightRatio = availableHeight / targetHeight; - var imgMode = mode; - var switchMode = false; - var ratio = 1; - var linkWeight = {all: "normal", width: "normal", height: "normal"}; + delayMe(function() { + var active = document.activeElement; - if (mode === "swap") { // The image is being swapped between the original and sample image so everything needs to be reset. - switchMode = true; - imgMode = "none"; - } - else if (mode === currentMode || (mode === "width" && widthRatio >= 1) || (mode === "height" && heightRatio >= 1) || (mode === "all" && widthRatio >= 1 && heightRatio >= 1)) { // Cases where resizing is being toggled off or isn't needed. - if (currentMode !== "none") { // No need to do anything if the content is already at the original dimensions. - switchMode = true; - imgMode = "none"; - } - } - else if (mode === "height" && (tooTall || currentMode !== "none")) { - switchMode = true; - ratio = heightRatio; - linkWeight.height = "bold"; - } - else if (mode === "width" && (tooWide || currentMode !== "none")) { - switchMode = true; - ratio = widthRatio; - linkWeight.width = "bold"; - } - else if (mode === "all" && (tooWide || tooTall || currentMode !== "none")) { - switchMode = true; - ratio = (widthRatio < heightRatio ? widthRatio : heightRatio); - linkWeight.all = "bold"; - } + if (active === target || (active !== searchInput && active !== searchSubmit && active !== searchStatus && active !== searchPin)) + searchDiv.bbbRemoveClass("bbb-quick-search-show"); + }); + }, true); - if (switchMode) { - if (currentRatio !== ratio || mode === "swap") { - if (img) { - img.style.width = targetWidth * ratio + "px"; - img.style.height = targetHeight * ratio + "px"; - Danbooru.Note.Box.scale_all(); - } - else if (swfEmb) { - swfObj.height = swfEmb.height = targetHeight * ratio; - swfObj.width = swfEmb.width = targetWidth * ratio; - } - else { - webmVid.height = targetHeight * ratio; - webmVid.width = targetWidth * ratio; - } - } + // If a mouse click misses an input within the quick search div, cancel it so the quick search doesn't minimize. + searchDiv.addEventListener("mousedown", function(event) { + var target = event.target; - bbb.post.resize.mode = imgMode; - bbb.post.resize.ratio = ratio; - resizeLinkAll.style.fontWeight = linkWeight.all; - resizeLinkWidth.style.fontWeight = linkWeight.width; - resizeLinkHeight.style.fontWeight = linkWeight.height; - } - } + if (target === searchDiv || target === searchForm) + event.preventDefault(); + }, false); - function swapImage() { - // Initiate the swap between the sample and original image. - var post = bbb.post.info; - var img = bbb.el.img; - var bbbLoader = bbb.el.bbbLoader; - var resizeStatus = bbb.el.resizeStatus; - var resizeLink = bbb.el.resizeLink; - var swapLink = bbb.el.swapLink; - var ratio = (post.image_width > 850 ? 850 / post.image_width : 1); + // Hide the search div if the escape key is pressed while using it and autocomplete isn't open. + searchDiv.addEventListener("keydown", function(event) { + if (event.keyCode === 27) { + var jQueryMenu = (searchInput.bbbHasClass("ui-autocomplete-input") ? $("#bbb-quick-search-input").autocomplete("widget")[0] : undefined); - if (!post.has_large) - return; + if (jQueryMenu && jQueryMenu.style.display !== "none") + return; - if (bbbLoader.src !== "about:blank") { - if (img.src.indexOf("/sample/") < 0) { - resizeStatus.innerHTML = "Viewing original"; - resizeLink.innerHTML = "view sample"; - swapLink.innerHTML = "View sample"; + document.activeElement.blur(); + event.preventDefault(); } - else { - resizeStatus.innerHTML = "Resized to " + Math.floor(ratio * 100) + "% of original"; - resizeLink.innerHTML = "view original"; - swapLink.innerHTML = "View original"; + }, true); + + // Show/hide the search div via a left click on the main status icon. If the shift key is held down, toggle the pinned status. + searchStatus.addEventListener("click", function(event) { + if (event.button === 0) { + if (event.shiftKey) + quickSearchPinToggle(); + else if (!searchDiv.bbbHasClass("bbb-quick-search-show")) + quickSearchOpen(); + else + searchDiv.bbbRemoveClass("bbb-quick-search-show"); } - bbbLoader.src = "about:blank"; - } - else { - if (img.src.indexOf("/sample/") < 0) { - resizeStatus.innerHTML = "Loading sample image..."; - resizeLink.innerHTML = "cancel"; - swapLink.innerHTML = "View sample (cancel)"; - bbbLoader.src = post.large_file_url; + event.preventDefault(); + }, false); - } - else { - resizeStatus.innerHTML = "Loading original image..."; - resizeLink.innerHTML = "cancel"; - swapLink.innerHTML = "View original (cancel)"; - bbbLoader.src = post.file_url; - } - } - } + // Reset via a right click on the main status icon. + searchStatus.addEventListener("mouseup", function(event) { + if (event.button === 2 && searchDiv.bbbHasClass("bbb-quick-search-active")) + quickSearchReset(); - function bbbHotkeys() { - // Create hotkeys or override Danbooru's existing ones. - document.addEventListener("keypress", function(event) { - if (document.activeElement.type !== "text" && document.activeElement.type !== "textarea") { - switch (event.charCode) { - case 118: // "v" - if (gLoc === "post") { - swapImage(); - event.stopPropagation(); - event.preventDefault(); - } - break; - } - } - }, true); - } + event.preventDefault(); + }, false); - function loadThumbCache() { - // Initialize or load up the thumbnail cache. - if (typeof(localStorage.bbb_thumb_cache) !== "undefined") - bbb.cache.stored = JSON.parse(localStorage.bbb_thumb_cache); - else { - bbb.cache.stored = {history: [], names: {}}; - localStorage.bbb_thumb_cache = JSON.stringify(bbb.cache.stored); - } - } + // Stop the context menu on the status icon. + searchStatus.addEventListener("contextmenu", disableEvent, false); - function updateThumbCache() { - // Add the current new thumbnail info to the saved thumbnail information. - if (!bbb.cache.current.history.length || !thumb_cache_limit) - return; + // Make the pin input toggle the pinned status. + searchPin.addEventListener("click", function(event) { + if (event.button === 0) + quickSearchPinToggle(); + }, false); - loadThumbCache(); + // Watch the input value and adjust the quick search as needed. + searchInput.addEventListener("input", quickSearchCheck, false); + searchInput.addEventListener("keyup", quickSearchCheck, false); + searchInput.addEventListener("cut", quickSearchCheck, false); + searchInput.addEventListener("paste", quickSearchCheck, false); + searchInput.addEventListener("change", quickSearchCheck, false); - var bcc = bbb.cache.current; - var bcs = bbb.cache.stored; - var i, il; // Loop variables. + document.body.insertBefore(searchDiv, document.body.firstElementChild); - // Make sure we don't have duplicates in the new info. - for (i = 0, il = bcc.history.length; i < il; i++) { - if (bcs.names[bcc.history[i]]) { - delete bcc.names[bcc.history[i]]; - bcc.history.splice(i, 1); - il--; - i--; - } - } + // Force the submit button to retain its width. + searchDiv.bbbAddClass("bbb-quick-search-show"); + searchSubmit.style.width = searchSubmit.offsetWidth + "px"; + searchDiv.bbbRemoveClass("bbb-quick-search-show"); - // Add the new thumbnail info in. - for (i in bcc.names) { - if (bcc.names.hasOwnProperty(i)) { - bcs.names[i] = bcc.names[i]; - } - } + // Take of a copy of Danbooru's autocomplete and modify it for the search. + if (allowAutocomplete && Danbooru.Autocomplete && Danbooru.Autocomplete.initialize_tag_autocomplete) { + try { + var autoComplete = Danbooru.Autocomplete.initialize_tag_autocomplete.toString().match(/\{([\s\S]*)\}/)[1]; + var searchAutoComplete = autoComplete.replace(/(,)#tags|#tags(,)/, "$1#tags,#bbb-quick-search-input$2"); // /\$\([\s\S]*?#tags[\s\S]*?\)([\s\S]*?)\$\([\s\S]*?#artist_name[\s\S]*?\)/, '$("#bbb-quick-search-input")$1$()' + var autoInit = new Function(searchAutoComplete); - bcs.history = bcs.history.concat(bcc.history); + autoInit(); - // Prune the cache if it's larger than the user limit. - if (bcs.history.length > thumb_cache_limit) { - var removedIds = bcs.history.splice(0, bcs.history.length - thumb_cache_limit); + // Counter normal autocomplete getting turned back on after submitting an input. + document.body.addEventListener("focus", function(event) { + var target = event.target; - for (i = 0, il = removedIds.length; i < il; i++) - delete bcs.names[removedIds[i]]; + if (target.bbbHasClass("ui-autocomplete-input")) + target.setAttribute("autocomplete", "off"); + }, true); + + // Make autocomplete fixed like the quick search. + $(searchInput).autocomplete("widget").css("position", "fixed"); + } + catch (error) { + bbbNotice("Unexpected error while trying to initialize autocomplete for the quick search. (Error: " + error.message + ")", -1); + } } - localStorage.bbb_thumb_cache = JSON.stringify(bcs); - bbb.cache.current = {history: [], names: {}}; - } + // Checked if the quick search has been pinned for this session. + var pinnedSearch = sessionStorage.bbb_quick_search; - function adjustThumbCache() { - // Prune the cache if it's larger than the user limit. - loadThumbCache(); + if (pinnedSearch) { + bbb.quick_search = pinnedSearch; + searchInput.value = pinnedSearch; + searchDiv.bbbAddClass("bbb-quick-search-pinned"); + quickSearchTest(); + } + } - thumb_cache_limit = bbb.user.thumb_cache_limit; + function quickSearchCheck() { + // Check the input value and adjust the submit button appearance accordingly. + var input = bbb.el.quickSearchInput; + var submit = bbb.el.quickSearchSubmit; + var oldValue = bbb.quick_search.bbbSpaceClean(); + var curValue = input.value.bbbSpaceClean(); - var bcs = bbb.cache.stored; + if (oldValue === curValue && curValue !== "") + submit.value = "X"; + else + submit.value = "Go"; + } - if (bcs.history.length > thumb_cache_limit) { - var removedIds = bcs.history.splice(0, bcs.history.length - thumb_cache_limit); + function quickSearchReset() { + // Complete reset the quick search. + var filtered = document.getElementsByClassName("bbb-quick-search-filtered"); - for (var i = 0, il = removedIds.length; i < il; i++) - delete bcs.names[removedIds[i]]; - } + bbb.quick_search = ""; + bbb.el.quickSearchInput.value = ""; + bbb.el.quickSearchSubmit.value = "Go"; + bbb.el.quickSearchStatus.title = ""; + sessionStorage.removeItem("bbb_quick_search"); + bbb.el.quickSearchDiv.bbbRemoveClass("bbb-quick-search-active bbb-quick-search-pinned"); - localStorage.bbb_thumb_cache = JSON.stringify(bcs); + while (filtered[0]) + filtered[0].bbbRemoveClass("bbb-quick-search-filtered"); } - function limitFix() { - // Add the limit variable to link URLs that are not thumbnails. - var page = document.getElementById("page"); - var header = document.getElementById("top"); - var searchParent = document.getElementById("search-box") || document.getElementById("a-intro"); - var links; - var link; - var linkHref; - var search; - var tagsInput; - var i, il; // Loop variables. + function quickSearchTest(target) { + // Test posts to see if they match the search. + var value = bbb.quick_search.bbbSpaceClean(); - if (page) { - links = page.getElementsByTagName("a"); + if (value === "") + return; - for (i = 0, il = links.length; i < il; i++) { - link = links[i]; - linkHref = link.getAttribute("href"); // Use getAttribute so that we get the exact value. "link.href" adds in the domain. + var posts = getPosts(target); + var search = createSearch(value); - if (linkHref && linkHref.indexOf("/posts?") === 0 && !/(?:page|limit)=/.test(linkHref)) - link.href = linkHref + "&limit=" + thumbnail_count; - } + bbb.el.quickSearchSubmit.value = "X"; + bbb.el.quickSearchStatus.title = value; + bbb.el.quickSearchDiv.bbbAddClass("bbb-quick-search-active"); + + for (var i = 0, il = posts.length; i < il; i++) { + var post = posts[i]; + + if (!thumbSearchMatch(post, search)) + post.bbbAddClass("bbb-quick-search-filtered"); + else + post.bbbRemoveClass("bbb-quick-search-filtered"); } + } - if (header) { - links = header.getElementsByTagName("a"); + function quickSearchOpen() { + // Open the quick search div and place focus on the input. + var searchInput = bbb.el.quickSearchInput; - for (i = 0, il = links.length; i < il; i++) { - link = links[i]; - linkHref = link.getAttribute("href"); + searchInput.value = bbb.quick_search; + quickSearchCheck(); + bbb.el.quickSearchDiv.bbbAddClass("bbb-quick-search-show"); + searchInput.focus(); + } - if (linkHref && (linkHref.indexOf("/posts") === 0 || linkHref === "/" || linkHref ==="/notes?group_by=post")) { - if (linkHref.indexOf("?") > -1) - link.href = linkHref + "&limit=" + thumbnail_count; - else - link.href = linkHref + "?limit=" + thumbnail_count; - } - } + function quickSearchPinToggle() { + // Toggle the quick search between pinned and not pinned for the session. + var searchDiv = bbb.el.quickSearchDiv; + + if (searchDiv.bbbHasClass("bbb-quick-search-show") || searchDiv.bbbHasClass("bbb-quick-search-active")) { + if (!searchDiv.bbbHasClass("bbb-quick-search-pinned")) + quickSearchPinEnable(); + else + quickSearchPinDisable(); } + } - // Fix the search. - if (searchParent && (gLoc === "search" || gLoc === "post" || gLoc === "intro")) { - search = searchParent.getElementsByTagName("form")[0]; + function quickSearchPinEnable() { + // Enable the quick search pin. + bbb.el.quickSearchDiv.bbbAddClass("bbb-quick-search-pinned"); - if (search) { - var limitInput = document.createElement("input"); - limitInput.name = "limit"; - limitInput.value = thumbnail_count; - limitInput.type = "hidden"; - search.appendChild(limitInput); - - // Remove the user's default limit if the user tries to specify a limit value in the tags. - tagsInput = document.getElementById("tags"); - - if (tagsInput) { - search.addEventListener("submit", function() { - if (/\blimit:\d+\b/.test(tagsInput.value)) - search.removeChild(limitInput); - else if (limitInput.parentNode !== search) - search.appendChild(limitInput); - }, false); - } - } - } + if (bbb.quick_search) + sessionStorage.bbb_quick_search = bbb.quick_search; } - function isThere(url) { - // Checks if file exists. Thanks to some random forum! - var req = new XMLHttpRequest(); // XMLHttpRequest object. - try { - req.open("HEAD", url, false); - req.send(null); - return (req.status === 200 ? true : false); - } catch(er) { - return false; - } + function quickSearchPinDisable() { + // Disable the quick search pin. + bbb.el.quickSearchDiv.bbbRemoveClass("bbb-quick-search-pinned"); + sessionStorage.removeItem("bbb_quick_search"); } - function getVar(urlVar, url) { - // Retrieve a variable value from a specified URL or the current URL. - if (!url) - url = gUrlQuery; + function moveSaveSearch() { + // Move the "save search" div into the sidebar related section and style it as a link. + var saveSearchDiv = document.getElementById("saved-searches-nav"); + var relatedSection = document.getElementById("related-box"); - var result = url.split(urlVar + "=")[1]; + if (!move_save_search || !saveSearchDiv || !relatedSection) + return; - if (!result) - return undefined; - else - result = result.split(/[#&]/, 1)[0]; + saveSearchDiv.parentNode.removeChild(saveSearchDiv); - if (!result) - return undefined; - else if (bbbIsNum(result)) - return Number(result); - else - return result; - } + var relatedSectionMenu = relatedSection.getElementsByTagName("ul")[0]; - function getLimitSearch(url) { - // Retrieve the limit tag value from the tag search string. - var limitTag = decodeURIComponent(getVar("tags", url) || "").match(/\blimit:(\d+)\b/); + var saveSearchItem = document.createElement("li"); + saveSearchItem.className = "bbb-saved-search-item"; + saveSearchItem.appendChild(saveSearchDiv); - return (limitTag ? Number(limitTag[1]) : undefined); + relatedSectionMenu.insertBefore(saveSearchItem, relatedSectionMenu.firstElementChild); } - function arrowNav() { - // Bind the arrow keys to Danbooru's page navigation. - var paginator = document.getElementsByClassName("paginator")[0]; - - if (paginator || gLoc === "popular") { // If the paginator exists, arrow navigation should be applicable. - document.addEventListener("keydown", function(event) { - if (document.activeElement.type !== "text" && document.activeElement.type !== "textarea") { - if (event.keyCode === 37) - danbooruNav("left"); - else if (event.keyCode === 39) - danbooruNav("right"); - } - }, false); - } - } + function commentScoreInit() { + // Set up the initial comment scores and get ready to handle new comments. + if (!comment_score || (gLoc !== "comments" && gLoc !== "comment_search" && gLoc !== "comment" && gLoc !== "post")) + return; - function danbooruNav(dir) { - // Determine the correct Danbooru page function and use it. - if (gLoc === "popular") { - if (dir === "left") - Danbooru.PostPopular.nav_prev(); - else if (dir === "right") - Danbooru.PostPopular.nav_next(); - } - else { - if (dir === "left") - Danbooru.Paginator.prev_page(); - else if (dir === "right") - Danbooru.Paginator.next_page(); - } + var paginator = getPaginator(); + var watchedNode = (paginator ? paginator.parentNode : document.body); + + commentScore(); + watchedNode.bbbWatchNodes(commentScore); } - function cleanLinks(target) { - // Remove the query portion of thumbnail links. - var targetContainer; - var links; - var link; + function commentScore() { + // Add score links to comments that link directly to that comment. + var comments = document.getElementsByClassName("comment"); + var scoredComments = document.getElementsByClassName("bbb-comment-score"); - if (target) - targetContainer = target; - else if (gLoc === "post") - targetContainer = document.getElementById("content"); - else if (gLoc === "pool") { - targetContainer = document.getElementById("a-show"); - targetContainer = (targetContainer ? targetContainer.getElementsByTagName("section")[0] : undefined); - } - else if (gLoc === "search") - targetContainer = document.getElementById("posts"); - else if (gLoc === "intro") - targetContainer = document.getElementById("a-intro"); + // If all the comments are scored, just stop. + if (comments.length === scoredComments.length) + return; - if (targetContainer) { - links = targetContainer.getElementsByTagName("a"); + for (var i = 0, il = comments.length; i < il; i++) { + var comment = comments[i]; - for (var i = 0, il = links.length; i < il; i++) { - link = links[i]; + // Skip if the comment is already scored. + if (comment.getElementsByClassName("bbb-comment-score")[0]) + continue; - if (link.parentNode.tagName === "ARTICLE" || link.parentNode.id.indexOf("nav-link-for-pool-") === 0) - link.href = link.href.split("?", 1)[0]; + var score = comment.getAttribute("data-score"); + var commentId = comment.getAttribute("data-comment-id"); + var content = comment.getElementsByClassName("content")[0]; + var menu = (content ? content.getElementsByTagName("menu")[0] : undefined); + + if (content && !menu) { + menu = document.createElement("menu"); + content.appendChild(menu); } - } - } - function autohideSidebar() { - // Show the sidebar when it gets focus, hide it when it loses focus, and don't allow its links to retain focus. - var sidebar = document.getElementById("sidebar"); + var menuItems = menu.getElementsByTagName("li"); + var listingItemSibling = menuItems[1]; - if (!sidebar) - return; + for (var j = 0, jl = menuItems.length; j < jl; j++) { + var menuItem = menuItems[j]; + var nextItem = menuItems[j + 1]; - sidebar.addEventListener("click", function(event) { - var target = event.target; + if (menuItem.textContent.indexOf("Reply") > -1) { + if (nextItem) + listingItemSibling = nextItem; + else + listingItemSibling = undefined; + } + } - if (target.href) - target.blur(); - }, false); - sidebar.addEventListener("focus", function() { - sidebar.className += " bbb-sidebar-show"; - }, true); - sidebar.addEventListener("blur", function() { - sidebar.className = sidebar.className.replace(/\s?bbb-sidebar-show/gi, ""); - }, true); - } + var scoreItem = document.createElement("li"); + scoreItem.className = "bbb-comment-score"; - function autoscrollImage() { - var imgContainer = document.getElementById("image-container"); - var img = document.getElementById("image"); - var swfObj = (imgContainer ? imgContainer.getElementsByTagName("object")[0] : undefined); - var webmVid = (imgContainer ? imgContainer.getElementsByTagName("video")[0] : undefined); - var target = img || swfObj || webmVid; + var scoreLink = document.createElement("a"); + scoreLink.innerHTML = "Score: " + score; + scoreLink.href = "/comments/" + commentId; + scoreItem.appendChild(scoreLink); - if (target) - target.scrollIntoView(); + if (listingItemSibling) + menu.insertBefore(scoreItem, listingItemSibling); + else + menu.appendChild(scoreItem); + } } - function allowUserLimit() { - // Allow use of the limit variable if it isn't currently set and we're on the first page. - if (thumbnail_count && !/(?:page|limit)=\d/.test(gUrlQuery) && getLimitSearch() === undefined) - return true; - else - return false; - } + function thumbInfo(target) { + // Add score, favorite count, and rating info to thumbnails. + var posts = getPosts(target); - function currentLoc() { - // Test the page URL to find which section of Danbooru the script is running on. - if (/\/posts\/\d+/.test(gUrlPath)) - return "post"; - else if (/^\/(?:posts|$)/.test(gUrlPath)) - return "search"; - else if (gUrlPath.indexOf("/notes") === 0 && gUrlQuery.indexOf("group_by=note") < 0) - return "notes"; - else if (/^\/comments\/?$/.test(gUrlPath) && gUrlQuery.indexOf("group_by=comment") < 0) - return "comments"; - else if (gUrlPath.indexOf("/explore/posts/popular") === 0) - return "popular"; - else if (/\/pools\/\d+/.test(gUrlPath)) - return "pool"; - else if (gUrlPath.indexOf("/uploads/new") === 0) - return "upload"; - else if (gUrlPath.indexOf("/pools/new") === 0) - return "new pool"; - else if (/\/forum_topics\/\d+/.test(gUrlPath)) - return "topic"; - else if (gUrlPath.indexOf("/explore/posts/intro") === 0) - return "intro"; - else - return undefined; - } + if (thumb_info === "disabled" || !posts[0]) + return; - function fetchMeta(meta, pageEl) { - var target = pageEl || document; - var metaTags = target.getElementsByTagName("meta"); - var tag; + for (var i = 0, il = posts.length; i < il; i++) { + var post = posts[i]; - for (var i = 1, il = metaTags.length; i < il; i++) { - tag = metaTags[i]; + // Skip thumbnails that already have the info added. + if (post.getElementsByClassName("bbb-thumb-info")[0]) + continue; - if (tag.name === meta || tag.property === meta) { - if (tag.hasAttribute("content")) - return tag.content; - else - return undefined; + var score = Number(post.getAttribute("data-score")); + var favCount = post.getAttribute("data-fav-count"); + var rating = post.getAttribute("data-rating").toUpperCase(); + var width = Number(post.getAttribute("data-width")); + var height = Number(post.getAttribute("data-height")); + var tooShort = (150 / width * height < 30); // Short thumbnails will need the info div position adjusted. + + if (gLoc === "comments") { // Add favorites info to the existing info in the comments listing. + var firstInfo = post.getElementsByClassName("info")[0]; + var infoParent = (firstInfo ? firstInfo.parentNode : undefined); + + if (infoParent) { + var favSpan = document.createElement("span"); + favSpan.className = "info bbb-thumb-info"; + favSpan.innerHTML = 'Favorites ' + favCount; + infoParent.appendChild(favSpan); + } } - } + else { // Add extra information inside of the thumbnail's parent element. + var thumbImg = post.getElementsByTagName("img")[0]; - return undefined; - } + // Don't add the info if there isn't a thumbnail. + if (!thumbImg) + continue; - function isLoggedIn() { - if (fetchMeta("current-user-id") !== "") - return true; - else - return false; - } + var thumbEl = post.getElementsByClassName("preview")[0] || post; + thumbEl.bbbAddClass("bbb-thumb-info-parent"); - function noXML() { - // Don't use XML requests on certain pages where it won't do any good. - var limit = getVar("limit"); - var limitTag = getLimitSearch(); - var page = getVar("page"); - var result = false; + var postLink = thumbEl.getElementsByTagName("a")[0]; + var before = (postLink ? postLink.nextElementSibling : undefined); - if (gLoc === "search") { - if (limit === 0 || page === "b1" || limitTag === 0) - result = true; - } - else if (gLoc === "notes") { - if (limit === 0) - result = true; - } - else if (gLoc === "comments") { - if (page === "b1") - result = true; - } + score = (score < 0 ? '' + score + '' : score); - return result; - } + var infoDiv = document.createElement("div"); + infoDiv.className = "bbb-thumb-info" + (tooShort ? " bbb-thumb-info-short" : ""); + infoDiv.innerHTML = "★" + score + "   ♥" + favCount + (location.host.indexOf("safebooru") < 0 ? "    " + rating : ""); - function useAPI() { - if ((show_loli || show_shota || show_toddlercon || show_deleted || show_banned) && (isLoggedIn() || !bypass_api)) - return true; - else - return false; + if (before) + thumbEl.insertBefore(infoDiv, before); + else + thumbEl.appendChild(infoDiv); + } + } } - function useAccount() { - if (isLoggedIn() && !override_account) - return true; - else - return false; - } + function postLinkNewWindow() { + // Make thumbnail clicks open in a new tab/window. + if (post_link_new_window === "disabled" || (gLoc !== "search" && gLoc !== "pool" && gLoc !== "notes" && gLoc !== "favorites" && gLoc !== "popular" && gLoc !== "popular_view" && gLoc !== "favorite_group")) + return; - function checkSetting(metaName, metaData, scriptSetting) { - // Check for the user's account settings and use the script setting if they're logged out or want to override them. - if (useAccount()) - return fetchMeta(metaName) === metaData; - else - return scriptSetting; - } + document.addEventListener("click", function(event) { + var bypass = (event.shiftKey && event.ctrlKey); + var modeSection = document.getElementById("mode-box"); + var danbMode = getCookie().mode || "view"; - function searchAdd() { - if (gLoc === "search" || gLoc === "post") { - // Where = array of
  • in tag-sidebar. - var where = document.getElementById("tag-box") || document.getElementById("tag-list"); + if (event.button !== 0 || event.altKey || !bypass && (event.shiftKey || event.ctrlKey) || (modeSection && danbMode !== "view")) + return; + + var runEndless = (post_link_new_window.indexOf("endless") > -1); + var runNormal = (post_link_new_window.indexOf("normal") > -1); - if (!where) + if ((bbb.endless.enabled && !runEndless) || (!bbb.endless.enabled && !runNormal)) return; - where = where.getElementsByTagName("li"); + var target = event.target; + var targetTag = target.tagName; + var url; - var tag = getVar("tags"); - tag = (tag ? "+" + tag : ""); + if (targetTag === "IMG" && target.parentNode) + url = target.parentNode.href; + else if (targetTag === "A" && target.bbbHasClass("bbb-post-link", "bbb-thumb-link")) + url = target.href; - for (var i = 0, il = where.length; i < il; i++) { - var listItem = where[i]; - var newTag = getVar("tags", listItem.getElementsByClassName("search-tag")[0].href); + if (url && /\/posts\/\d+/.test(url)) { + if (bypass) + location.href = url; + else + window.open(url); - listItem.innerHTML = '- + ' + listItem.innerHTML; + event.preventDefault(); } - } + }, false); } - function formatThumbnails(target) { - // Create thumbnail titles and borders. - var posts = getPosts(target); - var i, il; // Loop variables. + function formatTip(event, el, content, x, y) { + // Position + resize the tip and display it. + var tip = el; + var windowX = event.clientX; + var windowY = event.clientY; + var topOffset = 0; + var leftOffset = 0; - if (!posts.length) - return; + if (typeof(content) === "string") + tip.innerHTML = content; + else { + tip.innerHTML = ""; + tip.appendChild(content); + } - var searches = bbb.custom_tag.searches; + tip.style.visibility = "hidden"; + tip.style.display = "block"; - // Create and cache border search objects. - if (custom_tag_borders && !searches.length) { - for (i = 0, il = tag_borders.length; i < il; i++) - searches.push(createSearch(tag_borders[i].tags)); - } + // Resize the tip to minimize blank space. + var origHeight = tip.clientHeight; + var padding = tip.bbbGetPadding(); + var paddingWidth = padding.width; - // Cycle through each post and apply titles and borders. - for (i = 0, il = posts.length; i < il; i++) { - var post = posts[i]; - var img = post.getElementsByTagName("img")[0]; + while (origHeight >= tip.clientHeight && tip.clientWidth > 15) + tip.style.width = tip.clientWidth - paddingWidth - 2 + "px"; - if (!img) - continue; + tip.style.width = tip.clientWidth - paddingWidth + 2 + "px"; - var link = img.parentNode; - var tags = post.getAttribute("data-tags"); - var user = " user:" + post.getAttribute("data-uploader"); - var rating = " rating:" + post.getAttribute("data-rating"); - var score = " score:" + post.getAttribute("data-score"); - var title = tags + user + rating + score; - var id = post.getAttribute("data-id"); - var hasChildren = (post.getAttribute("data-has-children") === "true" ? true : false); - var secondary = []; - var secondaryLength = 0; - var borderStyle; - var styleList = bbb.custom_tag.style_list; + if (tip.scrollWidth > tip.clientWidth) + tip.style.width = "auto"; - // Create title. - img.title = title; + // Don't allow the tip to go above the top of the window. + if (windowY - tip.offsetHeight < 5) + topOffset = windowY - tip.offsetHeight - 5; - // Correct parent status borders on "no active children" posts for logged out users. - if (hasChildren && show_deleted && post.className.indexOf("post-status-has-children") < 0) - post.className += " post-status-has-children"; + // Don't allow the tip to go beyond the left edge of the window. + if (windowX - tip.offsetWidth < 5) + leftOffset = tip.offsetWidth + 1; - // Secondary custom tag borders. - if (custom_tag_borders) { - if (typeof(styleList[id]) === "undefined") { - for (var j = 0, jl = tag_borders.length; j < jl; j++) { - var tagBorderItem = tag_borders[j]; + tip.style.left = x - tip.offsetWidth + leftOffset + "px"; + tip.style.top = y - tip.offsetHeight - topOffset + "px"; + tip.style.visibility = "visible"; + } - if (tagBorderItem.is_enabled && thumbSearchMatch(post, searches[j])) { - secondary.push([tagBorderItem.border_color, tagBorderItem.border_style]); + function bbbHotkeys() { + // Handle keydown events not taking place in text inputs and check if they're a hotkey. + document.addEventListener("keydown", function(event) { + var active = document.activeElement; + var activeTag = active.tagName; + var activeType = active.type; - if (secondary.length === 4) - break; - } - } + if (activeTag === "SELECT" || activeTag === "TEXTAREA" || (activeTag === "INPUT" && !/^(:?button|checkbox|file|hidden|image|radio|reset|submit)$/.test(activeType))) // Input types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes + return; - secondaryLength = secondary.length; + var loc = (gLoc === "post" ? "post" : "other"); + var hotkeyCode = createHotkeyCode(event); + var hotkey = bbb.hotkeys[loc][hotkeyCode]; - if (secondaryLength) { - link.className += " bbb-custom-tag"; + if (hotkey) { + var customHandler = hotkey.custom_handler; + customHandler = (typeof(customHandler) !== "boolean" || customHandler !== true ? false : true); - if (secondaryLength === 1 || (single_color_borders && secondaryLength > 1)) - borderStyle = "border: " + border_width + "px " + secondary[0][0] + " " + secondary[0][1] + " !important;"; - else if (secondaryLength === 2) - borderStyle = "border-color: " + secondary[0][0] + " " + secondary[1][0] + " " + secondary[1][0] + " " + secondary[0][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[1][1] + " " + secondary[1][1] + " " + secondary[0][1] + " !important;"; - else if (secondaryLength === 3) - borderStyle = "border-color: " + secondary[0][0] + " " + secondary[1][0] + " " + secondary[2][0] + " " + secondary[0][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[1][1] + " " + secondary[2][1] + " " + secondary[0][1] + " !important;"; - else if (secondaryLength === 4) - borderStyle = "border-color: " + secondary[0][0] + " " + secondary[2][0] + " " + secondary[3][0] + " " + secondary[1][0] + " !important; border-style: " + secondary[0][1] + " " + secondary[2][1] + " " + secondary[3][1] + " " + secondary[1][1] + " !important;"; + hotkey.func(event); // The event object will always be the first argument passed to the provided function (previously declared or anonymous). - link.setAttribute("style", borderStyle); - styleList[id] = borderStyle; - } - else - styleList[id] = false; - } - else if (styleList[id] !== false && post.className.indexOf("bbb-custom-tag") < 0) { - link.className += " bbb-custom-tag"; - link.setAttribute("style", styleList[id]); + if (!customHandler) { + event.stopPropagation(); + event.preventDefault(); } } - } + }, true); } - function formatInfo(post) { - // Add information to/alter information in the post object. - if (!post) - return undefined; + function createHotkeyCode(event) { + // Take a keyboard event and create a code for its key combination. + // Examples: s49 = Shift + "1", a50 = Alt + "2", cs51 = Control + Shift + "3" + // Alt, control, meta, and shift abbreviations should be alphabetical. Keycode numbers: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode + var hotkeycode = ""; - var flags = ""; - var thumbClass = ""; + if (event.altKey) + hotkeycode += "a"; - if (post.is_deleted) { - flags += " deleted"; - thumbClass += " post-status-deleted"; - } - if (post.is_pending) { - flags += " pending"; - thumbClass += " post-status-pending"; - } - if (post.is_banned) - flags += " banned"; - if (post.is_flagged) { - flags += " flagged"; - thumbClass += " post-status-flagged"; + if (event.ctrlKey) + hotkeycode += "c"; + + if (event.metaKey) + hotkeycode += "m"; + + if (event.shiftKey) + hotkeycode += "s"; + + if (event.keyCode) + hotkeycode += event.keyCode; + + return hotkeycode || undefined; + } + + function createHotkey(hotkeyCode, func, propObject) { + // Create hotkeys or override Danbooru's existing ones. Creating a hotkey for a hotkey that already exists will replace it. + var loc = (gLoc === "post" ? "post" : "other"); + var hotkeyObject = {func: func}; + + if (propObject) { + for (var i in propObject) { + if (propObject.hasOwnProperty(i)) + hotkeyObject[i] = propObject[i]; + } } - if (post.has_children && (post.has_active_children || show_deleted)) - thumbClass += " post-status-has-children"; - if (post.parent_id) - thumbClass += " post-status-has-parent"; - // Hidden post fixes. - post.md5 = post.md5 || ""; - post.file_ext = post.file_ext || ""; - post.preview_file_url = post.preview_file_url || bbbHiddenImg; - post.large_file_url = post.large_file_url || ""; - post.file_url = post.file_url || ""; + bbb.hotkeys[loc][hotkeyCode] = hotkeyObject; + } - // Potential null value fixes. - post.approver_id = post.approver_id || ""; - post.parent_id = post.parent_id || ""; - post.pixiv_id = post.pixiv_id || ""; + function removeHotkey(hotkeyCode) { + // Remove a hotkey. + var loc = (gLoc === "post" ? "post" : "other"); - post.flags = flags.bbbSpaceClean(); - post.thumb_class = thumbClass; + delete bbb.hotkeys[loc][hotkeyCode]; + } - return post; + function resizeHotkey(event) { + // Handle the resize post hotkeys and make sure they don't interfere with the favorite group dialog box hotkeys. + var favGroup = document.querySelector("div[aria-describedby='add-to-favgroup-dialog']"); + + if (favGroup && favGroup.style.display !== "none") + return; + + var keyCode = event.keyCode; + var mode; + + switch (keyCode) { + case 49: + mode = "all"; + break; + case 50: + mode = "width"; + break; + case 51: + mode = "height"; + break; + case 52: + default: + mode = "none"; + break; + } + + resizePost(mode); + event.preventDefault(); + event.stopPropagation(); } - function checkRelations() { - // Test whether the parent/child notice could have hidden posts. - var post = bbb.post.info; - var thumbCount; - var deletedCount; - var loggedIn = isLoggedIn(); - var fixParent = false; - var fixChild = false; - var relationCookie = getCookie()["show-relationship-previews"]; - var showPreview = (relationCookie === undefined || relationCookie === "1" ? true : false); - var parentLink = document.getElementById("has-children-relationship-preview-link"); - var childLink = document.getElementById("has-parent-relationship-preview-link"); + function fixLimit(limit) { + // Add the limit variable to link URLs that are not thumbnails. + if (!thumbnail_count && limit === undefined) + return; + + var newLimit = (limit === undefined ? thumbnail_count : limit) || undefined; + var page = document.getElementById("page"); + var header = document.getElementById("top"); + var searchParent = document.getElementById("search-box") || document.getElementById("a-intro"); + var links; + var link; + var linkHref; + var i, il; // Loop variables. - if (post.has_children) { - var parentNotice = document.getElementsByClassName("notice-parent")[0]; + if (page) { + links = page.getElementsByTagName("a"); - if (parentNotice) { - var parentText = parentNotice.textContent.match(/has (\d+|a) child/); - var parentCount = (parentText ? Number(parentText[1]) || 1 : 0); - thumbCount = getPosts(parentNotice).length; - deletedCount = parentNotice.getElementsByClassName("post-status-deleted").length; + for (i = 0, il = links.length; i < il; i++) { + link = links[i]; + linkHref = link.getAttribute("href"); // Use getAttribute so that we get the exact value. "link.href" adds in the domain. - if ((!loggedIn && show_deleted && !deletedCount) || (parentCount && parentCount + 1 !== thumbCount)) - fixParent = true; + if (linkHref && !/page=/.test(linkHref) && (linkHref.indexOf("/posts?") === 0 || linkHref.indexOf("/favorites?") === 0)) + link.href = updateURLQuery(linkHref, {limit: newLimit}); } - else if (show_deleted) - fixParent = true; - } - - if (fixParent) { - if (showPreview || !parentLink) - searchJSON("parent", post.id); - else - parentLink.addEventListener("click", requestRelations, false); } - if (post.parent_id) { - var childNotice = document.getElementsByClassName("notice-child")[0]; + if (header) { + links = header.getElementsByTagName("a"); - if (childNotice) { - var childText = childNotice.textContent.match(/has (\d+|a) sibling/); - var childCount = (childText ? Number(childText[1]) || 1 : 0) + 1; - thumbCount = getPosts(childNotice).length; - deletedCount = childNotice.getElementsByClassName("post-status-deleted").length; + for (i = 0, il = links.length; i < il; i++) { + link = links[i]; + linkHref = link.getAttribute("href"); - if ((!loggedIn && show_deleted && !deletedCount) || (childCount && childCount + 1 !== thumbCount)) - fixChild = true; + if (linkHref && (linkHref.indexOf("limit=") > -1 || linkHref.indexOf("/posts") === 0 || linkHref === "/" || linkHref === "/notes?group_by=post" || linkHref === "/favorites")) + link.href = updateURLQuery(linkHref, {limit: newLimit}); } } - if (fixChild) { - if (showPreview || !childLink) - searchJSON("child", post.parent_id); - else - childLink.addEventListener("click", requestRelations, false); + // Fix the search. + if (searchParent && (gLoc === "search" || gLoc === "post" || gLoc === "intro" || gLoc === "favorites")) { + var search = searchParent.getElementsByTagName("form")[0]; + + if (search) { + var limitInput = bbb.el.limitInput; + + if (!limitInput) { + limitInput = bbb.el.limitInput = document.createElement("input"); + limitInput.name = "limit"; + limitInput.value = newLimit; + limitInput.type = "hidden"; + search.appendChild(limitInput); + + // Change the form action if on the favorites page. It uses "/favorites", but that just goes to the normal "/posts" search while stripping out the limit. + search.action = "/posts"; + + // Remove the user's default limit if the user tries to specify a limit value in the tags. + var tagsInput = document.getElementById("tags"); + + if (tagsInput) { + search.addEventListener("submit", function() { + if (/(?:^|\s)limit:/.test(tagsInput.value)) + search.removeChild(limitInput); + else if (limitInput.parentNode !== search) + search.appendChild(limitInput); + }, false); + } + } + else + limitInput.value = newLimit || thumbnail_count_default; + } } } - function requestRelations(event) { - // Start the parent/child notice JSON request when the user chooses to display the thumbs in a notice. - var post = bbb.post.info; - var target = event.target; - - if (target.id === "has-children-relationship-preview-link") - searchJSON("parent", post.id); - else if (target.id === "has-parent-relationship-preview-link") - searchJSON("child", post.parent_id); + function getLimit(url) { + // Retrieve the current specified limit value. The query limit overrides the search limit. + var loc = danbLoc(url); + var limit; + + if (loc === "pool" || loc === "popular" || loc === "favorite_group") + limit = thumbnail_count_default; + else if (loc === "comments") + limit = 5; + else if (loc === "popular_view") + limit = 101; + else { + var queryLimit = getQueryLimit(url); + var searchLimit = getSearchLimit(url); - target.removeEventListener("click", requestRelations, false); + limit = (queryLimit !== undefined ? queryLimit : searchLimit); + } - event.preventDefault(); + return limit; } - function checkHiddenImg(post) { - // Alter a hidden image with cache info or queue it for the cache. - if (!post.md5) { - if (!bbb.cache.stored.history) - loadThumbCache(); - - var cacheName = bbb.cache.stored.names[post.id]; + function getQueryLimit(url) { + // Retrieve the limit from a URL's query portion. Always use the default for certain areas where the limit is not allowed. + var queryLimit = getVar("limit", url); - if (cacheName) { // Load the thumbnail info from the cache. - if (cacheName === "download-preview.png") - post.preview_file_url = "/images/download-preview.png"; - else { - var cacheValues = cacheName.split("."); - var cacheMd5 = cacheValues[0]; - var cacheExt = cacheValues[1]; + if (queryLimit !== null && queryLimit !== undefined) { // Treat the limit as undefined when the limit parameter is declared with no value. + queryLimit = decodeURIComponent(queryLimit); - post.md5 = cacheMd5; - post.file_ext = cacheExt; - post.preview_file_url = (!post.image_height || cacheExt === "swf" ? "/images/download-preview.png" : "/data/preview/" + cacheMd5 + ".jpg"); - post.large_file_url = (post.has_large ? "/data/sample/sample-" + cacheMd5 + ".jpg" : "/data/" + cacheName); - post.file_url = "/data/" + cacheName; - } - } - else // Queue hidden img for fixing. - bbb.cache.hidden_imgs.push(post.id); + if (queryLimit === "" || !/^\s*\d+/.test(queryLimit)) // No thumbnails show up when the limit is declared with a blank value or has no number directly after any potential white space. + return 0; + else // The query limit finds its value in a manner similar to parseInt. Dump leading spaces and grab numbers until a non-numerical character is hit. + return parseInt(queryLimit, 10); } + + return undefined; } - function fixHiddenImgs() { - // Fix hidden thumbnails by fetching the info from a page. - var hiddenImgs = bbb.cache.hidden_imgs; + function getSearchLimit(url) { + // Retrieve the limit from the search/limit tag used in a search. + var searchLimit = getTagVar("limit", url); - if (hiddenImgs.length) { - if (!bbb.cache.save_enabled) { - window.addEventListener("beforeunload", updateThumbCache); - bbb.cache.save_enabled = true; - } + if (searchLimit !== undefined) { + searchLimit = decodeURIComponent(searchLimit); - fetchPages("/posts/" + hiddenImgs[0], "hidden"); - bbbStatus("hidden"); + if (searchLimit === "") // No thumbnails show up when the limit is declared but left blank. + return 0; + else if (!bbbIsNum(searchLimit.replace(/\s/g, "")) || searchLimit.indexOf(".") > -1 || Number(searchLimit) < 0) // Non-numerical, negative, and decimal values are ignored. Treat the limit as undefined. + return undefined; + else + return Number(searchLimit); } + + return undefined; } - function customCSS() { - var i; // Loop variable. - var customStyles = document.createElement("style"); - customStyles.type = "text/css"; + function arrowNav() { + // Bind the arrow keys to Danbooru's page navigation. + var paginator = getPaginator(); - var styles = '#bbb_menu {background-color: #FFFFFF; border: 1px solid #CCCCCC; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5); padding: 15px; position: fixed; top: 25px; left: 50%; z-index: 9001;}' + - '#bbb_menu * {font-size: 14px; line-height: 16px; outline: 0px none; border: 0px none; margin: 0px; padding: 0px;}' + // Reset some base settings. - '#bbb_menu h1 {font-size: 24px; line-height: 42px;}' + - '#bbb_menu h2 {font-size: 16px; line-height: 25px;}' + - '#bbb_menu input, #bbb_menu select, #bbb_menu textarea {border: #CCCCCC 1px solid;}' + - '#bbb_menu input {height: 17px; padding: 1px 0px; margin-top: 4px; vertical-align: top;}' + - '#bbb_menu input[type="checkbox"] {margin: 0px; vertical-align: middle; position: relative; bottom: 2px;}' + - '#bbb_menu select {height: 21px; margin-top: 4px; vertical-align: top;}' + - '#bbb_menu option {padding: 0px 3px;}' + - '#bbb_menu textarea {padding: 2px; resize: none;}' + - '#bbb_menu ul, #bbb_menu ol {list-style: outside disc none; margin-top: 0px; margin-bottom: 0px; margin-left: 20px; display: block;}' + - '#bbb_menu .bbb-scroll-div {border: 1px solid #CCCCCC; margin: -1px 0px 5px 0px; padding: 5px 0px; overflow-y: auto;}' + - '#bbb_menu .bbb-page {position: relative; display: none;}' + - '#bbb_menu .bbb-button {border: 1px solid #CCCCCC; border-radius: 5px; display: inline-block; padding: 5px;}' + - '#bbb_menu .bbb-tab {border-top-left-radius: 5px; border-top-right-radius: 5px; display: inline-block; padding: 5px; border: 1px solid #CCCCCC; margin-right: -1px;}' + - '#bbb_menu .bbb-active-tab {background-color: #FFFFFF; border-bottom-width: 0px; padding-bottom: 6px;}' + - '#bbb_menu .bbb-header {border-bottom: 2px solid #CCCCCC; margin-bottom: 5px; width: 700px;}' + - '#bbb_menu .bbb-toc {list-style-type: upper-roman; margin-left: 30px;}' + - '#bbb_menu .bbb-section-options, #bbb_menu .bbb-section-text {margin-bottom: 5px; max-width: 902px;}' + - '#bbb_menu .bbb-section-options-left, #bbb_menu .bbb-section-options-right {display: inline-block; vertical-align: top; width: 435px;}' + - '#bbb_menu .bbb-section-options-left {border-right: 1px solid #CCCCCC; margin-right: 15px; padding-right: 15px;}' + - '#bbb_menu .bbb-general-label {display: block; height: 29px; padding: 0px 5px;}' + - '#bbb_menu .bbb-general-label:hover {background-color: #EEEEEE;}' + - '#bbb_menu .bbb-general-text {line-height: 29px;}' + - '#bbb_menu .bbb-general-input {float: right; line-height: 29px;}' + - '#bbb_menu .bbb-expl {background-color: #CCCCCC; border: 1px solid #000000; display: none; font-size: 12px; padding: 5px; position: fixed; max-width: 420px; width: 420px; overflow: hidden;}' + - '#bbb_menu .bbb-expl * {font-size: 12px;}' + - '#bbb_menu .bbb-expl-link {font-size: 12px; font-weight: bold; margin-left: 5px; padding: 2px;}' + - '#bbb_menu .bbb-border-div {background-color: #EEEEEE; padding: 2px; margin: 0px 5px 0px 0px;}' + - '#bbb_menu .bbb-border-bar, #bbb_menu .bbb-border-settings {height: 29px; padding: 0px 2px; overflow: hidden;}' + - '#bbb_menu .bbb-border-settings {background-color: #FFFFFF;}' + - '#bbb_menu .bbb-border-div label, #bbb_menu .bbb-border-div span {display: inline-block; line-height: 29px;}' + - '#bbb_menu .bbb-border-name {text-align: left; width: 540px;}' + - '#bbb_menu .bbb-border-name input {width:460px;}' + - '#bbb_menu .bbb-border-color {text-align: center; width: 210px;}' + - '#bbb_menu .bbb-border-color input {width: 148px;}' + - '#bbb_menu .bbb-border-style {float: right; text-align: right; width: 130px;}' + - '#bbb_menu .bbb-border-divider {height: 4px;}' + - '#bbb_menu .bbb-insert-highlight .bbb-border-divider {background-color: blue; cursor: pointer;}' + - '#bbb_menu .bbb-no-highlight .bbb-border-divider {background-color: transparent; cursor: auto;}' + - '#bbb_menu .bbb-border-button {border: 1px solid #CCCCCC; border-radius: 5px; display: inline-block; padding: 2px; margin: 0px 2px;}' + - '#bbb_menu .bbb-border-spacer {display: inline-block; height: 12px; width: 0px; border-right: 1px solid #CCCCCC; margin: 0px 5px;}' + - '#bbb_menu .bbb-backup-area {height: 200px; width: 896px; margin-top: 2px;}' + - '#bbb_menu .bbb-edit-blocker {display: none; height: 100%; width: 100%; background-color: rgba(0, 0, 0, 0.33); position: fixed; top: 0px; left: 0px;}' + - '#bbb_menu .bbb-edit-box {height: 500px; width: 800px; margin-left: -412px; margin-top: -262px; position: fixed; left: 50%; top: 50%; background-color: #FFFFFF; border: 2px solid #CCCCCC; padding: 10px; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.5);}' + - '#bbb_menu .bbb-edit-text {margin-bottom: 5px;}' + - '#bbb_menu .bbb-edit-area {height: 392px; width: 794px; margin-bottom: 5px;}' + - '#bbb_menu .bbb-edit-link {background-color: #FFFFFF; border: 1px solid #CCCCCC; display: inline-block; height: 19px; line-height: 19px; margin-left: -1px; padding: 0px 2px; margin-top: 4px; text-align: center; vertical-align: top;}' + - '.bbb-status {background-color: rgba(255, 255, 255, 0.75); border: 1px solid rgba(204, 204, 204, 0.75); font-size: 12px; font-weight: bold; display: none; padding: 3px; position: fixed; bottom: 0px; right: 0px; z-index: 9002;}' + - '.bbb-keep-notice {display: block !important; opacity: 1.0 !important;}' + - '#notice {padding-right: 55px !important;}'; // Fix Danbooru notice overlapping text for long messages. + if (!arrow_nav || (!paginator && gLoc !== "popular")) // If the paginator exists, arrow navigation should be applicable. + return; - // Provide a little extra space for listings that allow thumbnail_count. - if (thumbnail_count && (gLoc === "search" || gLoc === "notes")) { - styles += 'div#page {margin: 0px 10px 0px 20px !important;}' + - 'section#content {padding: 0px !important;}'; + // Create the hotkeys for the left and right arrows. + createHotkey("37", function() { danbooruNav("prev"); }); + createHotkey("39", function() { danbooruNav("next"); }); + } + + function danbooruNav(dir) { + // Determine the correct Danbooru page function and use it. + if (gLoc === "popular") { + if (dir === "prev") + Danbooru.PostPopular.nav_prev(); + else if (dir === "next") + Danbooru.PostPopular.nav_next(); + } + else { + if (dir === "prev") + Danbooru.Paginator.prev_page(); + else if (dir === "next") + Danbooru.Paginator.next_page(); } + } - // Border setup. - var totalBorderWidth = (custom_tag_borders ? border_width * 2 + (border_spacing * 2 || 1) : border_width + border_spacing); - var thumbMaxDim = 150 + totalBorderWidth * 2; - var listingExtraSpace = (14 - totalBorderWidth * 2 > 2 ? 14 - totalBorderWidth * 2 : 2); - var commentExtraSpace = 34 - totalBorderWidth * 2; - var sbsl = status_borders.length; - var statusBorderItem; + function autohideSidebar() { + // Show the sidebar when it gets focus, hide it when it loses focus, and only allow select elements to retain focus. + var sidebar = document.getElementById("sidebar"); - styles += 'article.post-preview {height: ' + thumbMaxDim + 'px !important; width: ' + thumbMaxDim + 'px !important; margin: 0px ' + listingExtraSpace + 'px ' + listingExtraSpace + 'px 0px !important;}' + - '#has-parent-relationship-preview article.post-preview, #has-children-relationship-preview article.post-preview {width: auto !important; max-width: ' + thumbMaxDim + 'px !important; margin: 0px !important;}' + - 'article.post-preview a {line-height: 0px !important;}' + - '.post-preview div.preview {height: ' + thumbMaxDim + 'px !important; width: ' + thumbMaxDim + 'px !important; margin-right: ' + commentExtraSpace + 'px !important;}' + - '.post-preview div.preview a {line-height: 0px !important;}' + - '.post-preview img {border-width: ' + border_width + 'px !important; padding: ' + border_spacing + 'px !important;}' + - '.bbb-custom-tag {border-width: ' + border_width + 'px !important;}' + - 'article.post-preview:before, .post-preview div.preview:before {margin: ' + totalBorderWidth + 'px !important;}'; // Thumbnail icon overlay position adjustment. + if (!autohide_sidebar || !sidebar) + return; - if (custom_status_borders) { - var activeStatusStyles = ""; - var statusBorderInfo = {}; + sidebar.addEventListener("click", function(event) { + var target = event.target; - for (i = 0; i < sbsl; i++) { - statusBorderItem = status_borders[i]; - statusBorderInfo[statusBorderItem.tags] = statusBorderItem; - } + if (target.id !== "tags") + target.blur(); + }, false); + sidebar.addEventListener("focus", function() { + sidebar.bbbAddClass("bbb-sidebar-show"); + }, true); + sidebar.addEventListener("blur", function() { + sidebar.bbbRemoveClass("bbb-sidebar-show"); + }, true); + } - for (i = 0; i < sbsl; i++) { - statusBorderItem = status_borders[i]; + function fixedSidebar() { + // Fix the scrollbar to the top/bottom of the window when it would normally scroll out of view. + var sidebar = bbb.fixed_sidebar.sidebar = document.getElementById("sidebar"); + var content = bbb.fixed_sidebar.content = document.getElementById("content"); + var comments = document.getElementById("comments"); - if (single_color_borders) { - if (statusBorderItem.is_enabled) - activeStatusStyles = '.post-preview.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}' + activeStatusStyles; - else - styles += '.post-preview.' + statusBorderItem.class_name + ' img {border-color: transparent !important;}'; // Disable status border by resetting it to transparent. - } - else { - if (statusBorderItem.is_enabled) { - if (statusBorderItem.tags === "parent") { - styles += '.post-preview.post-status-has-children img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; // Parent only status border. + if (!fixed_sidebar || autohide_sidebar || !sidebar || !content || (gLoc === "post" && !comments)) + return; - if (statusBorderInfo.child.is_enabled) - styles += '.post-preview.post-status-has-children.post-status-has-parent img {border-color: ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' ' + statusBorderInfo.child.border_color + ' ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' ' + statusBorderInfo.child.border_style + ' ' + statusBorderItem.border_style + ' !important;}'; // Parent and child status border. - } - else if (statusBorderItem.tags === "child") - styles += '.post-preview.post-status-has-parent img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; // Child only status border. - else { - activeStatusStyles = '.post-preview.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged only status border. + var docRect = document.documentElement.getBoundingClientRect(); + var sidebarRect = sidebar.getBoundingClientRect(); + var sidebarTop = bbb.fixed_sidebar.top = sidebarRect.top - docRect.top; + var sidebarLeft = bbb.fixed_sidebar.left = sidebarRect.left - docRect.left; + var sidebarHeight = sidebarRect.height; - if (statusBorderInfo.parent.is_enabled) - activeStatusStyles = '.post-preview.post-status-has-children.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderInfo.parent.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.parent.border_color + ' !important; border-style: ' + statusBorderInfo.parent.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.parent.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged and parent status border. + content.style.minHeight = sidebarHeight - 1 + "px"; + sidebar.style.overflow = "hidden"; // There are some cases where text overflows. - if (statusBorderInfo.child.is_enabled) - activeStatusStyles = '.post-preview.post-status-has-parent.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderInfo.child.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' !important; border-style: ' + statusBorderInfo.child.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged and child status border. + if (comments) + comments.style.overflow = "auto"; // Force the contained float elements to affect the dimensions. - if (statusBorderInfo.child.is_enabled && statusBorderInfo.parent.is_enabled) - activeStatusStyles = '.post-preview.post-status-has-children.post-status-has-parent.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderInfo.parent.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderItem.border_color + ' ' + statusBorderInfo.child.border_color + ' !important; border-style: ' + statusBorderInfo.parent.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderItem.border_style + ' ' + statusBorderInfo.child.border_style + ' !important;}' + activeStatusStyles; // Deleted/pending/flagged, parent, and child status border. - } - } - else - styles += '.post-preview.' + statusBorderItem.class_name + ' img {border-color: transparent !important;}'; // Disable status border by resetting it to transparent. - } - } + fixedSidebarCheck(); + document.body.bbbWatchNodes(fixedSidebarCheck); + document.addEventListener("keyup", fixedSidebarCheck, false); + document.addEventListener("click", fixedSidebarCheck, false); + window.addEventListener("scroll", fixedSidebarCheck, false); + window.addEventListener("resize", fixedSidebarCheck, false); + } - styles += activeStatusStyles; + function fixedSidebarCheck() { + // Event handler for adjusting the sidebar position. + var sidebar = bbb.fixed_sidebar.sidebar; + var content = bbb.fixed_sidebar.content; + var sidebarTop = bbb.fixed_sidebar.top; + var sidebarLeft = bbb.fixed_sidebar.left; + var verScrolled = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + var horScrolled = window.payeXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; + var sidebarHeight = sidebar.clientHeight; // Height can potentially change (blacklist update, etc.) so always recalculate it. + var contentHeight = content.clientHeight; + var viewHeight = document.documentElement.clientHeight; + var sidebarBottom = sidebarTop + sidebarHeight; + var contentBottom = sidebarTop + contentHeight; + var viewportBottom = verScrolled + viewHeight; + + if (sidebarHeight > contentHeight) // Don't fix to window if there's no space for it to scroll. + sidebar.style.position = "static"; + else if (sidebarHeight < viewHeight) { // Fix to the top of the window if not too tall and far enough down. + if (contentBottom < verScrolled + sidebarHeight) { + sidebar.style.position = "absolute"; + sidebar.style.bottom = viewHeight - contentBottom + "px"; + sidebar.style.top = "auto"; + } + else if (sidebarTop < verScrolled ) { + sidebar.style.position = "fixed"; + sidebar.style.bottom = "auto"; + sidebar.style.top = "0px"; + } + else + sidebar.style.position = "static"; } - else if (single_color_borders) { // Allow single color borders when not using custom status borders. Works off of the old border hierarchy: Deleted > Flagged > Pending > Child > Parent - var defaultStatusBorders = bbb.options.status_borders; - - for (i = defaultStatusBorders.length - 1; i >= 0; i--) { - statusBorderItem = defaultStatusBorders[i]; - - styles += '.post-preview.' + statusBorderItem.class_name + ' img {border-color: ' + statusBorderItem.border_color + ' !important; border-style: ' + statusBorderItem.border_style + ' !important;}'; + else { // If too tall, fix the sidebar bottom to the viewport bottom to avoid putting part of the sidebar permanently beyond reach. + if (viewportBottom > contentBottom) { + sidebar.style.position = "absolute"; + sidebar.style.bottom = viewHeight - contentBottom + "px"; + sidebar.style.top = "auto"; + } + else if (sidebarTop > verScrolled || sidebarTop > viewportBottom - sidebarHeight) + sidebar.style.position = "static"; + else if (sidebarBottom < viewportBottom) { + sidebar.style.position = "fixed"; + sidebar.style.bottom = "0px"; + sidebar.style.top = "auto"; } } - if (custom_tag_borders) { - var customBorderSpacing = (border_spacing || 1); - var marginAlignment = border_width + customBorderSpacing; + // Maintain horizontal position in the document. + if (horScrolled && sidebar.style.position !== "absolute") + sidebar.style.left = (sidebarLeft - horScrolled) + "px"; + else + sidebar.style.left = sidebarLeft + "px"; + } - styles += '.post-preview .bbb-custom-tag img {border-width: 0px !important;}' + // Remove the transparent border for images that get custom tag borders. - 'article.post-preview a, .post-preview div.preview a {display: inline-block; margin: ' + marginAlignment + 'px !important;}'; // Align one border images with two border images. + function fixedPaginator() { + // Set up the fixed paginator. + if (fixed_paginator === "disabled" || (gLoc !== "search" && gLoc !== "pool" && gLoc !== "notes" && gLoc !== "favorites" && gLoc !== "favorite_group")) + return; - for (i = 0; i < sbsl; i++) { - statusBorderItem = status_borders[i]; + var paginator = getPaginator(); + var paginatorMenu = (paginator ? paginator.getElementsByTagName("menu")[0] : undefined); + var paginatorLink = (paginatorMenu ? (paginatorMenu.getElementsByTagName("a")[0] || paginatorMenu.getElementsByTagName("span")[0]) : undefined); - if (statusBorderItem.is_enabled) - styles += '.post-preview.' + statusBorderItem.class_name + ' .bbb-custom-tag {margin: 0px !important; padding: ' + customBorderSpacing + 'px !important;}' + // Remove margin alignment and add border padding for images that have status and custom tag borders. - '.post-preview.' + statusBorderItem.class_name + ' .bbb-custom-tag img {border-width: ' + border_width + 'px !important;}'; // Override the removal of the transparent border for images that have status borders and custom tag borders. - } - } + if (!paginatorLink) + return; - // Hide sidebar. - if (autohide_sidebar.indexOf(gLoc) > -1) { - styles += 'div#page {margin: 0px 10px 0px 20px !important;}' + - 'aside#sidebar {background-color: transparent !important; border-width: 0px !important; height: 100% !important; width: 250px !important; position: fixed !important; left: -280px !important; overflow-y: hidden !important; padding: 0px 20px !important; top: 0px !important; z-index: 2001 !important;}' + - 'aside#sidebar.bbb-sidebar-show, aside#sidebar:hover {background-color: #FFFFFF !important; border-right: 1px solid #CCCCCC !important; left: 0px !important; overflow-y: auto !important; padding: 0px 15px !important;}' + - 'section#content {margin-left: 0px !important;}' + - '.ui-autocomplete {z-index: 2002 !important;}'; - } + // Get all our measurements. + var docRect = document.documentElement.getBoundingClientRect(); + var docWidth = docRect.width; + var docBottom = docRect.bottom; - if (direct_downloads) - styles += ".bbb-ddl {display: none !important;}"; + var paginatorRect = paginator.getBoundingClientRect(); + var paginatorBottom = paginatorRect.bottom; + var paginatorLeft = paginatorRect.left; + var paginatorRight = docWidth - paginatorRect.right; + var paginatorHeight = paginatorRect.height; - if (tag_scrollbars) - styles += "#tag-list ul {max-height: " + tag_scrollbars + "px !important; overflow-y: auto !important; font-size: 87.5% !important;}"; + var menuRect = paginatorMenu.getBoundingClientRect(); + var menuBottom = menuRect.bottom; - if (hide_tos_notice && document.getElementById("tos-notice")) { - styles += '#tos-notice {display: none !important;}'; + var linkRect = paginatorLink.getBoundingClientRect(); + var linkBottom = linkRect.bottom; - if (manage_cookies) - createCookie("accepted_tos", 1, 365); - } + var paginatorMargAdjust = (paginatorLeft - paginatorRight) / 2; + var menuBottomAdjust = linkBottom - menuBottom; - if (hide_sign_up_notice && document.getElementById("sign-up-notice")) { - styles += '#sign-up-notice {display: none !important;}'; + var paginatorSpacer = document.createElement("div"); // Prevents the document height form changing when the paginator is fixed to the bottom of the window. + paginatorSpacer.id = "bbb-fixed-paginator-spacer"; - if (manage_cookies) - createCookie("hide_sign_up_notice", 1, 7); - } + var paginatorSibling = paginator.nextElementSibling; + + if (paginatorSibling) + paginator.parentNode.insertBefore(paginatorSpacer, paginatorSibling); + else + paginator.parentNode.appendChild(paginatorSpacer); + + // Create the css for the fixed paginator separately from the main one since it needs to know what the page's final layout will be with the main css applied. + var style = document.createElement("style"); + style.type = "text/css"; + style.innerHTML = '.bbb-fixed-paginator div.paginator {position: fixed; padding: 0px; margin: 0px; bottom: 0px; left: 50%; margin-left: ' + paginatorMargAdjust + 'px;}' + + '.bbb-fixed-paginator div.paginator menu {position: relative; left: -50%; padding: ' + menuBottomAdjust + 'px 0px; background-color: #FFFFFF;}' + + '.bbb-fixed-paginator div.paginator menu li:first-child {padding-left: 0px;}' + + '.bbb-fixed-paginator div.paginator menu li:first-child > * {margin-left: 0px;}' + + '.bbb-fixed-paginator div.paginator menu li:last-child {padding-right: 0px;}' + + '.bbb-fixed-paginator div.paginator menu li:last-child > * {margin-right: 0px;}' + + '#bbb-fixed-paginator-spacer {display: none; height: ' + paginatorHeight + 'px; clear: both; width: 100%;}' + + '.bbb-fixed-paginator #bbb-fixed-paginator-spacer {display: block;}'; + + if (fixed_paginator.indexOf("minimal") > -1) { + style.innerHTML += '.bbb-fixed-paginator div.paginator menu {padding: 3px 0px;}' + + '.bbb-fixed-paginator div.paginator menu li a, .bbb-fixed-paginator div.paginator menu li span {padding: 2px; margin: 0px 2px 0px 0px;}' + + '.bbb-fixed-paginator div.paginator menu li {padding: 0px;}' + + '.bbb-fixed-paginator div.paginator menu li a {border-color: #CCCCCC;}'; + } + + document.getElementsByTagName("head")[0].appendChild(style); + + bbb.fixed_paginator_space = docBottom - paginatorBottom - menuBottomAdjust; // Store the amount of space between the bottom of the page and the paginator. + + document.body.bbbWatchNodes(fixedPaginatorCheck); + document.addEventListener("keyup", fixedPaginatorCheck, false); + document.addEventListener("click", fixedPaginatorCheck, false); + window.addEventListener("scroll", fixedPaginatorCheck, false); + window.addEventListener("resize", fixedPaginatorCheck, false); + + fixedPaginatorCheck(); + } + + function fixedPaginatorCheck() { + // Check if the paginator needs to be in its default position or fixed to the window. + if (!bbb.fixed_paginator_space) + return; + + var runEndless = (fixed_paginator.indexOf("endless") > -1); + var runNormal = (fixed_paginator.indexOf("normal") > -1); + var docHeight = document.documentElement.scrollHeight; + var scrolled = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + var viewHeight = document.documentElement.clientHeight; + + if (viewHeight + scrolled < docHeight - bbb.fixed_paginator_space && ((runEndless && bbb.endless.enabled) || (runNormal && !bbb.endless.enabled))) + document.body.bbbAddClass("bbb-fixed-paginator"); + else + document.body.bbbRemoveClass("bbb-fixed-paginator"); + } - if (hide_upgrade_notice && document.getElementById("upgrade-account-notice")) { - styles += '#upgrade-account-notice {display: none !important;}'; + function collapseSidebar() { + // Allow clicking on headers to collapse and expand their respective sections. + var sidebar = document.getElementById("sidebar"); - if (manage_cookies) - createCookie("hide_upgrade_account_notice", 1, 7); - } + if (!collapse_sidebar || !sidebar) + return; - if (hide_ban_notice) - styles += '#ban-notice {display: none !important;}'; + var dataLoc = (gLoc === "post" ? "post" : "thumb"); + var data = collapse_sidebar_data[dataLoc]; + var tagTypes = ["h1", "h2"]; // Tags that will be allowed to toggle. + var nameList = " "; + var removedOld = false; + var i, il; // Loop variables. - if (hide_comment_notice) { - var commentGuide; + // Grab the desired tags and turn them into toggle elements for their section. + for (i = 0, il = tagTypes.length; i < il; i++) { + var tags = sidebar.getElementsByTagName(tagTypes[i]); - if (gLoc === "post") { - commentGuide = document.evaluate('//section[@id="comments"]/h2/a[contains(@href,"/wiki_pages")]/..', document, null, 9, null).singleNodeValue; + for (var j = 0, jl = tags.length; j < jl; j++) { + var tag = tags[j]; + var name = tag.textContent.bbbSpaceClean().replace(" ", "_"); + var collapse = data[name]; + var sibling = tag.nextElementSibling; + nameList += name + " "; - if (commentGuide && commentGuide.textContent === "Before commenting, read the how to comment guide.") - commentGuide.style.display = "none"; - } - else if (gLoc === "comments") { - commentGuide = document.evaluate('//div[@id="a-index"]/div/h2/a[contains(@href,"/wiki_pages")]/..', document, null, 9, null).singleNodeValue; + tag.addEventListener("click", collapseSidebarToggle, false); + tag.addEventListener("mouseup", collapseSidebarDefaultToggle.bind(null, name), false); + tag.addEventListener("contextmenu", disableEvent, false); - if (commentGuide && commentGuide.textContent === "Before commenting, read the how to comment guide.") - commentGuide.style.display = "none"; + if (collapse && sibling) + sibling.bbbAddClass("bbb-collapsed-sidebar"); } } - if (hide_tag_notice && gLoc === "post") { - var tagGuide = document.evaluate('//section[@id="edit"]/div/p/a[contains(@href,"/howto:tag")]/..', document, null, 9, null).singleNodeValue; - - if (tagGuide && tagGuide.textContent === "Before editing, read the how to tag guide.") - tagGuide.style.display = "none"; + // Clean up potential old section names. + for (i in data) { + if (data.hasOwnProperty(i)) { + if (nameList.indexOf(i.bbbSpacePad()) < 0) { + removedOld = true; + delete data[i]; + } + } } - if (hide_upload_notice && gLoc === "upload") - styles += '#upload-guide-notice {display: none !important;}'; - - if (hide_pool_notice && gLoc === "new pool") { - var poolGuide = document.evaluate('//div[@id="c-new"]/p/a[contains(@href,"/howto:pools")]/..', document, null, 9, null).singleNodeValue; - - if (poolGuide && poolGuide.textContent === "Before creating a pool, read the pool guidelines.") - poolGuide.style.display = "none"; + if (removedOld) { + loadSettings(); + bbb.user.collapse_sidebar_data[dataLoc] = data; + saveSettings(); } - - customStyles.innerHTML = styles; - document.getElementsByTagName("head")[0].appendChild(customStyles); } - function removeTagHeaders() { - // Remove the "copyright", "characters", and "artist" headers in the post sidebar. - if (gLoc === "post") { - var tagList = document.getElementById("tag-list"); + function collapseSidebarToggle(event) { + // Collapse/expand a sidebar section. + var target = event.target; + var sibling = target.nextElementSibling; - if (tagList) - tagList.innerHTML = tagList.innerHTML.replace(/<\/ul>.+?