Created
September 15, 2012 07:15
-
-
Save zixaphir/3726733 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Config = | |
main: | |
Enhancing: | |
'Disable inline 4chan addon': [true, 'Avoid conflicts between 4chan X and 4chan\'s inline extension. <span class=disabledwarning><code>Style</code> is enabled. This option will be enabled regardless of this setting\'s value.</span>'] | |
'404 Redirect': [true, 'Redirect dead threads and images'] | |
'Keybinds': [true, 'Binds actions to keys'] | |
'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'] | |
'File Info Formatting': [true, 'Reformats the file information'] | |
'Comment Expansion': [true, 'Expand too long comments'] | |
'Thread Expansion': [true, 'View all replies'] | |
'Index Navigation': [true, 'Navigate to previous / next thread'] | |
'Rollover': [true, 'Index navigation will fallback to page navigation.'] | |
'Reply Navigation': [false, 'Navigate to top / bottom of thread'] | |
'Style': [true, 'Custom theming and styling options.'] | |
'Check for Updates': [false, 'Check for updated versions of Appchan X'] | |
Filtering: | |
'Anonymize': [false, 'Make everybody anonymous'] | |
'Filter': [true, 'Self-moderation placebo'] | |
'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively'] | |
'Reply Hiding': [true, 'Hide single replies'] | |
'Thread Hiding': [true, 'Hide entire threads'] | |
'Show Stubs': [true, 'Of hidden threads / replies'] | |
Imaging: | |
'Image Auto-Gif': [false, 'Animate gif thumbnails'] | |
'Png Thumbnail Fix': [false, 'Fixes transparent png thumbnails'] | |
'Image Expansion': [true, 'Expand images'] | |
'Image Hover': [false, 'Show full image on mouseover'] | |
'Sauce': [true, 'Add sauce to images'] | |
'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'] | |
'Expand From Current': [false, 'Expand images from current position to thread end.'] | |
'Prefetch': [false, 'Prefetch images.'] | |
Menu: | |
'Menu': [true, 'Add a drop-down menu in posts.'] | |
'Report Link': [true, 'Add a report link to the menu.'] | |
'Delete Link': [true, 'Add post and image deletion links to the menu.'] | |
'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.'] | |
'Archive Link': [true, 'Add an archive link to the menu.'] | |
Monitoring: | |
'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'] | |
'Unread Count': [true, 'Show unread post count in tab title'] | |
'Unread Favicon': [true, 'Show a different favicon when there are unread posts'] | |
'Post in Title': [true, 'Show the op\'s post in the tab title'] | |
'Thread Stats': [true, 'Display reply and image count'] | |
'Thread Watcher': [true, 'Bookmark threads'] | |
'Auto Watch': [true, 'Automatically watch threads that you start'] | |
'Auto Watch Reply': [false, 'Automatically watch threads that you reply to'] | |
Posting: | |
'Quick Reply': [true, 'Reply without leaving the page. <span class=disabledwarning><code>Style</code> is enabled. This option will be enabled regardless of this setting\'s value.</span>'] | |
'Cooldown': [true, 'Prevent "flood detected" errors.'] | |
'Persistent QR': [true, 'The Quick reply won\'t disappear after posting. <span class=disabledwarning><code>Style</code> is enabled. This option will be enabled regardless of this setting\'s value.</span>'] | |
'Auto Hide QR': [false, 'Automatically hide the quick reply when posting. <span class=disabledwarning><code>Style</code> is enabled. This option will be disabled regardless of this setting\'s value.</span>'] | |
'Open Reply in New Tab': [false, 'Open replies in a new tab that are made from the main board.'] | |
'Remember QR size': [false, 'Remember the size of the Quick reply (Firefox only).'] | |
'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.'] | |
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] | |
'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR. <span class=disabledwarning><code>Style</code> is enabled. This option will be disabled regardless of this setting\'s value.</span>'] | |
'Sage on /jp/': [true, 'Uses sage by default on /jp/'] | |
'Markdown': [false, 'Code, italic, bold, italic bold, double struck - `, *, **, ***, ||, respectively. _ can be used instead of *. <span class=warning><code>Markdown</code> is currently blocked server-side, and using it will simply remove markdowned elements from your post.</style>'] | |
Quoting: | |
'Quote Backlinks': [true, 'Add quote backlinks'] | |
'OP Backlinks': [false, 'Add backlinks to the OP'] | |
'Quote Highlighting': [true, 'Highlight the previewed post'] | |
'Quote Inline': [true, 'Show quoted post inline on quote click'] | |
'Quote Preview': [true, 'Show quote content on hover'] | |
'Resurrect Quotes': [true, 'Linkify dead quotes to archives'] | |
'Indicate OP quote': [true, 'Add \'(OP)\' to OP quotes'] | |
'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'] | |
'Forward Hiding': [true, 'Hide original posts of inlined backlinks'] | |
'Quote Threading': [false, 'Thread conversations'] | |
filter: | |
name: [ | |
'# Filter any namefags:' | |
'#/^(?!Anonymous$)/' | |
].join '\n' | |
uniqueid: [ | |
'# Filter a specific ID:' | |
'#/Txhvk1Tl/' | |
].join '\n' | |
tripcode: [ | |
'# Filter any tripfags' | |
'#/^!/' | |
].join '\n' | |
mod: [ | |
'# Set a custom class for mods:' | |
'#/Mod$/;highlight:mod;op:yes' | |
'# Set a custom class for moot:' | |
'#/Admin$/;highlight:moot;op:yes' | |
].join '\n' | |
email: [ | |
'# Filter any e-mails that are not `sage` on /a/ and /jp/:' | |
'#/^(?!sage$)/;boards:a,jp' | |
].join '\n' | |
subject: [ | |
'# Filter Generals on /v/:' | |
'#/general/i;boards:v;op:only' | |
].join '\n' | |
comment: [ | |
'# Filter Stallman copypasta on /g/:' | |
'#/what you\'re refer+ing to as linux/i;boards:g' | |
].join '\n' | |
country: [ | |
'' | |
].join '\n' | |
filename: [ | |
'' | |
].join '\n' | |
dimensions: [ | |
'# Highlight potential wallpapers:' | |
'#/1920x1080/;op:yes;highlight;top:no;boards:w,wg' | |
].join '\n' | |
filesize: [ | |
'' | |
].join '\n' | |
md5: [ | |
'' | |
].join '\n' | |
sauces: [ | |
'http://iqdb.org/?url=$1' | |
'http://www.google.com/searchbyimage?image_url=$1' | |
'#http://tineye.com/search?url=$1' | |
'#http://saucenao.com/search.php?db=999&url=$1' | |
'#http://3d.iqdb.org/?url=$1' | |
'#http://regex.info/exif.cgi?imgurl=$2' | |
'# uploaders:' | |
'#http://imgur.com/upload?url=$2;text:Upload to imgur' | |
'#http://omploader.org/upload?url1=$2;text:Upload to omploader' | |
'# "View Same" in archives:' | |
'#http://archive.foolz.us/_/search/image/$3/;text:View same on foolz' | |
'#http://archive.foolz.us/$4/search/image/$3/;text:View same on foolz /$4/' | |
'#https://archive.installgentoo.net/$4/image/$3;text:View same on installgentoo /$4/' | |
].join '\n' | |
time: '%m/%d/%y(%a)%H:%M' | |
backlink: '>>%id' | |
fileInfo: '%l (%p%s, %r)' | |
favicon: 'ferongr' | |
hotkeys: | |
# QR & Options | |
openQR: ['i', 'Open QR with post number inserted'] | |
openEmptyQR: ['I', 'Open QR without post number inserted'] | |
openOptions: ['ctrl+o', 'Open Options'] | |
close: ['Esc', 'Close Options or QR'] | |
spoiler: ['ctrl+s', 'Quick spoiler tags'] | |
code: ['alt+c', 'Quick code tags'] | |
sageru: ['alt+n', 'Sage keybind'] | |
submit: ['alt+s', 'Submit post'] | |
# Thread related | |
watch: ['w', 'Watch thread'] | |
update: ['u', 'Update now'] | |
unreadCountTo0: ['z', 'Mark thread as read'] | |
threading: ['t', 'Toggle threading'] | |
# Images | |
expandImage: ['m', 'Expand selected image'] | |
expandAllImages: ['M', 'Expand all images'] | |
# Board Navigation | |
zero: ['0', 'Jump to page 0'] | |
nextPage: ['L', 'Jump to the next page'] | |
previousPage: ['H', 'Jump to the previous page'] | |
# Thread Navigation | |
nextThread: ['n', 'See next thread'] | |
previousThread: ['p', 'See previous thread'] | |
expandThread: ['e', 'Expand thread'] | |
openThreadTab: ['o', 'Open thread in new tab'] | |
openThread: ['O', 'Open thread in current tab'] | |
# Reply Navigation | |
nextReply: ['J', 'Select next reply'] | |
previousReply: ['K', 'Select previous reply'] | |
hide: ['x', 'Hide thread'] | |
updater: | |
checkbox: | |
'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'] | |
'Scroll BG': [false, 'Scroll background tabs'] | |
'Verbose': [true, 'Show countdown timer, new post count'] | |
'Auto Update': [true, 'Automatically fetch new posts'] | |
'Interval': 30 | |
style: | |
Dialogs: | |
'Announcements': ['slideout', 'The style of announcements and the ability to hide them.', [ | |
'4chan default', 'slideout', 'hide' | |
]] | |
'Post Form Style': ['tabbed slideout', 'How the post form will sit on the page.', [ | |
'fixed', 'slideout', 'tabbed slideout', 'transparent fade' | |
]] | |
'Slideout Navigation': ['compact', 'How the slideout navigation will be displayed.', [ | |
'compact', 'list', 'hide' | |
]] | |
'Slideout Watcher': [true, 'Adds an icon you can hover over to show the watcher, as opposed to having the watcher always visible.'] | |
'Updater Position': ['top', 'The position of 4chan thread updater', [ | |
'top', 'bottom' | |
]] | |
Navigation: | |
'Boards Navigation': ['sticky top', 'The position of 4chan board navigation', [ | |
'sticky top', 'sticky bottom', 'top', 'hide' | |
]] | |
'Pagination': ['sticky bottom', 'The position of 4chan page navigation', [ | |
'sticky top', 'sticky bottom', 'top', 'bottom', 'on side', 'hide' | |
]] | |
Rice: | |
'Block Ads': [false, 'Block advertisements. It\'s probably better to use AdBlock for this.'] | |
'Checkboxes': ['show', 'Alter checkboxes.', [ | |
'show', 'make checkboxes circular', 'hide', 'do not style checkboxes' | |
]] | |
'Captcha Opacity': ['1.00', 'Transparency of the 4chan Captcha', [ | |
'1.00', '.75', '.50', '.25' | |
]] | |
'Emoji': ['enabled', 'Enable emoji', [ | |
'enabled', 'disable ponies', 'disable' | |
]] | |
'Emoji Position': ['before', 'Position of emoji icons, like sega and neko.', [ | |
'before', 'after' | |
]] | |
'Filtered Backlinks': [true, 'Mark backlinks to filtered posts.'] | |
'Font': ['Calibri', 'The font used by all elements of 4chan.', 'text'] | |
'Font Size': ['12px', 'The font size of posts and various UI. This does not change all font sizes.', 'text'] | |
'Mascots': [false, 'Add a pretty picture of your waifu to the sidebar.'] | |
'Mascots Overlap Posts': [true, 'Mascots overlap threads and posts.'] | |
'Rounded Edges': [true, 'Round the edges of various 4chan elements.'] | |
'Sage Highlighting': ['image', 'Icons or text to highlight saged posts.', [ | |
'text', 'image', 'none' | |
]] | |
'Underline Links': [true, 'Put lines under hyperlinks.'] | |
Layout: | |
'4chan Banner': ['in sidebar', 'The positioning of 4chan\'s image banner.', [ | |
'in sidebar', 'at top', 'hide' | |
]] | |
'Board Logo': ['in sidebar', 'The positioning of the board\'s logo and subtitle.', [ | |
'in sidebar', 'at top', 'hide' | |
]] | |
'Compact Post Form Inputs': [true, 'Use compact inputs on the post form.'] | |
'Expand Post Form Textarea': [true, 'Expands the post form text area when in use.'] | |
'Fit Width Replies': [true, 'Replies fit the entire width of the page.'] | |
'Page Margin': ['fully centered', 'Additional layout options, allowing you to center the page or use additional page margins. Disabling the sidebar will cause this option to affect both sides of the page, essentially centering the page content with all options.', [ | |
'none', 'small', 'medium', 'large', 'fully centered' | |
]] | |
'Reply Spacing': ['small', 'The amount of space between replies.', [ | |
'none', 'small', 'medium', 'large' | |
]] | |
'Sidebar': ['normal', 'Alter the sidebar size. Completely hiding it can cause content to overlap, but with the correct option combinations can create a minimal 4chan layout that has more efficient screen real-estate than vanilla 4chan.', [ | |
'normal', 'large', 'hide' | |
]] | |
theme : 'Yotsuba B' | |
styleenabled : '0' | |
navigation : {} | |
Conf = {} | |
userThemes = {} | |
editTheme = {} | |
userMascots = {} | |
editMascot = {} | |
editMode = false | |
newTheme = false | |
enabledmascots = {} | |
d = document | |
g = {} | |
Emoji = [ | |
['Neko', 'BMAAAARCAMAAAAIRmf1AAACoFBMVEUAAABnUFZoUVddU1T6+PvFwLzn4eFXVlT/+vZpZGCgm5dKU1Cfnpz//flbWljr5uLp5OCalpNZWFb//f3r6+n28ff9+PRaVVH59Pr//vr38vj57/Dp7eyjn5zq8O5aVVJbYV9nVFhjUFRiWFlZVlFgZGOboJzm5uZhamfz9/bt8fDw6+drb26bl5j/8/lkX1z06uldWFS5r61UT0tfWlbDwr3Ew76moqNRTU7Mx8P75OpeY19pWl1XW1qzr6x5eHaLiojv7+1UT0xIU0uzqadVS0nV0MxkZGT5+PPk497///ra29Xq5eFtY2H28e2hnJignJlUUE1dXV2vrqxkY2FkYF/m3d5vZmfDuruhl5aZlJHx8O75+PZWVVP29vT/9fTj3trv6ubh5eRdXFqTkpBOTUtqZmX88/RMQ0T78vPEvr7HwcHDwsDq6ef///3Gx8H++fXEv7tZWVedmZZXXVudnJp0c3FZU1f79fnb1dlXUVVjXWFrZmy8t7359/qLj455e3q4s69vamZjX1zy4+avpaReWFz/+f1NR0vu6Ozp4+f48/lnYmi8ur3Iw7/69fHz7+xbV1SZmJZVUk1ZV1zq5ez++f/c196uqbDn4uj9+P7z7vRVVVXt6ORiXl/OycXHw8CPi4ihoJ5aWF3/+v/k3+axrLOsp67LzMZYU1m2sq9dWF5WUU1WUk/Au7eYlJGqpqObmphYVV749f7p5Or38fPu6OpiXFz38fH79vLz7urv6+hhYF5cWWKal6D//f/Z09Xg29exraqbl5RqaW6kpKTq5uPv7Of/+PDj29D//vP18Ozs5+OloJymoZ1ZVVJZWVlkYF2hnpmblIyspJmVjYKQi4enop5STUlRTUpcWUhqY1BgWT9ZUjhcV1NiXVkkhke3AAAABHRSTlMA5vjapJ+a9wAAAP9JREFUGBk9wA1EAwEAhuHv3dTQAkLiUlJFJWF0QDLFYDRXIMkomBgxNIYxhOk4wwCqQhQjxgxSGIsALFA5BiYbMZHajz1oJlx51sBJpf6Gd3zONcrqm/r1W8ByK0r+XV1LXyOLLnjW6hMGpu0u1IzPSdO17DgrGC6AadrVodGcDQYbhguP6wAvAaC0BRZQalkUQ8UQDz5tAof0XbejOFcV5xiUoCfjj3O/nf0ZbqAMPYmzU18KSDaRQ08qnfw+B2JNdAEQt2O5vctUGjhoIBU4ygPsj2Vh5zYopDK73hsirdkPTwGCbSHpiYFwYVVC/17pCFSBeUmoqwYQuZtWxx+BVEz0LeVKIQAAAABJRU5ErkJggg=='] | |
['Madotsuki', 'BQAAAAPCAMAAADTRh9nAAAALVBMVEUAAAC3iopWLTtWPkHnvqUcBxx5GCZyAAARERGbdXJrRUyGRUyYbY23coZFGDRFGEYfAAAAAXRSTlMAQObYZgAAAGhJREFUeF5Vy1kOQyEMQ1Fshzd12P9y61AixLX4yJFo1cvVUfT23GaflF0HPLln6bhnZVKCcrIWGqpCUcKYSP3JSIRySKTtULPNwMaD8/NC8tsyqsd1hR+6qeqIDHc3LD0B3KdtV1f2A+LJBBIHSgcEAAAAAElFTkSuQmCC'] | |
['Sega', 'CwAAAALBAMAAAD2A3K8AAAAMFBMVEUAAACMjpOChImytLmdnqMrKzDIyM55dnkODQ94foQ7PkXm5Olsb3VUUVVhZmw8Sl6klHLxAAAAAXRSTlMAQObYZgAAANFJREFUGJVjYIACRiUlJUUGDHBk4syTkxQwhO3/rQ/4ZYsuymi3YEFUqAhC4LCJZJGIi1uimKKjk3KysbOxsaMnAwNLyqoopaXhttf2it1anrJqke1pr1DlBAZhicLnM5YXZ4RWlIYoezx0zrjYqG6czCDsYRzxIko6Q/qFaKy0690Ij0MxN8K2MIhJXF+hsfxJxuwdpYGVaUU3Mm5bqgKFOZOFit3Vp23J3pgsqLxFUXpLtlD5bgcGBs45794dn6mkOVFQUOjNmXPPz8ysOcAAANw6SHLtrqolAAAAAElFTkSuQmCC'] | |
['Sakamoto', 'BEAAAAQCAYAAADwMZRfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAxVJREFUOE+Nk19IU1EYwK+GQQTVQ39egh6ibKlzw91z7rn3bvfOmddNszl1bjKXc5rJJGmBUr7Yg9qTD0IalFgRBEYg6EDQQB+GovQyQgiaUZsoLcgHMcr069w7MgcGXfi453zn+37fv3MYZt/n99e76tzVj4JN/hP79fvXnV3hnNabwUBjoOHcgTYOu/JQspgTzsqKgn9BfD4vkWTzur287PqLVy+zM+yePB7KsRXLywTjnSpnZctBkPCdW8ccDuU55vBO8RXbkC/oP5ph19V5+7LIky0OY1BKbZEbLcFSt7u6pN7jLmltCVrr3DV5jY3+KovFEsccB1KJNVpefe10BqS2tqqO4/AuphBB4L/LkrRqNgtJs1lMypLls1kU38mytMLz/E8VIlutqVqX6/weZG52OttRXjbE0cP/FYLRlpVjDXuQ/r77x2XZPKkCHA4HBAIBkCQpAygIAvh8Pu2MZgO0Lz+QSa/sQfwN9RfpVN66XC6Ynp6GhYUFGBwczAC1t7fD0tISxONx6O7upgHILmsqvLcHodOggfiV/v5+SCaT4HQ6IRaLgdfr1bIRRREmJyfBZrNBNBqF+fl5sNsdgE2GiAbp6bmbdbXC7qWQbxMTE7C2tgY6nQ5SqRSEw2ENopaoZpCXlwdTU1NaoECgCbgiU6y8QH+ECYWaTymK7TWdys7MzIwGaWtrg42NDejo6AB1WjU1NZo+FArB2NgYrK6uQrAlCASxn2z6wkuMp87VIAhkE2MEAwMDkEgkYHx8HBYXF0HtkQpRy1BLiEQisLy8rPVNKSsFjEzrXH4+z1hlS4xDhKadNu7t7YPR0VHweDzAEVWfHru6HxkZgeHhYVAURYNjkylVWKArZjjMzqmdVi+QCsLUkQiEjvDvncEkvU7/qQ0Vgukeo48Go87IiCJnZNmipxiz7wXEbVDnbUxQOgM12h9n6qTq6NvapRdtkwaP0XK8RmPuYSbxYfaQ/sJJhjfknuFRURUi7AMOozcCwl94hLZp5F+EioDQVwqYI6jomZU1NFtM+rOSxZjVazcyvwHr/p/Kws1jegAAAABJRU5ErkJggg=='] | |
['Baka', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA0pJREFUOE91k3tI01EUx39JOpA0H4jNx0pbD3XTalISWf8YFlEgldqDsBLLyqjEKBCiLLWiggh6/KEV1WZ7OaelLZvDdDafNW1JFraWe/32+01FrUZ9uy4ylLpw4Z5z7/nc77n3HIqaMRIjZJyEcNX+uFCFeGmI/GZciEIsCFJUTvoAzDz+1y7K76MSwhX5hXl6z+WSbrzU2KB8YEGDwgrTaxZ3b7xHcaHhR3xw7Z5/UviB1ReP5XSg3+TAqYJOxMzWISFIC0GQDomhTVA9skCnsaAwp/vnMq66dBokNuBR9uFd7T9Z1zCunjci0qcRJUVdoJ3DYOhRnC/qBZ+jQbfeCc+37yjY2UEg0iwvJE0k9l8Z+8xqHmTgot0QLdQgTaQFQ2AsOzlHvOu1S5pwOLsHHo8HjHMCq2MazNvTlByKHyrJLDvdR25jMWRxYx5HjeMH2r1BDOOeguRua4OI14jx8a8YH5tA+al3EHKlW6mYOapb2oZBOOwMbEMseAE12L+jjUh3w+VipyAZ65oxn1NP/GMYGR6Ftn4Qsf7qa9S82Y/l/X122G0uL2TbxmZEz1WhXW8mUol8moXu+SCi/OoQ6VsDh3UUwyQ1k9GOaI5MTkX4yWTGHutvgI1F28sviAlRgxeoRm62HvsyW8En9pZ1TYgi6TntoyQtFm86rVgUoJZRvDnKMmXVAGxWmkAYOBwudBqGcHCvHulrGpGT2Uy+z4yT+QYsCXtCUpp8GxbKhx8gDK0ro+KjJGvzdjfDZnN6VdisLD5/JjArQ2zW66PJOj2lEZtStaBphkwah7K6kMJ/GEulp1bMWhAmMbTozOQRaWRtfoZVgjo4iRra4SYgGi26TwjxVeDKhR7Y7U606ixICq9tr7hd7+OthRWL7yUnJ1WPmXotqLhpRICPHCePtuFV6xdUPTAhcWEtRHEqfHpPyto4hPXLXnzflSEJnFaN3OCKDcsFsrEntR9RUmxARLAUgT5iBPuJsXWDBj0dZjRU9yNV+PTbpjTp9OA/pOSk24nRkXf1J462oPxcJ65f6ULlHSMulepRerYDgvj7A0cKpNz/tyTZqbzXO4t0ZZGQJ34RH11lFHIlA8LIqreCCMUZRY3cd2bwL/5/RmjNSXqtAAAAAElFTkSuQmCC'] | |
['Ponyo', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAuNJREFUOE+Nk3tI01EUx39BTytConQTt1am07m5abi5KT5S8z2dj1yOEMUC7aUgIoimlmUEWX9kBZGWaamEmE6U1BI1XNPNGTrnHs33IwuSXrL4NgcJ0mNdOHDh3PPhnPP9XoKwcroJYvMQiRSicHCQKCgUyZC9/T5rNet5KUFs0zCZbZMsFmZ9fTEjEEBDp4/KSSSb/4JoGIyWaTYbiykpWEhOxhSHAzWD0aqkUGhWAcVkW58xlvuPhfh4zItEmOHxYDR3MhcdDaNAsKJydAz5IySKRNjEUmy88vjOVaU8F0iPCqCNjEBHkC/UYaGYFwqxmJoKLYOhkxPElg0QsbNtTlmox9yjRD9UCbnoOR+J/lwRWtOCcdXfDc2BPpg0d7CQlIQZPh9KKlVkAQjJ2x2zmOSsQu7hpzUJfBhLjsNQmADjxcT10Bcl4rE4EHc5LjBEhEPn7f1WTqXSLQB/s1Tp7vslsoIkyPPiMJAbi86McBguiaHKjoEqR4jJy2K0nAxApzMN5iUGrclrKVaz2fUvuF4tRbxDKA90w5VjTFyLZKHpTBSq4/1QnxGB2qxoVIZx0JopRCPHFSNOThfWZzfrXDcZEowH4iA05ATg68hDtBaL0HAuCm3lJ9Bfcx2fFNUoi/DCjRgfNHHd1wCZA2TyXjNkE6F0cBDpPFiojeNi8EkJdFoN3vXch0nbBJOhDd907dANv8JITxNqziag3ZsJbUDAwLin50Q9QWwl1qSYoNOVvUcOoqOqAAa9Fu9H2/F9+B5WZLcwOyxFX18flLI+VASyMGVeoJHD+Tzq5BS1PoaKRrNT8127P74swsq4FCa9FKvqBqwaOiz3hdEuLKueYSyECT2LNW0eIfo3E/WmEbvnG1MUJnWdpWhDGDvxQXZHo+RR0uW2tnv+auPX+TvtJm7zKpaen/4y2yjBUlcxlvtvmvT16ZWDpQeoVv3/60F/NrHjTf4ugazIXtJ8ivjnz/sJ+yGQRjcqUdIAAAAASUVORK5CYII='] | |
['Rabite', 'BIAAAAQCAYAAAAbBi9cAAAD/0lEQVR4Xl2MXUxbdQDFz/9+9Lb3tkBLCxTKhzgoOOZAsokbJmZxDFHnd+LL4hKVzBgfNCY++ODbjDEaZowvErOM6HRu6hKZY2rIAOkCY4OSDTpFaAsrlJa2t5+39+NvjT7tnJzknIfzI98Nf/C6TuXdguWBd1q9rcb8/CwsZiu2Ywm4nDVo3VWLZCKDaDwJq9mCg31PgjAMKKUwmcyYvTbek9iJRDm6M/XswEDjwNz6plWW6wdZhjUAintFCEEhn0N04zYskljaDLaj8ar49oUrsYR6mrFJNj322w46H8y+mitM/ZJKZmyE4XAvjJSsazpyuSzslVZIkgWKOvvRgQ6Xrdlhqmds7o7bFZoLkctreKxf7GtuCE7IyUQjBQcQ8j/lvxCGQJZz0IoCVpamTtzfIh9nwiaIrCQyjNg8mq11oDLUhNXRJfT1Ozr3tS/PqpnQ80qRgjAmKIqBfK4ItbSLKoOZqR/6neLkENlSUAIhlktvEf+sD2rkm8nWTHtvZCGMVON1ePuaoBER31/MXGly1wSqq9Uug6FluYyWXJiPqFXmjd4Dh9oF9ZKKimYXRtYCx8lmMIDIxlIPGz591av0mtanF7FcCEN6iMXeox2wOJ0QJAmUAoRQaIqCnWAQY1/ewKNGNeQuYXkm0d2NC2e+wvmRr/Hx+6+8PHayrbDyyQBNDb9As3PHKDWG6MTM23RoeJAWsqeoWvyUUv0UHf7pBB0fe4OeeXe3/vmHbx3+8dwIGJ4IsFpMMFe0fbtAn+nwZePr1u4MBK8XIALG/Rt479wYrs2vgeNNAMNgMbiNzybuoKVvn+Gs9kbr6qpBfJfGYHFIkJUCoGwfqcoMX/b27EGhwgOjoCADDlP+CA51ugFFRzoB8FYNaQ1oqKD44+eNL+wNj7zJGQSIhe8+jgQ9thk+27v/KRY6L4FSCkVOwtlQj6P73Qgt/o1ERoKt4iUkE7+jrZMHyzIoK9cOBFfT4LbWAk+0a7ZLnvqHcTNdACgFScfAcjxEdy00VQclHGo7dqGeYxHbvIo6hwhSghCehb3G5p6eW7VxXC5/xGWToMgrKKoaCnIalI9CIARasQAqloMI/x4BWrLLYwE1AEPTwCGHaGjz7pw/leZUNV8wNm9BLy6CxsvxZ1kMbaY4TKIIXlNBsynoVjvAC4CuAoYOVi+CMfLYCUfg95tPHuzZB0YtKzsb58RMucWE/fZmhCbdOP9rNnLnxko6GVoB8lFwyVVw8b/AyeulHoJyN4Rb19dTFyeqBlu6njvfsWcvOJvLs7DMmw/7bvpeE4pU2OIcgcqmp4fGAgt2Txwvqr7lTp5V7LquZxXC6+BqEvGcY5pyjaM1tffJbk89NE3FP5VQ6y7a+paZAAAAAElFTkSuQmCC'] | |
['Arch', 'BAAAAAQCAMAAAAoLQ9TAAABCFBMVEUAAAAA//8rqtVAqtUQj88tpdIYks46otwVldUbktEaldMjldM2qNcXk9IWktQZkdIYlc8mnNUXlNEZktEZlNIYktIWlNMXktE7o9klmdMXktFHqdkXk9EWk9EYk9IlmtQXlNEXktAWk9AWlNEYlNFDptkZldMYk9E4otg/p9kXktEXk9AXlNA4otclmdQXk9IYktEXlNEwn9YXk9IXk9FFp9o3otgXk9FPrdwXk9E2otdCptkXk9E/ptkcldIXk9Edl9IXk9EjmdUXk9EXk9EXk9EbldIcldIjmdMmmtQsndUvntYyn9YyoNYzoNc0odc1odc2odc6pNg7pNg9pdlDp9pJqttOrdzlYlFbAAAARXRSTlMAAQYMEBEVFhgcHR0mLS8zNTY3PT4/RU1kdXp6e3+Cg4WIiYqMjZGXl5mbnqSnrbS3zMzV3OPk7Ozv8fT29vf4+fz8/f7SyXIjAAAAmUlEQVR4XlXI1WLCUBQF0YM3SHB3a1B3l7Bx1///E6ANkDtva0jKbCW2XIH1z2hiZEZ4uUgxo7JedTQye/KN/Sb5tbJ+7V9OXd1n+O+38257TL+tah3mADAwSMM7wzQWF4Hff6ubQIZIAIb6vxEF4CZyATXhZa4HwEnEA+2QgoiyQDnIEWkjVSBBZBqXbCRlKYo8+Rwkyx54AOYfFe7HhFa7AAAAAElFTkSuQmCC'] | |
['CentOS', 'BAAAAAQCAMAAAAoLQ9TAAAB5lBMVEUAAADy8tng4Ovs9tnk5O3c7bX44LLduNO1tdDh7r/eutj43q2kocX23az07N+qqsvUqcmXl7331ZXJj7r40o/Pn8T42qP63KjNw9n21p3Y387Ml7732JzR55z05MSxtMLGn8TC4Hx8eqt8e62Af6/B4HnG4oPC4HzH44fBf7LCgbOkoMTcsrmtn8PWqcfFtKrj4Jvs2ZOz2FnMqLXT3KfY5p60Z6NUU5XRuqHzwWSywqDn3JaiiLWahrWhkry5zJjRmqm1Z6P1wmb1y319fK632mK5cKi5nH+73Gu73Gy73W283W+9eK17e6y1yZS3aqRZWJdcW5ldXJplXZppaKBwb6VwcKV5eKswL306OYNPTpGkfK+m0kGpUJWq1EnEqIuXK3+Xh7ahP4qhkryMfK6BgK+CdpGMaKKMa6O9ea2+eq6+oYW/eq+NbqWVlL2Wlr7AjanA4HnA4HrBkqbBlafB33rCgbLCmKjCxIzC1mSs1UytV5mtxIWt1lCuz2evWpuvXJywxYzHjrvH4oXIjrrN2HXO5pTO5pXUlYnUlYvVl5Hb0G7e0XTg03rhr5fpzHPpzXTp0Hvtz3/wrDHytknyt0zyuE3yuVHzvVr0wGP1x3T1yHf1yXe0ZaL2zYP30o730pD31ZeRIcF5AAAAQ3RSTlMAFBkbHEhJS0xMTk5UWWBsd4SEiIiPkJCVlZaam6CjpK29wMPDxMTFxcnK193e3+Dg4uTn5+fo6e/v8/P4+fn7/P7+J4XBAAAAAOBJREFUeF5Vj1OvAwEYBb/yGlu717atLW0b17Zt2/6nze42TTpvMw8nOZCAmwUpiIY6c5IiLi9tPX64GairqszHQ4X2VB64v1Cs6PxMPJSdHM777s6/jyaMRGiRLyyrb88OpjZ3CzAXrm1sqzSNNeN7kVBPNgB7cG51abE5l9cXDces7emQ1uadHhutFUg6gpPKkSIqQGavwz7r7O/+/3t/rSdjI9XDM3qz4fr3B/3iA0aJTG9x71+9oR/PLDwUe2wm19bly+fTIxHyEETatbPewGEw6Mk/tKZCEqSQQUlIHB/QNBEjjVN1AAAAAElFTkSuQmCC'] | |
['Debian', 'A0AAAAQCAYAAADNo/U5AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAZ5JREFUOE+Nkk0oBHEYxv8fu5GQj3JwcaDkIAc5IpR87M7MKnIVJVKclaIQ5Sy5OLkgR7n5OigcSNpmd2c2Vyfl4KT8/muWiVU79TTv+7zv837NCBF6PG1X+NpZyEYSD9mIc+tHnBPe23B9xKrCuTmbQA/JKfABrhBswa1hH4A38IwfOxPdX1qcjiCQxO5NyrjKV70TnSbeRPwJvGN3i4yyqnEucPY8ZZX9GSEgGK+RvFfyjk2VKZxzBNG8wJWWgh/xtDOeUXZ7Slr6TrSLYL9N4SMgYTTcwdc2ArvJcElhSVcM6mCNSV8n9hA59yTU5UWMG6HIbLhIWlglgWiC2L4Z79qTdo40D6ISuOWwKCWHyk9Fv8ldpUHOuGTuynwSBUynddPdlbEosVpP9Eu4FnOsRzUYNTsdmZN/d5LDiqM0w+2CMdAFFsFGWgfXxZnheqe/z+0puwEM0HHYV3Z9Sgz8TEz7GkQvpuJ/36ggj2AaHLrSlkULWV5x+h2E8xkZL16YVjGNaAUscfZ/f6c/k9ywLKI2MMcRWl0RLy007idmRbQJ7RIfDAAAAABJRU5ErkJggg=='] | |
['Fedora', 'BAAAAAQCAMAAAAoLQ9TAAABPlBMVEUAAAApQXIpQXIpQXIqQ3UpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIpQXIqQ3QpQXIpQXIqRHYpQXIpQXIqQ3QqRHYpQXI8brT///8uTYMpQnM5Zqg5ZqnS1+I4ZaY4ZactSn8uRnYrQ3MrRXgsRHUsR3s8bbM8brMtSX4wUosxVI01XZw2X50vUIguToQvR3c6X5o6aKs6aq08Un8qQnM9VIFDWINJXohKcKlXapEqQ3UvUIc2X55bhcBdcJVgcpdhfapmd5tuk8dxgqJ1hKR5jbB6iah/m8Shudq3v9C4wNG/x9bFy9nFzNnFzNrIz9zK0NzK0t/O2+3P1eA2YaDU2eTb3+jb4Oje4urj6fHm6e/s7/Tz9fj3+fz7/P38/f3+/v83YaEa/NNxAAAAHnRSTlMABAoVGyY1SVlpeIuQsLfDzdHW4+3y8/b39/n6+vr4+ns8AAAAyklEQVR4XiWN5XrDMAxF75KOknYdZJS0klNmHjMzMzO9/wvMcH7I37mSJShsJ+5NjMT6umDoHyXDcI/2qJadh++P3cle1de+9yPe3/bTY92wzfzr7wGtP3JrAI72BZGVtcAdQlwHy+JS1pDbBE9qamZF3BYrjQxPEXwKc6dC8bXFm0QIpmt8kn0Rn093q82UCtK8oXZckwFJzuulV8bHkajPyXdbnJnARfDHs0trz+JQ+5AFvzp/L0+cL2qPAINUPrq5OC6p/64F/AMnrST+Dq/r7QAAAABJRU5ErkJggg=='] | |
['FreeBSD', 'BAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0QA/wD/AP+gvaeTAAADXklEQVQYGQXBS2wUZQDA8f83j33M9rF9d7u4loaWklaDpkSo9KDGaIKUaGxshD2YSPRiuDVeTDyhBxosJCoa40ktpAkPDcUqAYVIpUSUPrAulEdD2bbb7e7ObGcfM/P5+4kwKDvq6yJ1FYYcvb+YAkqAHo/HQ7FYrFIoCiurq9ZXJ06YSOkA+kBzfX06bys3zHxS9EL0tXDVyZfefacqV+X/ZSJx5+qLbx98LhaL9RiGEZWlEsWC/Thd9q6Pf3vs2u6Orc83rFsvTwwfLf5obgywT1Vjh2Hh+rbNsnTssJdNLedK5aIrpSuldKVXKsnH4+Pyn6FDXn5tMef9O+3NvdkvP1V4+EYw2AoQ+KSx8dRYS6NXXnwovaItXduSrrkinWxGOmZWJi9OyOK9m1LmsjIz9IH8QUMOd3WfAQwNKCy2tJwbHB5+XasPaxIHmc4g7WWEZ1MquBiRFlJTf1E7+Tl/H/8asavPzTY1nWd2ZkMDRPeBeHPz5ojwsilEQCBvTSKunCF3M8FSNkBGVTHDYYrLj8jVNhDZ2SMa2zo3MTamaIC/u6Ojr3DtrOrvP0BpdATnyBeIhTxpR5ABUlKSUlXS1dWstbVxdz6hPL0l1quGqkLaKwNvVcjEXNRd/4mit4Z19DjefBEPyCKxgQJQcF28dBrHNDGTSZSezsjeff0hraa2Vs2vrvit81O4vj9xLJcC4ADrQA7YAGqBGsAql/EtLdFQE/L7dF1XZmdnSrbPMJfXoLDmolQK8gJyQBowgQhQDRQBD+hsraVhd4e5MH+/oExfvWLJ9q3/3S7qMpNH2hsS40kFS4EUUAMA2IANRIBXv4uzuO67c2PykqkA5YmZ6bN18YPi0Yoknxc4AsJPCMLVAk2BLKDosCWqs/PZaulkuxk9fekcUBAAQGDks5FT0W++3NuYuC0DVUL4DIEdlIQDAj0IRkigaMjArkFx0tf523sffrQHyKsAgHPhwoXLL+yP9/kePNhk5ExUTyKFkJVAUAiCFZrQup4Rv9ftuLV/6ONBYBVABQAArMvJ5MXW7duD6P62sD8UrPAFRU1TpeCpCnGvPZr7WW///v0jpw+VC9ZdAAABAAAAAMLo7drWrmQyPWG/r8tnaGIjaM05ujr16x/ZBFh5AACA/wGZnIuw4Z4A3AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMi0wNy0wNFQxMDowOTo0OS0wNDowMOPVpFwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTItMDctMDRUMTA6MDk6NDktMDQ6MDCSiBzgAAAAAElFTkSuQmCC'] | |
['Gentoo', 'BAAAAAQCAMAAAAoLQ9TAAAB9VBMVEUAAAD///+AgICqqv+AgIC/v9+Ojqqii9GAgKptYZKQkOmPj/ddUYBgW4eVjeCTgfiWjO5wbJaZkvPBvepkXomYkNldV4Bzbpl6dJ+Uj7ynoO6Vi+1qZI63se2mnudjXYjOy+GCfaqZjvWlm/Pc2e+Oh7NeWIOWjfeXjeW1sd+gl+diXIfp5/KHgKnn5/F2cZx6c6ZgWoXc2e6dltrAvNu0scrX1eTOyujCvup4c5qpovVpY43///+6uPPJyPXq6fvm5vrz8/z8/P7+/v/d3PixqvmxrPSyrfe0sPO0sfS3tMve2/3r6vy6ufPz8/3d3fi3tM63tPO4tsu5tsu5tvO6tfe6t/Vva5KRjKy7tvW7t/W9vPO/vM+/vvPCwfPEw/TFwvTFxOfGxfTGxvTHxvTIx/TJx/aTiOrNzPXNzfXQzfnRzuHS0fbS0vbT0uHU0e/U0uTU0/bW0+zW1ffX1vfY1/jZ2Pjb2/jc2uSTiemVkLSlnvbe3PTe3vng3fzg3f3g4Pnh4Pnh4fri4enj4/nk5Prl5Prm4/ymn/bn5vro5/rp6O/p6funoPWsqs3t7Pvt7fXv7vzv7v3w7/nx7/3y8f3y8v3z8vytqPWuqPX09P319P319P719f339v739/34+P35+f37+/+uqev9/f6vqvSwrPQAR0dcAAAAPHRSTlMAAQIDBAgJCwwVFyAsNUFHSVBneH+Bh4mVmZmanKCxsrK2tr3ExtDW19rb4ODl5u3t7u/w8/T6+/z9/f4MkNJ1AAAA8ElEQVR4XjXNw5aDURSE0YrRtm3b54+dtm3btm3bz9k3Wek9+2pSYFwT8ibzE93hwAtdJqK3nZo4J9hFXbP+vFHOthV6gnGzstZq94wdCs4UCCDymQ2v7X0LdYoSQ0MIENRYzJbRlPTTHu73ZNAL8vivmVui98PpzuqffX0mIPHJGtOQenukteJ+aS3b9htNpDnT9TeZH1bHAwBRMhGpd6e6uNrLoRgxBKmsX47nBlp678ojpEA40fejcmW4e/No0V8IIPfj6eKgbEJ3ZUnzgE1OqWp9Q3VeWRAsg51f1dZ8c31RmAsc+N5JGbG+zvj3BzDCPrzMDC9SAAAAAElFTkSuQmCC'] | |
['Mint', 'BAAAAAQCAMAAAAoLQ9TAAACVVBMVEUAAADh4eEAAAAAAAAAAAAAAAAAAAAsLCyXl5dgYGCnp6eTk5N3d3fBwcGqqqq8vLzNzc3Ozs7Ozs7Pz8/Pz9DQ0NHR0dLS0tLS0tPT09Pf3t/Pz8/i4eLb29vZ2drZ2tna2dra2trf3t/u7O/u7e/u7O/r6+vt7O/w7/Lw8PDy8fTz8fXz8fbx8fHz8/P19fb49/j49/n6+vuPxlmWyGOx437h9NDr9eD6/fj////+/v75/vTA5Jv6/fb7/fnL5bDL5q+AxjeDxUCEzTyGxUaGzjyHxkiHzz6J0D+Kxk6K0kCLyE2M00WNy06P00mSz1OUyF+W2FGX1FiY0F6Z02CZ21ac0Wiez2yfz2+f2mOh4GCi4GOi4WKi4mOk12+k3Wul32um1Hin0nun4G6n5Gin5Wmo23Op2Huq1n+q43Cr526s4Hit23+v6XSw34Cw34Gw6nWx4IKy4IOy44Cy63ez146z34az4IWz4YW03Y217nu38H2625e645G74pK83pu98Iq984W+4ZjA4px0tzDA5ZrB8ZDC5p7D55/E947F6KHF+JHH4qvH6qTI46/K5LLL5LN1tzLL5bN1uTDL57DM5bPM6qzM66/N5rTP6LbP6bTR6rfS573T67vT7LrV7r3X68XX7MHX773Y77/Y9rvZ8cHa7cjd88bi88/j8tTk8djk9tHm8trn89vo89zo9N3p9N3p9d7p9tvq9d/s+93s/dzy+erz+O73+vT4/PX5/fT5/fX5/vN1uzB3vTD6/ff6/fh5uTj8/fv9/vr9/vx8wjV/xDmrMRH0AAAAOXRSTlMAAAECAwQJDzk/RUlNU3F0kpSVlpeYmpucnaKjpKWqqqqtu8LExMTEzdTU1NXY4evy8vP+/v7+/v6LaR1mAAABD0lEQVR4XiXI03bEABAA0KltW9kaW3eSZW3btm3btm3b/q4mp/fxgqKOtpamhrqaqoqykrQYABh+PVMU9fjE5Xp8o54kgPHN0EBHU2N5YXZykiua0HHd2759VF2Sk5IYE5GGsmCEWLV1kVWwt5O+3x/qpgsy8k4ja+cJl2/v5C22tlgCAHtw9TQSa4s+AzfPSm0BRNl9SydhWJzLC567KrNhgrNwHIJ5qTz/2f9w7Jw/DNqIjVr04exW0AEOXcN3Ab7enr9eDW2VTJgehONyc2Z8XP5YdD0Tcuhcc4/r45OjGX51TEjYPbh8THRPvbz+CHusgSZlT7rP8PkCwfQKaQUi9Igr6JsRBMFiWZgb/AHKElRzKopZJQAAAABJRU5ErkJggg=='] | |
['osx', 'BAAAAAQCAMAAAAoLQ9TAAABrVBMVEUAAAD///////+qqqr///+ZmZn///+qqqqAgID///////+tra339/eAgICoqKjx8fGMjIzm5ubh4eGPj4/g4ODIyMiAgICSkpKLi4vS1tbPz8+Xl5eMjIypqanIyMjW1tZ2dnbR0dGamprFxcV3d3d+fn60tbV3d3dcXFx3d3epqal7fHxxcXF+foCnp6hYWFhyc3Ojo6SMjI5fX196enp+fn6Li4xERERqamqgoKFpaWmFhoeen6A/Pz9QUFCWlpeSk5SUlZWUlZaOjo+Tk5RHR0cuLi5YWFgwMDAeHh40NDQ3Nzc6OjpcXF1rbG0XFxdSU1NVVVVXV1dZWVlbW1tnZ2lwcHABAQEEBAQXFxchISI+P0BISUpaW1xHR0kNDg4qKyszNDU1NTY9Pj8NDQ1cXF4XFxhSU1QSEhIDAwMrKywtLS4uLi4wMDFHSElISEggISE0NDVJSktNTU1FRUVWVlhGRkYEBAVBQUE0NTZQUVJQUVMFBQUqKitWV1lXV1daWlpaWlw+Pj8bGxtcXV9dXV1fX19fYGFgYGBkZGRlZmhpaWlsbGxwcHB2dna844Y9AAAAV3RSTlMAAQIDAwUFBggMDhkeICMkKCgqMDIzPj9ERFBib4CCg4iMjZCcnp+jqamrw83W1tvb3ePl6Ojp6+vs7u7v8PHy9PT09PT3+vr7/f39/f39/v7+/v7+/v50ou7NAAAA30lEQVR4XkXIY3vDYABG4SepMdq2bRSz/capzdm2fvOuDO397Rw0Ly4tz2QAQPbcxuZ2E/STJwfxPhWgG355fRrVAIVb1zeP9UDLfiSwkAcADe8fn7tFxWuEXFRDoer/OgoMTRBCumj8yJwPBo8Zhpk14U856/HI8n0ZUtpZ1udrSzfVneA4roNKjdrwpcMRilb8d8G60+lKnrpWcn9bO+B23w2O8Tzfq4aiNSZJqzn5O4Kw16h06fPZ+VUlUHfo97+VAEb7rSh2UgDd4/U+TBlQY7FMj5gBIGvcarVVfQPVPTG94D0j9QAAAABJRU5ErkJggg=='] | |
['Rhel', 'BAAAAAQCAMAAAAoLQ9TAAABj1BMVEUAAAD///////8AAAD///////8AAAD///8AAAD///////8AAAD///8AAAD+/v4AAAAAAAAAAAArKysAAAD///////8AAAAAAAAAAAAAAAD///8AAAAAAAAAAAD///8AAAD///8AAAAAAAAAAAAAAAB5eXn+/v5JSUnKysrS0tJ5eXmqqqqxsrL+/v4ZCgknJyeHh4eIiIjo6OgZCAdOTk7t7e3///8GCwwPAAArKyv19fX29vb9/f0EAAD////+/v4AAAAGBgYHAAAJAAAMAAANAQAPAQAVAQFyCQV9fX2pIRzmEQjn5+cBAAAFAAAAAADnEQjvEgn////uEQjyEgnsEQjzEgnxEgljBwPaEAj9EwnwEglHBQJHBQNNBQIBAAB3CQR5CQSHCgWLCgWRCgWTCwadDAWmDAapDAa/DgfKDwjWEAgGAADh4eHiEQjmEQjmEQkKAADoEQgLAQDtEQgMAQDuEQnvEQjvEQkPAQAfAgEuAwEvAwE8BAL1Egn3Egn4Egn6Egk+BAL+/v5CBQJrB0muAAAAT3RSTlMAAAMEBAkYGhsbMTRLUmpvcHeIjLe6vcHCxM3P0NbW3Ojp6u/w9ff5+fn6+vr6+/v7+/v8/Pz9/f39/f39/f7+/v7+/v7+/v7+/v7+/v7+Q8UoNAAAAO5JREFUeF4tiwVPA0EYRL9SXIsWl+LuxfcOd2Z3764quLu788NZNrxkksmbDP2R7vH6GioLs+iffEzNXd4+TqPErUUpVqMOvwgdzMPn1rv5vPsVeufBTaBK/bH2FPvkEUuIG5jIIc+sHYn/HJ3dC/Hxuo4y8s44dzwBbFkisHN8bVIdXs6jb+H97aCwbHEIqgcml64CD7YllNkAVQC940MLYe5YzvIeQAXNrd19Roc5MdzfdQLUUKaUYyuG9I8y1g4gj6hIak4X5cBIT2MquZJrJdOqpY11ZpAiqVwbY/C7KY1cRCrZxX4pWXVuiuq/hs49kg4OyP4AAAAASUVORK5CYII='] | |
['Sabayon', 'BAAAAAQCAMAAAAoLQ9TAAABvFBMVEUAAAAcUaYdVKwAAAAAAAUABAwWRY4YSZYhZtIhaNYHDx0KCgoFDBcKCgoRMmYSNm0fXL0fXb8AAAAYS5gaTp8fXLwgXsEGBgYFBQUZSpgZTZ4JFSgODg4IEiIOJkwOKVIkW7EnXbQLGzUTExMKGC8LHjwMIkITExMiIiIPEBEPJ00QEhMXOXAaPncOJEgoXbApXbEcHBwwMDAEAgAfHRgQDgo3NC8AAAAHBwcKCgoLCwsJCQkaGhofHx8lJSUwMDA0NDQ4ODiRkZEICQocHBweHh4GBgYHCg8mJiYnJycpKSkrKystLS0uLi4ICAgODg43NzcRERF1dXUUFBSjo6O1tbUbGxsEBAMLGS8MDA0iIiIjIyMkJCQNDQ0NHTYKCQkoKCgPDw8QEBArMDkKCgkRERIREhMxMTEyMjISIz00Njk1NTU2NjYCAgIVFRU5OTo5P0c8PD0+Pj4/QURAQEBHR0dKSkpMTExSUlJiYmJlZWVnZ2cWFhZ2dnZ4eHh8fHx9fX2FhYUXFxeVlZWXl5eYmJiZmZmcnJwZGRmlpaWrq6usrKyvr68KFiq/v7/FxcXY2Nji4uLn5+ft7e0yif9uAAAAN3RSTlMAAAApKSkqKioqg4OEhISEhoa1tra3t7y9vr7S09PT09TU+Pj5+fn5+/v7+/v7+/v7/v7+/v7+70RY/wAAAPpJREFUeF4dyWNjw2AUBeC7dfYyorM6rx1exKltzLZt2/rDa/J8OgBVVlFDX39jcTZoUqCse251a2dvu6ccUtWlanLQ4Vpel+ThlWq1l3wEz58tx4dOt1dMlAJk9A5gMjG75LHwo46hzkwosGOMbejumoRvubC9EOrMviT0E0Us9fvN9dA6zxJCNv6+ECGsb6oNWsgmpZT9/UTUZo3Em6AW34guTL4jiAudiCM1kLcw8/SmHERfT1/eueBiDqR1GK1n9w+K8nglxYxd6QAML4ztXoQuj8YFgWcgqdJp8qzty26vaboCNIxBCshyQDKov0aXr29v1ufq1PwPx5Q7bCoh6eoAAAAASUVORK5CYII='] | |
['Slackware', 'BAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AcEDi0qZWWDgAAAAx1JREFUOMt9kktoXHUchb/ffc1M7rySSdJMOknFPMRitLgoNKKI8ZHGKkgrjU8SitidimSh2UkXoQmoO1dGQSxJjdvOtqSaqlR0USEGSjVJGxuSmWR6M3fu4/93YX0g4rc9HA6cc4Q7DI+fpzz7PA8++2mxvZAeBZ4xhHtFcJRmXWsWvb36/OLcyxf5B/KHeYHy7DmGx1+YSDjmWTdlobTGMAStQGkNoLXS4tXDq7u7tUcWz49tA8jR8QUuzB5n5NTCV13F9JEo1JJwTLKuzU61QiOMcd0UDb+BncwQK3Rl15eNja3ui/Njq8aF2eMcO/XlBz0H8oO2ZUkum6A13WB99TtyzXlaCi24SaFa+ZFCzsG2DNnfkdbFjsI1APPhk+d6ujqznycdCxFozadYWvyMpx47wa+bPkGksKwUNnsk3TaCGASRXDZh5LpHXPPg4Rcni+3uYBxrtBbQghlscOVKmYHeEm0ZIZ9xyLffw41ND6VAa43SmjiMByzHYtjzwr9arfshxf5jOKlvKZfn8es77N2uks24PPfSFD/9Uvt7AtPKWmEU9d645eHYJo5tcKi/FX/zG+zmQxQH+rANk862DOW5N/hhaY64cJSa5xNFCgDDILZACMKYWAmh73HmzFsMlBQJ06LeiMinE1S3KzRCm5rXIIoUIoKIYCVM36urZFbEoiBLNMIhAE6/NsSB7h6SKZdL8xsUOnpx9j1KbTdARACIowArYe1ergfNT2i0mIbJys0GI6PT3N1/hJvrPxOFdRJNBQIy/FapI4Bpgohgcjuw+jq8jy8tV55MNBWI4ohS802CpizKv8q+FgALZAfYgSyAZtNro1oLaU1VvxCA029Oraxs7u/tKnXiNjn8HyKwur6lI++6vPK4V7IA7u+1Dyu1tr183ddNbkHuXP8/zEIYeFqiLRl6YO/p0bHJdflT/PD9qZa1W+ry99fcvlAlcZwUpuUAglIRYVgnDEIOlna4q0M/NPnuO1/PzMwg/045O/XeibUt5/Xangx6viSVFpK2jtMpvdyWCz+5ryf10clX3/amp6eZmJjgd441URWWJY8BAAAAAElFTkSuQmCC'] | |
['Trisquel', 'BAAAAAQCAMAAAAoLQ9TAAABjFBMVEX///8AAAAAAAAAAAAAADMAAGYAAAAAHFUAGWYAF10AImYAIGAAHloAHGMAKGsAGmYAJmYAJGEAKnUAJ1gAMXYAJnEAJGQAI2EAK28AK3cAGTEAMHgALXEALXgALG0AFUAAI2oAK3EAMngANoYALXMANIAAM4IANIIAL3gANIcANokANoQANYQAOY0ANIYANooAN4kAN40AOY0APZMANIUAOY0AO5AAPZUAPJAAP5MAPpQAQJUAOYsAPpYANoUAPpoAPpUAM4AAQJkAPZIAPJEAQpgAN4cAPpQAPZUAPJEAO4oAOosAOo8AQJoAOYsAO44AQpsAO48AQp0AP5UAQpoARJwAQ58ARaAAQZgAQ54AQ50AQpgARaIARqMARaMARaIAR6QARaIARaEASakARKEAR6MASqsARKEASKcAR6MARqYAR6UATbEATa8ARqUARKAAR6oARqMASKgATK8AR6QATbIATbAASq0AR6cASKgASqwAR6UASKcATa8ASqoASqwAS6wASKoAS60ATbHn4CTpAAAAhHRSTlMAAQIFBQUGCQoLDxAREhMUFBUYGhobHB0eHh8gIiIjJCQkJCYoLC0xMTE0NDo6Oz1BQUNHSUxOVFVVVldaWl5iY2RkZWZoamtsb3FycnR1ent9f4KDhIiJioyNkJGYm5+foqOkpqamqKmqrKytsLKzs7e4uLy8v8TFxcXGx8rO0NXY2eZc4XYcAAAA00lEQVR4XkWN1VoCUQAG/3NWtwh7CTsQJOyk7BaDxuxA6bbrxf32gt25m7kZqDRYxziooDV7+1AalMUavQh2AsEZoWvzigLun+T17/c8QiJZ7qu2QKiNmyZthdcR1/as353jIeU1GxMHo5XHdqPFeX8IaDMdHPYN6dRN7LR4qQewdTa35HWkyh+fbxERAMjwlAWJv3CPSKDQ+H7XvHdkV4Pua3Gtm4sPKIF/WV8dop4VKBw/NU33B3x1JbTt+XwhkJQoqRfWvHOy28uqH8JIdomR/R+s9yR3Cso77AAAAABJRU5ErkJggg=='] | |
['Ubuntu', 'BAAAAAQCAMAAAAoLQ9TAAABKVBMVEX////ojFzplGf1zbnqnHLvs5P10b3yuZv1xKrytZXvtJXys5LysI32waT0n3HxiVHwg0jxhk31kFn0h0zxf0P0hUrveTv2iU3yfkD1hEfyejv5eDLybSX0aR7zZxvyayH6ZxnxZBj4YhH7XAb5WALlUQLeTwHgUAHeTgHfTwD65NzdTQDdTQHdTgD31MfcTgLcTADcTQD////xt5/31Mf54dfmfE/dUAbeVQ/jcUDcTgHeWBnnflHohFvpjGbqkGztnX342Mz53dLgXiP65d399PHdUgrtoYLyu6Xzvaf76eLfXB/rkm/fWhvupojwrpTeVhTgYSfgYynzwa30xbL1ybnngFT31snngljhZS3539XhZzDiajbibDn77OX88Ovrl3X99vTjbz1fisGCAAAAMHRSTlMABgYGBwcHJiorMDA1NXGHjY2Nl5mZmZyfn6O5u8XHzc3X193j9fj4+vr6/f39/f08OUojAAAAx0lEQVR4Xi3HZVbDYBhGwQctWqzFPXiQ+36pu+LubvtfBKcN82/UEhld2vWXxyL6F92gbTPabse8hU/uHMx1SZoyyJWPTwq1Rs7GpYE9+Cg+OJcs1MHvU9y4fnrN31yUm18vMCIPjtw3QMndw4rs8ieVzAAcBlewpe1KM3uaBuD3Dda1BhWXAsi6AFY1a2SqifxZ+rnxWYcJDRkUS3fO1R5vwe+XZgw4D4L3RAJiknoXCVX3WeiUpJ5pIxTvVmg45pl5k4Ot/AGV2iqZBWgJJAAAAABJRU5ErkJggg=='] | |
['Windows', 'BIAAAAQCAYAAAAbBi9cAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA+pJREFUOE+F0n84FHYcB3CWSsL9ojo/6ik64c6PnTjmSS0limmrpBm2G002y++xzXRz6zE0R4nbw+RnTj/WD4sbanLkkAe55ccYlyNme4SrO9u9d13PI3/saZ+/vs/3831ez+f9eb5aWsuqy2mjRYeNUa7YmtjfTico7jNJ8z0eG24NB9vvnDrvufzpq89Npnr8VjMddNmuRh9rDfp36mFg91oM7qPIc5JdbDJq3An/JfCu7Hl53W2lpS220pP2OuniN299jAYbYizSENIoAgbCTdrTKtxOJVdvGo8psUwKy7Vxe4ez1YEVudGP8YEZzyveInFJ6mZRHHqYazDspw/pJwTIuERM5JIwmUdGdyo9K7/BszGzzg6fXzZHGJ8KvzQqXKOpoIeZLjofWR++BPWyCEnPY4xFGEKWQcLjMjKmr1MwfcMYwmz/Y4KOgNki0V5k1dkjUWCK93Kp2PMFFawos8cm1gZ2GqjLXktL4mbQPHLQ4B9ZDFE5+S356fQlyuJMqzH++HnTo6ui2OO1ko9Ul+4fxfd3d4F7k4YTReqpuFS88bGZUE2QNNDobuIq8Q5CduHb7lFJaTnvnym9ergjMWD/FG8zf+aKS3G9JO5C01Asah6wUXrvALKEDoitMMHhDKrKJdg8RU2s0EB2EWWur8dd7PDPFv6dUC0Gv3kAN36VPRGP/5k5NS6lljWxG0TDiSr1VKhoPwhevRMSqkwRxDObc/DavGtpP6zoi8XOyZfhnyNEvKANBU0P8VPfI/wyNCGXSn7wlEmyA9KrgmOKGth3eDVvPfyywq2dnUEv2R9qG2rLsH7xJXziKnWcI8tlTvEC7Mu8hROlImTU9aKqcwQ1vWOihWFu+sJknmph5CvxQh87c7bNh/NXo03hrMCosyvLmMNgMF7TQL6J1dsZIUVwjKqEO+cajp5vxPN439U/gKBt8PTcYHzL/BgHCyOf4unAISj6mFC2bYC82kB5Ls460NHRUVsDeYSXpGw7UgC7sAtwShDgzdM38W7BbURXtqpqhfmB8sEQuXwoCM/6faGQuGCxyxyKWhIm+PrSD495WL3cT0hhi8Whc3NbAs9KaOyCTvrJ8qkdX19XBeTUDU00+55USFzVU2yHstcaix0mUAjJkJeuRU868Ucmk0lcguiBnMAVxjbbdHV1yeq8+u4Hgo22huSG+iQXp83ftaxW3lsPZcs6KG5T8OwaAfJiPcxlrVRVRhvF02i0F/t5VbHZ7JWDfErKTLnhE3mFPuRFepg/uxqz6TqLv6euGj3ut87t/4ylvre3t3ZehOWWO1zjSFEqMVP4GfGb/DBykJcjmaZOoLsc+hcVY/LaAgcTQAAAAABJRU5ErkJggg=='] | |
['Pinkie', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA3dJREFUGBlNwUtoXFUcB+Df/zzuY553pp2MmUwSk5TGpnamiokWRdNCSkCrUChKCnVZQUEUdy5sQZC6cyd2VWgoutCFXWTjIyp1UdqmEDBRsSZNmkmaZF6Zx32ccyzowu8j/M883pH5A9kBYfNkFOpu0OiulyqXmnhkDmdYHYJexzX1Ef51EQDhP9fxpjU0PDCd7IldYIxGVag3/KZ/ZX1p8/P0k/0U47qs291M2NS3f6ncuLeFeQ3A8KuYoNPoY/3e2Ej6scSnqUJ8gksmhC2y3OJHpSUHU0/3HU+WCuddyV6VSpVyYv/aUuPefWAP4iDG8AhJWyYYo972tg8DQ1wyWHGZSfcmZmQ+YeKTw1bQ70H8uJw3xtDp6NzG15VLf/DLWMBZHGPkwuWGyq7njLoZyzAiCtqRIddioifBxYBHIpeE0oaw0yoG7WA755dvi8Xih66BOSZj4rwds45bSQkuOeOCQYWG2PjjcEq94JwjQgQ+kCW+tBl3H7Ym4jnbE/nDmamwqz9mnEaYoBgiZaJIGW5zEIHEPheykMD2w12ztPIXCrZHec+GdOVAUI8ygjvifeHQESiNoKtMlIoRxSV0owMjAeY5+P3BKrbTDq3n02B/7yDTDkBANSXiewKgjFbahEwQe34IiVIfRNqCv1qDanQR9Di4+tU16N409o2WMXnyJeNWb9PO4s6WroZawOiSiozCoR7lPFUQezICCzXF+pPGYRna6/rotNqY/eJLUzh4mM5dP4Va0YXV45x0O9F9FhkN5auq4eznaq3WmP1pDkuibW5uraNaqyNh23ihPA6v7wAVS+PwXAGkbYiUnU3kYm8JzvgGpJGdG6vzm15+ce6H79/9bnnBhCxG702dwnTaw4nyM/jsiTHsHx+DEyjKWnGEUpBOyjTTgbpsNHyLojPe7PK3qci58NvNu0Gl0YA8NIxWp4MkdzCdK2Ci6iNYXIV6UEfUDBC2Q/A3WqVbUUfVucWftYhP9fLiFf7yRPGVmZmhE88dJVmpGRMqRH4E3emSbnQR3lkzaqNB3br/J39tb1ibJglGfJDZbMReb37Td/bFhcnB/iNppXNUbZEKFGBJ6FBT+9cVo5c3yd/trDV3OxdFDDHFOV8IffVJtNNOC+J3xtYqATWw0Mm6RIJ9YAy9rdtt07q1ZtjdVXCYFRBG4Bv8A+lliGhzN164AAAAAElFTkSuQmCC', "pony"] | |
['Applejack', 'A4AAAAQCAYAAAAmlE46AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAv9JREFUOE9dkmtIU2EYx88Roi9FfahkfQmS6kNGEBRlZWSY5tylTKepqR8MHYGl2R1POm2u5bCVlzbog2Ze591pzZPZxUskDZLMMLSWzcIca3MXt7N/55woqhf+PC8Pz+99n+fPQxAEQf6vhINb1nG5/ISdobWXo+9eSd4tyM7OJimKImmaJhsaGjjmX/DGqfDQmkvRg1x+9YrlZPd18fdupXiu6mxkOAcqlUqyuLiYB/+cayfD1rKFH0w3pYEHV4/omhTCyieVcYEB7TEYSyX21Mita/6u/91qUBMV00JrjmKwMg4zI2fgnlfD90PLx+nhMyinIrb91SFBFqaHBevPHb7G/fS06jhs0wXwO8rBOLXws2Kct/k4//HKRE+jZD0Pl2buD2FnmOlVSUFrpJg15/JFgcWKP0Bg8Q6fs1sVs+11wmAebKaEuiG1CC81Yozci+cL4KoC3JUIuCp4+R23+Ee4Dr5bisZmJi7fJzpLRJZPOin8vSlwdSXDO54Hz+vT8LzLh3uuCIuzBfDa1DzMPcrJMVfkIHpVEu94uYgH/aaTvOxdJzDZkI76smhY2mVwDmfg8zM5RukcvH8pbx96mLiPMBTG0nSpGK7mePg6k+DsSUZbSQwem02oba3DRsFKzNQfx9sHSdi1dzve5Ow4xM+ozorY1K2U2MY0IrhbEuB7lIqB6gxY7B9R3XoHAoEAivN74O5LAaXNwvNLe9PlcjlJACANRaIRztFh1iRvfRyYx5kIOCwY+GCE9GIUOjrzwZjS4H16FV80UT1WqzWIWFhYIBsLhDf7y46Ck1UvATNKgXlxHgHbJDyub2DGVPC2s+bVyGDTx74ym80kwe2fKvNASN8NySK3NeayWNagNPj7WaP62Uhn8HdPkwyWW3IoEjdv0Ol0JGE0GvmV0+dFpj9SS5kOKuahr01Wwbb2lXV6aakjkfF1p8DXlwHnaB5yTm1bbzAYfs34e/+0pyNic+N2ruIWmQWXcdE1dUEGd9UYq6kle1mXqVW6imWIn290AGVZutJTAAAAAElFTkSuQmCC', "pony"] | |
['Fluttershy', 'BAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA2xJREFUOE9dU91PWmcYP2uybDfrxdIlu9vN/oglverWNN3Fmu1iN7vY2q4utnE2Nu26ukyrUUGwExGpn3xY+TyACjrskFrcEYoWnCAM4YAgcJjROkFA1q789nJczNaTPHnfk+f5/d7n4/dQ1Cvf3Ut3Xp//Qnze36gYCt56kIgJpyqRFvrvcIvxMNxhSa9eV993XJK/+yqO/zdf7j7tbRz1RdstLzOKReRoLxJSOzb7HyKtdCEumgErmEbwO03U2aR8738kzq8ln8e6bXlWYMWmZA6Z8SUk5U5ytyPeY0Oy1w5O50FO+wQ5jbtG4lK19L5BGehzb9sE19+JtFt2c8ZlJPvmwAqtSA06EWs3g+2aQnacwdbwAmLknuiZxaZ4FiTD6tLFvi+pBeenb/3mvvo4Yu3D5v1ZsP1axHpUiAo0iPyg41/dGiNgiQI5PXmdXkai92dkVItYbZ6YpVZWLrrKFSOynBip9W6U/7LwViqZ8SykRWpcR8BqJNlmJCZp1LLMkIxSAw6s39WHqUCo/mDnWTdKhwRUMaNMzvLh5NFZsaBIbD+rJ34jgsxtcLQH3IQbKakDoVZDmnpk+irA/fEjCkXlv+AawX+MEJQJcaFEY8bWAJdMgYxyESn5PILNumUqJNVVA4EG7OXlx8Bf3T2QyRuh0X2P5ad9pCQTcjtqDI3UwTMuReIeaaKagb9u6B6VVi9Wg1YRUhkhH1g6NKFf3gD/2gAYz08YVd5AdltDfDS2d2QIrH6DcNcwUjLHc+aC8AMqLrW/4EwesBoligUTCgc05h52IH9gwu6+ERwBb+9pkc0IwLJNWHPXIyrUIdysW2POd52gopIZjtOSpgzOI2NToVAmwD0D9osmvvZSxcCXtr5wA08627Ah0yHZ74D3ysBNXokR8XQ8q2SQM3gQbZtAPm1AiZRyNIUawZGFl5qIRqbBdk4Sndjy1iviIymzIquXldirWRXDzzdOZr63q8J66OqOf+2yL8be+nMr3fry91A9NlRjvKT9tx88Pt6Djdaps0RZxQRZmCzpbHrMBV9b5/YM/dn7tSCT/cNTvpauFdasR5xkkCaS9n07Kj0mIKm+GbujP5OQ/vI8Ofyomhx0sOmxhU9W6wYp5uOO12qB3guik2TuI2QPXmwpXLGnjSMf3RRdO1Hz/QNneMt7Iqmg5QAAAABJRU5ErkJggg==', "pony"] | |
['Twilight', 'BIAAAAQCAYAAAAbBi9cAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA6lJREFUOE+VlF9Mk1cYxj+Kc3+yXWimFxuk2zTIn4bQFkppF0hDNmBpBtgKixhhQZAhbSkFBp1uZWg3ZLRMxFKE0qKtrMy2E2ztn2+1tLQgbuJiorvQ7c5pgplZNjfmePZ9nwtZMm52kufqvOeX5zznfQ9B/M9l/8CXPP2R/6ajy+u0amZeoI8D2PpfTLqMlZQpT9vE2fPOc9l73302q7rs6Sz5K6zM3ZuJzD2EVf1VytejC4hNXoWj2/vlF71+FgVKIsZVHrbnEzLoPkYOqqtPNm7j1l1J4R9Y4wgVkOR3Qcvrg+uNXmTnt9zfmdcUFRd1XqQhC+eWMXP8MiwKdyUDOqMLEG49qYtYlhA+vQi7zocGmQHFYi2UnM9wq/RzNEsOQyDWMBIWtjNurjivw2ucg+toyM+A6LWZU72vvsqwFjwVZwrmrEvoq7DBLDDiltQAobidgeRRUipMTA0t32AU3hNzD7zGSANBZMi2UFe5nyZohrREB9dxEnMTS+jgnUBYMghv2afrbhhHb3aAnFxkQMHhOALDid8p0EHiKU6VklvQil0UiJakqBsf77dCmTmASPEAhoqPIEN4CGmCJvAkauzKfw/5pRr4J+JUTtfo693zGSM7iBdzan10sE9gh5AragNXoEKtvB+93ZMY0TthGraB92oJVlYewDTgQJ96DKTtiStXb8jvNoafIV7i19+lndC2X+bXPyqXffj4kmV+PYexY1aQMwnkv1YGWUUljryvQ0/dqfV9+Vs9zVTYLILKZ5UGsXMbb2/llJaWCN8OnzNMrxda9JNYjt+ENL0RrQol0nekQVtlRHA8gsWpZQhEmrviws5yIpXfcG87t+52UpY8NZXN3lIjPRiOReZxfugCA7s4EsCN727ArHChQiKDYGchRrumELbFEbQmkFvQ+ofg9TYX8Xx2zfnkLDmHbgM2m00M1tortQf06FC2Y2HqGgMjvSR+WfkVplYPzCoX3EOziDmuwjMSRk6BajVP1PYT/fzb/j0nZ7tmN+n3mUlpUTmCo1EGFHJE8NvDR/g+egd0fj5LDN6xKHo6bOAL1D/niTTRDUd2rMW13VBj/zFu/5YZBaYBp69j0blMPfs8zhj9KCjspPNZ+6fjd28IGld4MgIn5x/HJr9ByJRYDz5oS2B6KIT9Nf3IEaj+pCBrXFELOTERZm0Ichy+lHy2czZlpv+y80JfmILFVwPDsTvmo26SJ1I9zBU1/UVBfqAk35ujpb+RpL8BJjxIUjyXvSgAAAAASUVORK5CYII=', "pony"] | |
['Rainbow', 'A8AAAAQCAYAAADJViUEAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAA3tJREFUGBk9wV9MG3UAB/Dv7+531971aKGMlr+OwjoGBUZwRDrRBJwj0bHEmeiS6Xwyxn8x8UVNzHAPPvliFhMzsy0m8uDD5h/QZWoUNxYMBoZbZCBjZTBoKRwtLde7cv9+bg/6+ZDnzk6C44lw6f6whdOnETpzla+0803RMD3ZGSH95V62lzGQtMH9M7MhfpPUyIX5HE1uvNXDaCQgtykB70cR/4unrn3aqzYkZt7v18ZfezyTkfy0HlJ7FMWKEBJFpYMSVq7bngMlGvvc/OTiLzRYLp8K1waObaS16MDIRfupG9c6SuwCsSt2kJ+/B+3HMdC6MBofa0N1a2sVJTWj02mh4BFCCpV84jN4oHyX3KYEJAi2BWYR2CkPmMlBiOgwE0mYiymo1Qu0Mx4/8VLVnrtnF4VxfuCN9z5mDBA9FJt7mzDe3oXkjou69CqoxkA4gC9xQAggankMa7uTm3m32SLKD+Sz6XXGGCDJAv6j7di4MzqBo199Adk2EIqkQGQHDy3Ij2Q+bHr9g3UxyFHLdFyvJHAg+J/ipYgdjuMyzwELCfRsTWG/NQEwhqCVC0YLy/qKGJzmD77w9pHSoFyjbWWxtjAH5jIIHi8EKkCpq8JteCD2H0F2u4BwZhE+x8BEWbt6i6df8kr/s0+H/HKMc1yo02MYaG9APjGLxJ+T2NxYRV7fxu66GqjwYyrn2AG7YFGw4FygeYiXjva/KoipxoaKGPY1N+PJfRHEauvQaIj47vsLSN67i87ew6hOLGFeTS38FO45XhR8lQlffS0tmGViwbmCdKEb3tJSGLYLieMwMfQr1tZSqOzqheCVkDWIk7i/vvJ7WdVVxd96XWBU4kzb55qOiZvqJazmCxhLGzBFiqbnuzD71xyij8bxEN/XzXccf7PyxJ6+lkxuwknnftP4vorBd9O1mXBAnsbfaQW6VQadcWC7gmiIH0JlrBWuw+DYgFyiSGqu+O2NjZllPMBJRUevuH4Ipu1DyOefrS6RzmQN211iFGUtzSAcD8dh2Ll8cyStai8vra/8MQhgEADvjx/bX78c6rgT1ddl722/btSelEz69eaWoZqms1kwrGVt27xV1I1zgdWfRw6Ww8lmswQAo6QR2dnM6JC6HT3PEfvctjSsnx+3J1uob6qt6gAtSgEu4BbdV2KO80T3O0QQBFiWRQRBwL/txI3OlzkSKwAAAABJRU5ErkJggg==', "pony"] | |
['Rarity', 'BMAAAAQCAYAAAD0xERiAAAEEElEQVR4Xm2SW2xURRyHfzPnsmcvlO4ulN1uF2sLrIpdJNS0KUZFUq1t0AiKkpASbyQSjRKENEGrPuCTiUoTjSENKAnFYKokbZOmIBaoTRXB1AjbWmrabmVpt3SvZ899PFnTxAe+ZF7+D998mf/gbmwt30131B58YM+WTw7vbTnW/+oTHZda6490723uPP1KY0fna40dh/Y0fFz/4pq3XRFEsATB/2i71EauvDcplHN173p8of2gnI8KPHLxm/AEqwgIARUEeywyS1dVPZ+9kJ6OHdB/uzF2BmcYXRIdHxkhO/0vR/e9+c4p7+pIO+92+wlHaGE+QV1lYWpLCe90kdKVTvJo80rqDTic4nJfk7c62kM3rltfgQpSLGOM4ZfR0apQIPQTpSR04uhVqhUYSkoItLyMVFaEIjNENpTg8ZbVyGYK6PpyHIYGBhCmLiYHZ2NDzxZlpwYHaX3V2mMet3sPpZSbjc/B5y+Fw8GDgWEukcbURBLR2jB0TcPpz4cwO5aBBQJuWSnsbC09eeN50tnZSYy0s6p5V+MwIVghSQ4iFwqQHBIIIcVjGEaxXtd1XO2P4dr3N6EqCvJyFoqmgvqDlqZqp+jxD4/z8etKGxjxm6ZJxmIxnB8YwNDQEGITE5iemQHHcRAEATYIVPvB8ZQRQu05D45QGPNx2PYNNFxWV21y/h0AiCiKkGUZcwsZnDjTg7cOtuOr098hYxLYQJIklK8ps5hoaAyM2ZeAFwRQEJi5FEclT/BpxZBKFhdkQimFx+NBTbQG+1pfQFZ34tZtFd29PTAtC+N2dU9vH/t18sKCwPP4r46DQ3QySzcGKBGERzRFpYl4CkubPdd3Fj1nu5GduAxvdQNIPgNV1zBw/hy6+y+D510xUZQYzwlM5CXT5iID+5RailLNDINN/ZUCoQTLlnkQj8dx8uRJW2DA7V2F6H0RGJoGt8vFgqF7c2vD0T4wMANgd0yjP2Mqb+Ty2RkqMrhhmbh+JYnk7TSWl/pwuP0DrIvWoX73EWx/LIIV3lKIgoitT21Dy7aWPzU125/JpbOLukrA8U1ly8uGwxWVz1CXwOvE0qHIGq4NJ4qPHApVoKurC4defw6bKigCwfLiRkMBPzavL39w5/tPChk5vV+ZvzVHUknm4DhB13RKeZ5LlthlzDAQG00jkykU/5VTYKgJiTANE6LkhKIqTNW0nKqpvYauj89PzX5jcqxG0/WmeGK6bj6V+IHPy7nfV/hWbS5kM0gnC5iMLWBjXfhnAA0FRQGz0XVtzmJsZEHOH52a+uPirubtOmw2BfYmg9cSP2YsJ7uIbxlpfaitdk3l/Q/rlv7FnVzucmXdPS+1HtjyD8dzWCIvy76/Z6bY5MTs4tfjn7HBjwZxN/4Fq6rr1ZuF0oUAAAAASUVORK5CYII=', "pony"] | |
['Spike', 'A4AAAAQCAYAAAAmlE46AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAsFJREFUOE+Fk1tM0nEUx/9QPtCD7z30nE9sbbVeXJJR6j8DkVsIhg6HTqSXnBHSMMEbEy+AgPwVQpcgNy+kKLc/lCgF09Wquaab67kHX1pulif+mHRdne3sd3Z2Pt/fOee3H4J8N/ow2lrj4H64OljRfEXBIZ/k/3lWquXIrQl2ROAVA98jOro2XKUtvV9Dpj/iFV/ppwvLVfzThEBZGRWh0S4hmFx+rId2ysmMSU6WAAUeMfDcdYe0gUrGdUOl7rZXBDRdRQtRp1PeIRlVctIzk+lHR6itJnwC1nkbgOXgZlhO3h6RY9rZKYT7W9NUKpUklUqRKjPDQADEjYTz3SLgzQjzMWua/5E5xLpQrqOX/jEzamTc4LqEX/KQRwRMBwfEDgnUOyXAdgk+1zr5e0w7J/vA15OfN28PW5SnZlRuVT3WeMia5oHW1AthawSS40mIjcWhW98HfF89Ifa6qb+hqAA6FA5xzIp/dVncYDc/hkQOiI/jBcctCegwdRJgsERWcszpZTrKU/3S7s+Ff4vn9UG4aWbGyofoaB60d05dDJuiR/8DcXMCpLY24GPsrlRWcxZxKmaqF0aCsDy8ArgtAVFL/Jc2C4LWBEwFNLCUbt9PZrpEiEk2VjbmMYIdm4TQ6Cq4RmYB02CwZAlB2ByBkHEVYhYcEmEreNZl4F+/C8F0+0vE2x1IL3qDsDgZhKg5Bt7ULAgHa+HVzlt4v7MHMQyHpM8LrlQzuNdaIfJCub+R0Z5DfNrAxsJAEHJbhXhue5nQJmS3t2D73S6suVK5XBKiYQMs4B3xSEbZ83xTc3ljq5eMmNts5/3d82/8jicQDc0Cbo8BjiVyQsez4rYkeNRzfqfadUYgEJBRFCVRKBQS0tTUSM7BxaauUelyenwunnZ+SnhXDkKG0EGgb+5g4p5dpa5TFEkk1bmfQSu8/TfTXs+Z8UbptgAAAABJRU5ErkJggg=='] | |
] | |
Themes = | |
'AppChan': | |
'Author' : 'Zixaphir', | |
'Author Tripcode' : '!..NoTrip..', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(44,44,44,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Dialog Background' : 'rgba(44,44,44,1)', | |
'Dialog Border' : 'rgba(44,44,44,1)', | |
'Reply Background' : 'rgba(51,51,51,1)', | |
'Reply Border' : 'rgba(51,51,51,1)', | |
'Highlighted Reply Background': 'rgba(57,57,57,1)', | |
'Highlighted Reply Border' : 'rgba(57,57,57,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141)', | |
'Input Background' : 'rgba(51,51,51,1)', | |
'Input Border' : 'rgba(51,51,51,1)', | |
'Checkbox Background' : 'rgba(68,68,68,1)', | |
'Checkbox Border' : 'rgba(68,68,68,1)', | |
'Checkbox Checked Background' : 'rgba(17,17,17,1)', | |
'Buttons Background' : 'rgba(48,48,48,1)', | |
'Buttons Border' : 'rgba(48,48,48,1)', | |
'Focused Input Background' : 'rgba(63,63,63,1)', | |
'Focused Input Border' : 'rgba(63,63,63,1)', | |
'Hovered Input Background' : 'rgba(57,57,57,1)', | |
'Hovered Input Border' : 'rgba(57,57,57,1)', | |
'Navigation Background' : 'rgba(44,44,44,0.9)', | |
'Navigation Border' : 'rgba(44,44,44,0.9)', | |
'Quotelinks' : 'rgb(79,95,143)', | |
'Backlinks' : 'rgb(79,95,143)', | |
'Links' : 'rgb(102,136,170)', | |
'Hovered Links' : 'rgb(78,110,142)', | |
'Navigation Links' : 'rgb(170,170,170)', | |
'Hovered Navigation Links' : 'rgb(78,110,142)', | |
'Names' : 'rgb(170,170,170)', | |
'Tripcodes' : 'rgb(170,170,170)', | |
'Emails' : 'rgb(102,136,170)', | |
'Subjects' : 'rgb(144,144,144)', | |
'Text' : 'rgb(170,170,170)', | |
'Inputs' : 'rgb(170,170,170)', | |
'Post Numbers' : 'rgb(102,136,170)', | |
'Greentext' : 'rgb(120,153,34)', | |
'Sage' : 'rgb(150,150,150)', | |
'Board Title' : 'rgb(170,170,170)', | |
'Timestamps' : 'rgb(170,170,170)', | |
'Warnings' : 'rbg(102,136,170)', | |
'Shadow Color' : 'rgba(44,44,44,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '' | |
'BakaBT': | |
'Author' : 'seaweed-chan', | |
'Author Tripcode' : '!POMF.9waa', | |
'Background Image' : 'url("http://i.imgur.com/rTkxi.jpg")', | |
'Background Attachment' : 'fixed', | |
'Background Position' : '20px 20px', | |
'Background Repeat' : 'repeat', | |
'Background Color' : 'rgba(238,238,238,1)', | |
'Thread Wrapper Background' : 'rgba(255,255,255,1)', | |
'Thread Wrapper Border' : 'rgba(204,204,204,1)', | |
'Dialog Background' : 'rgba(238,221,255,1)', | |
'Dialog Border' : 'rgba(238,221,255,1)', | |
'Reply Background' : 'rgba(238,221,255,1)', | |
'Reply Border' : 'rgba(209,162,255,1)', | |
'Highlighted Reply Background': 'rgba(238,221,255,1)', | |
'Highlighted Reply Border' : 'rgba(209,162,255,1)', | |
'Backlinked Reply Outline' : 'rgba(204,101,99,1)', | |
'Input Background' : 'rgba(255,255,255,1)', | |
'Input Border' : 'rgba(204,204,204,1)', | |
'Checkbox Background' : 'rgba(255,255,238,1)', | |
'Checkbox Border' : 'rgba(204,204,204,1)', | |
'Checkbox Checked Background' : 'rgba(188,192,212,1)', | |
'Buttons Background' : 'rgba(255,255,255,1)', | |
'Buttons Border' : 'rgba(204,204,204,1)', | |
'Focused Input Background' : 'rgba(255,255,255,1)', | |
'Focused Input Border' : 'rgba(209,162,255,1)', | |
'Hovered Input Background' : 'rgba(238,221,255,1)', | |
'Hovered Input Border' : 'rgba(204,204,204,1)', | |
'Navigation Background' : 'rgba(255,255,255,0.8)', | |
'Navigation Border' : 'rgba(255,255,255,0.8)', | |
'Quotelinks' : 'rgb(146,92,141)', | |
'Backlinks' : 'rgb(146,92,141)', | |
'Links' : 'rgb(133,76,158)', | |
'Hovered Links' : 'rgb(198,23,230)', | |
'Navigation Links' : 'rgb(17,17,17)', | |
'Hovered Navigation Links' : 'rgb(198,23,230)', | |
'Names' : 'rgb(133,76,158)', | |
'Tripcodes' : 'rgb(146,92,141)', | |
'Emails' : 'rgb(133,76,158)', | |
'Subjects' : 'rgb(17,17,17)', | |
'Text' : 'rgb(0,0,0)', | |
'Inputs' : 'rgb(0,0,0)', | |
'Post Numbers' : 'rgb(146,92,141)', | |
'Greentext' : 'rgb(129,153,65)', | |
'Sage' : 'rgb(146,92,141)', | |
'Board Title' : 'rgb(133,76,158)', | |
'Timestamps' : 'rgb(0,0,0)', | |
'Warnings' : 'rbg(133,76,158)', | |
'Shadow Color' : 'rgba(128,128,128,0.5)', | |
'Dark Theme' : '0', | |
'Custom CSS' : '#delform{ box-shadow: 0px 10px 10px 2px rgba(128,128,128,0.5); border-radius: 3px;padding:10px;}#options.reply.dialog,#options .dialog{background-color:#FFF;color:#000;border:2px solid #CCC;border-radius:6px;}#options ul{border-bottom:1px solid #DBD8D2;border-radius: 0px;}#options ul:last-of-type{border:none;}#qp div.post{background-color:rgba(255,255,255,0.9);border:1px solid #D1A2FF;color:#000;}' | |
'Blackberry Jam': | |
'Author' : 'seaweed-chan', | |
'Author Tripcode' : '!POMF.9waa', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Dialog Background' : 'rgba(27,27,27,1)', | |
'Dialog Border' : 'rgba(27,27,27,1)', | |
'Background Color' : 'rgba(45,45,45,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(27,27,27,1)', | |
'Reply Border' : 'rgba(38,38,38,1)', | |
'Highlighted Reply Background': 'rgba(17,17,17,1)', | |
'Highlighted Reply Border' : 'rgba(17,17,17,1)', | |
'Backlinked Reply Outline' : 'rgba(103,204,232,1)', | |
'Checkbox Background' : 'rgba(51,51,51,1)', | |
'Checkbox Border' : 'rgba(51,51,51,1)', | |
'Input Background' : 'rgba(27,27,27,1)', | |
'Input Border' : 'rgba(27,27,27,1)', | |
'Focused Input Background' : 'rgba(27,27,27,1)', | |
'Focused Input Border' : 'rgba(27,27,27,1)', | |
'Hovered Input Background' : 'rgba(17,17,17,1)', | |
'Hovered Input Border' : 'rgba(17,17,17,1)', | |
'Checkbox Checked Background' : 'rgba(17,17,17,1)', | |
'Buttons Background' : 'rgba(27,27,27,1)', | |
'Buttons Border' : 'rgba(27,27,27,1)', | |
'Navigation Background' : 'rgba(45,45,45,0.9)', | |
'Navigation Border' : 'rgba(45,45,45,0.9)', | |
'Links' : 'rgb(218,105,224)', | |
'Hovered Links' : 'rgb(255,0,255)', | |
'Navigation Links' : 'rgb(241,241,241)', | |
'Hovered Navigation Links' : 'rgb(255,0,255)', | |
'Subjects' : 'rgb(241,241,241)', | |
'Names' : 'rgb(103,204,232)', | |
'Sage' : 'rgb(103,204,232)', | |
'Tripcodes' : 'rgb(103,204,232)', | |
'Emails' : 'rgb(218,105,224)', | |
'Post Numbers' : 'rgb(218,105,224)', | |
'Text' : 'rgb(241,241,241)', | |
'Quotelinks' : 'rgb(223,153,247)', | |
'Backlinks' : 'rgb(223,153,247)', | |
'Greentext' : 'rgb(108,204,102)', | |
'Board Title' : 'rgb(103,204,232)', | |
'Timestamps' : 'rgb(218,105,224)', | |
'Inputs' : 'rgb(218,105,224)', | |
'Warnings' : 'rbg(103,204,232)', | |
'Shadow Color' : 'rgba(29,31,33,1)', | |
'Dark Theme' : '1', | |
'Custom CSS' : 'div.reply {box-shadow: inset 0px 1px 2px 1px #111;}#qr {box-shadow: none;}#qr textarea, #qr input[name="name"], #qr input[name="email"], #qr input[name="sub"], #qr input[title="Verification"] {box-shadow: inset 0px 1px 2px 0px #111;}#qp div.post {background-color: rgba(29,29,33,1); border: 1px solid rgba(95,137,172,0.4);}input:checked .rice { background: url("http://i.imgur.com/CAewG.png"); }' | |
'Midnight Caek': | |
'Author' : 'Zixaphir', | |
'Author Tripcode' : '!M.........', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(16,16,16,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Dialog Background' : 'rgba(28,28,28,1)', | |
'Dialog Border' : 'rgba(28,28,28,1)', | |
'Reply Background' : 'rgba(28,28,28,1)', | |
'Reply Border' : 'rgba(28,28,28,1)', | |
'Highlighted Reply Background': 'rgba(24,24,24,1)', | |
'Highlighted Reply Border' : 'rgba(24,24,24,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Input Background' : 'rgba(28,28,28,1)', | |
'Input Border' : 'rgba(28,28,28,1)', | |
'Hovered Input Background' : 'rgba(24,24,24,1)', | |
'Hovered Input Border' : 'rgba(24,24,24,1)', | |
'Focused Input Background' : 'rgba(16,16,16,1)', | |
'Focused Input Border' : 'rgba(28,28,28,1)', | |
'Checkbox Background' : 'rgba(0,0,0,1)', | |
'Checkbox Border' : 'rgba(60,60,60,1)', | |
'Checkbox Checked Background' : 'rgba(60,60,60,1)', | |
'Buttons Background' : 'rgba(24,24,24,1)', | |
'Buttons Border' : 'rgba(24,24,24,1)', | |
'Navigation Background' : 'rgba(16,16,16,0.9)', | |
'Navigation Border' : 'rgba(16,16,16,0.9)', | |
'Quotelinks' : 'rgb(71,71,91)', | |
'Backlinks' : 'rgb(66,66,71)', | |
'Links' : 'rgb(87,87,123)', | |
'Hovered Links' : 'rgb(71,71,91)', | |
'Navigation Links' : 'rgb(144,144,144)', | |
'Hovered Navigation Links' : 'rgb(71,71,91)', | |
'Names' : 'rgb(124,45,45)', | |
'Tripcodes' : 'rgb(62,113,87)', | |
'Emails' : 'rgb(68,68,68)', | |
'Subjects' : 'rgb(170,170,170)', | |
'Text' : 'rgb(144,144,144)', | |
'Inputs' : 'rgb(144,144,144)', | |
'Post Numbers' : 'rgb(144,144,144)', | |
'Greentext' : 'rgb(113,121,62)', | |
'Sage' : 'rgb(68,68,68)', | |
'Board Title' : 'rgb(144,144,144)', | |
'Timestamps' : 'rgb(144,144,144)', | |
'Warnings' : 'rbg(87,87,123)', | |
'Shadow Color' : 'rgba(16,16,16,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '' | |
'Minimalistic Mayhem': | |
'Author' : 'Mayhem', | |
'Author Tripcode' : '!MayhemYDG.', | |
'Background Image' : 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAACdJREFUCNdNxzEBADAMwzCnOMwfWYDs2JNPCgCoH9m0zQa4jXob4AGJFwxchPNwQAAAAABJRU5ErkJggg==")', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(25,25,25,1)', | |
'Dialog Background' : 'rgba(34,34,34,1)', | |
'Dialog Border' : 'rgba(41,41,41,1)', | |
'Thread Wrapper Background' : 'rgba(34,34,34,1)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,1)', | |
'Reply Background' : 'rgba(51,51,51,1)', | |
'Reply Border' : 'rgba(17,17,17,1)', | |
'Highlighted Reply Background': 'rgba(37,38,42,1)', | |
'Highlighted Reply Border' : 'rgba(85,85,85,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(57,57,57,1)', | |
'Checkbox Border' : 'rgba(25,25,25,1)', | |
'Input Background' : 'rgba(34,34,34,1)', | |
'Input Border' : 'rgba(21,21,21,1)', | |
'Focused Input Background' : 'rgba(32,32,32,1)', | |
'Focused Input Border' : 'rgba(102,102,102,1)', | |
'Hovered Input Background' : 'rgba(24,24,24,1)', | |
'Hovered Input Border' : 'rgba(21,21,21,1)', | |
'Checkbox Checked Background' : 'rgba(57,57,57,1)', | |
'Buttons Background' : 'rgba(32,32,32,1)', | |
'Buttons Border' : 'rgba(16,16,16,1)', | |
'Navigation Background' : 'rgba(26,26,26,0.9)', | |
'Navigation Border' : 'rgba(26,26,26,0.9)', | |
'Links' : 'rgb(85,156,122)', | |
'Hovered Links' : 'rgb(199,222,26)', | |
'Navigation Links' : 'rgb(144,144,144)', | |
'Hovered Navigation Links' : 'rgb(198,23,230)', | |
'Subjects' : 'rgb(72,98,115)', | |
'Names' : 'rgb(46,136,166)', | |
'Sage' : 'rgb(124,45,45)', | |
'Tripcodes' : 'rgb(140,93,42)', | |
'Emails' : 'rgb(174,43,41)', | |
'Post Numbers' : 'rgb(137,115,153)', | |
'Text' : 'rgb(221,221,221)', | |
'Quotelinks' : 'rgb(139,164,70)', | |
'Backlinks' : 'rgb(139,164,70)', | |
'Greentext' : 'rgb(139,164,70)', | |
'Board Title' : 'rgb(187,187,187)', | |
'Timestamps' : 'rgb(221,221,221)', | |
'Inputs' : 'rgb(187,187,187)', | |
'Warnings' : 'rbg(87,87,123)', | |
'Shadow Color' : 'rgba(16,16,16,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '.nameBlock>.useremail>postertrip{color: rgb(137,115,153);}a.backlink:hover{color: rgb(198,23,230);}.reply:target,.reply.highlight:target{background:rgb(37,38,42);}[alt="sticky"]+a{color: rgb(242,141,0);}[alt="closed"]+a{color: rgb(178,171,130);}input:checked .rice{border-color:rgb(21,21,21)}}input[type="submit"], input[type="button"], button {background: linear-gradient(#393939, #292929);border: 1px solid #191919;color: #AAA;text-shadow: 0 1px 1px #191919;}input[type="checkbox"], input[type="radio"] {background-color: #393939;border: 1px solid #191919;}input[type="checkbox"]:checked, input[type="radio"]:checked {background: linear-gradient(#595959, #393939);border: 1px solid #151515;} #delform { padding: 7px; }.subject:hover,div.post:hover .subject{color: #3F8DBF !important;}.postertrip:hover,div.post:hover .postertrip{color:#CC7212 !important;}.name:hover, div.post:hover .name { color: #0AAEE7 !important;}.name,.subject,.postertrip {-webkit-transition:color .3s ease-in-out;-moz-transition:color .3s ease-in-out;}' | |
'ObsidianChan': | |
'Author' : 'seaweed-chan', | |
'Author Tripcode' : '!POMF.9waa', | |
'Background Image' : 'url("http://i.imgur.com/sbi8u.png")', | |
'Background Attachment' : 'fixed', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Dialog Background' : 'rgba(0,0,0,0.7)', | |
'Dialog Border' : 'rgba(0,0,0,0.7)', | |
'Background Color' : 'rgba(0,0,0,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0.3)', | |
'Thread Wrapper Border' : 'rgba(51,51,51,1)', | |
'Reply Background' : 'rgba(0,0,0,0.6)', | |
'Reply Border' : 'rgba(0,0,0,0.6)', | |
'Highlighted Reply Background': 'rgba(0,0,0,0.4)', | |
'Highlighted Reply Border' : 'rgba(0,0,0,0.4)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(68,68,68,1)', | |
'Checkbox Border' : 'rgba(68,68,68,1)', | |
'Input Background' : 'rgba(0,0,0,0.6)', | |
'Input Border' : 'rgba(0,0,0,0.6)', | |
'Hovered Input Background' : 'rgba(0,0,0,0.4)', | |
'Hovered Input Border' : 'rgba(0,0,0,0.4)', | |
'Focused Input Background' : 'rgba(0,0,0,0.4)', | |
'Focused Input Border' : 'rgba(0,0,0,0.4)', | |
'Checkbox Checked Background' : 'rgba(255,0,127,1)', | |
'Buttons Background' : 'rgba(0,0,0,0.4)', | |
'Buttons Border' : 'rgba(0,0,0,0.4)', | |
'Navigation Background' : 'rgba(0,0,0,0.7)', | |
'Navigation Border' : 'rgba(0,0,0,0.7)', | |
'Links' : 'rgb(0,255,255)', | |
'Hovered Links' : 'rgb(0,255,255)', | |
'Navigation Links' : 'rgb(253,254,255)', | |
'Hovered Navigation Links' : 'rgb(253,254,255)', | |
'Subjects' : 'rgb(144,144,144)', | |
'Names' : 'rgb(253,254,255)', | |
'Sage' : 'rgb(253,254,255)', | |
'Tripcodes' : 'rgb(255,82,203)', | |
'Emails' : 'rgb(0,255,255)', | |
'Post Numbers' : 'rgb(253,254,255)', | |
'Text' : 'rgb(253,254,255)', | |
'Quotelinks' : 'rgb(212,212,212)', | |
'Backlinks' : 'rgb(0,255,255)', | |
'Greentext' : 'rgb(67,204,103)', | |
'Board Title' : 'rgb(253,254,255)', | |
'Timestamps' : 'rgb(253,254,255)', | |
'Inputs' : 'rgb(253,254,255)', | |
'Warnings' : 'rbg(0,255,255)', | |
'Shadow Color' : 'rgba(44,44,44,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '#qp div.post{background-color:rgba(0,0,0,0.8);border-radius:4px;border: 1px solid #333;}#qr {background-color: rgba(0,0,0,0.7);border: 1px solid #333;}' | |
'PaisleyChan': | |
'Author' : 'Ubuntufriend', | |
'Author Tripcode' : '!TRip.C0d3', | |
'Background Image' : 'url(http://i.imgur.com/DRaZf.jpg)', | |
'Background Attachment' : 'fixed', | |
'Background Position' : '', | |
'Background Repeat' : 'repeat', | |
'Background Color' : 'rgba(19,19,19,1)', | |
'Dialog Background' : 'rgba(16,16,16,1)', | |
'Dialog Border' : 'rgba(16,16,16,1)', | |
'Thread Wrapper Background' : 'rgba(52,56,56,0.3)', | |
'Thread Wrapper Border' : 'rgba(52,56,56,0.3)', | |
'Reply Background' : 'rgba(52,56,56,1)', | |
'Reply Border' : 'rgba(0,0,0,0)', | |
'Highlighted Reply Background': 'rgba(0,0,0,0)', | |
'Highlighted Reply Border' : 'rgba(0,0,0,0)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(34,34,34,1)', | |
'Checkbox Border' : 'rgba(60,60,60,1)', | |
'Input Background' : 'rgba(28,28,28,1)', | |
'Input Border' : 'rgba(28,28,28,1)', | |
'Hovered Input Background' : 'rgba(24,24,24,1)', | |
'Hovered Input Border' : 'rgba(24,24,24,1)', | |
'Focused Input Background' : 'rgba(32,32,32,1)', | |
'Focused Input Border' : 'rgba(32,32,32,1)', | |
'Checkbox Checked Background' : 'rgba(34,34,34,1)', | |
'Buttons Background' : 'rgba(32,32,32,1)', | |
'Buttons Border' : 'rgba(32,32,32,1)', | |
'Navigation Background' : 'rgba(16,16,16,0.9)', | |
'Navigation Border' : 'rgba(16,16,16,0.9)', | |
'Links' : 'rgb(187,187,187)', | |
'Hovered Links' : 'rgb(0,223,252)', | |
'Navigation Links' : 'rgb(153,153,153)', | |
'Hovered Navigation Links' : 'rgb(0,223,252)', | |
'Subjects' : 'rgb(170,170,170)', | |
'Names' : 'rgb(128,172,206)', | |
'Sage' : 'rgb(153,153,153)', | |
'Tripcodes' : 'rgb(128,172,206)', | |
'Emails' : 'rgb(187,187,187)', | |
'Post Numbers' : 'rgb(153,153,153)', | |
'Text' : 'rgb(153,153,153)', | |
'Quotelinks' : 'rgb(212,212,212)', | |
'Backlinks' : 'rgb(212,212,212)', | |
'Greentext' : 'rgb(152,185,98)', | |
'Board Title' : 'rgb(153,153,153)', | |
'Timestamps' : 'rgb(153,153,153)', | |
'Inputs' : 'rgb(153,153,153)', | |
'Warnings' : 'rbg(187,187,187)', | |
'Shadow Color' : 'rgba(20,20,20,0.9)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '#options{background-color: rgba(16,16,16,1) !important;}#delform blockquote {border-radius:3px;color:#bbb;background:#343838;padding:8px;box-shadow:0px 0px 20px rgba(25,25,25,0.6);border:1px solid #343838;border-bottom:2px solid #444848;border-radius:0px 6px 6px 6px;padding-top:15px;}.name {font-weight:800;}.nameBlock > .useremail > .name:hover,.nameBlock> .useremail> .postertrip:hover {color:#00dffc;}a.forwardlink {color:#608cae;font-weight:800;}div.reply,.reply.highlight{padding:0;}#qp div.post{border:1px solid rgba(128,172,206,0.5) !important;background-color:rgba(24,24,24,0.9) !important;}.name,.postertrip {text-shadow:0px 0px 6px rgba(20,20,20,0.9);font-weight:bold;background:#343838;border:1px solid #343838;border-radius:5px 5px 0px 0px;padding:4px 6px;padding-top:2px;}#delform,#delform blockquote {margin:0 10px 15px 0 !important;padding:0px;}a{-moz-transition:all 0.5s ease;-webkit-transition:all 0.5s ease;-o-transition:all 0.5s ease;}a.pointer{font-weight:bold;font-weight:normal;color:#777;padding-right:5px;}#delform .opContainer,#delform .replyContainer {opacity:0.45;-moz-transition:all 0.5s ease;-webkit-transition:all 0.5s ease;-o-transition:all 0.5s ease;}#delform .opContainer:hover,#delform .replyContainer:hover{opacity:1;}.reply,.reply.highlight{background:transparent;border:0px;padding:0px;padding-bottom:0px;border-radius:6px;}#delform blockquote{padding:5px;background:#343838;margin-top:0px;min-height:20px;padding-top:10px;clear:none;}#delform .file + blockquote{margin-top:-16px !important;padding-left:150px !important;}a.backlink{border:1px solid #343838;border-radius:5px 5px 0px 0px;background:#343838;padding:2px 4px 2px;text-decoration:none;}a.forwardlink{color:#608CAE;text-shadow:0 0 6px rgba(96,140,174,0.8);}.subject{font-weight: bold;letter-spacing: 3px;background: transparent;}div.reply,div.reply.highlight {background-color: rgba(0,0,0,0) !important;border: none !important;}#qp div.post .name,#qp div.post a.backlink,#qp div.post blockquote {background:none !important;border:none !important;box-shadow:none !important;border-radius:0px; !important}' | |
'Photon': | |
'Author' : 'seaweed-chan', | |
'Author Tripcode' : '!POMF.9waa', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(238,238,238,1)', | |
'Dialog Background' : 'rgba(238,238,238,1)', | |
'Dialog Border' : 'rgba(204,204,204,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(221,221,221,1)', | |
'Reply Border' : 'rgba(204,204,204,1)', | |
'Highlighted Reply Background': 'rgba(204,204,204,1)', | |
'Highlighted Reply Border' : 'rgba(204,204,204,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(255,255,238)', | |
'Checkbox Border' : 'rgba(255,255,238)', | |
'Checkbox Checked Background' : 'rgba(188,192,212)', | |
'Input Background' : 'rgba(255,255,255,1)', | |
'Input Border' : 'rgba(204,204,204,1)', | |
'Hovered Input Background' : 'rgba(204,204,204,1)', | |
'Hovered Input Border' : 'rgba(204,204,204,1)', | |
'Focused Input Background' : 'rgba(255,255,255,1)', | |
'Focused Input Border' : 'rgba(0,74,153,1)', | |
'Buttons Background' : 'rgba(255,255,238,1)', | |
'Buttons Border' : 'rgba(204,204,204,1)', | |
'Navigation Background' : 'rgba(238,238,238,0.9)', | |
'Navigation Border' : 'rgba(238,238,238,0.9)', | |
'Links' : 'rgb(255,102,0)', | |
'Hovered Links' : 'rgb(255,51,0)', | |
'Navigation Links' : 'rgb(17,17,17)', | |
'Hovered Navigation Links' : 'rgb(255,51,0)', | |
'Subjects' : 'rgb(17,17,17)', | |
'Names' : 'rgb(0,74,153)', | |
'Sage' : 'rgb(51,51,51)', | |
'Tripcodes' : 'rgb(255,51,0)', | |
'Emails' : 'rgb(255,102,0)', | |
'Post Numbers' : 'rgb(51,51,51)', | |
'Text' : 'rgb(51,51,51)', | |
'Quotelinks' : 'rgb(17,17,17)', | |
'Backlinks' : 'rgb(17,17,17)', | |
'Greentext' : 'rgb(120,153,34)', | |
'Board Title' : 'rgb(0,74,153)', | |
'Timestamps' : 'rgb(51,51,51)', | |
'Inputs' : 'rgb(0,0,0)', | |
'Warnings' : 'rbg(51,51,51)', | |
'Shadow Color' : 'rgba(128,128,128,0.5)', | |
'Dark Theme' : '0', | |
'Custom CSS' : '.fileText{color: rgb(102,102,102);}#qp div.post{background-color:rgba(21,21,21,0.8);color:#FFF;border:1px solid rgba(0,0,0,1);}#qp div.post .subject,#qp div.post .name,#qp div.post .container a,#qp div.post .postNum a{color:#FFF;}#qp div.post .postertrip{color:#999;}#qp div.post .fileText{color:#999;}span.quote > a.quotelink{color: #ff6600;}' | |
'RedUX': | |
'Author' : 'Zixaphir', | |
'Author Tripcode' : '!VGsTHECURE', | |
'Background Image' : 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAACEAQMAAABrihHkAAAABlBMVEX///8AAABVwtN+AAAAAnRSTlMASuCaZbYAAAA+SURBVHhe7c2xCQAgDAXRKywsHcFRdDNxchtBkhHk4Lp88ui7hhaztBCkyYZ7fFHzI/Jk/GRpaWlpaWlpaR3scHNQSY3kigAAAABJRU5ErkJggg=="), radial-gradient(rgb(190,0,0), rgb(15,0,0))', | |
'Background Attachment' : 'scroll, fixed', | |
'Background Position' : 'center, center', | |
'Background Repeat' : 'repeat, no-repeat', | |
'Background Color' : 'rgba(0,0,0,0,1)', | |
'Thread Wrapper Background' : 'linear-gradient(rgb(220,210,210), rgb(240,240,240) 400px, rgb(240,240,240))', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Dialog Background' : 'rgba(238,242,255,1)', | |
'Dialog Border' : 'rgba(238,242,255,1)', | |
'Reply Background' : 'rgba(240,240,240,1)', | |
'Reply Border' : 'rgba(204,204,204,1)', | |
'Highlighted Reply Background': 'rgba(219,219,219,1)', | |
'Highlighted Reply Border' : 'rgba(219,219,219,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Input Background' : 'rgba(255,255,255,1)', | |
'Input Border' : 'rgba(255,255,255,1)', | |
'Hovered Input Background' : 'rgba(214,186,208,1)', | |
'Hovered Input Border' : 'rgba(214,186,208,1)', | |
'Focused Input Background' : 'rgba(255,255,255,1)', | |
'Focused Input Border' : 'rgba(153,136,238,1)', | |
'Checkbox Background' : 'rgba(238,242,255,1)', | |
'Checkbox Checked Background' : 'rgba(188,192,212,1)', | |
'Checkbox Border' : 'rgba(153,51,51,1)', | |
'Buttons Background' : 'rgba(255,255,255,1)', | |
'Buttons Border' : 'rgba(255,255,255,1)', | |
'Navigation Background' : 'rgba(0,0,0,0.7)', | |
'Navigation Border' : 'rgba(0,0,0,0.7)', | |
'Quotelinks' : 'rgb(153,51,51)', | |
'Backlinks' : 'rgb(153,51,51)', | |
'Links' : 'rgb(87,87,123)', | |
'Hovered Links' : 'rgb(221,0,0)', | |
'Navigation Links' : 'rgb(238,187,204)', | |
'Hovered Navigation Links' : 'rgb(255,119,119)', | |
'Names' : 'rgb(119,51,51)', | |
'Tripcodes' : 'rgb(119,51,51)', | |
'Emails' : 'rgb(87,87,123)', | |
'Subjects' : 'rgb(15,12,93)', | |
'Text' : 'rgb(0,0,0)', | |
'Inputs' : 'rgb(0,0,0)', | |
'Post Numbers' : 'rgb(0,0,0)', | |
'Greentext' : 'rgb(120,153,34)', | |
'Sage' : 'rgb(87,87,123)', | |
'Board Title' : 'rgb(238,187,204)', | |
'Timestamps' : 'rgb(0,0,0)', | |
'Warnings' : 'rbg(87,87,123)', | |
'Shadow Color' : 'rgba(60,60,60,0.6)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '.replyContainer > .reply {background-color: transparent; border: 0; border-bottom: 1px #ccc solid;} #qp div.post { background-color: rgba(0,0,0,0.7); border-color: rgba(0,0,0,0.7); } #qp div.post, #qp .postNum a { color: #fcd; } #qp .nameBlock > .useremail > .name, #qp .nameBlock > .useremail > .postertrip, #qp .name, #qp .postertrip, #qp .trip { color: #ffaac0; } #qp a { color: #aaaac8; } .boardBanner a, #qp a.backlink, #qp span.quote > a.quotelink { color: rgb(255,255,255); } #updater:not(:hover), #updater:not(:hover) #count:not(.new)::after, #stats { color: rgb(123,123,123); } .boardBanner {color: rgb(238,187,204)} .boardTitle { text-shadow: 1px 1px 1px #222; } #delform { padding: 1px 15px 2px 15px; box-shadow: 0 20px 15px 20px rgba(0,0,0,0.7); border-radius: 4px; }' | |
'Solarized': | |
'Author' : 'ubuntufriend', | |
'Author Tripcode' : '!TRip.C0d', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(7,54,66,1)', | |
'Dialog Background' : 'rgba(0,43,54,1)', | |
'Dialog Border' : 'rgba(0,43,54,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(0,43,54,1)', | |
'Reply Border' : 'rgba(0,43,54,1)', | |
'Highlighted Reply Background': 'rgba(7,54,66,1)', | |
'Highlighted Reply Border' : 'rgba(7,54,66,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(88,110,117,1)', | |
'Checkbox Border' : 'rgba(88,110,117,1)', | |
'Checkbox Checked Background' : 'rgba(17,17,17,1)', | |
'Input Background' : 'rgba(0,43,54,1)', | |
'Input Border' : 'rgba(0,43,54,1)', | |
'Hovered Input Background' : 'rgba(7,54,66,1)', | |
'Hovered Input Border' : 'rgba(7,54,66,1)', | |
'Focused Input Background' : 'rgba(7,54,66,1)', | |
'Focused Input Border' : 'rgba(7,54,66,1)', | |
'Buttons Background' : 'rgba(0,43,54,1)', | |
'Buttons Border' : 'rgba(0,43,54,1)', | |
'Navigation Background' : 'rgba(7,54,66,1)', | |
'Navigation Border' : 'rgba(7,54,66,1)', | |
'Links' : 'rgb(108,113,196)', | |
'Hovered Links' : 'rgb(211,54,130)', | |
'Navigation Links' : 'rgb(147,161,161)', | |
'Hovered Navigation Links' : 'rgb(211,54,130)', | |
'Subjects' : 'rgb(203,75,22)', | |
'Names' : 'rgb(88,110,117)', | |
'Sage' : 'rgb(108,113,196)', | |
'Tripcodes' : 'rgb(42,161,152)', | |
'Emails' : 'rgb(108,113,196)', | |
'Post Numbers' : 'rgb(147,161,161)', | |
'Text' : 'rgb(147,161,161)', | |
'Quotelinks' : 'rgb(79,95,143)', | |
'Backlinks' : 'rgb(79,95,143)', | |
'Greentext' : 'rgb(133,153,0)', | |
'Board Title' : 'rgb(147,161,161)', | |
'Timestamps' : 'rgb(147,161,161)', | |
'Inputs' : 'rgb(147,161,161)', | |
'Warnings' : 'rbg(108,113,196)', | |
'Shadow Color' : 'rgba(0,0,0,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '#qp div.post{background-color:rgba(7,54,66,0.9);border:1px solid rgba(79,95,143,0.9);}' | |
'Yotsuba': | |
'Author' : 'moot', | |
'Author Tripcode' : '!Ep8pui8Vw2', | |
'Background Image' : 'linear-gradient(rgb(254,214,175), rgb(255,255,238) 200px, rgb(255,255,238))', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(255,255,238,1)', | |
'Dialog Background' : 'rgba(240,224,214,1)', | |
'Dialog Border' : 'rgba(217,191,183,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(240,224,214,1)', | |
'Reply Border' : 'rgba(217,191,183,1)', | |
'Highlighted Reply Background': 'rgba(240,192,176,1)', | |
'Highlighted Reply Border' : 'rgba(217,191,183,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(255,255,238,1)', | |
'Checkbox Border' : 'rgba(217,191,183,1)', | |
'Checkbox Checked Background' : 'rgba(255,255,238,1)', | |
'Input Background' : 'rgba(240,224,214,1)', | |
'Input Border' : 'rgba(217,191,183,1)', | |
'Hovered Input Background' : 'rgba(240,224,214,1)', | |
'Hovered Input Border' : 'rgba(217,191,183,1)', | |
'Focused Input Background' : 'rgba(255,255,255,1)', | |
'Focused Input Border' : 'rgba(128,0,0,1)', | |
'Buttons Background' : 'rgba(240,192,176,1)', | |
'Buttons Border' : 'rgba(217,191,183,1)', | |
'Navigation Background' : 'rgba(240,192,176,0.7)', | |
'Navigation Border' : 'rgba(217,191,183,1)', | |
'Links' : 'rgb(186,0,0)', | |
'Hovered Links' : 'rgb(221,0,0)', | |
'Navigation Links' : 'rgb(128,0,0)', | |
'Hovered Navigation Links' : 'rgb(221,0,0)', | |
'Subjects' : 'rgb(204,17,5)', | |
'Names' : 'rgb(17,119,67)', | |
'Sage' : 'rgb(204,17,17)', | |
'Tripcodes' : 'rgb(34,136,84)', | |
'Emails' : 'rgb(186,0,0)', | |
'Post Numbers' : 'rgb(128,0,0)', | |
'Text' : 'rgb(128,0,0)', | |
'Quotelinks' : 'rgb(221,0,0)', | |
'Backlinks' : 'rgb(220,0,0)', | |
'Greentext' : 'rgb(120,153,34)', | |
'Board Title' : 'rgb(204,17,5)', | |
'Timestamps' : 'rgb(186,0,0)', | |
'Inputs' : 'rgb(0,0,0)', | |
'Warnings' : 'rbg(128,0,0)', | |
'Shadow Color' : 'rgba(119,46,40,1)', | |
'Dark Theme' : '0', | |
'Custom CSS' : '#qp div.post{background-color:rgba(240,192,176,1);box-shadow:5px 5px 5px rgba(128,128,128,0.5);}' | |
'Yotsuba B': | |
'Author' : 'moot', | |
'Author Tripcode' : '!Ep8pui8Vw2', | |
'Background Image' : 'linear-gradient(rgb(209,213,238), rgb(238,242,255) 200px, rgb(255,255,238))', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(238,242,255,1)', | |
'Dialog Background' : 'rgba(214,218,240,1)', | |
'Dialog Border' : 'rgba(183,197,217,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(214,218,240,1)', | |
'Reply Border' : 'rgba(183,197,217,1)', | |
'Highlighted Reply Background': 'rgba(214,186,208,1)', | |
'Highlighted Reply Border' : 'rgba(183,197,217,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141)', | |
'Checkbox Background' : 'rgba(238,242,255,1)', | |
'Checkbox Border' : 'rgba(183,197,217,1)', | |
'Checkbox Checked Background' : 'rgba(188,192,212,1)', | |
'Input Background' : 'rgba(238,242,255,1)', | |
'Input Border' : 'rgba(183,197,217,1)', | |
'Hovered Input Background' : 'rgba(214,186,208,1)', | |
'Hovered Input Border' : 'rgba(183,197,217,1)', | |
'Focused Input Background' : 'rgba(214,218,240,1)', | |
'Focused Input Border' : 'rgba(153,136,238,1)', | |
'Buttons Background' : 'rgba(214,218,240,1)', | |
'Buttons Border' : 'rgba(183,197,217,1)', | |
'Navigation Background' : 'rgba(211,215,238,0.7)', | |
'Navigation Border' : 'rgba(183,197,217,1)', | |
'Links' : 'rgb(52,52,92)', | |
'Hovered Links' : 'rgb(221,0,0)', | |
'Navigation Links' : 'rgb(0,0,0)', | |
'Hovered Navigation Links' : 'rgb(221,0,0)', | |
'Subjects' : 'rgb(15,12,93)', | |
'Names' : 'rgb(17,119,67)', | |
'Sage' : 'rgb(153,0,0)', | |
'Tripcodes' : 'rgb(34,136,84)', | |
'Emails' : 'rgb(87,87,123)', | |
'Post Numbers' : 'rgb(0,0,0)', | |
'Text' : 'rgb(0,0,0)', | |
'Quotelinks' : 'rgb(221,0,0)', | |
'Backlinks' : 'rgb(52,52,92)', | |
'Greentext' : 'rgb(120,153,34)', | |
'Board Title' : 'rgb(175,10,15)', | |
'Timestamps' : 'rgb(0,0,0)', | |
'Inputs' : 'rgb(0,0,0)', | |
'Warnings' : 'rbg(87,87,123)', | |
'Shadow Color' : 'rgba(128,128,128,0.5)', | |
'Dark Theme' : '0', | |
'Custom CSS' : '#qp div.post{background-color:rgba(214,186,208,1);box-shadow:5px 5px 5px rgba(128,128,128,0.5);}' | |
'Zenburned': | |
'Author' : 'lazy', | |
'Author Tripcode' : '!HONKYn7h1.', | |
'Background Image' : '', | |
'Background Attachment' : '', | |
'Background Position' : '', | |
'Background Repeat' : '', | |
'Background Color' : 'rgba(63,63,63,1)', | |
'Dialog Background' : 'rgba(87,87,87,1)', | |
'Dialog Border' : 'rgba(87,87,87,1)', | |
'Thread Wrapper Background' : 'rgba(0,0,0,0)', | |
'Thread Wrapper Border' : 'rgba(0,0,0,0)', | |
'Reply Background' : 'rgba(87,87,87,1)', | |
'Reply Border' : 'rgba(87,87,87,1)', | |
'Highlighted Reply Background': 'rgba(38,38,38,1)', | |
'Highlighted Reply Border' : 'rgba(38,38,38,1)', | |
'Backlinked Reply Outline' : 'rgba(98,124,141,1)', | |
'Checkbox Background' : 'rgba(63,63,63,1)', | |
'Checkbox Border' : 'rgba(136,136,136,1)', | |
'Checkbox Checked Background' : 'rgba(17,17,17,1)', | |
'Input Background' : 'rgba(87,87,87,1)', | |
'Input Border' : 'rgba(136,136,136,1)', | |
'Hovered Input Background' : 'rgba(38,38,38,1)', | |
'Hovered Input Border' : 'rgba(38,38,38,1)', | |
'Focused Input Background' : 'rgba(38,38,38,1)', | |
'Focused Input Border' : 'rgba(153,136,238,1)', | |
'Buttons Background' : 'rgba(49,60,54,1)', | |
'Buttons Border' : 'rgba(136,136,136,1)', | |
'Navigation Background' : 'rgba(63,63,63,0.9)', | |
'Navigation Border' : 'rgba(63,63,63,0.9)', | |
'Links' : 'rgb(239,220,188)', | |
'Hovered Links' : 'rgb(248,248,147)', | |
'Navigation Links' : 'rgb(220,220,204)', | |
'Hovered Navigation Links' : 'rgb(248,248,147)', | |
'Subjects' : 'rgb(170,170,170)', | |
'Names' : 'rgb(192,190,209)', | |
'Sage' : 'rgb(220,220,204)', | |
'Tripcodes' : 'rgb(140,208,211)', | |
'Emails' : 'rgb(239,220,188)', | |
'Post Numbers' : 'rgb(220,220,204)', | |
'Text' : 'rgb(220,220,204)', | |
'Quotelinks' : 'rgb(220,163,163)', | |
'Backlinks' : 'rgb(220,163,163)', | |
'Greentext' : 'rgb(127,159,127)', | |
'Board Title' : 'rgb(220,220,204)', | |
'Timestamps' : 'rgb(220,220,204)', | |
'Inputs' : 'rgb(220,220,204)', | |
'Warnings' : 'rbg(239,220,188)', | |
'Shadow Color' : 'rgba(63,63,63,0.4)', | |
'Dark Theme' : '1', | |
'Custom CSS' : '' | |
Mascots = | |
'Akiyama_Mio': | |
category: 'SFW' | |
image: 'http://i.imgur.com/MdE9K.png' | |
'Akiyama_Mio_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/pBuG2.png' | |
'Akiyama_Mio_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/s3ffU.png' | |
center: true | |
'Akiyama_Mio_sitting': | |
category: 'SFW' | |
image: 'http://i.imgur.com/0x4Rr.png' | |
center: true | |
'Anime_Girl_in_Bondage': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/B3h3c.png' | |
center: true | |
'Anime_Girl_in_Bondage_2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/1TUjP.png' | |
'Applejack': | |
category: 'MLP' | |
image: 'http://i.imgur.com/1ufSL.png' | |
center: true | |
'Asuka_Langley_Soryu': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Wj7s7.png' | |
center: true | |
'Asuka_Langley_Soryu_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/FMbEx.png' | |
center: true | |
'Asuka_Langley_Soryu_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/qunYm.png' | |
center: true | |
'Asuka_Langley_Soryu_4': | |
category: 'SFW' | |
image: 'http://i.imgur.com/MvYCD.png' | |
center: true | |
'Asuka_Langley_Soryu_5': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/1rDfl.png' | |
center: true | |
'Ayanami_Rei': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Ye6OS.png' | |
center: true | |
'Ayase_Yue': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/i2MT8.png' | |
'Ayase': | |
category: 'SFW' | |
image: 'http://i.imgur.com/4Ehza.png' | |
center: true | |
'Ayase_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/48nYb.png' | |
center: true | |
'BLACK_ROCK_SHOOTER': | |
category: 'SFW' | |
image: 'http://i.imgur.com/VidKo.png' | |
center: true | |
'Blue_Rose': | |
category: 'SFW' | |
image: 'http://i.imgur.com/q5RwP.png' | |
center: true | |
'Brioche_d_Arquien': | |
category: 'SFW' | |
image: 'http://i.imgur.com/rxTsB.png' | |
center: true | |
'CC': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Ir1v3.png' | |
center: true | |
'CC2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/AU1H8.png' | |
center: true | |
'Chie': | |
category: 'SFW' | |
image: 'http://i.imgur.com/E3ge5.png' | |
center: true | |
'Cirno': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/q735W.png' | |
center: true | |
'Cirno_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/dLrCn.png' | |
center: true | |
'Dawn_Hikari': | |
category: 'SFW' | |
image: 'http://i.imgur.com/lxLdH.png' | |
center: true | |
'Doppleganger': | |
category: 'SFW' | |
image: 'http://i.imgur.com/w3GwS.png' | |
position: 'bottom' | |
'Dragonkid': | |
category: 'SFW' | |
image: 'http://i.imgur.com/tEoVT.png' | |
center: true | |
'Dragonkid_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/3tIpW.png' | |
center: true | |
'Eclair': | |
category: 'SFW' | |
image: 'http://i.imgur.com/DEh76.png' | |
center: true | |
'Erio_Touwa': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/zhPlM.png' | |
'Evangeline_AK_McDowell': | |
category: 'SFW' | |
image: 'http://i.imgur.com/cRhjg.png' | |
center: true | |
'Fluttershy': | |
category: 'MLP' | |
image: 'http://i.imgur.com/x88ZT.png' | |
center: true | |
'Fluttershy_2': | |
category: 'MLP' | |
image: 'http://i.imgur.com/hokhQ.png' | |
center: true | |
'Fluttershy_Cutiemark': | |
category: 'MLP' | |
image: 'http://i.imgur.com/vBqiB.png' | |
center: true | |
'Fujiwara_no_Mokou': | |
category: 'SFW' | |
image: 'http://i.imgur.com/NaKmF.png' | |
'Furudo_Erika': | |
category: 'SFW' | |
image: 'http://i.imgur.com/zLsPY.png' | |
center: true | |
'Gally': | |
category: 'SFW' | |
image: 'http://i.imgur.com/PJSjp.png' | |
center: true | |
position: 'bottom' | |
'Gasai_Yuno': | |
category: 'SFW' | |
image: 'http://i.imgur.com/iG1F2.png' | |
'Gasai_Yuno_2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/XI0DZ.png' | |
center: true | |
'George_Costanza': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Nnsrf.png' | |
'Golden_Darkness': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/lYGo4.png' | |
position: 'bottom' | |
'Hanako': | |
category: 'SFW' | |
image: 'http://i.imgur.com/G3iN7.png' | |
center: true | |
'Hasekura_Youko': | |
category: 'SFW' | |
image: 'http://i.imgur.com/qTQqY.png' | |
center: true | |
'Hatsune_Miku': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/lKQHW.png' | |
center: true | |
'Hatsune_Miku_2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/ULksz.png' | |
center: true | |
'Hatsune_Miku_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/H1pgZ.png' | |
center: true | |
position: 'bottom' | |
'Hatsune_Miku_4': | |
category: 'SFW' | |
image: 'http://i.imgur.com/vE3FJ.png' | |
center: true | |
'Hatsune_Miku_5': | |
category: 'SFW' | |
image: 'http://i.imgur.com/yCB6B.png' | |
center: true | |
'Hatsune_Miku_6': | |
category: 'SFW' | |
image: 'http://i.imgur.com/DKGQb.png' | |
center: true | |
'Hatsune_Miku_7': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/vc7rU.png' | |
center: true | |
'Hirasawa_Yui': | |
category: 'SFW' | |
image: 'http://i.imgur.com/sL1Uo.png' | |
center: true | |
'Homura_Akemi': | |
category: 'SFW' | |
image: 'http://i.imgur.com/b9KmB.png' | |
'Horo': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/6f8wd.png' | |
position: 'bottom' | |
center: true | |
'Horo_2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/KJLui.png' | |
center: true | |
'Horo_3': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/HMpug.png' | |
center: true | |
'Horo_4': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/PKfl4.png' | |
center: true | |
'Horo_5': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/BjV3U.png' | |
center: true | |
'Horo_6': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/8fcrD.png' | |
center: true | |
'Ika_Musume': | |
category: 'SFW' | |
image: 'http://i.imgur.com/rKT7L.png' | |
center: true | |
'Ika_Musume_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/uUhGG.png' | |
center: true | |
'Ika_Musume_3': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/PeckP.png' | |
center: true | |
'Inori': | |
category: 'SFW' | |
image: 'http://i.imgur.com/u4zuy.png' | |
'Iwakura_Lain': | |
category: 'SFW' | |
image: 'http://i.imgur.com/AfjG9.png' | |
center: true | |
'Iwakura_Lain_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/hIBLa.png' | |
center: true | |
'KOn_Girls': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Sc1Pa.png' | |
center: true | |
'Kagamine_Rin': | |
category: 'SFW' | |
image: 'http://i.imgur.com/fXXd2.png' | |
center: true | |
'Kagari_Izuriha': | |
category: 'SFW' | |
image: 'http://i.imgur.com/coMey.png' | |
'Kaname_Madoka': | |
category: 'SFW' | |
image: 'http://i.imgur.com/4PHsl.png' | |
center: true | |
'Karina': | |
category: 'SFW' | |
image: 'http://i.imgur.com/5oPTS.png' | |
center: true | |
'Kigurumi_Harokitei': | |
category: 'SFW' | |
image: 'http://i.imgur.com/4JV1K.png' | |
center: true | |
'Kinomoto_Sakura': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Ve0hl.png' | |
center: true | |
'Kinomoto_Sakura_2': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/Rdk9s.png' | |
center: true | |
'Kirino_Kosaka_and_Ruri_Goko': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/MGaLr.png' | |
center: true | |
'Koiwai_Yotsuba': | |
category: 'SFW' | |
image: 'http://i.imgur.com/1MyDM.png' | |
center: true | |
'Koko': | |
category: 'SFW' | |
image: 'http://i.imgur.com/QMDHh.png' | |
center: true | |
'Kotobuki_Tsumugi': | |
category: 'SFW' | |
image: 'http://i.imgur.com/fzhbH.png' | |
center: true | |
'Kurisu_Makise': | |
category: 'SFW' | |
image: 'http://i.imgur.com/HKnLt.png' | |
'Kyouko_Sakura': | |
category: 'SFW' | |
image: 'http://i.imgur.com/78HS9.png' | |
center: true | |
'Kyubee': | |
category: 'SFW' | |
image: 'http://i.imgur.com/PW5gt.png' | |
'Kyubee_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/fuTo9.png' | |
center: true | |
'Leonmitchelli': | |
category: 'SFW' | |
image: 'http://i.imgur.com/KkOsZ.png' | |
center: true | |
'Li_Syaoran': | |
category: 'SFW' | |
image: 'http://i.imgur.com/GySuy.png' | |
'Link': | |
category: 'SFW' | |
image: 'http://i.imgur.com/OyTWU.png' | |
center: true | |
'Lizardgirl': | |
category: 'SFW' | |
image: 'http://i.imgur.com/R8zzn.png' | |
position: 'bottom' | |
'Luka': | |
category: 'SFW' | |
image: 'http://i.imgur.com/WUIMw.png' | |
position: 'bottom' | |
'Madotsuki': | |
category: 'SFW' | |
image: 'http://i.imgur.com/J1i26.png' | |
position: 'bottom' | |
'Makoto': | |
category: 'SFW' | |
image: 'http://i.imgur.com/H2Nmv.png' | |
center: true | |
'Mantis': | |
category: 'SFW' | |
image: 'http://i.imgur.com/kQs3e.png' | |
'Megurine_Luka': | |
category: 'SFW' | |
image: 'http://i.imgur.com/BxybK.png' | |
center: true | |
'Mei_Sunohara': | |
category: 'SFW' | |
image: 'http://i.imgur.com/zZ5pE.png' | |
center: true | |
'Millefiori': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Ygq8D.png' | |
center: true | |
'Millefiori_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/00gF5.png' | |
center: true | |
'Millefiori_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/bl7RT.png' | |
center: true | |
'Misaki_Mei': | |
category: 'SFW' | |
image: 'http://i.imgur.com/zVKmv.png' | |
center: true | |
'Mizunashi_Akari': | |
category: 'SFW' | |
image: 'http://i.imgur.com/LF8wU.png' | |
center: true | |
'Motoko': | |
category: 'SFW' | |
image: 'http://i.imgur.com/FZD2l.png' | |
center: true | |
'Nagato_Yuki': | |
category: 'SFW' | |
image: 'http://i.imgur.com/ucnzg.png' | |
center: true | |
'Nagato_Yuki_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/J2eZi.png' | |
center: true | |
'Nagato_Yuki_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/sUSvI.png' | |
center: true | |
'Nagato_Yuki_4': | |
category: 'SFW' | |
image: 'http://i.imgur.com/atnqf.png' | |
center: true | |
'Nagato_Yuki_5': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/2BUww.png' | |
position: 'bottom' | |
center: true | |
'Nagato_Yuki_6': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/uR35P.png' | |
center: true | |
'Nagato_Yuki_7': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/aGFCl.png' | |
center: true | |
'Nagato_Yuki_8': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/L9ZAT.png' | |
center: true | |
'Nagato_Yuki_9': | |
category: 'Silhouette' | |
image: 'http://i.imgur.com/MwoI9.png' | |
center: true | |
'Nakano_Azusa': | |
category: 'SFW' | |
image: 'http://i.imgur.com/6c3p3.png' | |
'Nichijou': | |
category: 'SFW' | |
image: 'http://i.imgur.com/w5oaQ.png' | |
'Noir_VinoCacao': | |
category: 'SFW' | |
image: 'http://i.imgur.com/VNbnm.png' | |
center: true | |
'Pinkie_Pie': | |
category: 'MLP' | |
image: 'http://i.imgur.com/rY3w4.png' | |
center: true | |
'Pinkie_Pie_2': | |
category: 'MLP' | |
image: 'http://i.imgur.com/zy6rO.png' | |
center: true | |
'Oshino_Shinobu': | |
category: 'SFW' | |
image: 'http://i.imgur.com/UMq6v.png' | |
'Oshino_Shinobu_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/ObCSS.png' | |
position: 'bottom' | |
'Patchouli_Knowledge': | |
category: 'SFW' | |
image: 'http://i.imgur.com/QoKJb.png' | |
center: true | |
'Patchouli_Knowledge_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/dK9Pn.png' | |
center: true | |
'Pink_Doggy': | |
category: 'SFW' | |
image: 'http://i.imgur.com/zv5BS.png' | |
center: true | |
'Pink_Hair': | |
category: 'SFW' | |
image: 'http://i.imgur.com/1r1Vx.png' | |
center: true | |
'Pixie': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/KPGKl.png' | |
center: true | |
'Railgun': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/AEjIx.png' | |
center: true | |
'Railgun_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/KD6s7.png' | |
center: true | |
'Railgun_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/7wJEW.png' | |
'Railgun_4': | |
category: 'SFW' | |
image: 'http://i.imgur.com/xM4rx.png' | |
center: true | |
'Rainbow_Dash': | |
category: 'MLP' | |
image: 'http://i.imgur.com/Zf3eQ.png' | |
center: true | |
'Rarity': | |
category: 'MLP' | |
image: 'http://i.imgur.com/Mbhf7.png' | |
center: true | |
'Revi': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Cn4nK.png' | |
position: 'bottom' | |
center: true | |
'Ruri_Gokou': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Ht6dr.png' | |
position: 'bottom' | |
center: true | |
'Ryuu': | |
category: 'SFW' | |
image: 'http://i.imgur.com/LFUs2.png' | |
position: 'bottom' | |
'Saber': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/6AIbD.png' | |
position: 'bottom' | |
center: true | |
'Sakurazaki_Setsuna': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/QLChr.png' | |
'Samus_Aran': | |
category: 'SFW' | |
image: 'http://i.imgur.com/34viJ.png' | |
center: true | |
'Seraphim': | |
category: 'SFW' | |
image: 'http://i.imgur.com/PA7pJ.png' | |
center: true | |
'Shana': | |
category: 'SFW' | |
image: 'http://i.imgur.com/JNS1z.png' | |
center: true | |
'Shana_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/K1mLx.png' | |
center: true | |
'Shiki': | |
category: 'SFW' | |
image: 'http://i.imgur.com/FKDcd.png' | |
'Shinji_and_Girls': | |
category: 'SFW' | |
image: 'http://i.imgur.com/k5Dsb.png' | |
center: true | |
'Shinonome_Hakase': | |
category: 'SFW' | |
image: 'http://i.imgur.com/TBHI6.png' | |
center: true | |
'Shirakiin_Ririchiyo': | |
category: 'SFW' | |
image: 'http://i.imgur.com/cXsO6.png' | |
position: 'bottom' | |
center: true | |
'Shirohibe': | |
category: 'SFW' | |
image: 'http://i.imgur.com/mfNR3.png' | |
position: 'bottom' | |
'Suruga_Kanbaru': | |
category: 'SFW' | |
image: 'http://i.imgur.com/rgcAA.png' | |
center: true | |
'Suzumiya_Haruhi': | |
category: 'SFW' | |
image: 'http://i.imgur.com/iVl5d.png' | |
center: true | |
'Suzumiya_Haruhi_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/rW9Q6.png' | |
center: true | |
'Tardis': | |
category: 'SFW' | |
image: 'http://goput.it/vig.png' | |
center: true | |
'Three_Wet_Girls': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/AbU8b.png' | |
center: true | |
'Tifa': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/SEKOZ.png' | |
center: true | |
'Tomozo_Kaoru': | |
category: 'SFW' | |
image: 'http://i.imgur.com/awpTb.png' | |
center: true | |
'Twilight_Sparkle': | |
category: 'MLP' | |
image: 'http://i.imgur.com/r5q9h.png' | |
center: true | |
'Udine': | |
category: 'SFW' | |
image: 'http://i.imgur.com/2QkXW.png' | |
position: 'bottom' | |
'Wanwan': | |
category: 'NSFW' | |
image: 'http://i.imgur.com/CcyTm.png' | |
position: 'bottom' | |
center: true | |
'White_Curious': | |
category: 'SFW' | |
image: 'http://i.imgur.com/2FQfj.png' | |
center: true | |
'Yin': | |
category: 'SFW' | |
image: 'http://i.imgur.com/haBSN.png' | |
'Yin_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/3zj9M.png' | |
center: true | |
'Yuzuki_Yukari': | |
category: 'SFW' | |
image: 'http://i.imgur.com/c8Lal.png' | |
center: true | |
'Yoko_Littner': | |
category: 'SFW' | |
image: 'http://i.imgur.com/3goQm.png' | |
position: 'bottom' | |
'Yoko_Littner_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/Pj9i5.png' | |
position: 'bottom' | |
center: true | |
'Yoko_Littner_3': | |
category: 'SFW' | |
image: 'http://i.imgur.com/0S2WK.png' | |
position: 'bottom' | |
center: true | |
'Yukkikaze': | |
category: 'SFW' | |
image: 'http://i.imgur.com/EkUXJ.png' | |
center: true | |
'Yukkihaze_2': | |
category: 'SFW' | |
image: 'http://i.imgur.com/oIKZb.png' | |
center: true | |
UI = | |
dialog: (id, position, html) -> | |
el = d.createElement 'div' | |
el.className = 'reply dialog' | |
el.innerHTML = html | |
el.id = id | |
el.style.cssText = localStorage.getItem("#{Main.namespace}#{id}.position") or position | |
el.querySelector('.move')?.addEventListener 'mousedown', UI.dragstart, false | |
el | |
dragstart: (e) -> | |
#prevent text selection | |
e.preventDefault() | |
UI.el = el = @parentNode | |
d.addEventListener 'mousemove', UI.drag, false | |
d.addEventListener 'mouseup', UI.dragend, false | |
# distance from pointer to el edge is constant; calculate it here. | |
rect = el.getBoundingClientRect() | |
UI.dx = e.clientX - rect.left | |
UI.dy = e.clientY - rect.top | |
UI.width = d.documentElement.clientWidth - rect.width | |
UI.height = d.documentElement.clientHeight - rect.height | |
drag: (e) -> | |
left = e.clientX - UI.dx | |
top = e.clientY - UI.dy | |
left = | |
if left < 10 then '0px' | |
else if UI.width - left < 10 then null | |
else left + 'px' | |
top = | |
if top < 10 then '0px' | |
else if UI.height - top < 10 then null | |
else top + 'px' | |
#using null instead of '' is 4% faster | |
#these 4 statements are 40% faster than 1 style.cssText | |
{style} = UI.el | |
style.left = left | |
style.top = top | |
style.right = if left is null then '0px' else null | |
style.bottom = if top is null then '0px' else null | |
dragend: -> | |
localStorage.setItem "#{Main.namespace}#{UI.el.id}.position", UI.el.style.cssText | |
d.removeEventListener 'mousemove', UI.drag, false | |
d.removeEventListener 'mouseup', UI.dragend, false | |
delete UI.el | |
hover: (e) -> | |
{clientX, clientY} = e | |
{style} = UI.el | |
{clientHeight, clientWidth} = d.documentElement | |
height = UI.el.offsetHeight | |
top = clientY - 120 | |
style.top = | |
if clientHeight <= height or top <= 0 | |
'0px' | |
else if top + height >= clientHeight | |
clientHeight - height + 'px' | |
else | |
top + 'px' | |
if clientX <= clientWidth - 400 | |
style.left = clientX + 45 + 'px' | |
style.right = null | |
else | |
style.left = null | |
style.right = clientWidth - clientX + 45 + 'px' | |
hoverend: -> | |
$.rm UI.el | |
delete UI.el | |
### | |
loosely follows the jquery api: | |
http://api.jquery.com/ | |
not chainable | |
### | |
$ = (selector, root=d.body) -> | |
root.querySelector selector | |
$.extend = (object, properties) -> | |
for key, val of properties | |
object[key] = val | |
return | |
$.extend $, | |
NBSP: '\u00A0' | |
SECOND: 1000 | |
MINUTE: 1000*60 | |
HOUR : 1000*60*60 | |
DAY : 1000*60*60*24 | |
log : if !console then console = unsafeWindow.console else console | |
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() | |
ready: (fc) -> | |
if /interactive|complete/.test d.readyState | |
# Execute the functions in parallel. | |
# If one fails, do not stop the others. | |
return setTimeout fc | |
cb = -> | |
$.off d, 'DOMContentLoaded', cb | |
fc() | |
$.on d, 'DOMContentLoaded', cb | |
sync: (key, cb) -> | |
$.on window, 'storage', (e) -> | |
cb JSON.parse e.newValue if e.key is "#{Main.namespace}#{key}" | |
id: (id) -> | |
d.getElementById id | |
formData: (arg) -> | |
if arg instanceof HTMLFormElement | |
fd = new FormData arg | |
else | |
fd = new FormData() | |
for key, val of arg | |
fd.append key, val if val | |
fd | |
ajax: (url, callbacks, opts={}) -> | |
#XXX `form` should be `data` | |
{type, headers, upCallbacks, form} = opts | |
r = new XMLHttpRequest() | |
r.overrideMimeType 'text/html' | |
type or= form and 'post' or 'get' | |
r.open type, url, true | |
for key, val of headers | |
r.setRequestHeader key, val | |
$.extend r, callbacks | |
$.extend r.upload, upCallbacks | |
r.send form | |
r | |
cache: (url, cb) -> | |
if req = $.cache.requests[url] | |
if req.readyState is 4 | |
cb.call req | |
else | |
req.callbacks.push cb | |
else | |
req = $.ajax url, | |
onload: -> cb.call @ for cb in @callbacks | |
onabort: -> delete $.cache.requests[url] | |
onerror: -> delete $.cache.requests[url] | |
req.callbacks = [cb] | |
$.cache.requests[url] = req | |
cb: | |
checked: -> | |
$.set @name, @checked | |
Conf[@name] = @checked | |
value: -> | |
$.set @name, @value.trim() | |
Conf[@name] = @value | |
addStyle: (css, identifier) -> | |
style = $.el 'style', | |
textContent: css | |
id: identifier | |
$.add d.head, style | |
style | |
x: (path, root=d.body) -> | |
d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null). | |
singleNodeValue | |
X: (path, root=d.body) -> | |
d.evaluate(path, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null) | |
addClass: (el, className) -> | |
el.classList.add className | |
rmClass: (el, className) -> | |
el.classList.remove className | |
rm: (el) -> | |
el.parentNode.removeChild el | |
tn: (s) -> | |
d.createTextNode s | |
nodes: (nodes) -> | |
# In (at least) Chrome, elements created inside different | |
# scripts/window contexts inherit from unequal prototypes. | |
# window_ext1.Node !== window_ext2.Node | |
unless nodes instanceof Array | |
return nodes | |
frag = d.createDocumentFragment() | |
for node in nodes | |
frag.appendChild node | |
frag | |
add: (parent, children) -> | |
parent.appendChild $.nodes children | |
prepend: (parent, children) -> | |
parent.insertBefore $.nodes(children), parent.firstChild | |
after: (root, el) -> | |
root.parentNode.insertBefore $.nodes(el), root.nextSibling | |
before: (root, el) -> | |
root.parentNode.insertBefore $.nodes(el), root | |
replace: (root, el) -> | |
root.parentNode.replaceChild $.nodes(el), root | |
el: (tag, properties) -> | |
el = d.createElement tag | |
$.extend el, properties if properties | |
el | |
on: (el, events, handler) -> | |
for event in events.split ' ' | |
el.addEventListener event, handler, false | |
return | |
off: (el, events, handler) -> | |
for event in events.split ' ' | |
el.removeEventListener event, handler, false | |
return | |
event: (el, e) -> | |
el.dispatchEvent e | |
globalEval: (code) -> | |
script = $.el 'script', textContent: "(#{code})()" | |
$.add d.head, script | |
$.rm script | |
shortenFilename: (filename, isOP) -> | |
# FILENAME SHORTENING SCIENCE: | |
# OPs have a +10 characters threshold. | |
# The file extension is not taken into account. | |
threshold = 30 + 10 * isOP | |
if filename.replace(/\.\w+$/, '').length > threshold | |
"#{filename[...threshold - 5]}(...)#{filename.match(/\.\w+$/)}" | |
else | |
filename | |
bytesToString: (size) -> | |
unit = 0 # Bytes | |
while size >= 1024 | |
size /= 1024 | |
unit++ | |
# Remove trailing 0s. | |
size = | |
if unit > 1 | |
# Keep the size as a float if the size is greater than 2^20 B. | |
# Round to hundredth. | |
Math.round(size * 100) / 100 | |
else | |
# Round to an integer otherwise. | |
Math.round size | |
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" | |
RandomAccessList: class | |
constructor: -> | |
@first = null | |
@last = null | |
@length = 0 | |
push: (id, el) -> | |
{last} = @ | |
@[id] = item = | |
prev: last | |
next: null | |
el: el | |
id: id | |
@last = item | |
if last | |
last.next = item | |
else | |
@first = item | |
@length++ | |
shift: -> | |
@rm @first.id | |
after: (root, item) -> | |
return if item.prev is root | |
@rmi item | |
{next} = root | |
root.next = item | |
item.prev = root | |
item.next = next | |
next.prev = item | |
rm: (id) -> | |
item = @[id] | |
return unless item | |
delete @[id] | |
@length-- | |
@rmi item | |
rmi: (item) -> | |
{prev, next} = item | |
if prev | |
prev.next = next | |
else | |
@first = next | |
if next | |
next.prev = prev | |
else | |
@last = prev | |
$.cache.requests = {} | |
$.extend $, | |
if GM_deleteValue? | |
delete: (name) -> | |
name = Main.namespace + name | |
GM_deleteValue name | |
get: (name, defaultValue) -> | |
name = Main.namespace + name | |
if value = GM_getValue name | |
JSON.parse value | |
else | |
defaultValue | |
set: (name, value) -> | |
name = Main.namespace + name | |
# for `storage` events | |
localStorage.setItem name, JSON.stringify value | |
GM_setValue name, JSON.stringify value | |
open: (url) -> | |
#https://github.com/scriptish/scriptish/wiki/GM_openInTab | |
#string url, bool loadInBackground, bool reuseTab | |
GM_openInTab location.protocol + url, true | |
else | |
delete: (name) -> | |
localStorage.removeItem Main.namespace + name | |
get: (name, defaultValue) -> | |
if value = localStorage.getItem Main.namespace + name | |
JSON.parse value | |
else | |
defaultValue | |
set: (name, value) -> | |
localStorage.setItem Main.namespace + name, JSON.stringify value | |
open: (url) -> | |
window.open location.protocol + url, '_blank' | |
$$ = (selector, root=d.body) -> | |
Array::slice.call root.querySelectorAll selector | |
Options = | |
init: -> | |
unless $.get 'firstrun' | |
$.set 'firstrun', true | |
# Prevent race conditions | |
Favicon.init() unless Favicon.el | |
Options.dialog() | |
for settings in ['navtopright', 'navbotright'] | |
a = $.el 'a', | |
href: 'javascript:;' | |
className: 'settingsWindowLink' | |
textContent: 'AppChan X Settings' | |
$.on a, 'click', -> | |
Options.dialog() | |
if Conf['Style'] | |
Style.allrice() | |
$.prepend $.id(settings), [$.tn('['), a, $.tn('] ')] | |
dialog: -> | |
if editMode | |
if confirm "Opening the options dialog will close and discard any theme changes made with the theme editor." | |
ThemeTools.close() | |
editMode = false | |
else | |
return | |
dialog = $.el 'div' | |
id: 'options' | |
className: 'reply dialog' | |
innerHTML: '<div id=optionsbar> | |
<div id=credits> | |
<label for=apply>Apply</label> | |
| <a target=_blank href=http://zixaphir.github.com/appchan-x/>AppChan X</a> | |
| <a target=_blank href=https://raw.github.com/zixaphir/appchan-x/master/changelog>' + Main.version + '</a> | |
| <a target=_blank href=http://zixaphir.github.com/appchan-x/#bug-report>Issues</a> | |
</div> | |
<div> | |
<label for=main_tab>Main</label> | |
| <label for=filter_tab>Filter</label> | |
| <label for=sauces_tab>Sauce</label> | |
| <label for=rice_tab>Rice</label> | |
| <label for=keybinds_tab>Keybinds</label> | |
| <label for=style_tab>Style</label> | |
| <label for=theme_tab>Themes</label> | |
| <label for=mascot_tab>Mascots</label> | |
</div> | |
</div> | |
<hr> | |
<div id=content> | |
<input type=radio name=tab hidden id=main_tab checked> | |
<div></div> | |
<input type=radio name=tab hidden id=sauces_tab> | |
<div> | |
<div class=warning><code>Sauce</code> is disabled.</div> | |
Lines starting with a <code>#</code> will be ignored.<br> | |
You can specify a certain display text by appending <code>;text:[text]</code> to the url. | |
<ul>These parameters will be replaced by their corresponding values: | |
<li>$1: Thumbnail url.</li> | |
<li>$2: Full image url.</li> | |
<li>$3: MD5 hash.</li> | |
<li>$4: Current board.</li> | |
</ul> | |
<textarea name=sauces id=sauces class=field></textarea> | |
</div> | |
<input type=radio name=tab hidden id=filter_tab> | |
<div> | |
<div class=warning><code>Filter</code> is disabled.</div> | |
<select name=filter> | |
<option value=guide>Guide</option> | |
<option value=name>Name</option> | |
<option value=uniqueid>Unique ID</option> | |
<option value=tripcode>Tripcode</option> | |
<option value=mod>Admin/Mod</option> | |
<option value=email>E-mail</option> | |
<option value=subject>Subject</option> | |
<option value=comment>Comment</option> | |
<option value=country>Country</option> | |
<option value=filename>Filename</option> | |
<option value=dimensions>Image dimensions</option> | |
<option value=filesize>Filesize</option> | |
<option value=md5>Image MD5 (uses exact string matching, not regular expressions)</option> | |
</select> | |
</div> | |
<input type=radio name=tab hidden id=rice_tab> | |
<div> | |
<div class=warning><code>Quote Backlinks</code> are disabled.</div> | |
<ul> | |
Backlink formatting | |
<li><input name=backlink class=field> : <span id=backlinkPreview></span></li> | |
</ul> | |
<div class=warning><code>Time Formatting</code> is disabled.</div> | |
<ul> | |
Time formatting | |
<li><input name=time class=field> : <span id=timePreview></span></li> | |
<li>Supported <a href=http://en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</li> | |
<li>Day: %a, %A, %d, %e</li> | |
<li>Month: %m, %b, %B</li> | |
<li>Year: %y</li> | |
<li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li> | |
<li>Minutes: %M</li> | |
<li>Seconds: %S</li> | |
</ul> | |
<div class=warning><code>File Info Formatting</code> is disabled.</div> | |
<ul> | |
File Info Formatting | |
<li><input name=fileInfo class=field> : <span id=fileInfoPreview class=fileText></span></li> | |
<li>Link (with original file name): %l (lowercase L, truncated), %L (untruncated)</li> | |
<li>Original file name: %n (Truncated), %N (Untruncated)</li> | |
<li>Spoiler indicator: %p</li> | |
<li>Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)</li> | |
<li>Resolution: %r (Displays PDF on /po/, for PDFs)</li> | |
</ul> | |
<div class=warning><code>Unread Favicon</code> is disabled.</div> | |
Unread favicons<br> | |
<select name=favicon> | |
<option value=ferongr>ferongr</option> | |
<option value=xat->xat-</option> | |
<option value=Mayhem>Mayhem</option> | |
<option value=Original>Original</option> | |
</select> | |
<span></span> | |
</div> | |
<input type=radio name=tab hidden id=keybinds_tab> | |
<div> | |
<div class=warning><code>Keybinds</code> are disabled.</div> | |
<div>Allowed keys: Ctrl, Alt, Meta, a-z, A-Z, 0-9, Up, Down, Right, Left.</div> | |
<table><tbody> | |
<tr><th>Actions</th><th>Keybinds</th></tr> | |
</tbody></table> | |
</div> | |
<input type=radio name=tab hidden id=style_tab> | |
<div> | |
<div class=warning><code>Style</code> is currently disabled. Please enable it in the Main tab to use styling options.</div> | |
</div> | |
<input type=radio name=tab hidden id=theme_tab> | |
<div> | |
<div class=warning><code>Style</code> is currently disabled. Please enable it in the Main tab to use theming options.</div></div> | |
<input type=radio name=tab hidden id=mascot_tab> | |
<div> | |
<div class=warning><code>Style</code> is currently disabled. Please enable it in the Main tab to use mascot options.</div> | |
</div> | |
<input type=radio name=tab hidden onClick="javascript:location.reload(true)" id=apply> | |
<div>Reloading page with new settings.</div> | |
</div>' | |
#main | |
for key, obj of Config.main | |
ul = $.el 'ul', | |
textContent: key | |
for key, arr of obj | |
checked = if $.get(key, Conf[key]) then 'checked' else '' | |
description = arr[1] | |
li = $.el 'li', | |
innerHTML: "<label><input type=checkbox name=\"#{key}\" #{checked}><span class=\"optionlabel\">#{key}</span></label><span class=description>: #{description}</span>" | |
$.on $('input', li), 'click', $.cb.checked | |
$.add ul, li | |
$.add $('#main_tab + div', dialog), ul | |
hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {} | |
hiddenNum = Object.keys(g.hiddenReplies).length + Object.keys(hiddenThreads).length | |
li = $.el 'li', | |
innerHTML: "<button>hidden: #{hiddenNum}</button> <span class=description>: Forget all hidden posts. Useful if you accidentally hide a post and have \"Show Stubs\" disabled." | |
$.on $('button', li), 'click', Options.clearHidden | |
$.add $('ul:nth-child(2)', dialog), li | |
#filter | |
filter = $ 'select[name=filter]', dialog | |
$.on filter, 'change', Options.filter | |
#sauce | |
sauce = $ '#sauces', dialog | |
sauce.value = $.get sauce.name, Conf[sauce.name] | |
$.on sauce, 'change', $.cb.value | |
#rice | |
(back = $ '[name=backlink]', dialog).value = $.get 'backlink', Conf['backlink'] | |
(time = $ '[name=time]', dialog).value = $.get 'time', Conf['time'] | |
(fileInfo = $ '[name=fileInfo]', dialog).value = $.get 'fileInfo', Conf['fileInfo'] | |
$.on back, 'input', $.cb.value | |
$.on back, 'input', Options.backlink | |
$.on time, 'input', $.cb.value | |
$.on time, 'input', Options.time | |
$.on fileInfo, 'input', $.cb.value | |
$.on fileInfo, 'input', Options.fileInfo | |
favicon = $ 'select[name=favicon]', dialog | |
favicon.value = $.get 'favicon', Conf['favicon'] | |
$.on favicon, 'change', $.cb.value | |
$.on favicon, 'change', Options.favicon | |
#keybinds | |
for key, arr of Config.hotkeys | |
tr = $.el 'tr', | |
innerHTML: "<td>#{arr[1]}</td><td><input name=#{key} class=field></td>" | |
input = $ 'input', tr | |
input.value = $.get key, Conf[key] | |
$.on input, 'keydown', Options.keybind | |
$.add $('#keybinds_tab + div tbody', dialog), tr | |
#style | |
div = $.el 'div', | |
className: "suboptions" | |
for category, obj of Config.style | |
ul = $.el 'ul', | |
textContent: category | |
for optionname, arr of obj | |
description = arr[1] | |
if arr[2] == 'text' | |
li = $.el 'li', | |
innerHTML: "<label><span class=\"optionlabel\">#{optionname}</span></label><span class=description>: #{description}</span><input name=\"#{optionname}\" style=\"width: 100%\"><br>" | |
styleSetting = $ "input[name='#{optionname}']", li | |
styleSetting.value = $.get optionname, Conf[optionname] | |
$.on styleSetting, 'change', $.cb.value | |
$.on styleSetting, 'change', Options.style | |
else if arr[2] | |
liHTML = "<label><span class=\"optionlabel\">#{optionname}</span></label><span class=description>: #{description}</span><select name=\"#{optionname}\"><br>" | |
for selectoption, optionvalue in arr[2] | |
liHTML = liHTML + "<option value=\"#{selectoption}\">#{selectoption}</option>" | |
liHTML = liHTML + "</select>" | |
li = $.el 'li', | |
innerHTML: liHTML | |
styleSetting = $ "select[name='#{optionname}']", li | |
styleSetting.value = $.get optionname, Conf[optionname] | |
$.on styleSetting, 'change', $.cb.value | |
$.on styleSetting, 'change', Options.style | |
else | |
checked = if $.get(optionname, Conf[optionname]) then 'checked' else '' | |
li = $.el 'li', | |
innerHTML: "<label><input type=checkbox name=\"#{optionname}\" #{checked}><span class=\"optionlabel\">#{optionname}<span></label><span class=description>: #{description}</span>" | |
$.on $('input', li), 'click', $.cb.checked | |
$.add ul, li | |
$.add div, ul | |
$.add $('#style_tab + div', dialog), div | |
Options.applyStyle(dialog, 'style_tab') | |
#themes | |
@themeTab dialog | |
#mascots | |
parentdiv = $.el 'div', | |
className: "suboptions" | |
ul = $.el 'ul', | |
className: 'mascots' | |
for name, mascot of Mascots | |
description = name | |
li = $.el 'li', | |
innerHTML: "<div id='#{name}' class='#{mascot.category}' style='background-image: url(#{mascot.image});'></div>" | |
className: 'mascot' | |
div = $('div', li) | |
if enabledmascots[name] == true | |
$.addClass div, 'enabled' | |
$.on div, 'click', -> | |
if enabledmascots[@.id] == true | |
$.rmClass @, 'enabled' | |
$.set @.id, false | |
enabledmascots[@.id] = false | |
else | |
$.addClass @, 'enabled' | |
$.set @.id, true | |
enabledmascots[@.id] = true | |
$.add ul, li | |
$.add parentdiv, ul | |
$.add $('#mascot_tab + div', dialog), parentdiv | |
batchmascots = $.el 'div', | |
id: "mascots_batch" | |
innerHTML: "<a href=\"javascript:;\" id=\"clear\">Clear All</a> / <a href=\"javascript:;\" id=\"selectAll\">Select All</a>" | |
$.on $('#clear', batchmascots), 'click', -> | |
for mascotname, mascot of enabledmascots | |
if enabledmascots[mascotname] == true | |
$.rmClass $('#' + mascotname, @parentElement.parentElement), 'enabled' | |
$.set mascotname, false | |
enabledmascots[mascotname] = false | |
$.on $('#selectAll', batchmascots), 'click', -> | |
for mascotname, mascot of enabledmascots | |
if enabledmascots[mascotname] == false | |
$.addClass $('#' + mascotname, @parentElement.parentElement), 'enabled' | |
$.set mascotname, true | |
enabledmascots[mascotname] = true | |
$.add $('#mascot_tab + div', dialog), batchmascots | |
Options.applyStyle(dialog, 'mascot_tab') | |
Options.indicators dialog | |
overlay = $.el 'div', id: 'overlay' | |
$.on overlay, 'click', Options.close | |
$.add d.body, overlay | |
dialog.style.visibility = 'hidden' | |
$.add d.body, dialog | |
dialog.style.visibility = 'visible' | |
Options.filter.call filter | |
Options.backlink.call back | |
Options.time.call time | |
Options.fileInfo.call fileInfo | |
Options.favicon.call favicon | |
indicators: (dialog) -> | |
indicators = {} | |
for indicator in $$ '.warning', dialog | |
key = indicator.firstChild.textContent | |
indicator.hidden = $.get key, Conf[key] | |
indicators[key] = indicator | |
$.on $("[name='#{key}']", dialog), 'click', -> | |
indicators[@name].hidden = @checked | |
for indicator in $$ '.disabledwarning', dialog | |
key = indicator.firstChild.textContent | |
indicator.hidden = not $.get key, Conf[key] | |
indicators[key] = indicator | |
$.on $("[name='#{key}']", dialog), 'click', -> | |
Options.indicators dialog | |
themeTab: (dialog) -> | |
unless dialog | |
dialog = $("#options", d.body) | |
parentdiv = $.el 'div', | |
className: "suboptions" | |
id: "themes" | |
for themename, theme of userThemes | |
unless theme["Deleted"] | |
div = $.el 'div', | |
className: if themename == Conf['theme'] then 'selectedtheme replyContainer' else 'replyContainer' | |
id: themename | |
innerHTML: " | |
<div class='reply' style='position: relative; width: 100%; box-shadow: none !important; background-color:#{theme['Reply Background']}!important;border:1px solid #{theme['Reply Border']}!important;color:#{theme['Text']}!important'> | |
<div class='rice' style='cursor: pointer; width: 12px;height: 12px;margin: 0 3px;vertical-align: middle;display: inline-block;background-color:#{theme['Checkbox Background']};border: 1px solid #{theme['Checkbox Border']};'></div> | |
<span style='color:#{theme['Subjects']}!important; font-weight: 700 !important'> #{themename}</span> | |
<span style='color:#{theme['Names']}!important; font-weight: 700 !important'> #{theme['Author']}</span> | |
<span style='color:#{theme['Sage']}!important'> (SAGE)</span> | |
<span style='color:#{theme['Tripcodes']}!important'> #{theme['Author Tripcode']}</span> | |
<time style='color:#{theme['Timestamps']}'> 20XX.01.01 12:00 </time> | |
<a onmouseout='this.setAttribute("style","color:#{theme['Post Numbers']}!important")' onmouseover='this.setAttribute("style","color:#{theme['Hovered Links']}!important")' style='color:#{theme['Post Numbers']}!important;' href='javascript:;'>No.27583594</a> | |
<a class=edit name='#{themename}' onmouseout='this.setAttribute("style","color:#{theme['Backlinks']}!important; font-weight: 800;")' onmouseover='this.setAttribute("style"," font-weight: 800;color:#{theme['Hovered Links']}!important;")' style='color:#{theme['Backlinks']}!important; font-weight: 800;' href='javascript:;'> >>edit</a> | |
<a class=export name='#{themename}' onmouseout='this.setAttribute("style","color:#{theme['Backlinks']}!important; font-weight: 800;")' onmouseover='this.setAttribute("style","color:#{theme['Hovered Links']}!important; font-weight: 800;")' style='color:#{theme['Backlinks']}!important; font-weight: 800;' href='javascript:;'> >>export</a> | |
<a class=delete onmouseout='this.setAttribute("style","color:#{theme['Backlinks']}!important; font-weight: 800;")' onmouseover='this.setAttribute("style","color:#{theme['Hovered Links']}!important; font-weight: 800;")' style='color:#{theme['Backlinks']}!important; font-weight: 800;' href='javascript:;'> >>delete</a> | |
<br> | |
<blockquote style='cursor: pointer; margin: 0; padding: 12px 40px'> | |
<a style='color:#{theme['Quotelinks']}!important; font-weight: 800;'>>>27582902</a> | |
<br> | |
Post content is right here. | |
</blockquote> | |
<h1 style='color: #{theme['Text']}'>Selected</h1> | |
</div>" | |
$.on $('a.edit', div), 'click', -> | |
ThemeTools.init @.name | |
Options.close() | |
$.on $('a.export', div), 'click', -> | |
exportTheme = userThemes[@.name] | |
exportTheme['Theme'] = @.name | |
exportedTheme = "data:application/json," + encodeURIComponent(JSON.stringify(exportTheme)) | |
if window.open exportedTheme, "_blank" | |
return | |
else if confirm "Your popup blocker is preventing Appchan X from exporting this theme. Would you like to open the exported theme in this window?" | |
window.location exportedTheme | |
$.on $('a.delete', div), 'click', -> | |
container = @.parentElement.parentElement | |
unless container.previousSibling or container.nextSibling | |
alert "Cannot delete theme (No other themes available)." | |
return | |
if confirm "Are you sure you want to delete \"#{container.id}\"?" | |
if container.id == Conf['theme'] | |
if settheme = container.previousSibling or container.nextSibling | |
Conf['theme'] = settheme.id | |
$.addClass settheme, 'selectedtheme' | |
$.set 'theme', Conf['theme'] | |
userThemes[container.id]["Deleted"] = true | |
$.set 'userThemes', userThemes | |
$.rm container | |
$.on $('.rice', div), 'click', Options.selectTheme | |
$.on $('blockquote', div), 'click', Options.selectTheme | |
$.add parentdiv, div | |
div = $.el 'div', | |
id: 'addthemes' | |
innerHTML: "<a id=import href='javascript:;'>Import Theme</a><input type=file hidden> / <a id=newtheme href='javascript:;'>New Theme</a>" | |
$.on $("#import", div), 'click', -> | |
@nextSibling.click() | |
$.on $("#newtheme", div), 'click', -> | |
newTheme = true | |
ThemeTools.init "untitled" | |
Options.close() | |
$.on $("input", div), 'change', (evt) -> | |
file = evt.target.files[0] | |
reader = new FileReader() | |
reader.onload = (e) -> | |
try | |
theme = JSON.parse e.target.result | |
catch err | |
alert err | |
return | |
unless theme["Author Tripcode"] | |
alert "Theme file is invalid." | |
return | |
name = theme["Theme"] | |
delete theme["Theme"] | |
if userThemes[name] and not userThemes[name]["Deleted"] | |
if confirm "A theme with this name already exists. Would you like to over-write?" | |
delete userThemes[name] | |
else | |
return | |
userThemes[name] = theme | |
$.set 'userThemes', userThemes | |
alert "Theme \"#{name}\" imported!" | |
$.rm $("#themes", d.body) | |
Options.themeTab() | |
reader.readAsText(file); | |
$.add $('#theme_tab + div', dialog), parentdiv | |
$.add $('#theme_tab + div', dialog), div | |
Options.applyStyle(dialog, 'theme_tab') | |
close: -> | |
$.rm $('#options', d.body) | |
$.rm $('#overlay', d.body) | |
clearHidden: -> | |
#'hidden' might be misleading; it's the number of IDs we're *looking* for, | |
# not the number of posts actually hidden on the page. | |
$.delete "hiddenReplies/#{g.BOARD}/" | |
$.delete "hiddenThreads/#{g.BOARD}/" | |
@textContent = "hidden: 0" | |
g.hiddenReplies = {} | |
keybind: (e) -> | |
return if e.keyCode is 9 | |
e.preventDefault() | |
e.stopPropagation() | |
return unless (key = Keybinds.keyCode e)? | |
@value = key | |
$.cb.value.call @ | |
filter: -> | |
el = @nextSibling | |
if (name = @value) isnt 'guide' | |
ta = $.el 'textarea', | |
name: name | |
className: 'field' | |
value: $.get name, Conf[name] | |
$.on ta, 'change', $.cb.value | |
$.replace el, ta | |
return | |
$.rm el if el | |
$.after @, $.el 'article', | |
innerHTML: '<p>Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br> | |
Lines starting with a <code>#</code> will be ignored.<br> | |
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.</p> | |
<ul>You can use these settings with each regular expression, separate them with semicolons: | |
<li> | |
Per boards, separate them with commas. It is global if not specified.<br> | |
For example: <code>boards:a,jp;</code>. | |
</li> | |
<li> | |
Filter OPs only along with their threads (`only`), replies only (`no`, this is default), or both (`yes`).<br> | |
For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>. | |
</li> | |
<li> | |
Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br> | |
For example: <code>stub:yes;</code> or <code>stub:no;</code>. | |
</li> | |
<li> | |
Highlight instead of hiding. You can specify a class name to use with a userstyle.<br> | |
For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>. | |
</li> | |
<li> | |
Highlighted OPs will have their threads put on top of board pages by default.<br> | |
For example: <code>top:yes;</code> or <code>top:no;</code>. | |
</li> | |
</ul>' | |
time: -> | |
Time.foo() | |
Time.date = new Date() | |
$.id('timePreview').textContent = Time.funk Time | |
backlink: -> | |
$.id('backlinkPreview').textContent = Conf['backlink'].replace /%id/, '123456789' | |
fileInfo: -> | |
FileInfo.data = | |
link: 'javascript:;' | |
spoiler: true | |
size: '276' | |
unit: 'KB' | |
resolution: '1280x720' | |
fullname: 'd9bb2efc98dd0df141a94399ff5880b7.jpg' | |
shortname: 'd9bb2efc98dd0df141a94399ff5880(...).jpg' | |
FileInfo.setFormats() | |
$.id('fileInfoPreview').innerHTML = FileInfo.funk FileInfo | |
favicon: -> | |
Favicon.switch() | |
Unread.update true | |
@nextElementSibling.innerHTML = "<img src=#{Favicon.unreadSFW}> <img src=#{Favicon.unreadNSFW}> <img src=#{Favicon.unreadDead}>" | |
applyStyle: (dialog, tab) -> | |
if Conf['styleenabled'] == '1' | |
save = $.el 'div', | |
innerHTML: '<a href="javascript:;">Save Style Settings</a>' | |
className: 'stylesettings' | |
$.on $('a', save), 'click', -> | |
Style.addStyle() | |
$.add $('#' + tab + ' + div', dialog), save | |
selectTheme: -> | |
container = @.parentElement.parentElement | |
if currentTheme = $.id(Conf['theme']) | |
$.rmClass currentTheme, 'selectedtheme' | |
$.set 'theme', container.id | |
Conf['theme'] = container.id | |
$.addClass container, 'selectedtheme' | |
Markdown = | |
format: (text) -> | |
tag_patterns = | |
bi: /(\*\*\*|___)(?=\S)([^\r\n]*?\S)\1/g | |
b: /(\*\*|__)(?=\S)([^\r\n]*?\S)\1/g | |
i: /(\*|_)(?=\S)([^\r\n]*?\S)\1/g | |
code: /(`)(?=\S)([^\r\n]*?\S)\1/g | |
ds: /(\|\||__)(?=\S)([^\r\n]*?\S)\1/g | |
unless text == null | |
for tag, pattern of tag_patterns | |
text = text.replace pattern, Markdown.unicode_convert | |
text | |
unicode_convert: (str, tag, inner) -> | |
if tag is "_" or tag is "*" | |
fmt = "i" | |
else if tag is "__" or tag is "**" | |
fmt = "b" | |
else if tag is "***" or tag is "___" | |
fmt = "bi" | |
else if tag is "||" | |
fmt = "ds" | |
else fmt = "code" if tag is "`" or tag is "```" | |
#Unicode codepoints for the characters '0', 'A', and 'a' | |
#http://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols | |
codepoints = | |
b: [ 0x1D7CE, 0x1D400, 0x1D41A ] #MATHEMATICAL BOLD | |
i: [ 0x1D7F6, 0x1D434, 0x1D44E ] #MATHEMATICAL ITALIC | |
bi: [ 0x1D7CE, 0x1D468, 0x1D482 ] #MATHEMATICAL BOLD ITALIC | |
code: [ 0x1D7F6, 0x1D670, 0x1D68A ] #MATHEMATICAL MONOSPACE | |
ds: [ 0x1D7D8, 0x1D538, 0x1D552 ] #I FUCKING LOVE CAPS LOCK | |
charcodes = (inner.charCodeAt i for c, i in inner) | |
codes = for charcode in charcodes | |
if charcode >= 48 and charcode <= 57 | |
charcode - 48 + codepoints[fmt][0] | |
else if charcode >= 65 and charcode <= 90 | |
charcode - 65 + codepoints[fmt][1] | |
else if charcode >= 97 and charcode <= 122 | |
if charcode is 104 and tag is "i" | |
#http://blogs.msdn.com/b/michkap/archive/2006/04/21/580328.aspx | |
#mathematical small h -> planck constant | |
0x210E | |
else | |
charcode - 97 + codepoints[fmt][2] | |
else | |
charcode | |
unicode_text = codes.map(Markdown.ucs2_encode).join "" | |
unicode_text = unicode_text.replace(/\x20/g, "\xA0") if tag is "code" | |
unicode_text | |
ucs2_encode: (value) -> | |
#translates Unicode codepoint integers directly into text. Javascript does this in an ugly fashion by default. | |
### | |
From Punycode.js: https://github.com/bestiejs/punycode.js | |
Copyright Mathias Bynens <http://mathiasbynens.be/> | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the | |
"Software"), to deal in the Software without restriction, including | |
without limitation the rights to use, copy, modify, merge, publish, | |
distribute, sublicense, and/or sell copies of the Software, and to | |
permit persons to whom the Software is furnished to do so, subject to | |
the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF` | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
### | |
output = "" | |
if value > 0xFFFF | |
value -= 0x10000 | |
output += String.fromCharCode value >>> 10 & 0x3FF | 0xD800 | |
value = 0xDC00 | value & 0x3FF | |
output += String.fromCharCode value | |
output | |
Filter = | |
filters: {} | |
init: -> | |
for key of Config.filter | |
@filters[key] = [] | |
for filter in Conf[key].split '\n' | |
continue if filter[0] is '#' | |
unless regexp = filter.match /\/(.+)\/(\w*)/ | |
continue | |
# Don't mix up filter flags with the regular expression. | |
filter = filter.replace regexp[0], '' | |
# Do not add this filter to the list if it's not a global one | |
# and it's not specifically applicable to the current board. | |
# Defaults to global. | |
boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global' | |
if boards isnt 'global' and boards.split(',').indexOf(g.BOARD) is -1 | |
continue | |
if key is 'md5' | |
# MD5 filter will use strings instead of regular expressions. | |
regexp = regexp[1] | |
else | |
try | |
# Please, don't write silly regular expressions. | |
regexp = RegExp regexp[1], regexp[2] | |
catch err | |
# I warned you, bro. | |
alert err.message | |
continue | |
# Filter OPs along with their threads, replies only, or both. | |
# Defaults to replies only. | |
op = filter.match(/[^t]op:(yes|no|only)/)?[1] or 'no' | |
# Overrule the `Show Stubs` setting. | |
# Defaults to stub showing. | |
stub = switch filter.match(/stub:(yes|no)/)?[1] | |
when 'yes' | |
true | |
when 'no' | |
false | |
else | |
Conf['Show Stubs'] | |
# Highlight the post, or hide it. | |
# If not specified, the highlight class will be filter_highlight. | |
# Defaults to post hiding. | |
if hl = /highlight/.test filter | |
hl = filter.match(/highlight:(\w+)/)?[1] or 'filter_highlight' | |
# Put highlighted OP's thread on top of the board page or not. | |
# Defaults to on top. | |
top = filter.match(/top:(yes|no)/)?[1] or 'yes' | |
top = top is 'yes' # Turn it into a boolean | |
@filters[key].push @createFilter regexp, op, stub, hl, top | |
# Only execute filter types that contain valid filters. | |
unless @filters[key].length | |
delete @filters[key] | |
if Object.keys(@filters).length | |
Main.callbacks.push @node | |
createFilter: (regexp, op, stub, hl, top) -> | |
test = | |
if typeof regexp is 'string' | |
# MD5 checking | |
(value) -> regexp is value | |
else | |
(value) -> regexp.test value | |
settings = | |
hide: !hl | |
stub: stub | |
class: hl | |
top: top | |
(value, isOP) -> | |
if isOP and op is 'no' or !isOP and op is 'only' | |
return false | |
unless test value | |
return false | |
settings | |
node: (post) -> | |
return if post.isInlined | |
isOP = post.ID is post.threadID | |
{root} = post | |
for key of Filter.filters | |
value = Filter[key] post | |
if value is false | |
# Continue if there's nothing to filter (no tripcode for example). | |
continue | |
for filter in Filter.filters[key] | |
unless result = filter value, isOP | |
continue | |
# Hide | |
if result.hide | |
if isOP | |
unless g.REPLY | |
ThreadHiding.hide root.parentNode, result.stub | |
else | |
continue | |
else | |
ReplyHiding.hide post.root, result.stub | |
return | |
# Highlight | |
$.addClass root, result.class | |
name: (post) -> | |
$('.name', post.el).textContent | |
uniqueid: (post) -> | |
if uid = $ '.posteruid', post.el | |
return uid.textContent[5...-1] | |
false | |
tripcode: (post) -> | |
if trip = $ '.postertrip', post.el | |
return trip.textContent | |
false | |
mod: (post) -> | |
if mod = $ '.capcode', post.el | |
return mod.textContent | |
false | |
email: (post) -> | |
if mail = $ '.useremail', post.el | |
# remove 'mailto:' | |
# decode %20 into space for example | |
return decodeURIComponent mail.href[7..] | |
false | |
subject: (post) -> | |
if subject = $ '.postInfo .subject', post.el | |
return subject.textContent | |
false | |
comment: (post) -> | |
text = [] | |
nodes = d.evaluate './/br|.//text()', post.blockquote, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null | |
for i in [0...nodes.snapshotLength] | |
text.push if data = nodes.snapshotItem(i).data then data else '\n' | |
text.join '' | |
country: (post) -> | |
if flag = $ '.countryFlag', post.el | |
return flag.title | |
false | |
filename: (post) -> | |
{fileInfo} = post | |
if fileInfo | |
if file = $ '.fileText > span', fileInfo | |
return file.title | |
else | |
return fileInfo.firstElementChild.dataset.filename | |
false | |
dimensions: (post) -> | |
{fileInfo} = post | |
if fileInfo and match = fileInfo.textContent.match /\d+x\d+/ | |
return match[0] | |
false | |
filesize: (post) -> | |
{img} = post | |
if img | |
return img.alt.replace 'Spoiler Image, ', '' | |
false | |
md5: (post) -> | |
{img} = post | |
if img | |
return img.dataset.md5 | |
false | |
menuInit: -> | |
div = $.el 'div', | |
textContent: 'Filter' | |
entry = | |
el: div | |
open: -> true | |
children: [] | |
for type in [ | |
['Name', 'name'] | |
['Unique ID', 'uniqueid'] | |
['Tripcode', 'tripcode'] | |
['Admin/Mod', 'mod'] | |
['E-mail', 'email'] | |
['Subject', 'subject'] | |
['Comment', 'comment'] | |
['Country', 'country'] | |
['Filename', 'filename'] | |
['Image dimensions', 'dimensions'] | |
['Filesize', 'filesize'] | |
['Image MD5', 'md5'] | |
] | |
# Add a sub entry for each filter type. | |
entry.children.push Filter.createSubEntry type[0], type[1] | |
Menu.addEntry entry | |
createSubEntry: (text, type) -> | |
el = $.el 'a', | |
href: 'javascript:;' | |
textContent: text | |
# Define the onclick var outside of open's scope to $.off it properly. | |
onclick = null | |
open = (post) -> | |
value = Filter[type] post | |
return false if value is false | |
$.off el, 'click', onclick | |
onclick = -> | |
# Convert value -> regexp, unless type is md5 | |
re = if type is 'md5' then value else value.replace /// | |
/ | |
| \\ | |
| \^ | |
| \$ | |
| \n | |
| \. | |
| \( | |
| \) | |
| \{ | |
| \} | |
| \[ | |
| \] | |
| \? | |
| \* | |
| \+ | |
| \| | |
///g, (c) -> | |
if c is '\n' | |
'\\n' | |
else if c is '\\' | |
'\\\\' | |
else | |
"\\#{c}" | |
re = | |
if type is 'md5' | |
"/#{value}/" | |
else | |
"/^#{re}$/" | |
if /\bop\b/.test post.class | |
re += ';op:yes' | |
# Add a new line before the regexp unless the text is empty. | |
save = if save = $.get type, '' then "#{save}\n#{re}" else re | |
$.set type, save | |
# Open the options and display & focus the relevant filter textarea. | |
Options.dialog() | |
select = $ 'select[name=filter]', $.id 'options' | |
select.value = type | |
$.event select, new Event 'change' | |
$.id('filter_tab').checked = true | |
ta = select.nextElementSibling | |
tl = ta.textLength | |
ta.setSelectionRange tl, tl | |
ta.focus() | |
$.on el, 'click', onclick | |
true | |
return el: el, open: open | |
StrikethroughQuotes = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined | |
for quote in post.quotes | |
if (el = $.id quote.hash[1..]) and el.hidden | |
$.addClass quote, 'filtered' | |
if Conf['Recursive Filtering'] | |
show_stub = !!$.x 'preceding-sibling::div[contains(@class,"stub")]', el | |
ReplyHiding.hide post.root, show_stub | |
return | |
ExpandComment = | |
init: -> | |
for a in $$ '.abbr' | |
$.on a.firstElementChild, 'click', ExpandComment.expand | |
return | |
expand: (e) -> | |
e.preventDefault() | |
[_, threadID, replyID] = @href.match /(\d+)#p(\d+)/ | |
@textContent = "Loading No.#{replyID}..." | |
a = @ | |
$.cache "//api.4chan.org#{@pathname}.json", -> ExpandComment.parse @, a, threadID, replyID | |
parse: (req, a, threadID, replyID) -> | |
if req.status isnt 200 | |
a.textContent = "#{req.status} #{req.statusText}" | |
return | |
posts = JSON.parse(req.response).posts | |
if spoilerRange = posts[0].custom_spoiler | |
Build.spoilerRange[g.BOARD] = spoilerRange | |
replyID = +replyID | |
for post in posts | |
break if post.no is replyID | |
if post.no isnt replyID | |
a.textContent = 'No.#{replyID} not found.' | |
return | |
bq = $.id "m#{replyID}" | |
clone = bq.cloneNode false | |
clone.innerHTML = post.com | |
quotes = clone.getElementsByClassName 'quotelink' | |
for quote in quotes | |
href = quote.getAttribute 'href' | |
continue if href[0] is '/' # Cross-board quote | |
quote.href = "res/#{href}" # Fix pathnames | |
post = | |
blockquote: clone | |
threadID: threadID | |
quotes: quotes | |
backlinks: [] | |
if Conf['Resurrect Quotes'] | |
Quotify.node post | |
if Conf['Quote Preview'] | |
QuotePreview.node post | |
if Conf['Quote Inline'] | |
QuoteInline.node post | |
if Conf['Indicate OP quote'] | |
QuoteOP.node post | |
if Conf['Indicate Cross-thread Quotes'] | |
QuoteCT.node post | |
$.replace bq, clone | |
Main.prettify clone | |
ExpandThread = | |
init: -> | |
for span in $$ '.summary' | |
a = $.el 'a', | |
textContent: "+ #{span.textContent}" | |
className: 'summary desktop' | |
href: 'javascript:;' | |
$.on a, 'click', -> ExpandThread.toggle @parentNode | |
$.replace span, a | |
toggle: (thread) -> | |
url = "//api.4chan.org/#{g.BOARD}/res/#{thread.id[1..]}.json" | |
a = $ '.summary', thread | |
switch a.textContent[0] | |
when '+' | |
a.textContent = a.textContent.replace '+', '× Loading...' | |
$.cache url, -> ExpandThread.parse @, thread, a | |
when 'X' | |
a.textContent = a.textContent.replace '× Loading...', '+' | |
$.cache.requests[url].abort() | |
when '-' | |
a.textContent = a.textContent.replace '-', '+' | |
#goddamit moot | |
num = switch g.BOARD | |
when 'b', 'vg', 'q' then 3 | |
when 't' then 1 | |
else 5 | |
replies = $$ '.replyContainer', thread | |
replies.splice replies.length - num, num | |
for reply in replies | |
$.rm reply | |
return | |
parse: (req, thread, a) -> | |
if req.status isnt 200 | |
a.textContent = "#{req.status} #{req.statusText}" | |
$.off a, 'click', ExpandThread.cb.toggle | |
return | |
a.textContent = a.textContent.replace '× Loading...', '-' | |
posts = JSON.parse(req.response).posts | |
if spoilerRange = posts[0].custom_spoiler | |
Build.spoilerRange[g.BOARD] = spoilerRange | |
replies = posts[1..] | |
threadID = thread.id[1..] | |
nodes = [] | |
for reply in replies | |
post = Build.postFromObject reply, g.BOARD | |
id = reply.no | |
link = $ 'a[title="Highlight this post"]', post | |
link.href = "res/#{threadID}#p#{id}" | |
link.nextSibling.href = "res/#{threadID}#q#{id}" | |
nodes.push post | |
# eat everything, then replace with fresh full posts | |
for post in $$ '.summary ~ .replyContainer', a.parentNode | |
$.rm post | |
for backlink in $$ '.backlink', a.previousElementSibling | |
# Keep backlinks from other threads. | |
$.rm backlink unless $.id backlink.hash[1..] | |
$.after a, nodes | |
ThreadHiding = | |
init: -> | |
hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {} | |
for thread in $$ '.thread' | |
a = $.el 'a', | |
className: 'hide_thread_button' | |
innerHTML: '<span>[ - ]</span>' | |
href: 'javascript:;' | |
$.on a, 'click', ThreadHiding.cb | |
$.prepend thread, a | |
if thread.id[1..] of hiddenThreads | |
ThreadHiding.hide thread | |
return | |
cb: -> | |
ThreadHiding.toggle $.x 'ancestor::div[parent::div[@class="board"]]', @ | |
toggle: (thread) -> | |
hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {} | |
id = thread.id[1..] | |
if thread.hidden or /\bhidden_thread\b/.test thread.firstChild.className | |
ThreadHiding.show thread | |
delete hiddenThreads[id] | |
else | |
ThreadHiding.hide thread | |
hiddenThreads[id] = Date.now() | |
$.set "hiddenThreads/#{g.BOARD}/", hiddenThreads | |
hide: (thread, show_stub=Conf['Show Stubs']) -> | |
unless show_stub | |
thread.hidden = true | |
thread.nextElementSibling.hidden = true | |
return | |
return if /\bhidden_thread\b/.test thread.firstChild.className # already hidden once by the filter | |
num = 0 | |
if span = $ '.summary', thread | |
num = Number span.textContent.match /\d+/ | |
num += $$('.opContainer ~ .replyContainer', thread).length | |
text = if num is 1 then '1 reply' else "#{num} replies" | |
opInfo = $('.desktop > .nameBlock', thread).textContent | |
stub = $.el 'div', | |
className: 'hide_thread_button hidden_thread' | |
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>' | |
a = stub.firstChild | |
$.on a, 'click', ThreadHiding.cb | |
$.add a, $.tn "#{opInfo} (#{text})" | |
if Conf['Menu'] | |
menuButton = Menu.a.cloneNode true | |
$.on menuButton, 'click', Menu.toggle | |
$.add stub, [$.tn(' '), menuButton] | |
$.prepend thread, stub | |
show: (thread) -> | |
if stub = $ '.hidden_thread', thread | |
$.rm stub | |
thread.hidden = false | |
thread.nextElementSibling.hidden = false | |
ReplyHiding = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined or post.ID is post.threadID | |
side = $ '.sideArrows', post.root | |
$.addClass side, 'hide_reply_button' | |
side.innerHTML = '<a href="javascript:;"><span>[ - ]</span></a>' | |
$.on side.firstChild, 'click', ReplyHiding.toggle | |
if post.ID of g.hiddenReplies | |
ReplyHiding.hide post.root | |
toggle: -> | |
button = @parentNode | |
root = button.parentNode | |
id = root.id[2..] | |
quotes = $$ ".quotelink[href$='#p#{id}'], .backlink[href$='#p#{id}']" | |
if /\bstub\b/.test button.className | |
ReplyHiding.show root | |
for quote in quotes | |
$.rmClass quote, 'filtered' | |
delete g.hiddenReplies[id] | |
else | |
ReplyHiding.hide root | |
for quote in quotes | |
$.addClass quote, 'filtered' | |
g.hiddenReplies[id] = Date.now() | |
$.set "hiddenReplies/#{g.BOARD}/", g.hiddenReplies | |
hide: (root, show_stub=Conf['Show Stubs']) -> | |
side = $ '.sideArrows', root | |
return if side.hidden # already hidden once by the filter | |
side.hidden = true | |
el = side.nextElementSibling | |
el.hidden = true | |
$.addClass root, 'hidden' | |
return unless show_stub | |
stub = $.el 'div', | |
className: 'hide_reply_button stub' | |
innerHTML: '<a href="javascript:;"><span>[ + ]</span> </a>' | |
a = stub.firstChild | |
$.on a, 'click', ReplyHiding.toggle | |
$.add a, $.tn $('.desktop > .nameBlock', el).textContent | |
if Conf['Menu'] | |
menuButton = Menu.a.cloneNode true | |
$.on menuButton, 'click', Menu.toggle | |
$.add stub, [$.tn(' '), menuButton] | |
$.prepend root, stub | |
show: (root) -> | |
if stub = $ '.stub', root | |
$.rm stub | |
$('.sideArrows', root).hidden = false | |
$('.post', root).hidden = false | |
$.rmClass root, 'hidden' | |
Menu = | |
entries: [] | |
init: -> | |
@a = $.el 'a', | |
className: 'menu_button' | |
href: 'javascript:;' | |
innerHTML: '[<span></span>]' | |
@el = $.el 'div', | |
className: 'reply dialog' | |
id: 'menu' | |
tabIndex: 0 | |
$.on @el, 'click', (e) -> e.stopPropagation() | |
$.on @el, 'keydown', @keybinds | |
# Doc here: https://github.com/MayhemYDG/4chan-x/wiki/Menu-API | |
$.on d, 'AddMenuEntry', (e) -> Menu.addEntry e.detail | |
Main.callbacks.push @node | |
node: (post) -> | |
if post.isInlined and !post.isCrosspost | |
a = $ '.menu_button', post.el | |
else | |
a = Menu.a.cloneNode true | |
# \u00A0 is nbsp | |
$.add $('.postInfo', post.el), [$.tn('\u00A0'), a] | |
$.on a, 'click', Menu.toggle | |
toggle: (e) -> | |
e.preventDefault() | |
e.stopPropagation() | |
if Menu.el.parentNode | |
# Close if it's already opened. | |
# Reopen if we clicked on another button. | |
{lastOpener} = Menu | |
Menu.close() | |
return if lastOpener is @ | |
Menu.lastOpener = @ | |
post = | |
if /\bhidden_thread\b/.test @parentNode.className | |
$.x 'ancestor::div[parent::div[@class="board"]]/child::div[contains(@class,"opContainer")]', @ | |
else | |
$.x 'ancestor::div[contains(@class,"postContainer")][1]', @ | |
Menu.open @, Main.preParse post | |
open: (button, post) -> | |
{el} = Menu | |
# XXX GM/Scriptish require setAttribute | |
el.setAttribute 'data-id', post.ID | |
el.setAttribute 'data-rootid', post.root.id | |
funk = (entry, parent) -> | |
{children} = entry | |
return unless entry.open post | |
$.add parent, entry.el | |
return unless children | |
if subMenu = $ '.subMenu', entry.el | |
# Reset sub menu, remove irrelevant entries. | |
$.rm subMenu | |
subMenu = $.el 'div', | |
className: 'reply dialog subMenu' | |
$.add entry.el, subMenu | |
for child in children | |
funk child, subMenu | |
return | |
for entry in Menu.entries | |
funk entry, el | |
Menu.focus $ '.entry', Menu.el | |
$.on d, 'click', Menu.close | |
$.add d.body, el | |
# Position | |
mRect = el.getBoundingClientRect() | |
bRect = button.getBoundingClientRect() | |
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top | |
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left | |
el.style.top = | |
if bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight | |
bTop + bRect.height + 2 + 'px' | |
else | |
bTop - mRect.height - 2 + 'px' | |
el.style.left = | |
if bRect.left + mRect.width < d.documentElement.clientWidth | |
bLeft + 'px' | |
else | |
bLeft + bRect.width - mRect.width + 'px' | |
el.focus() | |
close: -> | |
{el} = Menu | |
$.rm el | |
for focused in $$ '.focused.entry', el | |
$.rmClass focused, 'focused' | |
el.innerHTML = null | |
el.removeAttribute 'style' | |
delete Menu.lastOpener | |
delete Menu.focusedEntry | |
$.off d, 'click', Menu.close | |
keybinds: (e) -> | |
el = Menu.focusedEntry | |
switch Keybinds.keyCode(e) or e.keyCode | |
when 'Esc' | |
Menu.lastOpener.focus() | |
Menu.close() | |
when 13, 32 # 'Enter', 'Space' | |
el.click() | |
when 'Up' | |
if next = el.previousElementSibling | |
Menu.focus next | |
when 'Down' | |
if next = el.nextElementSibling | |
Menu.focus next | |
when 'Right' | |
if (subMenu = $ '.subMenu', el) and next = subMenu.firstElementChild | |
Menu.focus next | |
when 'Left' | |
if next = $.x 'parent::*[contains(@class,"subMenu")]/parent::*', el | |
Menu.focus next | |
else | |
return | |
e.preventDefault() | |
e.stopPropagation() | |
focus: (el) -> | |
if focused = $.x 'parent::*/child::*[contains(@class,"focused")]', el | |
$.rmClass focused, 'focused' | |
for focused in $$ '.focused', el | |
$.rmClass focused, 'focused' | |
Menu.focusedEntry = el | |
$.addClass el, 'focused' | |
addEntry: (entry) -> | |
funk = (entry) -> | |
{el, children} = entry | |
$.addClass el, 'entry' | |
$.on el, 'focus mouseover', (e) -> | |
e.stopPropagation() | |
Menu.focus @ | |
return unless children | |
$.addClass el, 'hasSubMenu' | |
for child in children | |
funk child | |
return | |
funk entry | |
Menu.entries.push entry | |
Keybinds = | |
init: -> | |
for node in $$ '[accesskey]' | |
node.removeAttribute 'accesskey' | |
$.on d, 'keydown', Keybinds.keydown | |
keydown: (e) -> | |
return unless key = Keybinds.keyCode e | |
{target} = e | |
if /TEXTAREA|INPUT/.test target.nodeName | |
return unless (key is 'Esc') or (/\+/.test key) | |
thread = Nav.getThread() | |
switch key | |
# QR & Options | |
when Conf.openQR | |
Keybinds.qr thread, true | |
when Conf.openEmptyQR | |
Keybinds.qr thread | |
when Conf.openOptions | |
Options.dialog() unless $.id 'overlay' | |
when Conf.close | |
if o = $.id 'overlay' | |
Options.close.call o | |
else if QR.el | |
QR.close() | |
when Conf.submit | |
QR.submit() if QR.el and !QR.status() | |
when Conf.spoiler | |
return if target.nodeName isnt 'TEXTAREA' | |
Keybinds.tags 'spoiler', target | |
when Conf.code | |
return if target.nodeName isnt 'TEXTAREA' | |
Keybinds.tags 'code', target | |
when Conf.sageru | |
$("[name=email]", QR.el).value = "sage" | |
QR.selected.email = "sage" | |
# Thread related | |
when Conf.watch | |
Watcher.toggle thread | |
when Conf.update | |
Updater.updateReset() | |
when Conf.unreadCountTo0 | |
Unread.replies = new $.RandomAccessList | |
Unread.update true | |
when Conf.threading | |
QuoteThreading.public.toggle() | |
# Images | |
when Conf.expandImage | |
Keybinds.img thread | |
when Conf.expandAllImages | |
Keybinds.img thread, true | |
# Board Navigation | |
when Conf.zero | |
window.location = "/#{g.BOARD}/0#delform" | |
when Conf.nextPage | |
if link = $ 'link[rel=next]', d.head | |
window.location = link.href + '#delform' | |
when Conf.previousPage | |
if link = $ 'link[rel=prev]', d.head | |
window.location = link.href + '#delform' | |
# Thread Navigation | |
when Conf.nextThread | |
return if g.REPLY | |
Nav.scroll +1 | |
when Conf.previousThread | |
return if g.REPLY | |
Nav.scroll -1 | |
when Conf.expandThread | |
ExpandThread.toggle thread | |
when Conf.openThread | |
Keybinds.open thread | |
when Conf.openThreadTab | |
Keybinds.open thread, true | |
# Reply Navigation | |
when Conf.nextReply | |
Keybinds.hl +1, thread | |
when Conf.previousReply | |
Keybinds.hl -1, thread | |
when Conf.hide | |
ThreadHiding.toggle thread if /\bthread\b/.test thread.className | |
else | |
return | |
e.preventDefault() | |
keyCode: (e) -> | |
key = switch kc = e.keyCode | |
when 8 | |
'' | |
when 27 | |
'Esc' | |
when 37 | |
'Left' | |
when 38 | |
'Up' | |
when 39 | |
'Right' | |
when 40 | |
'Down' | |
when 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 #0-9, A-Z | |
c = String.fromCharCode kc | |
if e.shiftKey then c else c.toLowerCase() | |
else | |
null | |
if key | |
if e.altKey then key = 'alt+' + key | |
if e.ctrlKey then key = 'ctrl+' + key | |
if e.metaKey then key = 'meta+' + key | |
key | |
tags: (tag, ta) -> | |
value = ta.value | |
selStart = ta.selectionStart | |
selEnd = ta.selectionEnd | |
ta.value = | |
value[...selStart] + | |
"[#{tag}]" + value[selStart...selEnd] + "[/#{tag}]" + | |
value[selEnd..] | |
range = "[#{tag}]".length + selEnd | |
# Move the caret to the end of the selection. | |
ta.setSelectionRange range, range | |
# Fire the 'input' event | |
$.event ta, new Event 'input' | |
img: (thread, all) -> | |
if all | |
$.id('imageExpand').click() | |
else | |
thumb = $ 'img[data-md5]', $('.post.highlight', thread) or thread | |
ImageExpand.toggle thumb.parentNode | |
qr: (thread, quote) -> | |
if quote | |
QR.quote.call $ 'a[title="Quote this post"]', $('.post.highlight', thread) or thread | |
else | |
QR.open() | |
$('textarea', QR.el).focus() | |
open: (thread, tab) -> | |
id = thread.id[1..] | |
url = "//boards.4chan.org/#{g.BOARD}/res/#{id}" | |
if tab | |
$.open url | |
else | |
location.href = url | |
hl: (delta, thread) -> | |
if post = $ '.reply.highlight', thread | |
$.rmClass post, 'highlight' | |
rect = post.getBoundingClientRect() | |
if rect.bottom >= 0 and rect.top <= d.documentElement.clientHeight # We're at least partially visible | |
axis = if delta is +1 then 'following' else 'preceding' | |
next = $.x axis + '::div[contains(@class,"post reply")][1]', post | |
return unless next | |
return unless g.REPLY or $.x('ancestor::div[parent::div[@class="board"]]', next) is thread | |
rect = next.getBoundingClientRect() | |
if rect.top < 0 or rect.bottom > d.documentElement.clientHeight | |
next.scrollIntoView delta is -1 | |
@focus next | |
return | |
replies = $$ '.reply', thread | |
replies.reverse() if delta is -1 | |
for reply in replies | |
rect = reply.getBoundingClientRect() | |
if delta is +1 and rect.top >= 0 or delta is -1 and rect.bottom <= d.documentElement.clientHeight | |
@focus reply | |
return | |
focus: (post) -> | |
$.addClass post, 'highlight' | |
post.focus() | |
Nav = | |
# ? ? | |
init: -> | |
span = $.el 'span', | |
id: 'navlinks' | |
prev = $.el 'a', | |
textContent: 'â–²', | |
href: 'javascript:;' | |
next = $.el 'a', | |
textContent: 'â–¼' | |
href: 'javascript:;' | |
$.on prev, 'click', @prev | |
$.on next, 'click', @next | |
$.add span, [prev, $.tn(' '), next] | |
$.add d.body, span | |
prev: -> | |
if g.REPLY | |
window.scrollTo 0, 0 | |
else | |
Nav.scroll -1 | |
next: -> | |
if g.REPLY | |
window.scrollTo 0, d.body.scrollHeight | |
else | |
Nav.scroll +1 | |
getThread: (full) -> | |
Nav.threads = $$ '.thread:not(.hidden)' | |
for thread, i in Nav.threads | |
rect = thread.getBoundingClientRect() | |
{bottom} = rect | |
if bottom > 0 #we have not scrolled past | |
if full | |
return [thread, i, rect] | |
return thread | |
return $ '.board' | |
scroll: (delta) -> | |
[thread, i, rect] = Nav.getThread true | |
{top} = rect | |
#unless we're not at the beginning of the current thread | |
# (and thus wanting to move to beginning) | |
# or we're above the first thread and don't want to skip it | |
unless (delta is -1 and Math.ceil(top) < 0) or (delta is +1 and top > 1) | |
i += delta | |
if Conf['Rollover'] | |
if i is -1 | |
if link = $ 'link[rel=prev]', d.head | |
window.location = link.href + '#delform' | |
else | |
window.location = "/#{g.BOARD}/0#delform" | |
return | |
if (delta is +1) and ( (i is Nav.threads.length) or (innerHeight + pageYOffset == d.body.scrollHeight) ) | |
if link = $ 'link[rel=next]', d.head | |
window.location = link.href + '#delform' | |
return | |
{top} = Nav.threads[i]?.getBoundingClientRect() | |
window.scrollBy 0, top | |
Updater = | |
init: -> | |
html = '<div class=move><span id=count></span> <span id=timer></span></div>' | |
{checkbox} = Config.updater | |
for name of checkbox | |
title = checkbox[name][1] | |
checked = if Conf[name] then 'checked' else '' | |
html += "<div><label title='#{title}'>#{name}<input name='#{name}' type=checkbox #{checked}></label></div>" | |
checked = if Conf['Auto Update'] then 'checked' else '' | |
html += " | |
<div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox #{checked}></label></div> | |
<div><label>Interval (s)<input type=number name=Interval class=field min=5></label></div> | |
<div><input value='Update Now' type=button name='Update Now'></div>" | |
dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html | |
@count = $ '#count', dialog | |
@timer = $ '#timer', dialog | |
@thread = $.id "t#{g.THREAD_ID}" | |
@unsuccessfulFetchCount = 0 | |
@lastModified = '0' | |
for input in $$ 'input', dialog | |
if input.type is 'checkbox' | |
$.on input, 'click', $.cb.checked | |
switch input.name | |
when 'Scroll BG' | |
$.on input, 'click', @cb.scrollBG | |
@cb.scrollBG.call input | |
when 'Verbose' | |
$.on input, 'click', @cb.verbose | |
@cb.verbose.call input | |
when 'Auto Update This' | |
$.on input, 'click', @cb.autoUpdate | |
@cb.autoUpdate.call input | |
when 'Interval' | |
input.value = Conf['Interval'] | |
$.on input, 'change', @cb.interval | |
@cb.interval.call input | |
when 'Update Now' | |
$.on input, 'click', @update | |
$.add d.body, dialog | |
$.on d, 'QRPostSuccessful', @cb.post | |
$.on d, 'visibilitychange ovisibilitychange mozvisibilitychange webkitvisibilitychange', @cb.visibility | |
cb: | |
post: -> | |
return unless Conf['Auto Update This'] | |
Updater.unsuccessfulFetchCount = 0 | |
setTimeout Updater.update, 500 | |
visibility: -> | |
state = d.visibilityState or d.oVisibilityState or d.mozVisibilityState or d.webkitVisibilityState | |
return if state isnt 'visible' | |
# Reset the counter when we focus this tab. | |
Updater.unsuccessfulFetchCount = 0 | |
if Updater.timer.textContent < -Conf['Interval'] | |
Updater.set 'timer', -Updater.getInterval() | |
interval: -> | |
val = parseInt @value, 10 | |
@value = if val > 0 then val else 30 | |
$.cb.value.call @ | |
Updater.set 'timer', -Updater.getInterval() | |
verbose: -> | |
if Conf['Verbose'] | |
Updater.set 'count', '+0' | |
Updater.timer.hidden = false | |
else | |
Updater.set 'count', 'Thread Updater' | |
Updater.count.className = '' | |
Updater.timer.hidden = true | |
autoUpdate: -> | |
if Conf['Auto Update This'] = @checked | |
Updater.timeoutID = setTimeout Updater.timeout, 1000 | |
else | |
clearTimeout Updater.timeoutID | |
scrollBG: -> | |
Updater.scrollBG = | |
if @checked | |
-> true | |
else | |
-> !(d.hidden or d.oHidden or d.mozHidden or d.webkitHidden) | |
load: -> | |
switch @status | |
when 404 | |
Updater.set 'timer', '' | |
Updater.set 'count', 404 | |
Updater.count.className = 'warning' | |
clearTimeout Updater.timeoutID | |
g.dead = true | |
if Conf['Unread Count'] | |
Unread.title = Unread.title.match(/^.+-/)[0] + ' 404' | |
else | |
d.title = d.title.match(/^.+-/)[0] + ' 404' | |
Unread.update true | |
QR.abort() | |
# XXX 304 -> 0 in Opera | |
when 0, 304 | |
### | |
Status Code 304: Not modified | |
By sending the `If-Modified-Since` header we get a proper status code, and no response. | |
This saves bandwidth for both the user and the servers and avoid unnecessary computation. | |
### | |
Updater.unsuccessfulFetchCount++ | |
Updater.set 'timer', -Updater.getInterval() | |
if Conf['Verbose'] | |
Updater.set 'count', '+0' | |
Updater.count.className = null | |
when 200 | |
Updater.lastModified = @getResponseHeader 'Last-Modified' | |
Updater.cb.update JSON.parse(@response).posts | |
Updater.set 'timer', -Updater.getInterval() | |
else | |
Updater.unsuccessfulFetchCount++ | |
Updater.set 'timer', -Updater.getInterval() | |
if Conf['Verbose'] | |
Updater.set 'count', @statusText | |
Updater.count.className = 'warning' | |
delete Updater.request | |
update: (posts) -> | |
if spoilerRange = posts[0].custom_spoiler | |
Build.spoilerRange[g.BOARD] = spoilerRange | |
lastPost = Updater.thread.lastElementChild | |
id = +lastPost.id[2..] | |
nodes = [] | |
for post in posts.reverse() | |
break if post.no <= id # Make sure to not insert older posts. | |
nodes.push Build.postFromObject post, g.BOARD | |
count = nodes.length | |
if Conf['Verbose'] | |
Updater.set 'count', "+#{count}" | |
Updater.count.className = if count then 'new' else null | |
if count | |
Updater.unsuccessfulFetchCount = 0 | |
else | |
Updater.unsuccessfulFetchCount++ | |
return | |
scroll = Conf['Scrolling'] and Updater.scrollBG() and | |
lastPost.getBoundingClientRect().bottom - d.documentElement.clientHeight < 25 | |
$.add Updater.thread, nodes.reverse() | |
if scroll | |
nodes[0].scrollIntoView() | |
set: (name, text) -> | |
el = Updater[name] | |
if node = el.firstChild | |
# Prevent the creation of a new DOM Node | |
# by setting the text node's data. | |
node.data = text | |
else | |
el.textContent = text | |
getInterval: -> | |
i = +Conf['Interval'] | |
timeout: -> | |
Updater.timeoutID = setTimeout Updater.timeout, 1000 | |
n = 1 + Number Updater.timer.firstChild.data | |
if n is 0 | |
Updater.update() | |
else if n >= Updater.getInterval() | |
Updater.unsuccessfulFetchCount++ | |
Updater.set 'count', 'Retry' | |
Updater.count.className = null | |
Updater.update() | |
else | |
Updater.set 'timer', n | |
update: -> | |
Updater.set 'timer', 0 | |
{request} = Updater | |
if request | |
# Don't reset the counter when aborting. | |
request.onloadend = null | |
request.abort() | |
url = "//api.4chan.org/#{g.BOARD}/res/#{g.THREAD_ID}.json" | |
Updater.request = $.ajax url, onloadend: Updater.cb.load, | |
headers: 'If-Modified-Since': Updater.lastModified | |
Watcher = | |
init: -> | |
html = '<div class=move>Thread Watcher</div>' | |
@dialog = UI.dialog 'watcher', 'top: 50px; left: 0px;', html | |
$.add d.body, @dialog | |
#add watch buttons | |
for input in $$ '.op input' | |
favicon = $.el 'img', | |
className: 'favicon' | |
$.on favicon, 'click', @cb.toggle | |
$.before input, favicon | |
if g.THREAD_ID is $.get 'autoWatch', 0 | |
@watch g.THREAD_ID | |
$.delete 'autoWatch' | |
else | |
#populate watcher, display watch buttons | |
@refresh() | |
$.on d, 'QRPostSuccessful', @cb.post | |
$.sync 'watched', @refresh | |
refresh: (watched) -> | |
watched or= $.get 'watched', {} | |
nodes = [] | |
for board of watched | |
for id, props of watched[board] | |
x = $.el 'a', | |
textContent: '×' | |
href: 'javascript:;' | |
$.on x, 'click', Watcher.cb.x | |
link = $.el 'a', props | |
link.title = link.textContent | |
div = $.el 'div' | |
$.add div, [x, $.tn(' '), link] | |
nodes.push div | |
for div in $$ 'div:not(.move)', Watcher.dialog | |
$.rm div | |
$.add Watcher.dialog, nodes | |
watchedBoard = watched[g.BOARD] or {} | |
for favicon in $$ '.favicon' | |
id = favicon.nextSibling.name | |
if id of watchedBoard | |
favicon.src = Favicon.default | |
else | |
favicon.src = Favicon.empty | |
return | |
cb: | |
toggle: -> | |
Watcher.toggle @parentNode | |
x: -> | |
thread = @nextElementSibling.pathname.split '/' | |
Watcher.unwatch thread[3], thread[1] | |
post: (e) -> | |
{postID, threadID} = e.detail | |
if threadID is '0' | |
if Conf['Auto Watch'] | |
$.set 'autoWatch', postID | |
else if Conf['Auto Watch Reply'] | |
Watcher.watch threadID | |
toggle: (thread) -> | |
id = $('.favicon + input', thread).name | |
Watcher.watch(id) or Watcher.unwatch id, g.BOARD | |
unwatch: (id, board) -> | |
watched = $.get 'watched', {} | |
delete watched[board][id] | |
$.set 'watched', watched | |
Watcher.refresh() | |
watch: (id) -> | |
thread = $.id "t#{id}" | |
return false if $('.favicon', thread).src is Favicon.default | |
watched = $.get 'watched', {} | |
watched[g.BOARD] or= {} | |
watched[g.BOARD][id] = | |
href: "/#{g.BOARD}/res/#{id}" | |
textContent: Get.title thread | |
$.set 'watched', watched | |
Watcher.refresh() | |
true | |
Anonymize = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost | |
name = $ '.postInfo .name', post.el | |
name.textContent = 'Anonymous' | |
if (trip = name.nextElementSibling) and trip.className is 'postertrip' | |
$.rm trip | |
if (parent = name.parentNode).className is 'useremail' and not /^mailto:sage$/i.test parent.href | |
$.replace parent, name | |
Sauce = | |
init: -> | |
return if g.BOARD is 'f' | |
@links = [] | |
for link in Conf['sauces'].split '\n' | |
continue if link[0] is '#' | |
# XXX .trim() is there to fix Opera reading two different line breaks. | |
@links.push @createSauceLink link.trim() | |
return unless @links.length | |
Main.callbacks.push @node | |
createSauceLink: (link) -> | |
link = link.replace /(\$\d)/g, (parameter) -> | |
switch parameter | |
when '$1' | |
"' + (isArchived ? img.firstChild.src : 'http://thumbs.4chan.org' + img.pathname.replace(/src(\\/\\d+).+$/, 'thumb$1s.jpg')) + '" | |
when '$2' | |
"' + img.href + '" | |
when '$3' | |
"' + encodeURIComponent(img.firstChild.dataset.md5) + '" | |
when '$4' | |
g.BOARD | |
else | |
parameter | |
domain = if m = link.match(/;text:(.+)$/) then m[1] else link.match(/(\w+)\.\w+\//)[1] | |
href = link.replace /;text:.+$/, '' | |
href = Function 'img', 'isArchived', "return '#{href}'" | |
el = $.el 'a', | |
target: '_blank' | |
textContent: domain | |
(img, isArchived) -> | |
a = el.cloneNode true | |
a.href = href img, isArchived | |
a | |
node: (post) -> | |
{img} = post | |
return if post.isInlined and not post.isCrosspost or not img | |
img = img.parentNode | |
nodes = [] | |
for link in Sauce.links | |
nodes.push $.tn($.NBSP), link img, post.isArchived | |
$.add post.fileInfo, nodes | |
RevealSpoilers = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
{img} = post | |
if not (img and /^Spoiler/.test img.alt) or post.isInlined and not post.isCrosspost or post.isArchived | |
return | |
img.removeAttribute 'style' | |
# revealed spoilers do not have height/width set, this fixes auto-gifs dimensions. | |
s = img.style | |
s.maxHeight = s.maxWidth = if /\bop\b/.test post.class then '250px' else '125px' | |
img.src = "//thumbs.4chan.org#{img.parentNode.pathname.replace /src(\/\d+).+$/, 'thumb$1s.jpg'}" | |
Time = | |
init: -> | |
Time.foo() | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost | |
node = $ '.postInfo > .dateTime', post.el | |
Time.date = new Date node.dataset.utc * 1000 | |
node.textContent = Time.funk Time | |
foo: -> | |
code = Conf['time'].replace /%([A-Za-z])/g, (s, c) -> | |
if c of Time.formatters | |
"' + Time.formatters.#{c}() + '" | |
else | |
s | |
Time.funk = Function 'Time', "return '#{code}'" | |
day: [ | |
'Sunday' | |
'Monday' | |
'Tuesday' | |
'Wednesday' | |
'Thursday' | |
'Friday' | |
'Saturday' | |
] | |
month: [ | |
'January' | |
'February' | |
'March' | |
'April' | |
'May' | |
'June' | |
'July' | |
'August' | |
'September' | |
'October' | |
'November' | |
'December' | |
] | |
zeroPad: (n) -> if n < 10 then '0' + n else n | |
formatters: | |
a: -> Time.day[Time.date.getDay()][...3] | |
A: -> Time.day[Time.date.getDay()] | |
b: -> Time.month[Time.date.getMonth()][...3] | |
B: -> Time.month[Time.date.getMonth()] | |
d: -> Time.zeroPad Time.date.getDate() | |
e: -> Time.date.getDate() | |
H: -> Time.zeroPad Time.date.getHours() | |
I: -> Time.zeroPad Time.date.getHours() % 12 or 12 | |
k: -> Time.date.getHours() | |
l: -> Time.date.getHours() % 12 or 12 | |
m: -> Time.zeroPad Time.date.getMonth() + 1 | |
M: -> Time.zeroPad Time.date.getMinutes() | |
p: -> if Time.date.getHours() < 12 then 'AM' else 'PM' | |
P: -> if Time.date.getHours() < 12 then 'am' else 'pm' | |
S: -> Time.zeroPad Time.date.getSeconds() | |
y: -> Time.date.getFullYear() - 2000 | |
FileInfo = | |
init: -> | |
return if g.BOARD is 'f' | |
@setFormats() | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost or not post.fileInfo | |
node = post.fileInfo.firstElementChild | |
alt = post.img.alt | |
filename = $('span', node)?.title or node.title | |
FileInfo.data = | |
link: post.img.parentNode.href | |
spoiler: /^Spoiler/.test alt | |
size: alt.match(/\d+\.?\d*/)[0] | |
unit: alt.match(/\w+$/)[0] | |
resolution: node.textContent.match(/\d+x\d+|PDF/)[0] | |
fullname: filename | |
shortname: Build.shortFilename filename, post.ID is post.threadID | |
# XXX GM/Scriptish | |
node.setAttribute 'data-filename', filename | |
node.innerHTML = FileInfo.funk FileInfo | |
setFormats: -> | |
code = Conf['fileInfo'].replace /%([BKlLMnNprs])/g, (s, c) -> | |
if c of FileInfo.formatters | |
"' + f.formatters.#{c}() + '" | |
else | |
s | |
@funk = Function 'f', "return '#{code}'" | |
convertUnit: (unitT) -> | |
size = @data.size | |
unitF = @data.unit | |
if unitF isnt unitT | |
units = ['B', 'KB', 'MB'] | |
i = units.indexOf(unitF) - units.indexOf unitT | |
unitT = 'Bytes' if unitT is 'B' | |
if i > 0 | |
size *= 1024 while i-- > 0 | |
else if i < 0 | |
size /= 1024 while i++ < 0 | |
if size < 1 and size.toString().length > size.toFixed(2).length | |
size = size.toFixed 2 | |
"#{size} #{unitT}" | |
formatters: | |
l: -> "<a href=#{FileInfo.data.link} target=_blank>#{@n()}</a>" | |
L: -> "<a href=#{FileInfo.data.link} target=_blank>#{@N()}</a>" | |
n: -> | |
if FileInfo.data.fullname is FileInfo.data.shortname | |
FileInfo.data.fullname | |
else | |
"<span class=fntrunc>#{FileInfo.data.shortname}</span><span class=fnfull>#{FileInfo.data.fullname}</span>" | |
N: -> FileInfo.data.fullname | |
p: -> if FileInfo.data.spoiler then 'Spoiler, ' else '' | |
s: -> "#{FileInfo.data.size} #{FileInfo.data.unit}" | |
B: -> FileInfo.convertUnit 'B' | |
K: -> FileInfo.convertUnit 'KB' | |
M: -> FileInfo.convertUnit 'MB' | |
r: -> FileInfo.data.resolution | |
Get = | |
post: (board, threadID, postID, root, cb) -> | |
if board is g.BOARD and post = $.id "pc#{postID}" | |
$.add root, Get.cleanPost post.cloneNode true | |
return | |
root.textContent = "Loading post No.#{postID}..." | |
if threadID | |
$.cache "//api.4chan.org/#{board}/res/#{threadID}.json", -> | |
Get.parsePost @, board, threadID, postID, root, cb | |
else if url = Redirect.post board, postID | |
$.cache url, -> | |
Get.parseArchivedPost @, board, postID, root, cb | |
parsePost: (req, board, threadID, postID, root, cb) -> | |
{status} = req | |
if status isnt 200 | |
# The thread can die by the time we check a quote. | |
if url = Redirect.post board, postID | |
$.cache url, -> | |
Get.parseArchivedPost @, board, postID, root, cb | |
else | |
$.addClass root, 'warning' | |
root.textContent = | |
if status is 404 | |
"Thread No.#{threadID} 404'd." | |
else | |
"Error #{req.status}: #{req.statusText}." | |
return | |
posts = JSON.parse(req.response).posts | |
if spoilerRange = posts[0].custom_spoiler | |
Build.spoilerRange[board] = spoilerRange | |
postID = +postID | |
for post in posts | |
break if post.no is postID # we found it! | |
if post.no > postID | |
# The post can be deleted by the time we check a quote. | |
if url = Redirect.post board, postID | |
$.cache url, -> | |
Get.parseArchivedPost @, board, postID, root, cb | |
else | |
$.addClass root, 'warning' | |
root.textContent = "Post No.#{postID} was not found." | |
return | |
$.replace root.firstChild, Get.cleanPost Build.postFromObject post, board | |
cb() if cb | |
parseArchivedPost: (req, board, postID, root, cb) -> | |
data = JSON.parse req.response | |
if data.error | |
$.addClass root, 'warning' | |
root.textContent = data.error | |
return | |
# convert comment to html | |
bq = $.el 'blockquote', textContent: data.comment # set this first to convert text to HTML entities | |
# https://github.com/eksopl/fuuka/blob/master/Board/Yotsuba.pm#L413-452 | |
# https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138 | |
bq.innerHTML = bq.innerHTML.replace /// | |
\n | |
| \[/?b\] | |
| \[/?spoiler\] | |
| \[/?code\] | |
| \[/?moot\] | |
| \[/?banned\] | |
///g, (text) -> | |
switch text | |
when '\n' | |
'<br>' | |
when '[b]' | |
'<b>' | |
when '[/b]' | |
'</b>' | |
when '[spoiler]' | |
'<span class=spoiler>' | |
when '[/spoiler]' | |
'</span>' | |
when '[code]' | |
'<pre class=prettyprint>' | |
when '[/code]' | |
'</pre>' | |
when '[moot]' | |
'<div style="padding:5px;margin-left:.5em;border-color:#faa;border:2px dashed rgba(255,0,0,.1);border-radius:2px">' | |
when '[/moot]' | |
'</div>' | |
when '[banned]' | |
'<b style="color: red;">' | |
when '[/banned]' | |
'</b>' | |
# greentext | |
comment = bq.innerHTML.replace /(^|>)(>[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3' | |
o = | |
# id | |
postID: postID | |
threadID: data.thread_num | |
board: board | |
# info | |
name: data.name_processed | |
capcode: switch data.capcode | |
when 'M' then 'mod' | |
when 'A' then 'admin' | |
when 'D' then 'developer' | |
tripcode: data.trip | |
uniqueID: data.poster_hash | |
email: if data.email then encodeURIComponent data.email.replace /"/g, '"' else '' | |
subject: data.title_processed | |
flagCode: data.poster_country | |
flagName: data.poster_country_name_processed | |
date: data.fourchan_date | |
dateUTC: data.timestamp | |
comment: comment | |
# file | |
if data.media.media_filename | |
o.file = | |
name: data.media.media_filename_processed | |
timestamp: data.media.media_orig | |
url: data.media.media_link or data.media.remote_media_link | |
height: data.media.media_h | |
width: data.media.media_w | |
MD5: data.media.media_hash | |
size: data.media.media_size | |
turl: data.media.thumb_link or "//thumbs.4chan.org/#{board}/thumb/#{data.media.preview_orig}" | |
theight: data.media.preview_h | |
twidth: data.media.preview_w | |
isSpoiler: data.media.spoiler is '1' | |
$.replace root.firstChild, Get.cleanPost Build.post o, true | |
cb() if cb | |
cleanPost: (root) -> | |
post = $ '.post', root | |
for child in Array::slice.call root.childNodes | |
$.rm child unless child is post | |
# Remove inlined posts inside of this post. | |
for inline in $$ '.inline', post | |
$.rm inline | |
for inlined in $$ '.inlined', post | |
$.rmClass inlined, 'inlined' | |
# Don't mess with other features | |
now = Date.now() | |
els = $$ '[id]', root | |
els.push root | |
for el in els | |
el.id = "#{now}_#{el.id}" | |
$.rmClass root, 'forwarded' | |
$.rmClass root, 'qphl' # op | |
$.rmClass post, 'highlight' | |
$.rmClass post, 'qphl' # reply | |
root.hidden = post.hidden = false | |
root | |
title: (thread) -> | |
op = $ '.op', thread | |
el = $ '.postInfo .subject', op | |
unless el.textContent | |
el = $ 'blockquote', op | |
unless el.textContent | |
el = $ '.nameBlock', op | |
span = $.el 'span', innerHTML: el.innerHTML.replace /<br>/g, ' ' | |
"/#{g.BOARD}/ - #{span.textContent.trim()}" | |
Build = | |
spoilerRange: {} | |
shortFilename: (filename, isOP) -> | |
# FILENAME SHORTENING SCIENCE: | |
# OPs have a +10 characters threshold. | |
# The file extension is not taken into account. | |
threshold = if isOP then 40 else 30 | |
if filename.length - 4 > threshold | |
"#{filename[...threshold - 5]}(...).#{filename[-3..]}" | |
else | |
filename | |
postFromObject: (data, board) -> | |
o = | |
# id | |
postID: data.no | |
threadID: data.resto or data.no | |
board: board | |
# info | |
name: data.name | |
capcode: data.capcode | |
tripcode: data.trip | |
uniqueID: data.id | |
email: if data.email then encodeURIComponent data.email.replace /"/g, '"' else '' | |
subject: data.sub | |
flagCode: data.country | |
flagName: data.country_name | |
date: data.now | |
dateUTC: data.time | |
comment: data.com | |
# thread status | |
isSticky: !!data.sticky | |
isClosed: !!data.closed | |
# file | |
if data.ext or data.filedeleted | |
o.file = | |
name: data.filename + data.ext | |
timestamp: "#{data.tim}#{data.ext}" | |
url: "//images.4chan.org/#{board}/src/#{data.tim}#{data.ext}" | |
height: data.h | |
width: data.w | |
MD5: data.md5 | |
size: data.fsize | |
turl: "//thumbs.4chan.org/#{board}/thumb/#{data.tim}s.jpg" | |
theight: data.tn_h | |
twidth: data.tn_w | |
isSpoiler: !!data.spoiler | |
isDeleted: !!data.filedeleted | |
Build.post o | |
post: (o, isArchived) -> | |
### | |
This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS). | |
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE | |
### | |
{ | |
postID, threadID, board | |
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC | |
isSticky, isClosed | |
comment | |
file | |
} = o | |
isOP = postID is threadID | |
staticPath = '//static.4chan.org' | |
if email | |
emailStart = '<a href="mailto:' + email + '" class="useremail">' | |
emailEnd = '</a>' | |
else | |
emailStart = '' | |
emailEnd = '' | |
subject = | |
if subject | |
"<span class=subject>#{subject}</span>" | |
else | |
'' | |
userID = | |
if !capcode and uniqueID | |
" <span class='posteruid id_#{uniqueID}'>(ID: " + | |
"<span class=hand title='Highlight posts by this ID'>#{uniqueID}</span>)</span> " | |
else | |
'' | |
switch capcode | |
when 'admin', 'admin_highlight' | |
capcodeClass = " capcodeAdmin" | |
capcodeStart = " <strong class='capcode hand id_admin'" + | |
"title='Highlight posts by the Administrator'>## Admin</strong>" | |
capcode = " <img src='#{staticPath}/image/adminicon.gif' " + | |
"alt='This user is the 4chan Administrator.' " + | |
"title='This user is the 4chan Administrator.' class=identityIcon>" | |
when 'mod' | |
capcodeClass = " capcodeMod" | |
capcodeStart = " <strong class='capcode hand id_mod' " + | |
"title='Highlight posts by Moderators'>## Mod</strong>" | |
capcode = " <img src='#{staticPath}/image/modicon.gif' " + | |
"alt='This user is a 4chan Moderator.' " + | |
"title='This user is a 4chan Moderator.' class=identityIcon>" | |
when 'developer' | |
capcodeClass = " capcodeDeveloper" | |
capcodeStart = " <strong class='capcode hand id_developer' " + | |
"title='Highlight posts by Developers'>## Developer</strong>" | |
capcode = " <img src='#{staticPath}/image/developericon.gif' " + | |
"alt='This user is a 4chan Developer.' " + | |
"title='This user is a 4chan Developer.' class=identityIcon>" | |
else | |
capcodeClass = '' | |
capcodeStart = '' | |
capcode = '' | |
flag = | |
if flagCode | |
" <img src='#{staticPath}/image/country/#{if board is 'pol' then 'troll/' else ''}" + | |
flagCode.toLowerCase() + ".gif' alt=#{flagCode} title='#{flagName}' class=countryFlag>" | |
else | |
'' | |
if file?.isDeleted | |
fileHTML = | |
if isOP | |
"<div class=file id=f#{postID}><div class=fileInfo></div><span class=fileThumb>" + | |
"<img src='#{staticPath}/image/filedeleted.gif' alt='File deleted.'>" + | |
"</span></div>" | |
else | |
"<div id=f#{postID} class=file><span class=fileThumb>" + | |
"<img src='#{staticPath}/image/filedeleted-res.gif' alt='File deleted.'>" + | |
"</span></div>" | |
else if file | |
ext = file.name[-3..] | |
if !file.twidth and !file.theight and ext is 'gif' # wtf ? | |
file.twidth = file.width | |
file.theight = file.height | |
fileSize = $.bytesToString file.size | |
fileThumb = file.turl | |
if file.isSpoiler | |
fileSize = "Spoiler Image, #{fileSize}" | |
unless isArchived | |
fileThumb = '//static.4chan.org/image/spoiler' | |
if spoilerRange = Build.spoilerRange[board] | |
# Randomize the spoiler image. | |
fileThumb += "-#{board}" + Math.floor 1 + spoilerRange * Math.random() | |
fileThumb += '.png' | |
file.twidth = file.theight = 100 | |
imgSrc = "<a class='fileThumb#{if file.isSpoiler then ' imgspoiler' else ''}' href='#{file.url}' target=_blank>" + | |
"<img src='#{fileThumb}' alt='#{fileSize}' data-md5=#{file.MD5} style='width:#{file.twidth}px;height:#{file.theight}px'></a>" | |
# Ha Ha filenames. | |
# html -> text, translate WebKit's %22s into "s | |
a = $.el 'a', innerHTML: file.name | |
filename = a.textContent.replace /%22/g, '"' | |
# shorten filename, get html | |
a.textContent = Build.shortFilename filename | |
shortFilename = a.innerHTML | |
# get html | |
a.textContent = filename | |
filename = a.innerHTML.replace /'/g, ''' | |
fileDims = if ext is 'pdf' then 'PDF' else "#{file.width}x#{file.height}" | |
fileInfo = "<span class=fileText id=fT#{postID}#{if file.isSpoiler then " title='#{filename}'" else ''}>File: <a href='#{file.url}' target=_blank>#{file.timestamp}</a>" + | |
"-(#{fileSize}, #{fileDims}#{ | |
if file.isSpoiler | |
'' | |
else | |
", <span title='#{filename}'>#{shortFilename}</span>" | |
}" + ")</span>" | |
fileHTML = "<div id=f#{postID} class=file><div class=fileInfo>#{fileInfo}</div>#{imgSrc}</div>" | |
else | |
fileHTML = '' | |
tripcode = | |
if tripcode | |
" <span class=postertrip>#{tripcode}</span>" | |
else | |
'' | |
sticky = | |
if isSticky | |
' <img src=//static.4chan.org/image/sticky.gif alt=Sticky title=Sticky style="height:16px;width:16px">' | |
else | |
'' | |
closed = | |
if isClosed | |
' <img src=//static.4chan.org/image/closed.gif alt=Closed title=Closed style="height:16px;width:16px">' | |
else | |
'' | |
container = $.el 'div', | |
id: "pc#{postID}" | |
className: "postContainer #{if isOP then 'op' else 'reply'}Container" | |
innerHTML: \ | |
(if isOP then '' else "<div class=sideArrows id=sa#{postID}>>></div>") + | |
"<div id=p#{postID} class='post #{if isOP then 'op' else 'reply'}#{ | |
if capcode is 'admin_highlight' | |
' highlightPost' | |
else | |
'' | |
}'>" + | |
"<div class='postInfoM mobile' id=pim#{postID}>" + | |
"<span class='nameBlock#{capcodeClass}'>" + | |
"<span class=name>#{name or ''}</span>" + tripcode + | |
capcodeStart + capcode + userID + flag + sticky + closed + | |
"<br>#{subject}" + | |
"</span><span class='dateTime postNum' data-utc=#{dateUTC}>#{date}" + | |
'<br><em>' + | |
"<a href=#{"/#{board}/res/#{threadID}#p#{postID}"}>No.</a>" + | |
"<a href='#{ | |
if g.REPLY and g.THREAD_ID is threadID | |
"javascript:quote(#{postID})" | |
else | |
"/#{board}/res/#{threadID}#q#{postID}" | |
}'>#{postID}</a>" + | |
'</em></span>' + | |
'</div>' + | |
(if isOP then fileHTML else '') + | |
"<div class='postInfo desktop' id=pi#{postID}>" + | |
"<input type=checkbox name=#{postID} value=delete> " + | |
"#{subject} " + | |
"<span class='nameBlock#{capcodeClass}'>" + | |
emailStart + | |
"<span class=name>#{name or ''}</span>" + tripcode + | |
capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + | |
' </span> ' + | |
"<span class=dateTime data-utc=#{dateUTC}>#{date}</span> " + | |
"<span class='postNum desktop'>" + | |
"<a href=#{"/#{board}/res/#{threadID}#p#{postID}"} title='Highlight this post'>No.</a>" + | |
"<a href='#{ | |
if g.REPLY and g.THREAD_ID is threadID | |
"javascript:quote(#{postID})" | |
else | |
"/#{board}/res/#{threadID}#q#{postID}" | |
}' title='Quote this post'>#{postID}</a>" + | |
'</span>' + | |
'</div>' + | |
(if isOP then '' else fileHTML) + | |
"<blockquote class=postMessage id=m#{postID}>#{comment or ''}</blockquote> " + | |
'</div>' | |
for quote in $$ '.quotelink', container | |
href = quote.getAttribute 'href' | |
continue if href[0] is '/' # Cross-board quote, or board link | |
quote.href = "/#{board}/res/#{href}" # Fix pathnames | |
container | |
TitlePost = | |
init: -> | |
d.title = Get.title() | |
QuoteBacklink = | |
init: -> | |
format = Conf['backlink'].replace /%id/g, "' + id + '" | |
@funk = Function 'id', "return '#{format}'" | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined | |
quotes = {} | |
for quote in post.quotes | |
# Stop at 'Admin/Mod/Dev Replies:' on /q/ | |
break if quote.parentNode.getAttribute('style') is 'font-size: smaller;' | |
# Don't process >>>/b/. | |
if qid = quote.hash[2..] | |
# Duplicate quotes get overwritten. | |
quotes[qid] = true | |
a = $.el 'a', | |
href: "/#{g.BOARD}/res/#{post.threadID}#p#{post.ID}" | |
className: if post.el.hidden then 'filtered backlink' else 'backlink' | |
textContent: QuoteBacklink.funk post.ID | |
for qid of quotes | |
# Don't backlink the OP. | |
continue if !(el = $.id "pi#{qid}") or !Conf['OP Backlinks'] and /\bop\b/.test el.parentNode.className | |
link = a.cloneNode true | |
if Conf['Quote Preview'] | |
$.on link, 'mouseover', QuotePreview.mouseover | |
if Conf['Quote Inline'] | |
$.on link, 'click', QuoteInline.toggle | |
unless container = $.id "blc#{qid}" | |
container = $.el 'span', | |
className: 'container' | |
id: "blc#{qid}" | |
$.add el, container | |
$.add container, [$.tn(' '), link] | |
return | |
QuoteInline = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
for quote in post.quotes | |
continue unless quote.hash or /\bdeadlink\b/.test quote.className | |
$.on quote, 'click', QuoteInline.toggle | |
for quote in post.backlinks | |
$.on quote, 'click', QuoteInline.toggle | |
return | |
toggle: (e) -> | |
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 | |
e.preventDefault() | |
id = @dataset.id or @hash[2..] | |
if /\binlined\b/.test @className | |
QuoteInline.rm @, id | |
else | |
return if $.x "ancestor::div[contains(@id,'p#{id}')]", @ | |
QuoteInline.add @, id | |
@classList.toggle 'inlined' | |
add: (q, id) -> | |
if q.host is 'boards.4chan.org' | |
path = q.pathname.split '/' | |
board = path[1] | |
threadID = path[3] | |
postID = id | |
else | |
board = q.dataset.board | |
threadID = 0 | |
postID = q.dataset.id | |
el = if board is g.BOARD then $.id "p#{postID}" else false | |
inline = $.el 'div', | |
id: "i#{postID}" | |
className: if el then 'inline' else 'inline crosspost' | |
root = | |
if isBacklink = /\bbacklink\b/.test q.className | |
q.parentNode | |
else | |
$.x 'ancestor-or-self::*[parent::blockquote][1]', q | |
$.after root, inline | |
Get.post board, threadID, postID, inline | |
return unless el | |
# Will only unhide if there's no inlined backlinks of it anymore. | |
if isBacklink and Conf['Forward Hiding'] | |
$.addClass el.parentNode, 'forwarded' | |
++el.dataset.forwarded or el.dataset.forwarded = 1 | |
# Decrease the unread count if this post is unread | |
if Unread.replies and postID of Unread.replies | |
Unread.replies.rm postID | |
Unread.update true | |
rm: (q, id) -> | |
# select the corresponding inlined quote or loading quote | |
div = $.x "following::div[@id='i#{id}']", q | |
$.rm div | |
return unless Conf['Forward Hiding'] | |
for inlined in $$ '.backlink.inlined', div | |
div = $.id inlined.hash[1..] | |
$.rmClass div.parentNode, 'forwarded' unless --div.dataset.forwarded | |
if /\bbacklink\b/.test q.className | |
div = $.id "p#{id}" | |
$.rmClass div.parentNode, 'forwarded' unless --div.dataset.forwarded | |
QuotePreview = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
for quote in post.quotes | |
$.on quote, 'mouseover', QuotePreview.mouseover if quote.hash or /\bdeadlink\b/.test quote.className | |
for quote in post.backlinks | |
$.on quote, 'mouseover', QuotePreview.mouseover | |
return | |
mouseover: (e) -> | |
return if /\binlined\b/.test @className | |
# Make sure to remove the previous qp | |
# in case it got stuck. Opera-only bug? | |
if qp = $.id 'qp' | |
if qp is UI.el | |
delete UI.el | |
$.rm qp | |
# Don't stop other elements from dragging | |
return if UI.el | |
if @host is 'boards.4chan.org' | |
path = @pathname.split '/' | |
board = path[1] | |
threadID = path[3] | |
postID = @hash[2..] | |
else | |
board = @dataset.board | |
threadID = 0 | |
postID = @dataset.id | |
qp = UI.el = $.el 'div', | |
id: 'qp' | |
className: 'reply dialog' | |
UI.hover e | |
$.add d.body, qp | |
el = $.id "p#{postID}" if board is g.BOARD | |
Get.post board, threadID, postID, qp, -> | |
bq = $ 'blockquote', qp | |
Main.prettify bq | |
post = | |
el: qp | |
blockquote: bq | |
isArchived: /\barchivedPost\b/.test qp.className | |
if img = $ 'img[data-md5]', qp | |
post.fileInfo = img.parentNode.previousElementSibling | |
post.img = img | |
if Conf['Reveal Spoilers'] | |
RevealSpoilers.node post | |
if Conf['Image Auto-Gif'] | |
AutoGif.node post | |
if Conf['Time Formatting'] | |
Time.node post | |
if Conf['File Info Formatting'] | |
FileInfo.node post | |
if Conf['Resurrect Quotes'] | |
Quotify.node post | |
$.on @, 'mousemove', UI.hover | |
$.on @, 'mouseout click', QuotePreview.mouseout | |
return unless el | |
if Conf['Quote Highlighting'] | |
if /\bop\b/.test el.className | |
$.addClass el.parentNode, 'qphl' | |
else | |
$.addClass el, 'qphl' | |
quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0] | |
for quote in $$ '.quotelink, .backlink', qp | |
if quote.hash[2..] is quoterID | |
$.addClass quote, 'forwardlink' | |
return | |
mouseout: (e) -> | |
UI.hoverend() | |
if el = $.id @hash[1..] | |
$.rmClass el, 'qphl' # reply | |
$.rmClass el.parentNode, 'qphl' # op | |
$.off @, 'mousemove', UI.hover | |
$.off @, 'mouseout click', QuotePreview.mouseout | |
QuoteOP = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost | |
for quote in post.quotes | |
if quote.hash[2..] is post.threadID | |
$.add quote, $.tn $.NBSP + '(OP)' | |
return | |
QuoteCT = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost | |
for quote in post.quotes | |
unless quote.hash | |
# Make sure this isn't a link to the board we're on. | |
continue | |
path = quote.pathname.split '/' | |
# If quote leads to a different thread id and is located on the same board. | |
if path[1] is g.BOARD and path[3] isnt post.threadID | |
$.add quote, $.tn $.NBSP + '(Cross-thread)' | |
return | |
Quotify = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined and not post.isCrosspost | |
# Get all the text nodes that are not inside an anchor or pre ([code] tags). | |
snapshot = $.X './/text()[not(parent::a)][not(ancestor::pre)]', post.blockquote | |
for i in [0...snapshot.snapshotLength] | |
node = snapshot.snapshotItem i | |
data = node.data | |
unless quotes = data.match />>(>\/[a-z\d]+\/)?\d+/g | |
# Only accept nodes with potentially valid links | |
continue | |
nodes = [] | |
for quote in quotes | |
index = data.indexOf quote | |
if text = data[...index] | |
# Potential text before this valid quote. | |
nodes.push $.tn text | |
id = quote.match(/\d+$/)[0] | |
board = | |
if m = quote.match /^>>>\/([a-z\d]+)/ | |
m[1] | |
else | |
# Get the post's board, whether it's inlined or not. | |
$('a[title="Highlight this post"]', post.el).pathname.split('/')[1] | |
nodes.push a = $.el 'a', | |
textContent: "#{quote}#{$.NBSP}(Dead)" | |
if board is g.BOARD and $.id "p#{id}" | |
a.href = "#p#{id}" | |
a.className = 'quotelink' | |
else | |
a.href = Redirect.thread board, 0, id | |
a.className = 'deadlink' | |
a.target = '_blank' | |
if Redirect.post board, id | |
$.addClass a, 'quotelink' | |
# XXX WTF Scriptish/Greasemonkey? | |
# Setting dataset attributes that way doesn't affect the HTML, | |
# but are, I suspect, kept as object key/value pairs and GC'd later. | |
# a.dataset.board = board | |
# a.dataset.id = id | |
a.setAttribute 'data-board', board | |
a.setAttribute 'data-id', id | |
data = data[index + quote.length..] | |
if data | |
# Potential text after the last valid quote. | |
nodes.push $.tn data | |
$.replace node, nodes | |
return | |
QuoteThreading = | |
init: -> | |
return unless Conf['Unread Count'] or Conf['Unread Favicon'] | |
Main.callbacks.push @node | |
@enabled = true | |
controls = $.el 'span', | |
innerHTML: '<label>Threading<input id=threadingControl type=checkbox checked></label>' | |
input = $ 'input', controls | |
$.on input, 'change', QuoteThreading.toggle | |
form = $ '#delform' | |
$.prepend form, controls | |
node: (post) -> | |
#Random access list | |
# | |
#array implementation is very awkward - mid-array inserts, loop to find | |
#quoted post, loop to find inserted post(!), loop to find distance from | |
#threaded post to thread root | |
# | |
#of course, implementing your own data structure can be awkward... | |
return if post.isInlined or not QuoteThreading.enabled | |
{quotes, ID} = post | |
{replies} = Unread | |
return unless reply = replies[ID] #foresee, filtered | |
uniq = {} | |
for quote in quotes | |
qid = quote.hash[2..] | |
continue unless qid < ID | |
if qid of replies | |
uniq[qid] = true | |
keys = Object.keys uniq | |
return unless keys.length is 1 | |
qid = keys[0] | |
qreply = replies[qid] | |
qroot = qreply.el.parentNode | |
threadContainer = qroot.nextSibling | |
if threadContainer?.className isnt 'threadContainer' | |
threadContainer = $.el 'div', className: 'threadContainer' | |
$.after qroot, threadContainer | |
$.add threadContainer, reply.el.parentNode | |
pEl = $.x 'preceding::div[contains(@class,"post reply")][1]/parent::div', reply.el.parentNode | |
pid = pEl.id[2..] | |
preply = replies[pid] | |
replies.after preply, reply | |
toggle: -> | |
Main.disconnect() | |
Unread.replies = new $.RandomAccessList | |
thread = $ '.thread' | |
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread | |
QuoteThreading.enabled = @checked | |
if @checked | |
nodes = (Main.preParse reply for reply in replies) | |
Unread.node node for node in nodes | |
Unread.scroll() | |
QuoteThreading.node node for node in nodes | |
else | |
replies.sort (a, b) -> | |
aID = Number a.id[2..] | |
bID = Number b.id[2..] | |
aID - bID | |
$.add thread, replies | |
containers = $$ '.threadContainer', thread | |
$.rm container for container in containers | |
Unread.update true | |
Main.observe() | |
public: | |
toggle: -> | |
control = $.id 'threadingControl' | |
control.checked = not control.checked | |
QuoteThreading.toggle.call control | |
DeleteLink = | |
init: -> | |
div = $.el 'div', | |
className: 'delete_link' | |
textContent: 'Delete' | |
aPost = $.el 'a', | |
className: 'delete_post' | |
href: 'javascript:;' | |
aImage = $.el 'a', | |
className: 'delete_image' | |
href: 'javascript:;' | |
children = [] | |
children.push | |
el: aPost | |
open: -> | |
aPost.textContent = 'Post' | |
$.on aPost, 'click', DeleteLink.delete | |
true | |
children.push | |
el: aImage | |
open: (post) -> | |
return false unless post.img | |
aImage.textContent = 'Image' | |
$.on aImage, 'click', DeleteLink.delete | |
true | |
Menu.addEntry | |
el: div | |
open: (post) -> | |
if post.isArchived | |
return false | |
node = div.firstChild | |
if seconds = DeleteLink.cooldown[post.ID] | |
node.textContent = "Delete (#{seconds})" | |
DeleteLink.cooldown.el = node | |
else | |
node.textContent = 'Delete' | |
delete DeleteLink.cooldown.el | |
true | |
children: children | |
$.on d, 'QRPostSuccessful', @cooldown.start | |
delete: -> | |
menu = $.id 'menu' | |
{id} = menu.dataset | |
return if DeleteLink.cooldown[id] | |
$.off @, 'click', DeleteLink.delete | |
@textContent = 'Deleting...' | |
pwd = | |
if m = d.cookie.match /4chan_pass=([^;]+)/ | |
decodeURIComponent m[1] | |
else | |
$.id('delPassword').value | |
board = $('a[title="Highlight this post"]', | |
$.id menu.dataset.rootid).pathname.split('/')[1] | |
self = @ | |
form = | |
mode: 'usrdel' | |
onlyimgdel: /\bdelete_image\b/.test @className | |
pwd: pwd | |
form[id] = 'delete' | |
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{board}/"), { | |
onload: -> DeleteLink.load self, @response | |
onerror: -> DeleteLink.error self | |
}, { | |
form: $.formData form | |
} | |
load: (self, html) -> | |
doc = d.implementation.createHTMLDocument '' | |
doc.documentElement.innerHTML = html | |
if doc.title is '4chan - Banned' # Ban/warn check | |
s = 'Banned!' | |
else if msg = doc.getElementById 'errmsg' # error! | |
s = msg.textContent | |
$.on self, 'click', DeleteLink.delete | |
else | |
s = 'Deleted' | |
self.textContent = s | |
error: (self) -> | |
self.textContent = 'Connection error, please retry.' | |
$.on self, 'click', DeleteLink.delete | |
cooldown: | |
start: (e) -> | |
DeleteLink.cooldown.count e.detail.postID, 30 | |
count: (postID, seconds) -> | |
return unless 0 <= seconds <= 30 | |
setTimeout DeleteLink.cooldown.count, 1000, postID, seconds-1 | |
{el} = DeleteLink.cooldown | |
if seconds is 0 | |
el?.textContent = 'Delete' | |
delete DeleteLink.cooldown[postID] | |
delete DeleteLink.cooldown.el | |
return | |
el?.textContent = "Delete (#{seconds})" | |
DeleteLink.cooldown[postID] = seconds | |
ReportLink = | |
init: -> | |
a = $.el 'a', | |
className: 'report_link' | |
href: 'javascript:;' | |
textContent: 'Report this post' | |
$.on a, 'click', @report | |
Menu.addEntry | |
el: a | |
open: (post) -> | |
post.isArchived is false | |
report: -> | |
a = $ 'a[title="Highlight this post"]', $.id @parentNode.dataset.rootid | |
url = "//sys.4chan.org/#{a.pathname.split('/')[1]}/imgboard.php?mode=report&no=#{@parentNode.dataset.id}" | |
id = Date.now() | |
set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200" | |
window.open url, id, set | |
DownloadLink = | |
init: -> | |
# Test for download feature support. | |
return if $.el('a').download is undefined | |
a = $.el 'a', | |
className: 'download_link' | |
textContent: 'Download file' | |
Menu.addEntry | |
el: a | |
open: (post) -> | |
unless post.img | |
return false | |
a.href = post.img.parentNode.href | |
fileText = post.fileInfo.firstElementChild | |
a.download = | |
if Conf['File Info Formatting'] | |
fileText.dataset.filename | |
else | |
$('span', fileText).title | |
true | |
ArchiveLink = | |
init: -> | |
a = $.el 'a', | |
className: 'archive_link' | |
target: '_blank' | |
textContent: 'Archived post' | |
Menu.addEntry | |
el: a | |
open: (post) -> | |
path = $('a[title="Highlight this post"]', post.el).pathname.split '/' | |
if (href = Redirect.thread path[1], path[3], post.ID) is "//boards.4chan.org/#{path[1]}/" | |
return false | |
a.href = href | |
true | |
ThreadStats = | |
init: -> | |
dialog = UI.dialog 'stats', 'bottom: 0; left: 0;', '<div class=move><span id=postcount>0</span> / <span id=imagecount>0</span></div>' | |
dialog.className = 'dialog' | |
$.add d.body, dialog | |
@posts = @images = 0 | |
@imgLimit = | |
switch g.BOARD | |
when 'a', 'b', 'v', 'co', 'mlp' | |
251 | |
when 'vg' | |
376 | |
else | |
151 | |
Main.callbacks.push @node | |
node: (post) -> | |
return if post.isInlined | |
$.id('postcount').textContent = ++ThreadStats.posts | |
return unless post.img | |
imgcount = $.id 'imagecount' | |
imgcount.textContent = ++ThreadStats.images | |
if ThreadStats.images > ThreadStats.imgLimit | |
$.addClass imgcount, 'warning' | |
Unread = | |
init: -> | |
@replies = new $.RandomAccessList | |
@title = d.title | |
$.on d, 'QRPostSuccessful', @post | |
@update() | |
$.on window, 'scroll', Unread.scroll | |
Main.callbacks.push @node | |
foresee: [] | |
post: (e) -> | |
Unread.foresee.push e.detail.postID | |
node: (post) -> | |
{el} = post | |
if (index = Unread.foresee.indexOf post.ID) isnt -1 | |
Unread.foresee.splice index, 1 | |
return | |
return if el.hidden or /\bop\b/.test(post.class) or post.isInlined | |
{replies} = Unread | |
replies.push post.ID, el | |
Unread.update replies.length is 1 | |
scroll: -> | |
height = d.documentElement.clientHeight | |
{replies} = Unread | |
{first} = replies | |
update = false | |
while first | |
{bottom} = first.el.getBoundingClientRect() | |
if bottom > height #post is not completely read | |
break | |
update = true | |
replies.shift() | |
{first} = replies | |
return unless update | |
Unread.update replies.length is 0 | |
setTitle: (count) -> | |
d.title = "(#{count}) #{Unread.title}" | |
update: (updateFavicon) -> | |
return unless g.REPLY | |
count = @replies.length | |
if Conf['Unread Count'] | |
@setTitle count | |
unless Conf['Unread Favicon'] and updateFavicon | |
return | |
if $.engine is 'presto' | |
$.rm Favicon.el | |
Favicon.el.href = | |
if g.dead | |
if count | |
Favicon.unreadDead | |
else | |
Favicon.dead | |
else | |
if count | |
Favicon.unread | |
else | |
Favicon.default | |
if g.dead | |
$.addClass Favicon.el, 'dead' | |
else | |
$.rmClass Favicon.el, 'dead' | |
if count | |
$.addClass Favicon.el, 'unread' | |
else | |
$.rmClass Favicon.el, 'unread' | |
# `favicon.href = href` doesn't work on Firefox | |
# `favicon.href = href` isn't enough on Opera | |
# Opera won't always update the favicon if the href didn't change | |
unless $.engine is 'webkit' | |
$.add d.head, Favicon.el | |
Favicon = | |
init: -> | |
@el = $ 'link[rel="shortcut icon"]', d.head | |
@el.type = 'image/x-icon' | |
{href} = @el | |
@SFW = /ws.ico$/.test href | |
@default = href | |
@switch() | |
switch: -> | |
switch Conf['favicon'] | |
when 'ferongr' | |
@unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==' | |
@unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==' | |
@unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==' | |
when 'xat-' | |
@unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC' | |
@unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg==' | |
@unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII=' | |
when 'Mayhem' | |
@unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg==' | |
@unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC' | |
@unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII=' | |
when 'Original' | |
@unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=' | |
@unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=' | |
@unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=' | |
@unread = if @SFW then @unreadSFW else @unreadNSFW | |
empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==' | |
dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==' | |
Redirect = | |
image: (board, filename) -> | |
# Do not use g.BOARD, the image url can originate from a cross-quote. | |
switch board | |
when 'a', 'm', 'q', 'sp', 'tg', 'vg', 'wsg' | |
"//archive.foolz.us/#{board}/full_image/#{filename}" | |
when 'u' | |
"//nsfw.foolz.us/#{board}/full_image/#{filename}" | |
when 'ck', 'lit' | |
"//fuuka.warosu.org/#{board}/full_image/#{filename}" | |
when 'cgl', 'g', 'w' | |
"//archive.rebeccablacktech.com/#{board}/full_image/#{filename}" | |
when 'an', 'k', 'toy', 'x' | |
"http://archive.heinessen.com/#{board}/full_image/#{filename}" | |
# when 'e' | |
# "https://www.cliché.net/4chan/cgi-board.pl/#{board}/full_image/#{filename}" | |
post: (board, postID) -> | |
switch board | |
when 'a', 'co', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz' | |
"//archive.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}" | |
when 'u', 'kuku' | |
"//nsfw.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}" | |
thread: (board, threadID, postID) -> | |
# keep the number only if the location.hash was sent f.e. | |
postID = postID.match(/\d+/)[0] if postID | |
path = | |
if threadID | |
"#{board}/thread/#{threadID}" | |
else | |
"#{board}/post/#{postID}" | |
switch board | |
when 'a', 'co', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz' | |
url = "//archive.foolz.us/#{path}/" | |
if threadID and postID | |
url += "##{postID}" | |
when 'u', 'kuku' | |
url = "//nsfw.foolz.us/#{path}/" | |
if threadID and postID | |
url += "##{postID}" | |
when 'ck', 'jp', 'lit' | |
url = "//fuuka.warosu.org/#{path}" | |
if threadID and postID | |
url += "#p#{postID}" | |
when 'diy', 'g', 'sci' | |
url = "//archive.installgentoo.net/#{path}" | |
if threadID and postID | |
url += "#p#{postID}" | |
when 'cgl', 'mu', 'soc', 'w' | |
url = "//archive.rebeccablacktech.com/#{path}" | |
if threadID and postID | |
url += "#p#{postID}" | |
when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x' | |
url = "http://archive.heinessen.com/#{path}" | |
if threadID and postID | |
url += "#p#{postID}" | |
when 'e' | |
url = "https://www.cliché.net/4chan/cgi-board.pl/#{path}" | |
if threadID and postID | |
url += "#p#{postID}" | |
else | |
if threadID | |
url = "//boards.4chan.org/#{board}/" | |
url or null | |
ImageHover = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
return unless post.img | |
$.on post.img, 'mouseover', ImageHover.mouseover | |
mouseover: -> | |
# Make sure to remove the previous image hover | |
# in case it got stuck. Opera-only bug? | |
if el = $.id 'ihover' | |
if el is UI.el | |
delete UI.el | |
$.rm el | |
# Don't stop other elements from dragging | |
return if UI.el | |
el = UI.el = $.el 'img' | |
id: 'ihover' | |
src: @parentNode.href | |
$.add d.body, el | |
$.on el, 'load', ImageHover.load | |
$.on el, 'error', ImageHover.error | |
$.on @, 'mousemove', UI.hover | |
$.on @, 'mouseout', ImageHover.mouseout | |
load: -> | |
return unless @parentNode | |
# 'Fake' mousemove event by giving required values. | |
{style} = @ | |
UI.hover | |
clientX: - 45 + parseInt style.left | |
clientY: 120 + parseInt style.top | |
error: -> | |
src = @src.split '/' | |
unless src[2] is 'images.4chan.org' and url = Redirect.image src[3], src[5] | |
return if g.dead | |
url = "//images.4chan.org/#{src[3]}/src/#{src[5]}" | |
return if $.engine isnt 'webkit' and url.split('/')[2] is 'images.4chan.org' | |
timeoutID = setTimeout (=> @src = url), 3000 | |
# Only Chrome let userscripts do cross domain requests. | |
# Don't check for 404'd status in the archivers. | |
return if $.engine isnt 'webkit' or url.split('/')[2] isnt 'images.4chan.org' | |
$.ajax url, onreadystatechange: (-> clearTimeout timeoutID if @status is 404), | |
type: 'head' | |
mouseout: -> | |
UI.hoverend() | |
$.off @, 'mousemove', UI.hover | |
$.off @, 'mouseout', ImageHover.mouseout | |
AutoGif = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
{img} = post | |
return if post.el.hidden or not img | |
src = img.parentNode.href | |
if /gif$/.test(src) and !/spoiler/.test img.src | |
gif = $.el 'img' | |
$.on gif, 'load', -> | |
# Replace the thumbnail once the GIF has finished loading. | |
img.src = src | |
gif.src = src | |
Prefetch = | |
init: -> | |
@dialog() | |
dialog: -> | |
controls = $.el 'label', | |
id: 'prefetch' | |
innerHTML: | |
"Prefetch Images<input type=checkbox id=prefetch>" | |
input = $ 'input', controls | |
$.on input, 'change', Prefetch.change | |
first = $.id('delform').firstElementChild | |
if first.id is 'imgControls' | |
$.after first, controls | |
else | |
$.before first, controls | |
change: -> | |
$.off @, 'change', Prefetch.change | |
for thumb in $$ 'a.fileThumb' | |
img = $.el 'img', | |
src: thumb.href | |
Main.callbacks.push Prefetch.node | |
node: (post) -> | |
{img} = post | |
return unless img | |
$.el 'img', | |
src: img.parentNode.href | |
PngFix = | |
init: -> | |
Main.callbacks.push @node | |
node: (post) -> | |
{img} = post | |
return if post.el.hidden or not img | |
src = img.parentNode.href | |
if /png$/.test(src) and !/spoiler/.test img.src | |
png = $.el 'img' | |
$.on png, 'load', -> | |
img.src = src | |
png.src = src | |
ImageExpand = | |
init: -> | |
Main.callbacks.push @node | |
@dialog() | |
node: (post) -> | |
return unless post.img | |
sp = FileInfo.data.spoiler | |
a = post.img.parentNode | |
$.on a, 'click', ImageExpand.cb.toggle | |
if ImageExpand.on and !post.el.hidden and sp isnt true | |
ImageExpand.expand post.img | |
cb: | |
toggle: (e) -> | |
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 | |
e.preventDefault() | |
ImageExpand.toggle @ | |
all: -> | |
ImageExpand.on = @checked | |
if ImageExpand.on #expand | |
thumbs = $$ 'img[data-md5]' | |
if Conf['Expand From Current'] | |
for thumb, i in thumbs | |
if thumb.getBoundingClientRect().top > 0 | |
break | |
thumbs = thumbs[i...] | |
for thumb in thumbs | |
ImageExpand.expand thumb | |
else #contract | |
for thumb in $$ 'img[data-md5][hidden]' | |
ImageExpand.contract thumb | |
return | |
typeChange: -> | |
switch @value | |
when 'full' | |
klass = '' | |
when 'fit width' | |
klass = 'fitwidth' | |
when 'fit height' | |
klass = 'fitheight' | |
when 'fit screen' | |
klass = 'fitwidth fitheight' | |
$.id('delform').className = klass | |
if /\bfitheight\b/.test klass | |
$.on window, 'resize', ImageExpand.resize | |
unless ImageExpand.style | |
ImageExpand.style = $.addStyle '' | |
ImageExpand.resize() | |
else if ImageExpand.style | |
$.off window, 'resize', ImageExpand.resize | |
toggle: (a) -> | |
thumb = a.firstChild | |
if thumb.hidden | |
rect = a.getBoundingClientRect() | |
if $.engine is 'webkit' | |
d.body.scrollTop += rect.top - 42 if rect.top < 0 | |
d.body.scrollLeft += rect.left if rect.left < 0 | |
else | |
d.documentElement.scrollTop += rect.top - 42 if rect.top < 0 | |
d.documentElement.scrollLeft += rect.left if rect.left < 0 | |
ImageExpand.contract thumb | |
else | |
ImageExpand.expand thumb | |
contract: (thumb) -> | |
thumb.hidden = false | |
thumb.nextSibling.hidden = true | |
$.rmClass thumb.parentNode.parentNode.parentNode.parentNode, 'image_expanded' | |
expand: (thumb, url) -> | |
# Do not expand images of hidden/filtered replies, or already expanded pictures. | |
return if $.x 'ancestor-or-self::*[@hidden]', thumb | |
thumb.hidden = true | |
$.addClass thumb.parentNode.parentNode.parentNode.parentNode, 'image_expanded' | |
if img = thumb.nextSibling | |
# Expand already loaded picture | |
img.hidden = false | |
return | |
a = thumb.parentNode | |
img = $.el 'img', | |
src: url or a.href | |
$.on img, 'error', ImageExpand.error | |
$.add a, img | |
error: -> | |
thumb = @previousSibling | |
ImageExpand.contract thumb | |
$.rm @ | |
src = @src.split '/' | |
unless src[2] is 'images.4chan.org' and url = Redirect.image src[3], src[5] | |
return if g.dead | |
url = "//images.4chan.org/#{src[3]}/src/#{src[5]}" | |
return if $.engine isnt 'webkit' and url.split('/')[2] is 'images.4chan.org' | |
timeoutID = setTimeout ImageExpand.expand, 10000, thumb, url | |
# Only Chrome let userscripts do cross domain requests. | |
# Don't check for 404'd status in the archivers. | |
return if $.engine isnt 'webkit' or url.split('/')[2] isnt 'images.4chan.org' | |
$.ajax url, onreadystatechange: (-> clearTimeout timeoutID if @status is 404), | |
type: 'head' | |
dialog: -> | |
controls = $.el 'span', | |
id: 'imgControls' | |
innerHTML: | |
"<select id=imageType name=imageType><option value=full>Full</option><option value='fit width'>Fit Width</option><option value='fit height'>Fit Height</option value='fit screen'><option value='fit screen'>Fit Screen</option></select><label>Expand Images<input type=checkbox id=imageExpand></label>" | |
imageType = $.get 'imageType', 'full' | |
select = $ 'select', controls | |
select.value = imageType | |
ImageExpand.cb.typeChange.call select | |
$.on select, 'change', $.cb.value | |
$.on select, 'change', ImageExpand.cb.typeChange | |
$.on $('input', controls), 'click', ImageExpand.cb.all | |
$.prepend $.id('delform'), controls | |
resize: -> | |
ImageExpand.style.textContent = ".fitheight img[data-md5] + img {max-height:#{d.documentElement.clientHeight}px;}" | |
QR = | |
init: -> | |
return unless $.id 'postForm' | |
Main.callbacks.push @node | |
if Conf['Hide Original Post Form'] or Conf['Style'] | |
unless Conf['Style'] | |
link = $.el 'h1', innerHTML: "<a href=javascript:;>#{if g.REPLY then 'Reply to Thread' else 'Start a Thread'}</a>" | |
$.on link.firstChild, 'click', -> | |
QR.open() | |
$('select', QR.el).value = 'new' unless g.REPLY | |
$('textarea', QR.el).focus() | |
$.before $.id('postForm'), link | |
if Conf['Persistent QR'] or Conf['Style'] | |
QR.dialog() | |
QR.hide() if Conf['Auto Hide QR'] and not Conf['Style'] | |
$.on d, 'dragover', QR.dragOver | |
$.on d, 'drop', QR.dropFile | |
$.on d, 'dragstart dragend', QR.drag | |
node: (post) -> | |
$.on $('a[title="Quote this post"]', post.el), 'click', QR.quote | |
open: -> | |
if QR.el | |
unless Conf['Style'] | |
QR.el.hidden = false | |
QR.unhide() | |
else | |
QR.dialog() | |
close: -> | |
QR.el.hidden = true | |
QR.abort() | |
d.activeElement.blur() | |
$.rmClass QR.el, 'dump' | |
for i in QR.replies | |
QR.replies[0].rm() | |
QR.cooldown.auto = false | |
QR.status() | |
QR.resetFileInput() | |
if not Conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked | |
spoiler.click() | |
QR.cleanError() | |
hide: -> | |
d.activeElement.blur() | |
$.addClass QR.el, 'autohide' | |
$.id('autohide').checked = true | |
unhide: -> | |
$.rmClass QR.el, 'autohide' | |
$.id('autohide').checked = false | |
toggleHide: -> | |
@checked and QR.hide() or QR.unhide() | |
error: (err) -> | |
el = $ '.warning', QR.el | |
if typeof err is 'string' | |
el.textContent = err | |
else | |
el.innerHTML = null | |
$.add el, err | |
QR.open() | |
if /captcha|verification/i.test el.textContent | |
# Focus the captcha input on captcha error. | |
$('[autocomplete]', QR.el).focus() | |
alert el.textContent if d.hidden or d.oHidden or d.mozHidden or d.webkitHidden | |
cleanError: -> | |
$('.warning', QR.el).textContent = null | |
status: (data={}) -> | |
return unless QR.el | |
if g.dead | |
value = 404 | |
disabled = true | |
QR.cooldown.auto = false | |
value = QR.cooldown.seconds or data.progress or value | |
{input} = QR.status | |
input.value = | |
if QR.cooldown.auto and Conf['Cooldown'] | |
if value then "Auto #{value}" else 'Auto' | |
else | |
value or 'Submit' | |
input.disabled = disabled or false | |
cooldown: | |
init: -> | |
return unless Conf['Cooldown'] | |
QR.cooldown.start $.get "/#{g.BOARD}/cooldown", 0 | |
$.sync "/#{g.BOARD}/cooldown", QR.cooldown.start | |
start: (timeout) -> | |
seconds = Math.floor (timeout - Date.now()) / 1000 | |
QR.cooldown.count seconds | |
set: (seconds) -> | |
return unless Conf['Cooldown'] | |
QR.cooldown.count seconds | |
$.set "/#{g.BOARD}/cooldown", Date.now() + seconds*$.SECOND | |
count: (seconds) -> | |
return unless 0 <= seconds <= 60 | |
setTimeout QR.cooldown.count, 1000, seconds-1 | |
QR.cooldown.seconds = seconds | |
if seconds is 0 | |
$.delete "/#{g.BOARD}/cooldown" | |
QR.submit() if QR.cooldown.auto | |
QR.status() | |
quote: (e) -> | |
e?.preventDefault() | |
QR.open() | |
unless g.REPLY | |
$('select', QR.el).value = $.x('ancestor::div[parent::div[@class="board"]]', @).id[1..] | |
# Make sure we get the correct number, even with XXX censors | |
id = @previousSibling.hash[2..] | |
text = ">>#{id}\n" | |
sel = window.getSelection() | |
if (s = sel.toString().trim()) and id is $.x('ancestor-or-self::blockquote', sel.anchorNode)?.id.match(/\d+$/)[0] | |
# XXX Opera needs d.getSelection() to retain linebreaks from the selected text | |
s = d.getSelection() if $.engine is 'presto' | |
s = s.replace /\n/g, '\n>' | |
text += ">#{s}\n" | |
ta = $ 'textarea', QR.el | |
caretPos = ta.selectionStart | |
# Replace selection for text. | |
ta.value = ta.value[...caretPos] + text + ta.value[ta.selectionEnd..] | |
ta.focus() | |
# Move the caret to the end of the new quote. | |
range = caretPos + text.length | |
# XXX Opera counts newlines as double | |
range += text.match(/\n/g).length if $.engine is 'presto' | |
ta.setSelectionRange range, range | |
# Fire the 'input' event | |
$.event ta, new Event 'input' | |
characterCount: -> | |
counter = QR.charaCounter | |
count = @textLength | |
counter.textContent = count | |
counter.hidden = count < 1000 | |
(if count > 1500 then $.addClass else $.rmClass) counter, 'warning' | |
drag: (e) -> | |
# Let it drag anything from the page. | |
toggle = if e.type is 'dragstart' then $.off else $.on | |
toggle d, 'dragover', QR.dragOver | |
toggle d, 'drop', QR.dropFile | |
dragOver: (e) -> | |
e.preventDefault() | |
e.dataTransfer.dropEffect = 'copy' # cursor feedback | |
dropFile: (e) -> | |
# Let it only handle files from the desktop. | |
return unless e.dataTransfer.files.length | |
e.preventDefault() | |
QR.open() | |
QR.fileInput.call e.dataTransfer | |
$.addClass QR.el, 'dump' | |
fileInput: -> | |
QR.cleanError() | |
# Set or change current reply's file. | |
if @files.length is 1 | |
file = @files[0] | |
if file.size > @max | |
QR.error 'File too large.' | |
QR.resetFileInput() | |
else if -1 is QR.mimeTypes.indexOf file.type | |
QR.error 'Unsupported file type.' | |
QR.resetFileInput() | |
else | |
QR.selected.setFile file | |
return | |
# Create new replies with these files. | |
for file in @files | |
if file.size > @max | |
QR.error "File #{file.name} is too large." | |
break | |
else if -1 is QR.mimeTypes.indexOf file.type | |
QR.error "#{file.name}: Unsupported file type." | |
break | |
unless QR.replies[QR.replies.length - 1].file | |
# set last reply's file | |
QR.replies[QR.replies.length - 1].setFile file | |
else | |
new QR.reply().setFile file | |
$.addClass QR.el, 'dump' | |
QR.resetFileInput() # reset input | |
resetFileInput: -> | |
input = $ '[type=file]', QR.el | |
input.value = null | |
if Conf['Style'] | |
riceFile = $ '#file', QR.el | |
riceFile.textContent = null | |
return unless $.engine is 'presto' | |
# XXX Opera needs extra care to reset its file input's value | |
clone = $.el 'input', | |
type: 'file' | |
accept: input.accept | |
max: input.max | |
multiple: input.multiple | |
size: input.size | |
title: input.title | |
$.on clone, 'change', QR.fileInput | |
$.on clone, 'click', (e) -> if e.shiftKey then QR.selected.rmFile() or e.preventDefault() | |
$.replace input, clone | |
replies: [] | |
reply: class | |
constructor: -> | |
# set values, or null, to avoid 'undefined' values in inputs | |
prev = QR.replies[QR.replies.length-1] | |
persona = $.get 'QR.persona', {} | |
@name = if prev then prev.name else persona.name or null | |
@email = if prev and !/^sage$/.test prev.email then prev.email else if Conf['Sage on /jp/'] and g.BOARD is 'jp' then 'sage' else persona.email or null | |
@sub = if prev and Conf['Remember Subject'] then prev.sub else if Conf['Remember Subject'] then persona.sub else null | |
@spoiler = if prev and Conf['Remember Spoiler'] then prev.spoiler else false | |
@com = null | |
@el = $.el 'a', | |
className: 'thumbnail' | |
draggable: true | |
href: 'javascript:;' | |
innerHTML: '<a class=remove>X</a><label hidden><input type=checkbox> Spoiler</label><span></span>' | |
$('input', @el).checked = @spoiler | |
$.on @el, 'click', => @select() | |
$.on $('.remove', @el), 'click', (e) => | |
e.stopPropagation() | |
@rm() | |
$.on $('label', @el), 'click', (e) => e.stopPropagation() | |
$.on $('input', @el), 'change', (e) => | |
@spoiler = e.target.checked | |
$.id('spoiler').checked = @spoiler if @el.id is 'selected' | |
$.before $('#addReply', QR.el), @el | |
$.on @el, 'dragstart', @dragStart | |
$.on @el, 'dragenter', @dragEnter | |
$.on @el, 'dragleave', @dragLeave | |
$.on @el, 'dragover', @dragOver | |
$.on @el, 'dragend', @dragEnd | |
$.on @el, 'drop', @drop | |
QR.replies.push @ | |
setFile: (@file) -> | |
@el.title = "#{file.name} (#{$.bytesToString file.size})" | |
$('label', @el).hidden = false if QR.spoiler | |
unless /^image/.test file.type | |
@el.style.backgroundImage = null | |
return | |
url = window.URL or window.webkitURL | |
# XXX Opera does not support window.URL.revokeObjectURL | |
url.revokeObjectURL? @url | |
# Create a redimensioned thumbnail. | |
fileUrl = url.createObjectURL file | |
img = $.el 'img' | |
$.on img, 'load', => | |
# Generate thumbnails only if they're really big. | |
# Resized pictures through canvases look like ass, | |
# so we generate thumbnails `s` times bigger then expected | |
# to avoid crappy resized quality. | |
s = 90*3 | |
if img.height < s or img.width < s | |
@url = fileUrl | |
@el.style.backgroundImage = "url(#{@url})" | |
return | |
if img.height <= img.width | |
img.width = s / img.height * img.width | |
img.height = s | |
else | |
img.height = s / img.width * img.height | |
img.width = s | |
c = $.el 'canvas' | |
c.height = img.height | |
c.width = img.width | |
c.getContext('2d').drawImage img, 0, 0, img.width, img.height | |
# Support for toBlob fucking when? | |
data = atob c.toDataURL().split(',')[1] | |
# DataUrl to Binary code from Aeosynth's 4chan X repo | |
l = data.length | |
ui8a = new Uint8Array l | |
for i in [0...l] | |
ui8a[i] = data.charCodeAt i | |
@url = url.createObjectURL new Blob [ui8a], type: 'image/png' | |
@el.style.backgroundImage = "url(#{@url})" | |
url.revokeObjectURL? fileUrl | |
img.src = fileUrl | |
rmFile: -> | |
QR.resetFileInput() | |
delete @file | |
@el.title = null | |
@el.style.backgroundImage = null | |
$('label', @el).hidden = true if QR.spoiler | |
(window.URL or window.webkitURL).revokeObjectURL? @url | |
select: -> | |
QR.selected?.el.id = null | |
QR.selected = @ | |
@el.id = 'selected' | |
# Scroll the list to center the focused reply. | |
rectEl = @el.getBoundingClientRect() | |
rectList = @el.parentNode.getBoundingClientRect() | |
@el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2 | |
# Load this reply's values. | |
for data in ['name', 'email', 'sub', 'com'] | |
$("[name=#{data}]", QR.el).value = @[data] | |
QR.characterCount.call $ 'textarea', QR.el | |
$('#spoiler', QR.el).checked = @spoiler | |
dragStart: -> | |
$.addClass @, 'drag' | |
dragEnter: -> | |
$.addClass @, 'over' | |
dragLeave: -> | |
$.rmClass @, 'over' | |
dragOver: (e) -> | |
e.preventDefault() | |
e.dataTransfer.dropEffect = 'move' | |
drop: -> | |
el = $ '.drag', @parentNode | |
index = (el) -> Array::slice.call(el.parentNode.children).indexOf el | |
oldIndex = index el | |
newIndex = index @ | |
if oldIndex < newIndex | |
$.after @, el | |
else | |
$.before @, el | |
reply = QR.replies.splice(oldIndex, 1)[0] | |
QR.replies.splice newIndex, 0, reply | |
dragEnd: -> | |
$.rmClass @, 'drag' | |
if el = $ '.over', @parentNode | |
$.rmClass el, 'over' | |
rm: -> | |
QR.resetFileInput() | |
$.rm @el | |
index = QR.replies.indexOf @ | |
if QR.replies.length is 1 | |
new QR.reply().select() | |
else if @el.id is 'selected' | |
(QR.replies[index-1] or QR.replies[index+1]).select() | |
QR.replies.splice index, 1 | |
(window.URL or window.webkitURL).revokeObjectURL? @url | |
delete @ | |
captcha: | |
init: -> | |
return unless QR.captchaIsEnabled = !!$.id 'captchaFormPart' | |
if $.id 'recaptcha_challenge_field_holder' | |
@ready() | |
else | |
@onready = => @ready() | |
$.on $.id('recaptcha_widget_div'), 'DOMNodeInserted', @onready | |
ready: -> | |
if @challenge = $.id 'recaptcha_challenge_field_holder' | |
$.off $.id('recaptcha_widget_div'), 'DOMNodeInserted', @onready | |
delete @onready | |
else | |
return | |
$.after $('.textarea', QR.el), $.el 'div', | |
className: 'captchaimg' | |
title: 'Reload' | |
innerHTML: '<img>' | |
$.after $('.captchaimg', QR.el), $.el 'div', | |
className: 'captchainput' | |
innerHTML: '<input title=Verification class=field autocomplete=off size=1>' | |
@img = $ '.captchaimg > img', QR.el | |
@input = $ '.captchainput > input', QR.el | |
$.on @img.parentNode, 'click', @reload | |
$.on @input, 'keydown', @keydown | |
$.on @challenge, 'DOMNodeInserted', => @load() | |
$.sync 'captchas', (arr) => @count arr.length | |
@count $.get('captchas', []).length | |
# start with an uncached captcha | |
@reload() | |
save: -> | |
return unless response = @input.value | |
captchas = $.get 'captchas', [] | |
# Remove old captchas. | |
while (captcha = captchas[0]) and captcha.time < Date.now() | |
captchas.shift() | |
captchas.push | |
challenge: @challenge.firstChild.value | |
response: response | |
time: @timeout | |
$.set 'captchas', captchas | |
@count captchas.length | |
@reload() | |
load: -> | |
# Timeout is available at RecaptchaState.timeout in seconds. | |
# We use 5-1 minutes to give upload some time. | |
@timeout = Date.now() + 4*$.MINUTE | |
challenge = @challenge.firstChild.value | |
@img.alt = challenge | |
@img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" | |
@input.value = null | |
count: (count) -> | |
@input.placeholder = switch count | |
when 0 | |
'Verification (Shift + Enter to cache)' | |
when 1 | |
'Verification (1 cached captcha)' | |
else | |
"Verification (#{count} cached captchas)" | |
@input.alt = count # For XTRM RICE. | |
reload: (focus) -> | |
# the "t" argument prevents the input from being focused | |
window.location = 'javascript:Recaptcha.reload("t")' | |
# Focus if we meant to. | |
QR.captcha.input.focus() if focus | |
keydown: (e) -> | |
c = QR.captcha | |
if e.keyCode is 8 and not c.input.value | |
c.reload() | |
else if e.keyCode is 13 and e.shiftKey | |
c.save() | |
else | |
return | |
e.preventDefault() | |
dialog: -> | |
unless Conf['Style'] | |
QR.el = UI.dialog 'qr', 'top:0;right:0;', ' | |
<div class=move> | |
Quick Reply <input type=checkbox id=autohide title=Auto-hide> | |
<span> <a class=close title=Close>×</a></span> | |
</div> | |
<form> | |
<div><input id=dump type=button title="Dump list" value=+ class=field><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div> | |
<div id=replies><div><a id=addReply href=javascript:; title="Add a reply">+</a></div></div> | |
<div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div> | |
<div><input type=file title="Shift+Click to remove the selected file." multiple size=16><input type=submit></div> | |
<label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label> | |
<div class=warning></div> | |
</form>' | |
else | |
QR.el = UI.dialog 'qr', '', ' | |
<form> | |
<div><input id=dump type=button title="Dump list" value=+ class=field><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div> | |
<div id=replies><div><a id=addReply href=javascript:; title="Add a reply">+</a></div></div> | |
<div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div> | |
<div><input type=file title="Shift+Click to remove the selected file." multiple size=16><div id=browse>Browse...</div><div id=file></div></div> | |
<div id=submit><input type=submit></div> | |
<div id=threadselect></div> | |
<label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label> | |
<div class=warning></div> | |
</form>' | |
qrFile = $("[type=file]", QR.el) | |
riceFile = $("#file", QR.el) | |
$.on riceFile, 'click', -> | |
qrFile.click() | |
$.on $("#browse", QR.el), 'click', -> | |
qrFile.click() | |
$.on qrFile, 'change', -> | |
riceFile.textContent = qrFile.value | |
if Conf['Remember QR size'] and $.engine is 'gecko' | |
$.on ta = $('textarea', QR.el), 'mouseup', -> | |
$.set 'QR.size', @style.cssText | |
ta.style.cssText = $.get 'QR.size', '' | |
# Allow only this board's supported files. | |
mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) -> | |
switch type | |
when 'jpg' | |
'image/jpeg' | |
when 'pdf' | |
'application/pdf' | |
when 'swf' | |
'application/x-shockwave-flash' | |
else | |
"image/#{type}" | |
QR.mimeTypes = mimeTypes.split ', ' | |
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog. | |
QR.mimeTypes.push '' | |
fileInput = $ 'input[type=file]', QR.el | |
fileInput.max = $('input[name=MAX_FILE_SIZE]').value | |
fileInput.accept = mimeTypes if $.engine isnt 'presto' # Opera's accept attribute is fucked up | |
QR.spoiler = !!$ 'input[name=spoiler]' | |
spoiler = $ '#spoilerLabel', QR.el | |
spoiler.hidden = !QR.spoiler | |
QR.charaCounter = $ '#charCount', QR.el | |
ta = $ 'textarea', QR.el | |
unless g.REPLY | |
# Make a list with visible threads and an option to create a new one. | |
threads = '<option value=new>New thread</option>' | |
for thread in $$ '.thread' | |
id = thread.id[1..] | |
threads += "<option value=#{id}>Thread #{id}</option>" | |
unless Conf["Style"] | |
$.prepend $('.move > span', QR.el), $.el 'select' | |
innerHTML: threads | |
title: 'Create a new thread / Reply to a thread' | |
else | |
$.prepend $('#threadselect', QR.el), $.el 'select' | |
innerHTML: threads | |
title: 'Create a new thread / Reply to a thread' | |
$.on $('select', QR.el), 'mousedown', (e) -> e.stopPropagation() | |
unless Conf['Style'] | |
$.on $('#autohide', QR.el), 'change', QR.toggleHide | |
$.on $('.close', QR.el), 'click', QR.close | |
$.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump' | |
$.on $('#addReply', QR.el), 'click', -> new QR.reply().select() | |
$.on $('form', QR.el), 'submit', QR.submit | |
$.on ta, 'input', -> QR.selected.el.lastChild.textContent = @value | |
$.on ta, 'input', QR.characterCount | |
$.on fileInput, 'change', QR.fileInput | |
$.on fileInput, 'click', (e) -> if e.shiftKey then QR.selected.rmFile() or e.preventDefault() | |
$.on spoiler.firstChild, 'change', -> $('input', QR.selected.el).click() | |
$.on $('.warning', QR.el), 'click', QR.cleanError | |
new QR.reply().select() | |
# save selected reply's data | |
for name in ['name', 'email', 'sub', 'com'] | |
if Conf['Style'] | |
$.on $("[name=#{name}]", QR.el), 'focus', -> QR.el.classList.add 'focus' | |
$.on $("[name=#{name}]", QR.el), 'blur', -> QR.el.classList.remove 'focus' | |
# The input event replaces keyup, change and paste events. | |
$.on $("[name=#{name}]", QR.el), 'input', -> | |
QR.selected[@name] = @value | |
# Disable auto-posting if you're typing in the first reply | |
# during the last 5 seconds of the cooldown. | |
if QR.cooldown.auto and QR.selected is QR.replies[0] and 0 < QR.cooldown.seconds < 6 | |
QR.cooldown.auto = false | |
QR.status.input = $ 'input[type=submit]', QR.el | |
QR.status() | |
QR.cooldown.init() | |
QR.captcha.init() | |
if Conf['Style'] | |
$.on $(".captchainput .field", QR.el), 'focus', -> QR.el.classList.add 'focus' | |
$.on $(".captchainput .field", QR.el), 'blur', -> QR.el.classList.remove 'focus' | |
$.add d.body, QR.el | |
# Create a custom event when the QR dialog is first initialized. | |
# Use it to extend the QR's functionalities, or for XTRM RICE. | |
$.event QR.el, new CustomEvent 'QRDialogCreation', | |
bubbles: true | |
submit: (e) -> | |
e?.preventDefault() | |
if QR.cooldown.seconds | |
QR.cooldown.auto = !QR.cooldown.auto | |
QR.status() | |
return | |
QR.abort() | |
reply = QR.replies[0] | |
threadID = g.THREAD_ID or $('select', QR.el).value | |
# prevent errors | |
if threadID is 'new' | |
if g.BOARD in ['vg', 'q'] and !reply.sub | |
err = 'New threads require a subject.' | |
else unless reply.file or textOnly = !!$ 'input[name=textonly]', $.id 'postForm' | |
err = 'No file selected.' | |
else | |
unless reply.com or reply.file | |
err = 'No file selected.' | |
if QR.captchaIsEnabled and !err | |
# get oldest valid captcha | |
captchas = $.get 'captchas', [] | |
# remove old captchas | |
while (captcha = captchas[0]) and captcha.time < Date.now() | |
captchas.shift() | |
if captcha = captchas.shift() | |
challenge = captcha.challenge | |
response = captcha.response | |
else | |
challenge = QR.captcha.img.alt | |
if response = QR.captcha.input.value then QR.captcha.reload() | |
$.set 'captchas', captchas | |
QR.captcha.count captchas.length | |
unless response | |
err = 'No valid captcha.' | |
else | |
response = response.trim() | |
# one-word-captcha: | |
# If there's only one word, duplicate it. | |
response = "#{response} #{response}" unless /\s/.test response | |
if err | |
# stop auto-posting | |
QR.cooldown.auto = false | |
QR.status() | |
QR.error err | |
return | |
QR.cleanError() | |
# Enable auto-posting if we have stuff to post, disable it otherwise. | |
QR.cooldown.auto = QR.replies.length > 1 | |
if Conf['Auto Hide QR'] and not QR.cooldown.auto and not Conf['Style'] | |
QR.hide() | |
if not QR.cooldown.auto and $.x 'ancestor::div[@id="qr"]', d.activeElement | |
# Unfocus the focused element if it is one within the QR and we're not auto-posting. | |
d.activeElement.blur() | |
# Starting to upload might take some time. | |
# Provide some feedback that we're starting to submit. | |
QR.status progress: '...' | |
post = | |
resto: threadID | |
name: reply.name | |
email: reply.email | |
sub: reply.sub | |
com: if Conf['Markdown'] then Markdown.format reply.com else reply.com | |
upfile: reply.file | |
spoiler: reply.spoiler | |
textonly: textOnly | |
mode: 'regist' | |
pwd: if m = d.cookie.match(/4chan_pass=([^;]+)/) then decodeURIComponent m[1] else $('input[name=pwd]').value | |
recaptcha_challenge_field: challenge | |
recaptcha_response_field: response | |
callbacks = | |
onload: -> | |
QR.response @response | |
onerror: -> | |
# Connection error, or | |
# CORS disabled error on www.4chan.org/banned | |
QR.status() | |
QR.error $.el 'a', | |
href: '//www.4chan.org/banned', | |
target: '_blank', | |
textContent: 'Connection error, or you are banned.' | |
opts = | |
form: $.formData post | |
upCallbacks: | |
onload: -> | |
# Upload done, waiting for response. | |
QR.status progress: '...' | |
onprogress: (e) -> | |
# Uploading... | |
QR.status progress: "#{Math.round e.loaded / e.total * 100}%" | |
QR.ajax = $.ajax $.id('postForm').parentNode.action, callbacks, opts | |
response: (html) -> | |
doc = d.implementation.createHTMLDocument '' | |
doc.documentElement.innerHTML = html | |
if doc.title is '4chan - Banned' # Ban/warn check | |
bs = $$ 'b', doc | |
err = $.el 'span', | |
innerHTML: | |
if /^You were issued a warning/.test $('.boxcontent', doc).textContent.trim() | |
"You were issued a warning on #{bs[0].innerHTML} as #{bs[3].innerHTML}.<br>Warning reason: #{bs[1].innerHTML}" | |
else | |
"You are banned! ;_;<br>Please click <a href=//www.4chan.org/banned target=_blank>HERE</a> to see the reason." | |
else if err = doc.getElementById 'errmsg' # error! | |
$('a', err)?.target = '_blank' # duplicate image link | |
else unless msg = $ 'b', doc | |
err = 'Connection error with sys.4chan.org.' | |
if err | |
if /captcha|verification/i.test(err) or err is 'Connection error with sys.4chan.org.' | |
# Enable auto-post if we have some cached captchas. | |
QR.cooldown.auto = !!$.get('captchas', []).length | |
# Too many frequent mistyped captchas will auto-ban you! | |
# On connection error, the post most likely didn't go through. | |
QR.cooldown.set 2 | |
else # stop auto-posting | |
QR.cooldown.auto = false | |
QR.status() | |
QR.error err | |
return | |
reply = QR.replies[0] | |
persona = $.get 'QR.persona', {} | |
persona = | |
name: reply.name | |
email: if /^sage$/.test reply.email then persona.email else reply.email | |
sub: if Conf['Remember Subject'] then reply.sub else null | |
$.set 'QR.persona', persona | |
[_, threadID, postID] = msg.lastChild.textContent.match /thread:(\d+),no:(\d+)/ | |
# Post/upload confirmed as successful. | |
$.event QR.el, new CustomEvent 'QRPostSuccessful', | |
bubbles: true | |
detail: | |
threadID: threadID | |
postID: postID | |
if threadID is '0' # new thread | |
# auto-noko | |
location.pathname = "/#{g.BOARD}/res/#{postID}" | |
else | |
# Enable auto-posting if we have stuff to post, disable it otherwise. | |
QR.cooldown.auto = QR.replies.length > 1 | |
QR.cooldown.set if g.BOARD is 'q' or /sage/i.test reply.email then 60 else 30 | |
if Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto | |
$.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}" | |
if Conf['Persistent QR'] or QR.cooldown.auto or Conf['Style'] | |
reply.rm() | |
else | |
QR.close() | |
QR.status() | |
QR.resetFileInput() | |
abort: -> | |
QR.ajax?.abort() | |
delete QR.ajax | |
QR.status() | |
ThemeTools = | |
init: (key) -> | |
editMode = true | |
unless newTheme | |
Style.addStyle userThemes[key] | |
if newTheme | |
editTheme = {} | |
editTheme["Theme"] = "Untitled" | |
editTheme["Author"] = "Author" | |
editTheme["Author Tripcode"] = "Unknown" | |
else | |
editTheme = userThemes[key] | |
editTheme["Theme"] = key | |
layout = ["Background Image", "Background Attachment", "Background Position", "Background Repeat", "Background Color", "Thread Wrapper Background", "Thread Wrapper Border", "Dialog Background", "Dialog Border", "Reply Background", "Reply Border", "Highlighted Reply Background", "Highlighted Reply Border", "Backlinked Reply Outline", "Input Background", "Input Border", "Checkbox Background", "Checkbox Border", "Checkbox Checked Background", "Buttons Background", "Buttons Border", "Focused Input Background", "Focused Input Border", "Hovered Input Background", "Hovered Input Border", "Navigation Background", "Navigation Border", "Quotelinks", "Backlinks", "Links", "Hovered Links", "Navigation Links", "Hovered Navigation Links", "Names", "Tripcodes", "Emails", "Subjects", "Text", "Inputs", "Post Numbers", "Greentext", "Sage", "Board Title", "Timestamps", "Warnings", "Shadow Color", "Dark Theme", "Custom CSS"] | |
dialog = $.el "div", | |
id: "themeConf" | |
className: "reply dialog" | |
innerHTML: " | |
<div id=themebar> | |
</div> | |
<hr> | |
<div id=themecontent> | |
</div> | |
<div id=save> | |
<a href='javascript:;'>Save Theme</a> | |
</div> | |
<div id=cancel> | |
<a href='javascript:;'>Cancel</a> | |
</div> | |
" | |
header = $.el "div", | |
innerHTML: " | |
<input class='field subject' name='Theme' placeholder='Theme' value='#{key}'> by | |
<input class='field name' name='Author' placeholder='Author' value='#{editTheme['Author']}'> | |
<input class='field postertrip' name='Author Tripcode' placeholder='Author Tripcode' value='#{editTheme['Author Tripcode']}'>" | |
for input in $$("input", header) | |
$.on input, 'blur', -> | |
editTheme[@name] = @value | |
$.add $("#themebar", dialog), header | |
if newTheme | |
for item in layout | |
editTheme[item] = "" | |
div = $.el "div", | |
className: "themevar" | |
innerHTML: "<div class=optionname>#{item}</div><div class=option><input class=field name='#{item}' placeholder='#{item}' value='#{editTheme[item]}'>" | |
$.on $('input', div), 'blur', -> | |
editTheme[@name] = @value | |
Style.addStyle(editTheme) | |
$.add $("#themecontent", dialog), div | |
else | |
for item in layout | |
div = $.el "div", | |
className: "themevar" | |
innerHTML: "<div class=optionname>#{item}</div><div class=option><input class=field name='#{item}' placeholder='#{item}' value='#{editTheme[item]}'>" | |
$.on $('input', div), 'blur', -> | |
for i in [[email protected] - 1] | |
if @value[i] == '(' | |
depth++ | |
else if @value[i] == ')' | |
depth-- | |
if depth > 0 then | |
for i in [0..depth] | |
@value += ')' | |
editTheme[@name] = @value | |
Style.addStyle(editTheme) | |
$.add $("#themecontent", dialog), div | |
$.on $('#save > a', dialog), 'click', -> | |
ThemeTools.save editTheme | |
$.on $('#cancel > a', dialog), 'click', ThemeTools.close | |
if newTheme then Style.addStyle(editTheme) | |
$.add d.body, dialog | |
save: (theme) -> | |
name = theme["Theme"] | |
delete theme["Theme"] | |
if userThemes[name] and not userThemes[name]["Deleted"] | |
if confirm "A theme with this name already exists. Would you like to over-write?" | |
delete userThemes[name] | |
else | |
return | |
userThemes[name] = theme | |
$.set 'userThemes', userThemes | |
$.set "Style", name | |
Conf["Style"] = name | |
ThemeTools.close() | |
alert "Theme \"#{name}\" saved." | |
close: -> | |
newTheme = false | |
Conf['Edit Mode'] = false | |
$.rm $("#themeConf", d.body) | |
Style.addStyle Conf["Style"] | |
MascotTools = | |
init: (mascot) -> | |
if Conf['Post Form Style'] == "fixed" or Conf['Post Form Style'] == "transparent fade" then mascotposition = '264' else mascotposition = '0' | |
try | |
$.rm $('#mascot', d.body) | |
unless mascot | |
mascotnames = [] | |
for name, mascot of userMascots | |
if enabledmascots[name] == true | |
mascotnames.push name | |
unless mascot = userMascots[mascotnames[Math.floor(Math.random() * mascotnames.length)]] | |
return | |
div = $.el 'div', | |
id: "mascot" | |
div.innerHTML = "<img src='#{mascot.image}'>" | |
$.ready -> | |
$.add d.body, div | |
result = " | |
#mascot img { | |
position: fixed; | |
bottom: " + (if mascot.position == 'bottom' then (0 + (mascot.vOffset or 0) + "px") else if mascot.position == 'top' then "auto" else (mascotposition + mascot.vOffset) + "px") + "; | |
right: " + (mascot.hoffset or 0 + (unless Conf['Sidebar'] == 'large' and mascot.center then 0 else 25)) + "px; | |
top: " + (if mascot.position == 'top' then (mascot.vOffset or 0) + "px" else 'auto') + "; | |
left: auto; | |
pointer-events: none; | |
} | |
#mascot img { | |
z-index: " + (if Conf['Mascots Overlap Posts'] then '3' else '-1') + "; | |
} | |
" | |
return result | |
dialog: (key) -> | |
editMascot = userThemes[key] or {} | |
editMascot.name = key or '' | |
layout = | |
name: [ | |
"Mascot Name" | |
"" | |
"The name of the Mascot" | |
"text" | |
] | |
image: [ | |
"Image" | |
"" | |
"Image of Mascot. Accepts Base64 as well as URLs." | |
"text" | |
] | |
position: [ | |
"Position" | |
"default" | |
"Where the mascot is anchored in the Sidebar. The default option places the mascot above the Post Form or on the bottom of the page, depending on the Post Form setting." | |
"select" | |
["default", "top", "bottom"] | |
] | |
vOffset: [ | |
"Vertical Offset" | |
0 | |
"This value moves the mascot vertically away from the anchor point, in pixels (the post form is exactly \"264\" pixels tall if you'd like to force the mascot to sit above it)." | |
"number" | |
] | |
hOffset: [ | |
"Horizontal Offset" | |
0 | |
"This value moves the mascot further away from the edge of the screen, in pixels." | |
"number" | |
] | |
center: [ | |
"Center Mascot" | |
false | |
"If this is enabled, Appchan X will attempt to pad the mascot with 25 pixels of Horizontal Offset when the \"Sidebar Setting\" is set to \"large\" in an attempt to \"re-center\" the mascot. If you are having problems placing your mascot properly, ensure this is not enabled." | |
"checkbox" | |
] | |
dialog = $.el "div", | |
id: "mascotConf" | |
className: "reply dialog" | |
innerHTML: " | |
<div id=mascotbar> | |
</div> | |
<hr> | |
<div id=mascotcontent> | |
</div> | |
<div id=save> | |
<a href='javascript:;'>Save Mascot</a> | |
</div> | |
<div id=cancel> | |
<a href='javascript:;'>Cancel</a> | |
</div> | |
" | |
for item in layout | |
div = $.el "div", | |
className: "themevar" | |
innerHTML: "<div class=optionname>#{item}</div><div class=option><input class=field name='#{item}' placeholder='#{item}' value='#{editTheme[item]}'>" | |
$.on $('input', div), 'blur', -> | |
editTheme[@name] = @value | |
Style.addStyle(editTheme) | |
$.add $("#themecontent", dialog), div | |
$.on $('#save > a', dialog), 'click', -> | |
ThemeTools.save editTheme | |
$.on $('#cancel > a', dialog), 'click', ThemeTools.close | |
Style = | |
init: -> | |
@addStyle() | |
emoji: (position) -> | |
css = '' | |
for item in Emoji | |
unless Conf['Emoji'] == "disable ponies" and item[2] == "pony" | |
name = item[0] | |
image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA' + item[1] | |
css = css + ' | |
a.useremail[href*="' + name + '"]:last-of-type::' + position + ', | |
a.useremail[href*="' + name.toLowerCase() + '"]:last-of-type::' + position + ', | |
a.useremail[href*="' + name.toUpperCase() + '"]:last-of-type::' + position + ' { | |
content: url("' + image + '") " "; | |
} | |
' | |
return css | |
rice: (checkbox)-> | |
$.addClass checkbox, 'riced' | |
div = $.el 'div', | |
className: 'rice' | |
$.after checkbox, div | |
if div.parentElement.tagName.toLowerCase() != 'label' | |
$.on div, 'click', -> | |
checkbox.click() | |
noderice: (post) -> | |
if checkbox = $('[type=checkbox]:not(.riced)', post.root) | |
Style.rice checkbox | |
allrice: -> | |
checkboxes = $$('[type=checkbox]:not(.riced)', d.body) | |
for checkbox in checkboxes | |
Style.rice checkbox | |
agent: -> | |
switch $.engine | |
when 'gecko' | |
return '-moz-' | |
when 'webkit' | |
return '-webkit-' | |
when 'presto' | |
return '-o-' | |
addStyle: (theme) -> | |
$.off d, 'DOMNodeInserted', Style.addStyle | |
if d.head | |
if !theme or !theme.Author | |
theme = userThemes[Conf['theme']] | |
if existingStyle = $.id 'appchan' | |
$.rm existingStyle | |
$.addStyle Style.css(theme), 'appchan' | |
else # XXX fox | |
$.on d, 'DOMNodeInserted', Style.addStyle | |
remStyle: -> | |
$.off d, 'DOMNodeInserted', @remStyle | |
if d.head and d.head.childNodes.length > 10 | |
headNodes = d.head.childNodes | |
headNode = headNodes.length - 1 | |
for node, index in headNodes | |
current = headNodes[headNode - index] | |
if current.nodeType == 1 | |
if (current.rel == 'stylesheet' or current.rel == 'alternate stylesheet' or current.tagName.toLowerCase() == 'style') and current.id != 'appchan' | |
$.rm current | |
else # XXX fox | |
$.on d, 'DOMNodeInserted', @remStyle | |
css: (theme) -> | |
agent = Style.agent() | |
css=' | |
/* dialog styling */ | |
.dialog.reply { | |
display: block; | |
border: 1px solid rgba(0,0,0,.25); | |
padding: 0; | |
} | |
.move { | |
cursor: move; | |
} | |
label, | |
.favicon { | |
cursor: pointer; | |
} | |
a[href="javascript:;"] { | |
text-decoration: none; | |
} | |
.warning, | |
.disabledwarning { | |
color: red; | |
} | |
.hide_thread_button:not(.hidden_thread) { | |
float: left; | |
} | |
.thread > .hidden_thread ~ *, | |
[hidden], | |
#content > [name=tab]:not(:checked) + div, | |
#updater:not(:hover) > :not(.move), | |
.autohide:not(:hover) > form, | |
#qp input, | |
.forwarded, | |
#qp .rice { | |
display: none !important; | |
} | |
.menu_button { | |
display: inline-block; | |
} | |
.menu_button > span { | |
border-top: .5em solid; | |
border-right: .3em solid transparent; | |
border-left: .3em solid transparent; | |
display: inline-block; | |
margin: 2px; | |
vertical-align: middle; | |
} | |
#menu { | |
position: absolute; | |
outline: none; | |
} | |
.entry { | |
border-bottom: 1px solid rgba(0,0,0,.25); | |
cursor: pointer; | |
display: block; | |
outline: none; | |
padding: 3px 7px; | |
position: relative; | |
text-decoration: none; | |
white-space: nowrap; | |
} | |
.entry:last-child { | |
border: none; | |
} | |
.focused.entry { | |
background: rgba(255,255,255,.33); | |
} | |
.entry.hasSubMenu { | |
padding-right: 1.5em; | |
} | |
.hasSubMenu::after { | |
content: ""; | |
border-left: .5em solid; | |
border-top: .3em solid transparent; | |
border-bottom: .3em solid transparent; | |
display: inline-block; | |
margin: .3em; | |
position: absolute; | |
right: 3px; | |
} | |
.hasSubMenu:not(.focused) > .subMenu { | |
display: none; | |
} | |
.subMenu { | |
position: absolute; | |
left: 100%; | |
top: 0; | |
margin-top: -1px; | |
} | |
h1, | |
.boardBanner { | |
text-align: center; | |
} | |
#qr > .move { | |
min-width: 300px; | |
overflow: hidden; | |
box-sizing: border-box; | |
' + agent + 'box-sizing: border-box; | |
padding: 0 2px; | |
} | |
#qr > .move > span { | |
float: right; | |
} | |
#autohide, | |
.close, | |
#qr select, | |
#dump, | |
.remove, | |
.captchaimg, | |
#qr div.warning { | |
cursor: pointer; | |
} | |
#qr select, | |
#qr > form { | |
margin: 0; | |
} | |
#dump { | |
background: ' + agent + 'linear-gradient(#EEE, #CCC); | |
width: 10%; | |
} | |
.gecko #dump { | |
padding: 1px 0 2px; | |
} | |
#dump:hover, | |
#dump:focus { | |
background: ' + agent + 'linear-gradient(#FFF, #DDD); | |
} | |
#dump:active, | |
.dump #dump:not(:hover):not(:focus) { | |
background: ' + agent + 'linear-gradient(#CCC, #DDD); | |
} | |
#qr:not(.dump) #replies, | |
.dump > form > label { | |
display: none; | |
} | |
#replies { | |
display: block; | |
height: 100px; | |
position: relative; | |
' + agent + 'user-select: none; | |
user-select: none; | |
} | |
#replies > div { | |
counter-reset: thumbnails; | |
top: 0; right: 0; bottom: 0; left: 0; | |
margin: 0; padding: 0; | |
overflow: hidden; | |
position: absolute; | |
white-space: pre; | |
} | |
#replies > div:hover { | |
bottom: -10px; | |
overflow-x: auto; | |
z-index: 1; | |
} | |
.thumbnail { | |
background-color: rgba(0,0,0,.2) !important; | |
background-position: 50% 20% !important; | |
background-size: cover !important; | |
border: 1px solid #666; | |
box-sizing: border-box; | |
' + agent + 'box-sizing: border-box; | |
cursor: move; | |
display: inline-block; | |
height: 90px; width: 90px; | |
margin: 5px; padding: 2px; | |
opacity: .5; | |
outline: none; | |
overflow: hidden; | |
position: relative; | |
text-shadow: 0 1px 1px #000; | |
' + agent + 'transition: opacity .25s ease-in-out; | |
vertical-align: top; | |
} | |
.thumbnail:hover, | |
.thumbnail:focus { | |
opacity: .9; | |
} | |
.thumbnail#selected { | |
opacity: 1; | |
} | |
.thumbnail::before { | |
counter-increment: thumbnails; | |
content: counter(thumbnails); | |
color: #FFF; | |
font-weight: 700; | |
padding: 3px; | |
position: absolute; | |
top: 0; | |
right: 0; | |
text-shadow: 0 0 3px #000, 0 0 8px #000; | |
} | |
.thumbnail.drag { | |
box-shadow: 0 0 10px rgba(0,0,0,.5); | |
} | |
.thumbnail.over { | |
border-color: #FFF; | |
} | |
.thumbnail > span { | |
color: #FFF; | |
} | |
.remove { | |
background: none; | |
color: #E00; | |
font-weight: 700; | |
padding: 3px; | |
} | |
.remove:hover::after { | |
content: " Remove"; | |
} | |
.thumbnail > label { | |
background: rgba(0,0,0,.5); | |
color: #FFF; | |
right: 0; bottom: 0; left: 0; | |
position: absolute; | |
text-align: center; | |
} | |
.thumbnail > label > input { | |
margin: 0; | |
} | |
#addReply { | |
color: #333; | |
font-size: 3.5em; | |
line-height: 100px; | |
} | |
#addReply:hover, | |
#addReply:focus { | |
color: #000; | |
} | |
.field { | |
border: 1px solid #CCC; | |
box-sizing: border-box; | |
' + agent + 'box-sizing: border-box; | |
color: #333; | |
font: 13px sans-serif; | |
margin: 0; | |
padding: 2px 4px 3px; | |
' + agent + 'transition: color .25s, border .25s; | |
} | |
.field:-moz-placeholder, | |
.field:hover:-moz-placeholder { | |
color: #AAA; | |
} | |
.field:hover, | |
.field:focus { | |
border-color: #999; | |
color: #000; | |
outline: none; | |
} | |
#qr > form > div:first-child > .field:not(#dump) { | |
width: 30%; | |
} | |
#qr textarea.field { | |
display: -webkit-box; | |
min-height: 120px; | |
min-width: 100%; | |
} | |
#charCount { | |
color: #000; | |
background: hsla(0, 0%, 100%, .5); | |
position: absolute; | |
top: 100%; | |
right: 0; | |
} | |
#charCount.warning { | |
color: red; | |
} | |
.captchainput > .field { | |
min-width: 100%; | |
} | |
.captchaimg { | |
text-align: center; | |
} | |
.captchaimg > img { | |
display: block; | |
height: 57px; | |
width: 300px; | |
} | |
#qr [type=file] { | |
margin: 1px 0; | |
width: 70%; | |
} | |
#qr [type=submit] { | |
margin: 1px 0; | |
padding: 1px; /* not Gecko */ | |
width: 30%; | |
} | |
.gecko #qr [type=submit] { | |
padding: 0 1px; /* Gecko does not respect box-sizing: border-box */ | |
} | |
.fileText:hover .fntrunc, | |
.fileText:not(:hover) .fnfull { | |
display: none; | |
} | |
.fitwidth img[data-md5] + img { | |
max-width: 100%; | |
} | |
.gecko .fitwidth img[data-md5] + img, | |
.presto .fitwidth img[data-md5] + img { | |
width: 100%; | |
} | |
#qr, | |
#qp, | |
#updater, | |
#stats, | |
#ihover, | |
#overlay, | |
#navlinks { | |
position: fixed; | |
} | |
#ihover { | |
max-height: 97%; | |
max-width: 75%; | |
padding-bottom: 18px; | |
} | |
#navlinks { | |
font-size: 16px; | |
top: 25px; | |
right: 5px; | |
} | |
#overlay { | |
top: 0; | |
right: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,.5); | |
z-index: 1; | |
} | |
#options { | |
z-index: 2; | |
position: fixed; | |
display: inline-block; | |
padding: 5px; | |
text-align: left; | |
vertical-align: middle; | |
left: 5%; | |
right: 5%; | |
top: 10%; | |
bottom: 10%; | |
} | |
#options #style_tab + div select { | |
width: 100%; | |
} | |
#theme_tab + div > div > div:not(.selectedtheme) h1 { | |
color: transparent !important; | |
} | |
#theme_tab + div > div > div.selectedtheme h1 { | |
right: 11px; | |
} | |
#theme_tab + div > div h1 { | |
position: absolute; | |
right: 300px; | |
bottom: 10px; | |
margin: 0; | |
' + agent + 'transition: all .2s ease-in-out; | |
} | |
#theme_tab + div > div { | |
margin-bottom: 3px; | |
} | |
#credits { | |
float: right; | |
} | |
#options ul { | |
padding: 0; | |
} | |
#options ul li { | |
overflow: auto; | |
padding: 0 5px 0 7px; | |
} | |
#options input:checked + .optionlabel, | |
#options input:checked + .rice + .optionlabel { | |
font-weight: 800; | |
} | |
#options input, | |
#options .rice { | |
float: right; | |
clear: left; | |
} | |
#options article li { | |
margin: 10px 0 10px 2em; | |
} | |
#options code { | |
background: hsla(0, 0%, 100%, .5); | |
color: #000; | |
padding: 0 1px; | |
} | |
#options label { | |
text-decoration: underline; | |
} | |
#options .mascots { | |
text-align: center; | |
padding: 0; | |
} | |
#options .mascot { | |
display: inline; | |
padding: 0; | |
} | |
#options .mascot div { | |
border: 2px solid rgba(0,0,0,0); | |
width: 200px; | |
height: 250px; | |
display: inline-block; | |
margin: 5px; | |
cursor: pointer; | |
background-position: top center; | |
background-repeat: no-repeat; | |
background-size: 200px auto; | |
} | |
#options .mascot div.enabled { | |
border: 2px solid rgba(0,0,0,0.5); | |
background-color: rgba(255,255,255,0.1); | |
} | |
#content { | |
overflow: auto; | |
position: absolute; | |
top: 2.5em; | |
right: 5px; | |
bottom: 5px; | |
left: 5px; | |
} | |
.suboptions, | |
#themecontent { | |
overflow: auto; | |
position: absolute; | |
right: 0; | |
bottom: 1.5em; | |
left: 0; | |
} | |
.suboptions { | |
top: 0; | |
} | |
#themecontent { | |
top: 1.5em; | |
} | |
#save, | |
.stylesettings { | |
position: absolute; | |
right: 10px; | |
bottom: 0; | |
} | |
#addthemes { | |
position: absolute; | |
left: 10px; | |
bottom: 0; | |
} | |
#cancel, | |
#mascots_batch { | |
position: absolute; | |
left: 10px; | |
bottom: 0; | |
} | |
#content textarea { | |
font-family: monospace; | |
min-height: 350px; | |
resize: vertical; | |
width: 100%; | |
} | |
#updater { | |
text-align: right; | |
} | |
#updater:not(:hover) { | |
border: none; | |
background: transparent; | |
} | |
#updater input[type=number] { | |
width: 4em; | |
} | |
.new { | |
background: lime; | |
} | |
#watcher { | |
padding-bottom: 5px; | |
position: absolute; | |
overflow: hidden; | |
white-space: nowrap; | |
} | |
#watcher:not(:hover) { | |
max-height: 220px; | |
} | |
#watcher > div { | |
max-width: 200px; | |
overflow: hidden; | |
padding-left: 5px; | |
padding-right: 5px; | |
text-overflow: ellipsis; | |
} | |
#watcher > .move { | |
padding-top: 5px; | |
text-decoration: underline; | |
} | |
#qp { | |
padding: 2px 2px 5px; | |
} | |
#qp .post { | |
border: none; | |
margin: 0; | |
padding: 0; | |
} | |
#qp img { | |
max-height: 300px; | |
max-width: 500px; | |
} | |
.qphl { | |
outline: 2px solid rgba(216,94,49,.7); | |
} | |
.quotelink.deadlink { | |
text-decoration: underline !important; | |
} | |
.deadlink:not(.quotelink) { | |
text-decoration: none !important; | |
} | |
.image_expanded { | |
clear: both !important; | |
} | |
.inlined { | |
opacity: .5; | |
} | |
.inline { | |
background-color: rgba(255,255,255,0.15); | |
border: 1px solid rgba(128,128,128,0.5); | |
display: table; | |
margin: 2px; | |
padding: 2px; | |
} | |
.inline .post { | |
background: none; | |
border: none; | |
margin: 0; | |
padding: 0; | |
} | |
div.opContainer { | |
display: block !important; | |
} | |
.opContainer.filter_highlight { | |
box-shadow: inset 5px 0 rgba(255,0,0,0.5); | |
} | |
.filter_highlight > .reply { | |
box-shadow: -5px 0 rgba(255,0,0,0.5); | |
} | |
.filtered, | |
.quotelink.filtered { | |
text-decoration: underline; | |
text-decoration: line-through !important; | |
} | |
.quotelink.forwardlink, | |
.backlink.forwardlink { | |
text-decoration: none; | |
border-bottom: 1px dashed; | |
} | |
.threadContainer { | |
margin-left: 20px; | |
border-left: 1px solid black; | |
} | |
.stub ~ * { | |
display: none !important; | |
} | |
' | |
if (Conf['Quick Reply'] and Conf['Hide Original Post Form']) or Conf['Style'] | |
css += '#postForm { | |
display: none; | |
}' | |
if Conf['Recursive Filtering'] | |
css += '.hidden + .threadContainer { | |
display: none; | |
}' | |
if Conf['Style'] | |
Main.callbacks.push @noderice | |
$.ready @allrice | |
Conf['styleenabled'] = '1' | |
@remStyle() | |
if Conf['Sidebar'] == 'large' | |
sidebarOffsetW = 51 | |
sidebarOffsetH = 17 | |
else | |
sidebarOffsetW = 0 | |
sidebarOffsetH = 0 | |
css += ' | |
::' + agent + 'selection { | |
background: ' + theme["Text"] + '; | |
color: ' + theme["Background Color"] + '; | |
} | |
body { | |
padding: 16px 0 16px; | |
} | |
@media only screen and (max-width: 1100px) { | |
body { | |
padding-top: 32px; | |
} | |
} | |
@media only screen and (max-width:689px) { | |
body { | |
padding-top: 47px; | |
} | |
} | |
@media only screen and (max-width:553px) { | |
body { | |
padding-top: 62px; | |
} | |
} | |
html, | |
body { | |
min-height: 100%; | |
} | |
html, | |
body, | |
input, | |
select, | |
textarea, | |
.boardTitle { | |
font-family: "' + Conf["Font"] + '"; | |
} | |
#qr img, | |
.captcha img { | |
opacity: ' + Conf["Captcha Opacity"] + '; | |
} | |
#qp div.post .postertrip, | |
#qp div.post .subject, | |
.capcode, | |
.container::before, | |
.dateTime, | |
.file, | |
.fileInfo, | |
.fileText, | |
.fileText span:not([class])::after, | |
.name, | |
.postNum, | |
.postertrip, | |
.rules, | |
.subject, | |
.subjectm | |
.summary, | |
a, | |
blockquote, | |
div.post > blockquote .chanlinkify.YTLT-link.YTLT-text, | |
div.reply, | |
fieldset, | |
textarea, | |
time + span { | |
font-size: ' + Conf["Font Size"] + 'px; | |
} | |
.globalMessage { | |
bottom: auto; | |
padding: 10px 5px 10px 5px; | |
position: fixed; | |
left: auto; | |
right: 2px; | |
top: -1000px; | |
} | |
.globalMessage b { | |
font-weight: 100; | |
} | |
/* Cleanup */ | |
#absbot, | |
#autohide, | |
#ft li.fill, | |
#imgControls label:first-of-type input, | |
#imgControls .rice, | |
#logo, | |
#postPassword + span, | |
#settingsBox[style*="display: none;"], | |
.autoPagerS, | |
.board > hr:last-of-type, | |
.closed, | |
.deleteform br, | |
.entry:not(.focused) > .subMenu, | |
.error:empty, | |
.hidden_thread > .summary, | |
.inline .report_button, | |
.inline input, | |
.mobile, | |
.navLinksBot, | |
.next, | |
.postingMode, | |
.prev, | |
.qrHeader, | |
.replyContainer > .hide_reply_button.stub ~ .reply, | |
.replymode, | |
.rules, | |
.sideArrows:not(.hide_reply_button), | |
.stylechanger, | |
.warnicon, | |
.warning:empty, | |
.yui-menu-shadow, | |
body > .postingMode ~ #delform hr, | |
body > br, | |
body > hr, | |
div.reply[hidden], | |
html body > span[style="left: 5px; position: absolute;"]:nth-of-type(0), | |
table[style="text-align:center;width:100%;height:300px;"] { | |
display: none !important; | |
} | |
div.post > blockquote .prettyprint span { | |
font-family: monospace; | |
} | |
div.post div.file .fileThumb { | |
float: left; | |
margin: 3px 20px 0; | |
} | |
a { | |
outline: 0; | |
} | |
#boardNavDesktop, | |
#boardNavDesktop a, | |
#boardNavDesktopFoot a, | |
#count, | |
#imageType, | |
#imageType option | |
#imgControls, | |
#navtopright a[href="javascript:;"], | |
#postcount, | |
#stats, | |
#timer, | |
#updater, | |
.pages a, | |
.pages strong, | |
body:not([class]) a[href="javascript:;"], | |
input, | |
label { | |
font-size: 12px; | |
text-decoration: none; | |
} | |
.filtered { | |
text-decoration: line-through; | |
} | |
/* YouTube Link Title */ | |
div.post > blockquote .chanlinkify.YTLT-link.YTLT-na { | |
text-decoration: line-through; | |
} | |
div.post > blockquote .chanlinkify.YTLT-link.YTLT-text { | |
font-style: normal; | |
} | |
/* Z-INDEXES */ | |
#options.reply.dialog, | |
#themeConf { | |
z-index: 999 !important; | |
} | |
#qp { | |
z-index: 102 !important; | |
} | |
#autoPagerBorderPaging, | |
#boardNavDesktop, | |
#boardNavDesktopFoot:hover, | |
#ihover, | |
#menu.reply.dialog, | |
#navlinks, | |
#overlay, | |
#updater:hover, | |
.exPopup, | |
html .subMenu { | |
z-index: 101 !important; | |
} | |
.fileThumb { | |
z-index: 100 !important; | |
} | |
div.navLinks > a:first-of-type::after, | |
.deleteform { | |
z-index: 99 !important; | |
} | |
#qr, | |
body > form #imgControls { | |
z-index: 98 !important; | |
} | |
.fileText ~ a > img + img { | |
z-index: 96 !important; | |
} | |
#boardNavMobile, | |
#imageType, | |
#imgControls label:first-of-type, | |
#imgControls label:first-of-type::after, | |
#stats, | |
#updater { | |
z-index: 10 !important; | |
} | |
#settingsBox { | |
z-index: 9 !important; | |
} | |
.deleteform:hover input[type="checkbox"], | |
.deleteform:hover .rice { | |
z-index: 7 !important; | |
} | |
#boardNavDesktopFoot::after, | |
#navtopright, | |
.deleteform::before, | |
.qrMessage, | |
#navtopright .settingsWindowLink::after { | |
z-index: 6 !important; | |
} | |
#stats, | |
#watcher, | |
#watcher::before, | |
.menu_button, | |
.postInfo input, | |
.postInfo .rice, | |
.sideArrows { | |
z-index: 4 !important; | |
} | |
.boardBanner, | |
.globalMessage::before, | |
.replyhider a { | |
z-index: 1 !important; | |
} | |
div.reply, | |
div.reply.highlight { | |
z-index: 0 !important; | |
' + agent + 'box-sizing: border-box; | |
} | |
/* ICON POSITIONS */ | |
/* 4chan X Options / 4chan Options */ | |
#navtopright .settingsWindowLink::after { | |
position: fixed; | |
left: auto; | |
right: 17px; | |
opacity: 0.3; | |
} | |
#navtopright .settingsWindowLink:hover::after { | |
opacity: 1; | |
right: 16px; | |
} | |
/* 4sight */ | |
body > a[style="cursor: pointer; float: right;"]::after { | |
font-size: 12px; | |
position: fixed; | |
right: 0px; | |
opacity: 0.3; | |
} | |
body > a[style="cursor: pointer; float: right;"]:hover::after { | |
opacity: 1; | |
} | |
/* Back */ | |
div.navLinks > a:first-of-type::after { | |
position: fixed; | |
right: ' + (230 + sidebarOffsetW) + 'px; | |
cursor: pointer; | |
' + agent + 'transform: scale(.8); | |
opacity: 0.4; | |
bottom: 1px; | |
top: auto; | |
} | |
div.navLinks > a:first-of-type:hover::after { | |
opacity: 1; | |
} | |
/* Delete Form */ | |
.deleteform::before { | |
visibility: visible; | |
position: fixed; | |
right: ' + (210 + sidebarOffsetW) + 'px; | |
' + agent + 'transform: scale(.9); | |
opacity: 0.4; | |
top: auto; | |
bottom: 2px; | |
} | |
.deleteform:hover::before { | |
opacity: 1; | |
cursor: pointer; | |
bottom: -30px; | |
visibility: hidden; | |
} | |
/* Expand Images */ | |
#imgControls label:first-of-type::after { | |
opacity: 0.2; | |
position: relative; | |
top: 4px; | |
} | |
#imgControls label:hover:first-of-type::after { | |
opacity: 1; | |
} | |
/* Global Message */ | |
.globalMessage::before { | |
height: 9px; | |
position: fixed; | |
right: 70px; | |
min-width: 30px; | |
max-width: 30px; | |
padding-bottom: 5px; | |
opacity: 0.4; | |
} | |
.globalMessage:hover::before { | |
cursor: pointer; | |
opacity: 1; | |
} | |
/* Slideout Navigation */ | |
#boardNavDesktopFoot::after { | |
border: none; | |
position: fixed; | |
right: 37px; | |
opacity: 0.4; | |
} | |
#boardNavDesktopFoot:hover::after { | |
opacity: 1; | |
cursor: pointer; | |
} | |
/* Watcher */ | |
#watcher::before { | |
height: 9px; | |
font-size: 12px; | |
position: fixed; | |
right: 42px; | |
min-width: 30px; | |
max-width: 30px; | |
opacity: 0.4; | |
} | |
#watcher:hover::before { | |
opacity: 1; | |
cursor: pointer; | |
} | |
/* END OF ICON POSITIONS */ | |
.pageJump { | |
position: fixed; | |
top: -1000px; | |
pointer-events: all; | |
} | |
.extButton img { | |
margin-top: -4px; | |
} | |
#boardNavMobile select { | |
font-size: 11px; | |
pointer-events: all; | |
} | |
.qrMessage { | |
position: fixed; | |
right: 3px; | |
bottom: 250px; | |
font-size: 11px; | |
font-weight: 100; | |
background: none; | |
border: none; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
} | |
.boardTitle { | |
font-size: 30px; | |
font-weight: 400; | |
} | |
.boardSubtitle { | |
font-size: 13px; | |
} | |
hr { | |
padding: 0; | |
height: 0; | |
width: 100%; | |
clear: both; | |
border: none; | |
} | |
/* Front Page */ | |
.bd, | |
.bd ul, | |
img, | |
.pages, | |
#qr, | |
div[id^="qr"], | |
table.reply[style^="clear: both"], | |
.boxcontent > hr, | |
h3 { | |
border: none; | |
} | |
.boxcontent input { | |
height: 18px; | |
vertical-align: bottom; | |
margin-right: 1px; | |
} | |
a.yuimenuitemlabel { | |
padding: 0 20px; | |
} | |
/* Navigation */ | |
#boardNavDesktop, /* Top Navigation */ | |
.pages /* Bottom Navigation */ { | |
text-align: center; | |
font-size: 0; | |
color: transparent; | |
width: auto; | |
} | |
#boardNavDesktop{ | |
width: auto; | |
padding-right: 0px; | |
margin-right: 0px; | |
padding-top: 1px; | |
padding-bottom: 3px; | |
} | |
#boardNavDesktopFoot { | |
visibility: visible; | |
position: fixed; | |
top: -1000px; | |
right: 2px !important; | |
bottom: auto; | |
width: ' + (226 + sidebarOffsetW) + 'px; | |
color: transparent; | |
font-size: 0; | |
padding: 3px 10px 35px 10px; | |
border-width: 1px; | |
text-align: center; | |
word-spacing: -3px; | |
} | |
.fileThumb { | |
position: relative; | |
} | |
#boardNavDesktop a, | |
.pages a, | |
.pages strong { | |
display: inline-block; | |
font-size: 12px; | |
border: none; | |
text-align: center; | |
margin: 0 1px 0 2px; | |
} | |
.pages { | |
word-spacing: 10px; | |
} | |
/* moots announcements */ | |
.globalMessage { | |
font-size: 12px; | |
text-align: center; | |
font-weight: 200; | |
} | |
.pages strong, | |
a, | |
.new { | |
' + agent + 'transition: background-color .1s linear; | |
} | |
/* Post Form */ | |
/* Override OS-specific UI */ | |
#ft li, | |
#ft ul, | |
#options input:not([type="radio"]), | |
#updater input:not([type="radio"]), | |
.box-outer, | |
.boxbar, | |
.top-box, | |
h2, | |
input:not([type="radio"]), | |
input[type="submit"], | |
textarea { | |
' + agent + 'appearance: none; | |
} | |
input[type=checkbox] { | |
' + agent + 'appearance: checkbox !important; | |
} | |
/* Formatting for all postarea elements */ | |
#browse, | |
#file, | |
input, | |
input.field, | |
input[type="submit"], | |
textarea { | |
border-width: 1px !important; | |
border-style: solid !important; | |
padding: 1px !important; | |
height: 20px !important; | |
} | |
#browse, | |
#file, | |
#qr input[type="submit"], | |
#qr textarea, | |
#qr .field { | |
margin: 1px 0 0; | |
vertical-align: bottom; | |
} | |
/* Width and height of all postarea elements (excluding some captcha elements) */ | |
textarea.field, | |
#qr .field[type="password"], | |
.ys_playerContainer audio, | |
#qr input[title="Verification"], | |
#qr > form > div { | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
} | |
/* Buttons */ | |
#browse, | |
input[type="submit"], /* Any lingering buttons */ | |
input[value="Report"] { | |
height: 20px; | |
padding: 0; | |
font-size: 12px; | |
} | |
#qr input[type="submit"] { | |
width: 100%; | |
float: left; | |
clear: both; | |
} | |
#qr input[type="file"] { | |
position: absolute; | |
opacity: 0; | |
z-index: -1; | |
} | |
#file { | |
width: ' + (177 + sidebarOffsetW) + 'px; | |
} | |
#browse { | |
text-align: center; | |
width: 70px; | |
margin: 1px 1px 0 0; | |
} | |
#browse, | |
#file { | |
cursor: pointer; | |
' + agent + 'box-sizing: border-box; | |
box-sizing: border-box; | |
display: inline-block; | |
} | |
/* Image Hover and Image Expansion */ | |
#ihover { | |
max-width:85%; | |
max-height:85%; | |
} | |
#qp { | |
min-width: 500px; | |
} | |
.fileText ~ a > img + img { | |
position: relative; | |
top: 0px; | |
} | |
#imageType { | |
border: none; | |
width: 90px; | |
position: relative; | |
bottom: 1px; | |
background: none; | |
} | |
/* #qr dimensions */ | |
#qr { | |
height: auto; | |
} | |
.top-box .menubutton, | |
.boardTitle { | |
background-image: none; | |
} | |
#delform > div:not(.thread) input, | |
.deleteform input[type="checkbox"], | |
.rice { | |
vertical-align: middle; | |
} | |
#qr label input, | |
.boxcontent input, | |
.boxcontent textarea { | |
' + agent + 'appearance: none; | |
border: 0; | |
} | |
input[type=checkbox], | |
.reply input[type=checkbox], | |
#options input[type=checkbox] { | |
' + agent + 'appearance: none; | |
width: 12px !important; | |
height: 12px !important; | |
cursor: pointer; | |
} | |
.postingMode ~ #delform .opContainer input { | |
position: relative; | |
bottom: 2px; | |
} | |
/* Posts */ | |
body > .postingMode ~ #delform br[clear="left"], | |
#delform center { | |
position: fixed; | |
bottom: -500px; | |
} | |
.deleteform { | |
border-spacing: 0 1px; | |
} | |
#delform .fileText + br + a[target="_blank"] img, | |
#qp div.post .fileText + br + a[target="_blank"] img { | |
border: 0; | |
float: left; | |
margin: 5px 20px 15px; | |
} | |
#delform .fileText + br + a[target="_blank"] img + img { | |
margin: 0 0 25px; | |
} | |
.fileText { | |
margin-top: 17px; | |
} | |
.fileText span:not([class])::after { | |
font-size: 13px; | |
} | |
#updater:hover { | |
border: 0; | |
} | |
/* Fixes text spoilers */ | |
.spoiler:not(:hover), | |
.spoiler:not(:hover) .quote, | |
.spoiler:not(:hover) a { | |
color: rgb(0,0,0); | |
background-color: rgb(0,0,0); | |
text-shadow: none !important; | |
} | |
/* Remove default "inherit" background declaration */ | |
.span.subject, | |
.subject, | |
.name, | |
.postertrip { | |
background: transparent; | |
} | |
.name { | |
font-weight: 700; | |
} | |
/* Addons and such */ | |
body > div[style="width: 100%;"] { | |
margin-top: 34px; | |
} | |
#copyright, | |
#boardNavDesktop a, | |
#qr td, | |
#qr tr[height="73"]:nth-of-type(2), | |
.menubutton a, | |
.pages td, | |
td[style="padding-left: 7px;"], | |
div[id^="qr"] tr[height="73"]:nth-of-type(2) { | |
padding: 0; | |
} | |
#navtopright { | |
position: fixed; | |
right: 60px; | |
top: -100px; | |
bottom: auto; | |
font-size: 0; | |
color: transparent; | |
} | |
/* Expand Images div */ | |
#imgControls input { | |
width: 10px; | |
height: 10px; | |
margin: 4px 1px; | |
vertical-align: top; | |
} | |
#imgControls label { | |
font-size: 0; | |
color: transparent; | |
} | |
#imgControls label:first-of-type { | |
position: fixed; | |
right: ' + (232 + sidebarOffsetW) + 'px; | |
top: 0px; | |
bottom: auto; | |
} | |
#imageType { | |
position: fixed; | |
right: ' + (140 + sidebarOffsetW) + 'px; | |
top: 1px; | |
bottom: auto; | |
} | |
#imgControls label:nth-of-type(2)::after { | |
font-size: 12px; | |
content: "Preload?"; | |
} | |
#imgControls select { | |
float: right; | |
} | |
#imgControls select > option { | |
font-size: 80%; | |
} | |
/* End of Expand Images div */ | |
/* Reply Previews */ | |
#qp div.post /* 4chan x Quote Preview */ { | |
max-width: 70%; | |
visibility: visible; | |
} | |
#qp div.op { | |
display: table; | |
} | |
#qp div.post { | |
padding: 2px 6px; | |
} | |
#qp div.post img { | |
max-width: 300px; | |
height: auto; | |
} | |
.deleteform { | |
position: fixed; | |
top: -1000px; | |
} | |
.deleteform { | |
position: fixed; | |
top: -1000px; | |
right: 2px; | |
bottom: auto; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
margin: 0px; | |
padding: 0px; | |
font-size: 0px; | |
height: 18px; | |
} | |
.deleteform:hover { | |
position: fixed; | |
right: 3px; | |
} | |
.deleteform input[value="Delete"], | |
.deleteform input[value="Report"] { | |
float: left; | |
} | |
.deleteform { | |
width: ' + (246 + sidebarOffsetW) + 'px; | |
} | |
.deleteform:hover input[type="checkbox"], | |
.deleteform:hover .rice { | |
position: fixed; | |
right: ' + (130 + sidebarOffsetW) + 'px; | |
} | |
.deleteform:hover::after { | |
visibility: visible; | |
position: fixed; | |
right: ' + (50 + sidebarOffsetW) + 'px; | |
font-size: 12px; | |
content: "File Only"; | |
width: 50px; | |
} | |
div.navLinks { | |
font-size: 0; | |
visibility: hidden; | |
} | |
div.navLinks > a { | |
position: fixed; | |
top: auto; | |
right: -192px; | |
bottom: -1000px; | |
visibility: visible; | |
} | |
/* File Clearer support */ | |
.clearbutton { | |
position: fixed; | |
bottom: 45px; | |
right: 55px; | |
} | |
/* AutoPager */ | |
#autoPagerBorderPaging { | |
position: fixed !important; | |
right: 300px !important; | |
bottom: 0px; | |
} | |
/* Appchan x options */ | |
#options ul { | |
margin: 0; | |
} | |
#options ul { | |
margin-bottom: 5px; | |
padding-bottom: 7px; | |
} | |
#options ul:first-of-type { | |
padding-top: 5px; | |
} | |
/* End of Appchan x options */ | |
#stats, | |
#navlinks { | |
top: 0 !important; | |
left: auto !important; | |
bottom: auto !important; | |
width: 96px; | |
text-align: right; | |
padding: 0; | |
border: 0; | |
border-radius: 0; | |
} | |
#stats { | |
right: 45px !important; | |
font-size: 12px; | |
position: fixed; | |
cursor: default; | |
} | |
#navlinks { | |
right: 105px !important; | |
} | |
#updater { | |
right: 2px !important; | |
top: ' + (if Conf["Updater Position"] == "top" then "0" else "auto") + ' !important; | |
bottom: ' + (if Conf["Updater Position"] == "bottom" then "0" else "auto") + ' !important; | |
left: auto !important; | |
width: 58px !important; | |
border: 0; | |
font-size: 12px; | |
overflow: hidden; | |
padding-bottom: 2px; | |
} | |
#updater { | |
background: none; | |
} | |
#count.new { | |
background-color: transparent; | |
} | |
#updater:hover { | |
width: 150px !important; | |
right: 2px !important; | |
} | |
#updater #count:not(.new) { | |
font-size: 0; | |
color: transparent; | |
} | |
#updater #count:not(.new)::after { | |
font-size: 12px; | |
content: "+0"; | |
} | |
.opContainer .favicon { | |
position: relative; | |
top: 2px; | |
} | |
#watcher { | |
padding-left: 0px; | |
} | |
#watcher { | |
padding: 1px 0; | |
border-radius: 0; | |
} | |
#updater .move, | |
#options .move, | |
#stats .move { | |
cursor: default !important; | |
} | |
/* 4sight */ | |
body > a[style="cursor: pointer; float: right;"] { | |
position: fixed; | |
top:-119px; | |
right: 60px; | |
font-size: 0px; | |
} | |
body > a[style="cursor: pointer; float: right;"] ~ div[style^="width: 100%;"] { | |
display: block; | |
position: fixed; | |
top: 17px; | |
bottom: 17px; | |
left: 4px; | |
right: ' + (252 + sidebarOffsetW) + 'px; | |
width: auto; | |
margin: 0; | |
} | |
body > a[style="cursor: pointer; float: right;"] ~ div[style^="width: 100%;"] > table { | |
height: 100%; | |
vertical-align: top; | |
} | |
body > a[style="cursor: pointer; float: right;"] ~ div[style^="width: 100%;"]{ | |
height: 95%; | |
margin-top: 5px; | |
margin-bottom: 5px; | |
} | |
#fs_status { | |
width: auto; | |
height: 100%; | |
background: none; | |
padding: 10px; | |
overflow: scroll; | |
} | |
[alt="sticky"] + a::before { | |
content: "Sticky | "; | |
} | |
[alt="closed"] + a::before { | |
content: "Closed | "; | |
} | |
[alt="closed"] + a { | |
text-decoration: line-through; | |
} | |
/* Youtube Link Title */ | |
.chanlinkify.YTLT-link.YTLT-text { | |
font-family: monospace; | |
font-size: 11px; | |
} | |
.fileText+br+a[target="_blank"]:hover { | |
background: none; | |
} | |
.inline, | |
#qp { | |
background-color: transparent; | |
border: none; | |
} | |
input[type="submit"]:hover { | |
cursor: pointer; | |
} | |
/* 4chan Sounds */ | |
.ys_playerContainer.reply { | |
position: fixed; | |
bottom: 252px; | |
margin: 0; | |
right: 3px; | |
padding-right: 0; | |
padding-left: 0; | |
padding-top: 0; | |
} | |
#qr input:focus:' + agent + 'placeholder, | |
#qr textarea:focus:' + agent + 'placeholder { | |
color: transparent; | |
} | |
img[md5] { | |
image-rendering: optimizeSpeed; | |
} | |
input, | |
textarea { | |
text-rendering: geometricPrecision; | |
} | |
#boardNavDesktop .current { | |
font-weight: bold; | |
font-size: 13px; | |
} | |
#postPassword { | |
position: relative; | |
bottom: 3px; | |
} | |
.postContainer.inline { | |
border: none; | |
background: none; | |
padding-bottom: 2px; | |
} | |
div.pagelist { | |
background: none; | |
border: none; | |
} | |
a.forwardlink { | |
border: none; | |
} | |
.deleteform { | |
border-bottom: 2px solid transparent; | |
} | |
.exif td { | |
color: #999; | |
} | |
.callToAction.callToAction-big { | |
font-size: 18px; | |
color: rgb(255,255,255); | |
} | |
body > table[cellpadding="30"] h1, | |
body > table[cellpadding="30"] h3 { | |
position: static; | |
} | |
.focused.entry { | |
background-color: transparent; | |
} | |
#menu.reply.dialog, | |
html .subMenu { | |
padding: 0px; | |
} | |
#qr #charCount { | |
background: none; | |
position: absolute; | |
right: 2px; | |
top: auto; | |
bottom: 110px; | |
color: ' + (if theme["Dark Theme"] == "1" then "rgba(255,255,255,0.7)" else "rgba(0,0,0,0.7)") + '; | |
font-size: 10px; | |
height: 20px; | |
text-align: right; | |
vertical-align: middle; | |
padding-top: 2px; | |
} | |
#qr #charCount.warning { | |
color: rgb(255,0,0); | |
position: absolute; | |
top: auto; | |
right: 2px; | |
bottom: 110px; | |
height: 20px; | |
max-height: 20px; | |
border: none; | |
background: none; | |
} | |
textarea { | |
resize: none; | |
} | |
/* Position and Dimensions of the #qr */ | |
#qr { | |
overflow: visible; | |
position: fixed; | |
top: auto !important; | |
bottom: 20px !important; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
margin: 0; | |
padding: 0; | |
z-index: 5; | |
background-color: transparent !important; | |
} | |
/* Width and height of all #qr elements (excluding some captcha elements) */ | |
#qr textarea { | |
min-height: 0 !important; | |
} | |
body > .postingMode ~ #delform .reply a > img[src^="//images"] { | |
position: relative; | |
z-index: 96; | |
} | |
#qr img { | |
height: 47px; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
} | |
#dump { | |
background: none; | |
border: none; | |
width: 20px; | |
height: 17px; | |
margin: 0; | |
font-size: 14px; | |
vertical-align: middle; | |
outline: none; | |
} | |
#dump:hover { | |
background: none; | |
} | |
#qr select { | |
position: absolute; | |
bottom: -18px; | |
right: 65px; | |
background: none; | |
border: none; | |
font-size: 12px; | |
width: 128px; | |
} | |
#qr > form > label { | |
font-size: 0px; | |
color: transparent; | |
} | |
#qr > form > label::after { | |
content: "Spoiler?"; | |
font-size: 12px; | |
} | |
.dump > form > label { | |
display: block; | |
visibility: hidden; | |
} | |
#spoilerLabel { | |
position: absolute; | |
bottom: -20px; | |
right: 20px; | |
} | |
#spoilerLabel input { | |
position: relative; | |
top: 1px; | |
left: 2px; | |
} | |
#qr .warning { | |
position: absolute; | |
bottom: -18px; | |
right: 1px; | |
height: 20px; | |
text-align: right; | |
vertical-align: middle; | |
padding-top: 2px; | |
max-height: 16px; | |
} | |
.deleteform:hover { | |
top: auto; | |
bottom: 0px; | |
} | |
.deleteform:hover input[type="checkbox"], | |
.deleteform:hover .rice, | |
.deleteform:hover::after { | |
top: auto; | |
bottom: 2px; | |
} | |
.deleteform:hover input[name="pwd"] { | |
top: auto; | |
bottom: 0px; | |
} | |
input[title="Verification"], | |
.captchaimg img { | |
margin-top: 1px; | |
} | |
#qr textarea.field, | |
#qr div { | |
min-width: 0; | |
} | |
html body span[style="left: 5px; position: absolute;"] a { | |
height: 14px; | |
padding-top: 3px; | |
width: 56px; | |
} | |
#qr textarea.field { | |
height: 88px !important; | |
} | |
.textarea { | |
height: 89px; | |
} | |
hr { | |
position: relative; | |
top: 2px; | |
} | |
#updater input, | |
#options input, | |
#qr, | |
table.reply[style^="clear: both"] { | |
border: none; | |
} | |
#delform > div:not(.thread) select, | |
.pages input[type="submit"] { | |
margin: 0; | |
height: 17px; | |
} | |
.prettyprint { | |
white-space: pre-wrap; | |
border-radius: 2px; | |
font-size: 11px; | |
} | |
#themeConf { | |
position: fixed; | |
left: 2px; | |
width: 248px; | |
top: 0; | |
bottom: 0; | |
right: auto; | |
} | |
#themebar input { | |
width: 30%; | |
} | |
body { | |
background: ' + theme["Background Color"] + '; | |
background-image: ' + theme["Background Image"] + '; | |
background-repeat: ' + theme["Background Repeat"] + '; | |
background-attachment: ' + theme["Background Attachment"] + '; | |
background-position: ' + theme["Background Position"] + '; | |
} | |
#content, | |
#themecontent { | |
background: ' + theme["Background Color"] + '; | |
border: 1px solid ' + theme["Reply Border"] + '; | |
padding: 5px; | |
} | |
.suboptions { | |
padding: 5px; | |
} | |
.boardTitle { | |
text-shadow: | |
1px 1px 0 ' + theme["Background Color"] + ', | |
-1px 1px 0 ' + theme["Background Color"] + ', | |
1px -1px 0 ' + theme["Background Color"] + ', | |
-1px -1px 0 ' + theme["Background Color"] + ', | |
0px 0px 10px ' + theme["Text"] + '; | |
} | |
#browse, | |
#ft li, | |
#ft ul, | |
#options .dialog, | |
#qr::before, | |
#watcher, | |
#updater:hover, | |
.box-outer, | |
.boxbar, | |
.deleteform input[value=Delete], | |
.top-box, | |
.yuimenuitem-selected, | |
html body span[style="left: 5px; position: absolute;"] a, | |
input[type="submit"], | |
#options.reply.dialog, | |
.deleteform input[value=Delete], | |
input[value="Report"], | |
#qr.autohide .move { | |
background: ' + theme["Buttons Background"] + '; | |
border: 1px solid ' + theme["Buttons Border"] + '; | |
} | |
#file, | |
#dump, | |
#options input, | |
input, | |
input.field, | |
select, | |
textarea, | |
textarea.field { | |
background: ' + theme["Input Background"] + '; | |
border: 1px solid ' + theme["Input Border"] + '; | |
color: ' + theme["Inputs"] + '; | |
' + agent + 'transition: all .2s linear; | |
} | |
#browse:hover, | |
#file:hover, | |
div.navLinks > a:first-of-type:hover, | |
.deleteform input:hover, | |
input:hover, | |
input.field:hover, | |
input[type="submit"]:hover, | |
select:hover, | |
textarea:hover, | |
textarea.field:hover { | |
background: ' + theme["Hovered Input Background"] + '; | |
border-color: ' + theme["Hovered Input Border"] + '; | |
color: ' + theme["Inputs"] + '; | |
' + agent + 'transition: all .2s linear; | |
} | |
input:focus, | |
input.field:focus, | |
input[type="submit"]:focus, | |
select:focus, | |
textarea:focus, | |
textarea.field:focus { | |
background: ' + theme["Focused Input Background"] + '; | |
border-color: ' + theme["Focused Input Border"] + '; | |
color: ' + theme["Inputs"] + '; | |
} | |
#qp div.post, | |
div.reply { | |
background: ' + theme["Reply Background"] + '; | |
border: 1px solid ' + theme["Reply Border"] + '; | |
} | |
.reply.highlight { | |
background: ' + theme["Highlighted Reply Background"] + '; | |
border: 1px solid ' + theme["Highlighted Reply Border"] + '; | |
} | |
#boardNavDesktop, | |
.pages { | |
background: ' + theme["Navigation Background"] + '; | |
border: 1px solid ' + theme["Navigation Border"] + '; | |
} | |
#delform { | |
background: ' + theme["Thread Wrapper Background"] + '; | |
border: 1px solid ' + theme["Thread Wrapper Border"] + '; | |
} | |
#boardNavDesktopFoot, | |
#themeConf, | |
#watcher, | |
#watcher:hover, | |
.deleteform, | |
div.subMenu, | |
#menu { | |
background: ' + theme["Dialog Background"] + '; | |
border: 1px solid ' + theme["Dialog Border"] + '; | |
} | |
.inline div.reply { | |
/* Inline Quotes */ | |
background-color: ' + (if theme["Dark Theme"] == "1" then "rgba(255,255,255,0.03)" else "rgba(0,0,0,0.03)") + '; | |
border: 1px solid ' + theme["Reply Border"] + '; | |
box-shadow: 5px 5px 5px '+ theme["Shadow Color"] + '; | |
} | |
[id^="q"] .warning { | |
background: ' + theme["Input Background"] + '; | |
border: 1px solid ' + theme["Input Border"] + '; | |
} | |
.warning { | |
color: ' + theme["Warnings"] + '; | |
} | |
a, | |
#dump, | |
.entry, | |
div.post > blockquote a[href^="//"], | |
.sideArrows a, | |
div.postContainer span.postNum > .replylink { | |
color: ' + theme["Links"] + '; | |
} | |
.postNum a { | |
color: ' + theme["Post Numbers"] + '; | |
} | |
.subject { | |
color: ' + theme["Subjects"] + ' !important; | |
font-weight: 600; | |
} | |
#updater:not(:hover), | |
#updater:not(:hover) #count:not(.new)::after, | |
.summary, | |
body > form, | |
body, | |
html body span[style="left: 5px; position: absolute;"] a, | |
input, | |
.deleteform::after, | |
textarea, | |
.abbr, | |
.boxbar, | |
.boxcontent, | |
.pages strong, | |
.reply, | |
.reply.highlight, | |
#boardNavDesktop .title, | |
#imgControls label::after, | |
#boardNavDesktop::after, | |
#updater #count:not(.new)::after, | |
#qr > form > label::after, | |
#qr.autohide .move, | |
span.pln { | |
color: ' + theme["Text"] + '; | |
} | |
#options ul { | |
border-bottom: 1px solid ' + theme["Reply Border"] + '; | |
} | |
.quote { | |
color: ' + theme["Greentext"] + '; | |
} | |
a.backlink { | |
color: ' + theme["Backlinks"] + '; | |
font-weight: 800; | |
} | |
span.quote > a.quotelink, | |
a.quotelink { | |
color: ' + theme["Quotelinks"] + '; | |
} | |
div.subMenu, | |
#menu, | |
#qp div.post { | |
box-shadow: 5px 5px 5px '+ theme["Shadow Color"] + '; | |
} | |
.rice { | |
cursor: pointer; | |
width: 10px; | |
height: 10px; | |
margin: 3px; | |
display: inline-block; | |
background: ' + theme["Checkbox Background"] + '; | |
border: 1px solid ' + theme["Checkbox Border"] + '; | |
} | |
#qr label input, | |
#updater input, | |
.bd { | |
background: ' + theme["Buttons Background"] + '; | |
border: 1px solid ' + theme["Buttons Border"] + '; | |
} | |
.pages a, | |
#boardNavDesktop a { | |
color: ' + theme["Navigation Links"] + '; | |
} | |
input[type=checkbox]:checked + .rice { | |
background: ' + theme["Checkbox Checked Background"] + '; | |
background-image: url(' + (if theme["Dark Theme"] == "1" then "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJAQMAAADaX5RTAAAABlBMVEX///////9VfPVsAAAAAXRSTlMAQObYZgAAACJJREFUeF4FwLEJACAMBMATh3WN31jLFCEIsWLHicvQFM8HVtkHPQ48rNIAAAAASUVORK5CYII=" else "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJBAMAAAASvxsjAAAAG1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUUeIgAAAACHRSTlMAMN9AIL8Qn+Bdi9cAAAA4SURBVHheFcSxDQAgCATANzoAcQKNjSWxcgFncQ2gcmzhikOgKLe4CgonIxxZA9g6yX8dvrF/1fuGOAZL4rd0JQAAAABJRU5ErkJggg==") + '); | |
background-attachment: scroll; | |
background-repeat: no-repeat; | |
background-position: bottom right; | |
} | |
a:hover, | |
#dump:hover, | |
.entry:hover, | |
div.post > blockquote a[href^="//"]:hover, | |
.sideArrows a:hover, | |
div.post div.postInfo span.postNum a:hover, | |
div.postContainer span.postNum > .replylink:hover, | |
.nameBlock > .useremail > .name:hover, | |
.nameBlock > .useremail > .postertrip:hover { | |
color: ' + theme["Hovered Links"] + '; | |
} | |
.boardBanner a:hover, | |
#boardNavDesktop a:hover { | |
color: ' + theme["Hovered Navigation Links"] + '; | |
} | |
.boardBanner { | |
color: ' + theme["Board Title"] + '; | |
} | |
.name { | |
color: ' + theme["Names"] + ' !important; | |
} | |
.postertrip, | |
.trip { | |
color: ' + theme["Tripcodes"] + ' !important; | |
} | |
.nameBlock > .useremail > .postertrip, | |
.nameBlock > .useremail > .name { | |
color: ' + theme["Emails"] + '; | |
} | |
.nameBlock > .useremail > .name, | |
.name { | |
font-weight: 600; | |
} | |
a.forwardlink { | |
border-bottom: 1px dashed; | |
} | |
.qphl { | |
outline-color: ' + theme["Backlinked Reply Outline"] + '; | |
} | |
#qr input:' + agent + 'placeholder, | |
#qr textarea:' + agent + 'placeholder { | |
color: ' + (if theme["Dark Theme"] == "1" then "rgba(255,255,255,0.2)" else "rgba(0,0,0,0.3)") + ' !important; | |
} | |
.boxcontent dd, | |
#options ul { | |
border-color: ' + (if theme["Dark Theme"] == "1" then "rgba(255,255,255,0.1)" else "rgba(0,0,0,0.1)") + '; | |
} | |
' + theme['Custom CSS'] | |
if theme['Dark Theme'] == '1' | |
css += ' | |
.prettyprint { | |
background-color: rgba(255,255,255,.1); | |
border: 1px solid rgba(0,0,0,0.5); | |
} | |
span.tag { | |
color: #96562c; | |
} | |
span.pun { | |
color: #5b6f2a; | |
} | |
span.com { | |
color: #a34443; | |
} | |
span.str, | |
span.atv { | |
color: #8ba446; | |
} | |
span.kwd { | |
color: #987d3e; | |
} | |
span.typ, | |
span.atn { | |
color: #897399; | |
} | |
span.lit { | |
color: #558773; | |
} | |
/* 4chan X options */ | |
#navtopright .settingsWindowLink::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUGNOFkVENwCAMRE8CUiYBCZOAM6Rg4CWTMAlIuH0AG9mS0f7Q67W9FmkyMoWstxGIEgljTJKIhCd59IQp9+voBHaMOUmdnqgYs41qcxLYKZhCJFCxbrZJvUfz2LCm1liappoiYUxu8AiHVw2cPIXf6sXsl/L6Vb7c++9qi5v//dgFtjLxtKnNCFwAAAAASUVORK5CYII="); | |
} | |
/* Delete buttons */ | |
.deleteform::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAeRJREFUOE9tlM0rRFEYh0dMPiKxkWxsyEpWshCpURbWNPkvlCwYe6VJWU+zkmytZIOttZJsaHZioQkhH8+j++p0M/U0Z8597+/83o8zhUajUcjRwu8OGIAJmIFZmIJh6IHW/Ht5kXYCxmAVzuABvuAbXuAS9mAOesFDfzVSoW42FuEYXrOXFcjzyd41rMFQiIWQThS5AAP/E8jv3RO3Bf3hSHumo5MQeWZ9CjfJ3mO2d8d3pMuysQxFHVlYaxLpKLIP07ACV/AE2zAJ63CbuVbwBAYVsjsWNqzrRJE26ART3jAYdG/8bnJwk3VJIVtsd0LIdHSiiM+LYIdcKzQOR5DWsuJD5yRyVswA09GJItEQRUbgIHETh9cMctjyHbEmphNOjGuF+eyQfGfrBjixDluI2R0La00iHUVcm+4SOJipWNWHjr0P0mLbHVOJdHQSNetjvQNv2TvvfJcV8u7sJSc4J7bY7lhYa2LNdKLIApwn8T4bjdy9O469riy8c2KL7Y7zZRq61okiujDWmduEruiIRfXuOPaRYgjEb8VMJ2rzwfoQ7GRL2lovoHfHTzoO/907nShiLX9HJL39FtYL6N1x7JuJuxAzJWtiOjr5m7P8/1FMsq0vQQVqUIcqlGEUukxHJ8EPyeEKDPe5ibUAAAAASUVORK5CYII="); | |
} | |
/* Return button */ | |
div.navLinks > a:first-of-type::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFAAAA5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHk5OHkJaAqNQAAAA50Uk5TABAgQFBgcICPn6+/3+9ACPafAAAASElEQVQI15XMyxKAIAxD0eCr1ZT8/+eKDCOw07O700mBT45rrDXEXgul3sn0yCwsAaGBv/cw86xc92fbl0v7z7mBzeeudhJ/3aoUA1Vr0uhDAAAAAElFTkSuQmCC"); | |
} | |
/* Watcher */ | |
#watcher::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACpQTFRFAAAA0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLU0dLUmYS1qAAAAA10Uk5TABAgQFBggI+fv8/f74aeqbgAAABVSURBVAjXnY0xDsMwEMNo+5TYPen/3+2SpUCXlhvBgfAH2uecrcdWt5O4ewHIdVtTvmXB9BoRoIzy5DqsDIAszvXRlyfItS3kXRZA9StJ0l1f/z/xBlXVAtkqW+Q3AAAAAElFTkSuQmCC"); | |
} | |
/* Announcement */ | |
.globalMessage::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAK5JREFUGNN1kF0RxCAMhCMBCZVQCUhAwkmohDhAAhKQEAPfDBKQUAm5B3qU3s3t425mfyJygUTBMIzMLivYaTiGoigNpxJu8aSxLeeRTiOICIGOXfQLH8aT5eD8GKE4cTo4UTDKND1uWYRGFpzXkrnKGROc9JDnKBQTjDyJjbr0b+RnteO3WpgbhYSN/QQabX1L/HqLzyA2DKdTUCodx/E7dHgoFaOgJMo4kP8gEt+mlap7ZbvCVgAAAABJRU5ErkJggg=="); | |
} | |
/* Slideout nav */ | |
#boardNavDesktopFoot::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFAAAAzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMdShx9gAAAA50Uk5TABAgMEBggI+fr7/P3+82uMT1AAAAb0lEQVQIHQXBiQHDIAwEMOE8gGlu/3ErgWt297wAYyenT7IGjJNVqJUzsPMAniyuLODedspMQe3cKq8+8HxZOK0b9eUb6NYHO98Dv/am3FkDKq/Ktm9gp1h5AE8mxsku1M4ZMGby618yB6De7n4L/v79BDw2df22AAAAAElFTkSuQmCC"); | |
} | |
/* 4sight */ | |
body > a[style="cursor: pointer; float: right;"]::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADBQTFRFAAAAzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMzczMIXMlggAAAA90Uk5TABAgMEBQYICPn6+/z9/vD6iGsgAAAFRJREFUCB0FwYcBwyAMADAZCCN2y//fRgIAAG0MAMSba/8eAO9EVAe0BOMAxgISMDaIBKgGewKMesS+C0A7mXPdCgCQtwIAou6/A0DUPQCgnw4A4APNOQHMJOa9jgAAAABJRU5ErkJggg=="); | |
} | |
/* Expand */ | |
#imgControls label:first-of-type::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAALlJREFUKFOFkT0KwlAQhANWaUSb+AciVmLlSZLcVhttBH9QjyIIHiDOF2ZhOxeGebM7b3dfUnRdVwmN0Aq1UAqFGU2eekWSQ8RTh6HNMDqiwczNiJsnkR8Jx1RrSTKKDlE467wR9jY+XK9jN0bSKQwfGy/iqVcrMWespd82fsW7XP/XmZUmuXPsfHLHq3grHKzveef8NXjozKPH4mjAvf5rZPNLGtPAjI7ozUtfiD+1kp4LPLZxDV78APzYoty/jZXwAAAAAElFTkSuQmCC"); | |
} | |
' | |
else | |
css += ' | |
.prettyprint { | |
background-color: #e7e7e7; | |
border: 1px solid #dcdcdc; | |
} | |
span.com { | |
color: #d00; | |
} | |
span.str, | |
span.atv { | |
color: #7fa61b; | |
} | |
span.pun { | |
color: #61663a; | |
} | |
span.tag { | |
color: #117743; | |
} | |
span.kwd { | |
color: #5a6F9e; | |
} | |
span.typ, | |
span.atn { | |
color: #9474bd; | |
} | |
span.lit { | |
color: #368c72; | |
} | |
/* 4chan X options */ | |
#navtopright .settingsWindowLink::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUGNOFkVERwCAMQyMBKZOAhEng69lCChImYRKQkH0AG7fdjfaHpmmbFmkyMoWstxGIEgljTJKIhCd59IQp9+voBHaMOUmdnqgYs41qcxLYKZhCJFCxbrZJvUfz2LCm1liappoiYUxu8AiHVw2cPIXf6sXsl/L6Vb7c++9qi5v//dgFAGGyWuspVmQAAAAASUVORK5CYII="); | |
} | |
/* Delete buttons */ | |
.deleteform::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAftJREFUOE991M0rRFEYx/E7jYkRTWymycZmZCUrWYgUZTFrkub1T1Cy8LJXkrKWlWRrJZsZW+upSTZkJxbSmIzmXt/feK6O8aI+nnPOc+7vnHnB8zp+isVipFQq9SCJcUxjBpMYRj97op3Pfc1peujGKJtXUcEjfAR4RRUH7JlFQod+C7SQPmoG52gg+EOL9WusYehbmN1EIVfQxr9C3PUH9m1jsFAoeB6/Ino5dpMwpM68jBsn+MnW7qi+HXbP80uIKagHq2gggEKOMYUV5jU8M97BBNaZ39pen3qBlIKSqFhDQWVMoQtxZLChzbq97d93Dn5hPKegcTw6QTeMdZO4PhFqDAl4+XxeQWM4Q8t5ZkvNafgITItaQwax8ONVCNI4QcPZr+cOFTTTsajGMzaQcIKizOftEB0WHqx6pKBJvDqNJ8Y7SMHL5XIRtL/JzONYRBVu2J6aw9YITygzn7AAhaQxj/Z7Rh2gv4s3O7xJXVajHwdoIcAd1pHEGE5Qw6JCsIBLZ796IwqKYhbXFuRTb7GPMzTsoSp110KatrdO3URv+71kkMAaHmyDbhYGaCy68Ztzk3fGp9BL//zj1QBD2M5ms/fwEfyjTk8hei+/viLtMBoRDGIJF3j5JajJWg2bSCOGn/+atGjNFHUOWzjEEfawjBH06mA34QPkk++/bAlEMAAAAABJRU5ErkJggg=="); | |
} | |
/* Return button */ | |
div.navLinks > a:first-of-type::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGNJREFUGNNjYKAaiC2IXY9LyiH2fuz/2P3YpBRi9wOl/mORjhWIbYBKgeB7oEIIbIgVAEnfR5JEhf2Yuu8DeQ2x/UBTgCYh7J6PajdEC6rL9+ORBgsGgO3DJY2kMAHkegbaAgCK4libswvDKwAAAABJRU5ErkJggg=="); | |
} | |
/* Watcher */ | |
#watcher::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAALhJREFUOE+tkl0RgzAQhJFQCUhBAhJ4yENnMIEDJFRKJVRCJSCh3HfsMQfTFzI87CS5n+zuJU0ppalFdSOE9zY/x3EwvAzvBM7D2d7ObMnO8BUWW38JnCPXxSXeLDYKesNsYI+CNuWIE/Oce1YBAZgfYjtIVBNKyENAfUvzhDcp4AIvOvtT3CVrFlMNMwQb8x/PTHb3lXwSD8mb55CnBBP9SGKeNvuYdn+YdvYnpmvvXPO/7/2eVxSsk6VHBDjH8sAAAAAASUVORK5CYII="); | |
} | |
/* Announcement */ | |
.globalMessage::before { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAK5JREFUGNN1kE0VxCAMhCMBCZVQCUhAwp6+cyXEARKQgIRIQAISKiF7oEvp7ts5zuTNT0QukCgYhpHZZQU7DcdQFKXhVMItnjS25TzSaQQRIdCxi37hw3iyHJwfIxQnTgcnCkaZpscti9DIgvNaMlc5Y4KTHvIchWKCkSexUZf+jfysdvxWC3OjkLCxn0CjrW+JX2/xGcSG4XQKSqXjOH6HDg+lYhSURBkH8h9E4htm9nkTaedRxgAAAABJRU5ErkJggg=="); | |
} | |
/* Slideout nav */ | |
#boardNavDesktopFoot::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEVQTFRFAAAAZ2dnzczMZ2dnzczMZ2dntLOzZ2dnq6qqZ2dnZ2dnZ2dnkI+PZ2dnjIyMqKenZ2dnZ2dnZ2dndXR0e3t7Z2dndHR03/W/BgAAABV0Uk5TABAQICAwQGBggI+fn6+vr7/P3+/vEpdk4gAAAHlJREFUCB0FwYEBgjAQBLDUCghY/aLn/qOagOWoqmMBtJHMmsnZoM2cHf3MbBjZAFtOlpzA+jbSHelwf/1WPbua8PjkiVmqcP/ke0OVmnjl+4Cr7OnW3/MGPbue4b0CI50zG2DLgTYzOvrIbNCO5KorORqg71W1d/gDBFEGZ/GMsaMAAAAASUVORK5CYII="); | |
} | |
/* 4sight */ | |
body > a[style="cursor: pointer; float: right;"]::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAH9JREFUGNONkFEZgCAMhIlABCIQgQhE8O2vQQMiEMEIi7AIRjACPqGC4ueetu/udrcZ869wBMI7ZFkREpmN+IRXlpOo+HGt3KZA6eFA6mYZ4dzlkNFbcWefW467XonGYMnU3qrFKwjCQqKi2PmD5JOARansw/0PQpkbeMpUfdUBLYs3tDb03tIAAAAASUVORK5CYII="); | |
} | |
/* Expand */ | |
#imgControls label:first-of-type::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMRJREFUKFNtks0OAUEQhOdM4iBx8XMSwb6En0dw89CciQMXvIIb9XW6pXfYpDKlu6rV9G4ppfSElbB2THTGA486GrQmfjteOnfJAKcWfbQ2IQp38WkyDMWvqY/WDEyJxkl8JoyFg4sfrjEDOflrpoUAU/CLD0CT72dB8lRiIp6niD+0NkS8lvBfJCYfPf9ZJ4v4RqovjXjh8cLUujSGWOuzyjzS71vq25a2qcB690JH6DrPL26DYSDkT2PpNeqNwFSApv8BTpBEE3rYF6oAAAAASUVORK5CYII="); | |
} | |
' | |
switch Conf['4chan Banner'] | |
when 'in sidebar' | |
logoOffset = 83 + sidebarOffsetH | |
css += ' | |
.boardBanner img { | |
position: fixed; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
top: 19px; | |
right: 2px; | |
} | |
' | |
when 'at top' | |
logoOffset = 0 | |
when 'hide' | |
logoOffset = 0 | |
css += ' | |
.boardBanner img { | |
display: none; | |
} | |
' | |
css += ' | |
#watcher::before { | |
top: ' + (20 + logoOffset) + 'px; | |
} | |
#watcher:hover { | |
top: ' + (34 + logoOffset) + 'px; | |
} | |
#watcher { | |
position: fixed; | |
top: ' + (34 + logoOffset) + 'px; | |
} | |
#boardNavDesktopFoot::after { | |
top: ' + (19 + logoOffset) + 'px; | |
} | |
#boardNavDesktopFoot:hover { | |
top: ' + (34 + logoOffset) + 'px; | |
} | |
#navtopright .settingsWindowLink::after { | |
top: ' + (19 + logoOffset) + 'px; | |
} | |
#settingsBox { | |
top: ' + (25 + logoOffset) + 'px; | |
} | |
body > a[style="cursor: pointer; float: right;"]::after { | |
top: ' + (19 + logoOffset) + 'px; | |
} | |
.globalMessage::before { | |
top: ' + (19 + logoOffset) + 'px; | |
} | |
.globalMessage:hover { | |
top: ' + (34 + logoOffset) + 'px; | |
} | |
' | |
switch Conf['Board Logo'] | |
when 'in sidebar' | |
css += ' | |
.boardTitle { | |
position: fixed; | |
left: auto; | |
right: 2px; | |
top: ' + (45 + logoOffset) + 'px; | |
z-index: 1; | |
width: ' + (248 + sidebarOffsetW) + 'px; | |
} | |
.boardSubtitle { | |
display: none; | |
} | |
' | |
when 'hide' | |
css += ' | |
.boardTitle, | |
.boardSubtitle { | |
display: none; | |
} | |
' | |
switch Conf['Post Form Style'] | |
when 'fixed' | |
css += ' | |
#qr { | |
right: 2px !important; | |
left: auto !important; | |
} | |
' | |
when 'slideout' | |
css += ' | |
#qr { | |
right: -' + (233 + sidebarOffsetW) + 'px !important; | |
left: auto !important; | |
' + agent + 'transition: right .3s ease-in-out 1s, left .3s ease-in-out 1s; | |
} | |
#qr:hover, | |
#qr.focus, | |
#qr.dump { | |
right: 2px !important; | |
left: auto !important; | |
' + agent + 'transition: right .3s linear, left .3s linear; | |
} | |
' | |
when 'tabbed slideout' | |
css += ' | |
#qr { | |
right: -' + (249 + sidebarOffsetW) + 'px !important; | |
left: auto !important; | |
' + agent + 'transition: right .3s ease-in-out 1s, left .3s ease-in-out 1s; | |
} | |
#qr:hover, | |
#qr.focus, | |
#qr.dump { | |
right: 2px !important; | |
left: auto !important; | |
' + agent + 'transition: right .3s linear, left .3s linear; | |
} | |
#qr::before { | |
' + agent + 'transform: rotate(-90deg); | |
' + agent + 'transform-origin: bottom right; | |
margin-left: -210px; | |
margin-right: 264px; | |
margin-bottom: -20px; | |
width: 210px; | |
display: inline-block; | |
font-size: 12px; | |
opacity: 0.8; | |
height: 18px; | |
text-align: center; | |
content: "Post Form"; | |
padding-top: 3px; | |
vertical-align: middle; | |
color: ' + theme["Text"] + '; | |
' + agent + 'transition: opacity .3s ease-in-out 1s; | |
} | |
#qr:hover::before, | |
#qr.focus::before, | |
#qr.dump::before { | |
opacity: 0; | |
' + agent + 'transition: opacity .3s linear; | |
} | |
' | |
when 'transparent fade' | |
css += ' | |
#qr { | |
right: 2px !important; | |
left: auto !important; | |
opacity: 0.2; | |
' + agent + 'transition: opacity .3s ease-in-out 1s; | |
} | |
#qr:hover, | |
#qr.focus, | |
#qr.dump { | |
opacity: 1; | |
' + agent + 'transition: opacity .3s linear; | |
} | |
' | |
if Conf['Fit Width Replies'] | |
css += ' | |
.summary { | |
clear: both; | |
padding-left: 20px; | |
display: block; | |
} | |
.replyContainer { | |
clear: both; | |
} | |
.sideArrows { | |
z-index: 1; | |
position: absolute; | |
right: 0px; | |
height: 10px; | |
} | |
div.postInfo { | |
margin: 1px 0 0; | |
position: relative; | |
width: 100%; | |
} | |
.sideArrows a, | |
.sideArrows span { | |
position: static; | |
width: 20px; | |
font-size: 9px; | |
height: 10px; | |
} | |
.sideArrows { | |
width: 20px; | |
padding-top: 1px; | |
} | |
div.reply .report_button, | |
.sideArrows, | |
div.reply .postInfo input, | |
div.reply .postInfo .rice, | |
div.reply .menu_button { | |
opacity: 0; | |
} | |
form .replyContainer:not(:hover) div.reply .report_button, | |
form .replyContainer:not(:hover) div.reply .menu_button, | |
form .replyContainer:not(:hover) .sideArrows, | |
form .replyContainer:not(:hover) .postInfo input, | |
.postInfo .rice { | |
' + agent + 'transition: opacity .3s ease-out 0s; | |
} | |
form .replyContainer:hover div.reply .report_button, | |
form .replyContainer:hover div.reply .menu_button, | |
form .replyContainer:hover .sideArrows, | |
.replyContainer:hover .postInfo input, | |
.replyContainer:hover .postInfo .rice { | |
opacity: 1; | |
' + agent + 'transition: opacity .3s ease-in 0s; | |
} | |
div.reply input:checked { | |
opacity: 1; | |
} | |
form .postContainer blockquote { | |
margin-left: 30px; | |
} | |
div.reply { | |
padding-top: 6px; | |
padding-left: 10px; | |
} | |
div.reply .postInfo input, | |
div.reply .postInfo .rice { | |
position: absolute; | |
top: -3px; | |
right: 5px; | |
} | |
div.reply .report_button, | |
div.reply .menu_button { | |
position: absolute; | |
right: 26px; | |
top: -1px; | |
font-size: 9px; | |
} | |
.sideArrows a { | |
position: absolute; | |
right: 40px; | |
top: 7px; | |
} | |
.sideArrows a { | |
font-size: 9px; | |
} | |
div.thread { | |
padding: 0; | |
position: relative; | |
} | |
div.post:not(#qp):not([hidden]) { | |
margin: 0; | |
width: 100%; | |
} | |
div.reply { | |
display: table; | |
clear: both; | |
} | |
div.sideArrows { | |
float: none; | |
} | |
.hide_thread_button { | |
position: relative; | |
z-index: 2; | |
margin-right: 10px; | |
margin-left: 5px; | |
font-size: 9px; | |
} | |
.opContainer input { | |
opacity: 1; | |
} | |
#options.reply { | |
display: inline-block; | |
} | |
' | |
else | |
css += ' | |
.sideArrows a { | |
font-size: 9px; | |
} | |
.sideArrows a { | |
position: static; | |
} | |
div.reply { | |
padding-right: 5px; | |
} | |
.sideArrows { | |
margin-right: 5px; | |
width: 20px; | |
float: left; | |
} | |
.sideArrows a { | |
width: 20px; | |
font-size: 12px; | |
} | |
.hide_thread_button { | |
position: relative; | |
z-index: 2; | |
margin-right: 5px; | |
} | |
div.reply { | |
padding-top: 5px; | |
padding-left: 2px; | |
display: table; | |
} | |
div.thread { | |
overflow: visible; | |
padding: 0; | |
position: relative; | |
} | |
div.post:not(#qp):not([hidden]) { | |
margin: 0; | |
} | |
.thread > div > .post { | |
overflow: visible; | |
} | |
.sideArrows span { | |
font-size: 9px; | |
} | |
.sideArrows { | |
width: 20px; | |
} | |
.sideArrows a { | |
right: 27px; | |
} | |
div.reply .report_button, | |
div.reply .menu_button { | |
right: 13px; | |
} | |
div.reply { | |
padding-top: 6px; | |
padding-left: 8px; | |
} | |
.sideArrows { | |
margin-right: 2px; | |
width: 20px; | |
} | |
form .postContainer blockquote { | |
margin-left: 30px; | |
} | |
' | |
switch Conf['Page Margin'] | |
when 'none' | |
pagemargin = '2px' | |
when 'small' | |
pagemargin = '50px' | |
when 'medium' | |
pagemargin = '150px' | |
when 'fully centered' | |
pagemargin = (248 + sidebarOffsetW) + 'px' | |
when 'large' | |
pagemargin = '350px' | |
if editMode and pagemargin < 250 | |
pagemargin = '300px' | |
if Conf['Sidebar'] != 'hide' | |
css += ' | |
body { | |
margin: 1px ' + (252 + sidebarOffsetW) + 'px 0 ' + pagemargin + '; | |
} | |
#boardNavDesktop, | |
.pages { | |
left: ' + pagemargin + '; | |
right: ' + (252 + sidebarOffsetW) + 'px; | |
} | |
' | |
else | |
css += ' | |
body { | |
margin: 1px ' + pagemargin + ' 0 ' + pagemargin + '; | |
} | |
#boardNavDesktop, | |
.pages { | |
left: ' + pagemargin + '; | |
right: ' + pagemargin + '; | |
} | |
' | |
if Conf['Compact Post Form Inputs'] | |
css += ' | |
#qr textarea.field { | |
height: 114px !important; | |
} | |
.textarea { | |
height: 115px; | |
} | |
#qr .field[name="name"], | |
#qr .field[name="email"], | |
#qr .field[name="sub"] { | |
width: ' + (75 + (sidebarOffsetW / 3)) + 'px !important; | |
margin-left: 1px !important; | |
} | |
' | |
else | |
css += ' | |
#qr .field[name="email"], | |
#qr .field[name="sub"] { | |
width: ' + (248 + sidebarOffsetW) + 'px !important; | |
} | |
#qr .field[name="name"] { | |
width: ' + (227 + sidebarOffsetW) + 'px !important; | |
margin-left: 1px !important; | |
} | |
#qr .field[name="email"], | |
#qr .field[name="sub"] { | |
margin-top: 1px; | |
} | |
' | |
if Conf['Expand Post Form Textarea'] | |
css += ' | |
#qr textarea { | |
display: block; | |
' + agent + 'transition: all 0.25s ease 0s, width .3s ease-in-out .3s; | |
float: right; | |
} | |
#qr textarea:focus { | |
width: 400px; | |
} | |
' | |
if Conf['Filtered Backlinks'] | |
css += ' | |
.filtered.backlink { | |
display: none; | |
} | |
' | |
if Conf['Rounded Edges'] | |
css += ' | |
.rice { | |
border-radius: 2px; | |
} | |
div.reply, | |
div.reply.highlight, | |
#content, | |
#options, | |
#watcher, | |
#qp, | |
td[style="border: 1px dashed;"], | |
div.reply > tr > div.reply, | |
.inline div.reply, | |
h2, | |
.deleteform, | |
#boardNavDesktopFoot, | |
.globalMessage { | |
border-radius: 3px; | |
} | |
#qr::before { | |
border-radius: 6px 6px 0 0; | |
} | |
.qphl { | |
' + agent + 'outline-radius: 3px; | |
} | |
' | |
if Conf['Slideout Watcher'] | |
css += ' | |
#watcher { | |
position: fixed; | |
top: -1000px !important; | |
right: 2px !important; | |
left: auto !important; | |
bottom: auto !important; | |
width: ' + (246 + sidebarOffsetW) + 'px !important; | |
padding-bottom: 4px; | |
} | |
#watcher:hover { | |
z-index: 99 !important; | |
top: ' + ( 34 + logoOffset) + 'px !important; | |
} | |
' | |
else | |
css += ' | |
#watcher::before { | |
display: none; | |
} | |
#watcher { | |
right: 2px !important; | |
left: auto !important; | |
width: ' + (246 + sidebarOffsetW) + 'px; | |
padding-bottom: 4px; | |
z-index: 96; | |
} | |
' | |
if Conf['Underline Links'] | |
css += ' | |
#credits a, | |
.abbr a, | |
.backlink:not(.filtered), | |
.chanlinkify, | |
.file a, | |
.pages, | |
.pages a, | |
.quotejs, | |
.quotelink:not(.filtered), | |
.quotelink:not(.filtered), | |
.useremail, | |
a.deadlink, | |
a[href*="//dis"], | |
a[href*=res], | |
div.post > blockquote .chanlinkify.YTLT-link.YTLT-text, | |
div.postContainer span.postNum > .replylink { | |
text-decoration: underline; | |
} | |
' | |
switch Conf['Slideout Navigation'] | |
when 'compact' | |
css += ' | |
#boardNavDesktopFoot { | |
height: 84px; | |
padding-bottom: 0px; | |
padding-top: 0px; | |
word-spacing: 3px; | |
} | |
#navbotright { | |
display: none; | |
} | |
' | |
when 'list' | |
css += ' | |
#boardNavDesktopFoot a { | |
z-index: 1; | |
display: block; | |
} | |
#boardNavDesktopFoot { | |
height: 300px; | |
overflow-y: scroll; | |
padding-bottom: 0px; | |
padding-top: 0px; | |
word-spacing: 0px; | |
} | |
#boardNavDesktopFoot a::after{ | |
content: " - " attr(title); | |
font-size: 12px; | |
} | |
#boardNavDesktopFoot a[href*="//boards.4chan.org/"]::after, | |
#boardNavDesktopFoot a[href*="//rs.4chan.org/"]::after { | |
content: "/ - " attr(title); | |
font-size: 12px; | |
} | |
#boardNavDesktopFoot a[href*="//boards.4chan.org/"]::before, | |
#boardNavDesktopFoot a[href*="//rs.4chan.org/"]::before { | |
content: "/"; | |
font-size: 12px; | |
} | |
#navbotright { | |
display: none; | |
} | |
' | |
when 'hide' | |
css += ' | |
#boardNavDesktopFoot::after, | |
#boardNavDesktopFoot { | |
display: none; | |
} | |
' | |
switch Conf['Reply Spacing'] | |
when 'none' | |
css += ' | |
.replyContainer { | |
margin-bottom: 0px; | |
} | |
#delform { | |
margin-bottom: 12px; | |
} | |
' | |
when 'small' | |
css += ' | |
.replyContainer { | |
margin-bottom: 2px; | |
} | |
#delform { | |
margin-bottom: 10px; | |
} | |
' | |
when 'medium' | |
css += ' | |
.replyContainer { | |
margin-bottom: 4px; | |
} | |
#delform { | |
margin-bottom: 8px; | |
} | |
' | |
when 'large' | |
css += ' | |
.replyContainer { | |
margin-bottom: 6px; | |
} | |
#delform { | |
margin-bottom: 6px; | |
} | |
' | |
switch Conf['Sage Highlighting'] | |
when 'text' | |
css += ' | |
a.useremail[href*="sage"]:last-of-type::after, | |
a.useremail[href*="Sage"]:last-of-type::after, | |
a.useremail[href*="SAGE"]:last-of-type::after { | |
content: " (sage) "; | |
color: ' + theme["Sage"] + '; | |
} | |
' | |
when 'image' | |
css += ' | |
a.useremail[href*="sage"]:last-of-type::after, | |
a.useremail[href*="Sage"]:last-of-type::after, | |
a.useremail[href*="SAGE"]:last-of-type::after { | |
content: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAABa1BMVEUAAACqrKiCgYIAAAAAAAAAAACHmX5pgl5NUEx/hnx4hXRSUVMiIyKwrbFzn19SbkZ1d3OvtqtpaWhcX1ooMyRsd2aWkZddkEV8vWGcpZl+kHd7jHNdYFuRmI4bHRthaV5WhUFsfGZReUBFZjdJazpGVUBnamYfHB9TeUMzSSpHgS1cY1k1NDUyOC8yWiFywVBoh1lDSEAZHBpucW0ICQgUHhBjfFhCRUA+QTtEQUUBAQFyo1praWspKigWFRZHU0F6j3E9Oz5VWFN0j2hncWONk4sAAABASDxJWkJKTUgAAAAvNC0fJR0DAwMAAAA9QzoWGhQAAAA8YytvrFOJsnlqyT9oqExqtkdrsExpsUsqQx9rpVJDbzBBbi5utk9jiFRuk11iqUR64k5Wf0JIZTpadk5om1BkyjmF1GRNY0FheFdXpjVXhz86XSp2yFJwslR3w1NbxitbtDWW5nNnilhFXTtYqDRwp1dSijiJ7H99AAAAUnRSTlMAJTgNGQml71ypu3cPEN/RDh8HBbOwQN7wVg4CAQZ28vs9EDluXjo58Ge8xwMy0P3+rV8cT73sawEdTv63NAa3rQwo4cUdAl3hWQSWvS8qqYsjEDiCzAAAAIVJREFUeNpFx7GKAQAYAOD/A7GbZVAWZTBZFGQw6LyCF/MIkiTdcOmWSzYbJVE2u1KX0J1v+8QDv/EkyS0yXF/NgeEILiHfyc74mICTQltqYXBeAWU9HGxU09YqqEvAElGjyZYjPyLqitjzHSEiGkrsfMWr0VLe+oy/djGP//YwfbeP8bN3Or0bkqEVblAAAAAASUVORK5CYII=") " "; | |
} | |
' | |
switch Conf['Announcements'] | |
when '4chan default' | |
css += ' | |
.globalMessage { | |
position: static; | |
background: none; | |
border: none; | |
margin-top: 0px; | |
} | |
.globalMessage::before { | |
display: none; | |
} | |
.globalMessage:hover { | |
top: 0px; | |
} | |
' | |
when 'slideout' | |
css += ' | |
.globalMessage:hover { | |
position: fixed; | |
z-index: 99; | |
} | |
.globalMessage { | |
width: ' + (236 + sidebarOffsetW) + 'px; | |
background: ' + theme["Dialog Background"] + '; | |
border: 1px solid ' + theme["Dialog Border"] + '; | |
} | |
' | |
when 'hide' | |
css += ' | |
.globalMessage { | |
display: none; | |
} | |
.globalMessage::before { | |
display: none; | |
} | |
' | |
switch Conf['Boards Navigation'] | |
when 'sticky top' | |
css += ' | |
#boardNavDesktop { | |
position: fixed; | |
top: 0; | |
} | |
' | |
when 'sticky bottom' | |
css += ' | |
#boardNavDesktop { | |
position: fixed; | |
bottom: 0; | |
} | |
' | |
when 'top' | |
css += ' | |
#boardNavDesktop { | |
position: absolute; | |
top: 0; | |
} | |
' | |
when 'hide' | |
css += ' | |
#boardNavDesktop { | |
position: absolute; | |
top: -100px; | |
} | |
' | |
switch Conf['Pagination'] | |
when 'sticky top' | |
css += ' | |
.pages { | |
position: fixed; | |
top: 0; | |
z-index: 101; | |
} | |
' | |
when 'sticky bottom' | |
css += ' | |
.pages { | |
position: fixed; | |
bottom: 0; | |
z-index: 101; | |
} | |
' | |
when 'top' | |
css += ' | |
.pages { | |
position: absolute; | |
top: 0; | |
} | |
' | |
when 'on side' | |
css += ' | |
.pages { | |
padding: 0; | |
visibility: hidden; | |
top: auto; | |
bottom: 175px; | |
width: 290px; | |
left: auto; | |
right: ' + (251 + sidebarOffsetW) + 'px; | |
position: fixed; | |
' + agent + 'transform: rotate(90deg); | |
' + agent + 'transform-origin: bottom right; | |
letter-spacing: -1px; | |
word-spacing: -6px; | |
z-index: 6; | |
margin: 0; | |
height: 15px; | |
} | |
.pages a, | |
.pages strong { | |
visibility: visible; | |
min-width: 0; | |
} | |
' | |
when 'hide' | |
css += ' | |
.pages { | |
display: none; | |
} | |
' | |
switch Conf["Checkboxes"] | |
when "show" | |
css += ' | |
.rice { | |
display: none; | |
} | |
' | |
when "make checkboxes circular" | |
css += ' | |
input[type=checkbox] { | |
display: none; | |
} | |
.rice { | |
border-radius: 6px; | |
} | |
' | |
when "do not style checkboxes" | |
css += ' | |
.rice { | |
display: none; | |
} | |
' | |
when "hide" | |
css += ' | |
input[type=checkbox] { | |
display: none; | |
} | |
.rice { | |
display: none; | |
} | |
' | |
if Conf["Mascots"] | |
css += MascotTools.init() | |
if Conf["Block Ads"] | |
css += ' | |
/* AdBlock Minus */ | |
a[href*="jlist"], | |
img[src^="//static.4chan.org/support/"] { | |
display: none; | |
} | |
' | |
unless Conf['Emoji'] == 'disable' | |
if Conf['Emoji Position'] == 'hide' | |
$.set 'Emoji', 'disable' | |
Conf['Emoji'] = 'disable' | |
if Conf['Emoji Position'] == 'left' or Conf['Emoji Position'] == 'hide' | |
Conf['Emoji Position'] == 'before' | |
$.set 'Emoji Position', 'before' | |
if Conf['Emoji Position'] == 'right' | |
Conf['Emoji Position'] == 'after' | |
$.set 'Emoji Position', 'after' | |
css += Style.emoji Conf['Emoji Position'] | |
return css | |
Main = | |
init: -> | |
Main.flatten null, Config | |
# Load values from localStorage. | |
for key, val of Conf | |
Conf[key] = $.get key, val | |
userThemes = $.get "userThemes", Themes | |
userMascots = $.get "userMascots", Mascots | |
for name, mascot of userMascots | |
enabledmascots[name] = $.get name, -> | |
if mascot.category == 'SFW' then true else false | |
path = location.pathname | |
pathname = path[1..].split '/' | |
[g.BOARD, temp] = pathname | |
if temp is 'res' | |
g.REPLY = true | |
g.THREAD_ID = pathname[2] | |
switch location.hostname | |
when 'sys.4chan.org' | |
if /report/.test location.search | |
$.ready -> | |
form = $ 'form' | |
field = $.id 'recaptcha_response_field' | |
$.on field, 'keydown', (e) -> | |
window.location = 'javascript:Recaptcha.reload()' if e.keyCode is 8 and not e.target.value | |
$.on form, 'submit', (e) -> | |
e.preventDefault() | |
response = field.value.trim() | |
field.value = "#{response} #{response}" unless /\s/.test response | |
form.submit() | |
return | |
when 'images.4chan.org' | |
$.ready -> | |
if /^4chan - 404/.test(d.title) and Conf['404 Redirect'] | |
path = location.pathname.split '/' | |
url = Redirect.image path[1], path[3] | |
location.href = url if url | |
return | |
Main.pruneHidden() | |
#major features | |
Style.init() | |
now = Date.now() | |
if Conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 18*$.HOUR | |
$.ready -> | |
$.on window, 'message', Main.message | |
$.set 'lastUpdate', now | |
$.add d.head, $.el 'script', | |
src: 'https://github.com/zixaphir/appchan-x/raw/master/latest.js' | |
if Conf['Disable inline 4chan addon'] or Conf['Style'] | |
localStorage.setItem '4chan-settings', '{"disableAll":true}' | |
if Conf['Filter'] | |
Filter.init() | |
if Conf['Reply Hiding'] | |
ReplyHiding.init() | |
if Conf['Filter'] or Conf['Reply Hiding'] | |
StrikethroughQuotes.init() | |
if Conf['Anonymize'] | |
Anonymize.init() | |
if Conf['Time Formatting'] | |
Time.init() | |
if Conf['File Info Formatting'] | |
FileInfo.init() | |
if Conf['Sauce'] | |
Sauce.init() | |
if Conf['Reveal Spoilers'] | |
RevealSpoilers.init() | |
if Conf['Image Auto-Gif'] | |
AutoGif.init() | |
if Conf['Png Thumbnail Fix'] | |
PngFix.init() | |
if Conf['Image Hover'] | |
ImageHover.init() | |
if Conf['Menu'] | |
Menu.init() | |
if Conf['Report Link'] | |
ReportLink.init() | |
if Conf['Delete Link'] | |
DeleteLink.init() | |
if Conf['Filter'] | |
Filter.menuInit() | |
if Conf['Download Link'] | |
DownloadLink.init() | |
if Conf['Archive Link'] | |
ArchiveLink.init() | |
if Conf['Resurrect Quotes'] | |
Quotify.init() | |
if Conf['Quote Inline'] | |
QuoteInline.init() | |
if Conf['Quote Preview'] | |
QuotePreview.init() | |
if Conf['Quote Backlinks'] | |
QuoteBacklink.init() | |
if Conf['Indicate OP quote'] | |
QuoteOP.init() | |
if Conf['Indicate Cross-thread Quotes'] | |
QuoteCT.init() | |
$.ready Main.ready | |
ready: -> | |
if /^4chan - 404/.test d.title | |
if Conf['404 Redirect'] and /^\d+$/.test g.THREAD_ID | |
location.href = Redirect.thread g.BOARD, g.THREAD_ID, location.hash | |
return | |
unless $.id 'navtopright' | |
return | |
$.addClass d.body, $.engine | |
$.addClass d.body, 'fourchan_x' | |
for nav in ['boardNavDesktop', 'boardNavDesktopFoot'] | |
if a = $ "a[href$='/#{g.BOARD}/']", $.id nav | |
# Gotta make it work in temporary boards. | |
$.addClass a, 'current' | |
now = Date.now() | |
Favicon.init() | |
Options.init() | |
# Major features. | |
if Conf['Quick Reply'] | |
QR.init() | |
if Conf['Image Expansion'] | |
ImageExpand.init() | |
if Conf['Thread Watcher'] | |
Watcher.init() | |
if Conf['Keybinds'] | |
Keybinds.init() | |
if g.REPLY | |
if Conf['Prefetch'] | |
Prefetch.init() | |
if Conf['Thread Updater'] | |
Updater.init() | |
if Conf['Thread Stats'] | |
ThreadStats.init() | |
if Conf['Reply Navigation'] | |
Nav.init() | |
if Conf['Post in Title'] | |
TitlePost.init() | |
if Conf['Unread Count'] or Conf['Unread Favicon'] | |
Unread.init() | |
if Conf['Quote Threading'] | |
QuoteThreading.init() | |
else #not reply | |
if Conf['Thread Hiding'] | |
ThreadHiding.init() | |
if Conf['Thread Expansion'] | |
ExpandThread.init() | |
if Conf['Comment Expansion'] | |
ExpandComment.init() | |
if Conf['Index Navigation'] | |
Nav.init() | |
board = $ '.board' | |
nodes = [] | |
for node in $$ '.postContainer', board | |
nodes.push Main.preParse node | |
Main.node nodes, true | |
Main.prettify = Main._prettify | |
Main.observe() | |
observe: -> | |
board = $ '.board' | |
if MutationObserver = window.WebKitMutationObserver or window.MozMutationObserver or window.OMutationObserver or window.MutationObserver | |
Main.observer2 = observer = new MutationObserver Main.observer | |
observer.observe board, | |
childList: true | |
subtree: true | |
else | |
$.on board, 'DOMNodeInserted', Main.listener | |
disconnect: -> | |
if Main.observer2 | |
Main.observer2.disconnect() | |
else | |
board = $ '.board' | |
$.off board, 'DOMNodeInserted', Main.listener | |
pruneHidden: -> | |
now = Date.now() | |
g.hiddenReplies = $.get "hiddenReplies/#{g.BOARD}/", {} | |
if $.get('lastChecked', 0) < now - 1*$.DAY | |
$.set 'lastChecked', now | |
cutoff = now - 7*$.DAY | |
hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {} | |
for id, timestamp of hiddenThreads | |
if timestamp < cutoff | |
delete hiddenThreads[id] | |
for id, timestamp of g.hiddenReplies | |
if timestamp < cutoff | |
delete g.hiddenReplies[id] | |
$.set "hiddenThreads/#{g.BOARD}/", hiddenThreads | |
$.set "hiddenReplies/#{g.BOARD}/", g.hiddenReplies | |
flatten: (parent, obj) -> | |
if obj instanceof Array | |
Conf[parent] = obj[0] | |
else if typeof obj is 'object' | |
for key, val of obj | |
Main.flatten key, val | |
else # string or number | |
Conf[parent] = obj | |
return | |
message: (e) -> | |
{version} = e.data | |
if version and version isnt Main.version and confirm 'An updated version of appchan X is available, would you like to install it now?' | |
window.location = "https://raw.github.com/zixaphir/appchan-x/#{version}/appchan_x.user.js" | |
preParse: (node) -> | |
parentClass = node.parentNode.className | |
el = $ '.post', node | |
post = | |
root: node | |
el: el | |
class: el.className | |
ID: el.id.match(/\d+$/)[0] | |
threadID: g.THREAD_ID or $.x('ancestor::div[parent::div[@class="board"]]', node).id.match(/\d+$/)[0] | |
isArchived: /\barchivedPost\b/.test parentClass | |
isInlined: /\binline\b/.test parentClass | |
isCrosspost: /\bcrosspost\b/.test parentClass | |
blockquote: el.lastElementChild | |
quotes: el.getElementsByClassName 'quotelink' | |
backlinks: el.getElementsByClassName 'backlink' | |
fileInfo: false | |
img: false | |
if img = $ 'img[data-md5]', el | |
# Make sure to not add deleted images, | |
# those do not have a data-md5 attribute. | |
post.fileInfo = img.parentNode.previousElementSibling | |
post.img = img | |
Main.prettify post.blockquote | |
post | |
node: (nodes, notify) -> | |
for callback in Main.callbacks | |
try | |
callback node for node in nodes | |
catch err | |
alert "AppChan X has experienced an error. You can help by sending this snippet to:\nhttps://github.com/zixaphir/appchan-x/issues\n\n#{Main.version}\n#{window.location}\n#{navigator.userAgent}\n\n#{err.stack}" if notify | |
return | |
observer: (mutations) -> | |
nodes = [] | |
for mutation in mutations | |
for addedNode in mutation.addedNodes | |
if /\bpostContainer\b/.test(addedNode.className) and addedNode.parentNode.className isnt 'threadContainer' | |
nodes.push Main.preParse addedNode | |
Main.node nodes if nodes.length | |
listener: (e) -> | |
{target} = e | |
if /\bpostContainer\b/.test(target.className) and target.parentNode.className isnt 'threadContainer' | |
Main.node [Main.preParse target] | |
prettify: -> return | |
_prettify: (bq) -> | |
switch g.BOARD | |
when 'g' | |
code = -> | |
for pre in document.getElementById('_id_').getElementsByClassName 'prettyprint' | |
pre.innerHTML = prettyPrintOne pre.innerHTML.replace /\s/g, ' ' | |
return | |
when 'sci' | |
code = -> | |
jsMath.Process document.getElementById '_id_' | |
return | |
else | |
return | |
$.globalEval "#{code}".replace '_id_', bq.id | |
namespace: 'appchan_x.' | |
version: '0.8beta' | |
callbacks: [] | |
Main.init() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment