· 10 years ago · Aug 24, 2016, 05:06 PM
1// ==UserScript==
2// @name eSix Extend
3// @description Adds several functions to e621 (and similar sites).
4// @namespace com.xch3l.userscripts
5// @version 160221-1
6// @include *://e621.net/*
7// @include *://e926.net/*
8// @include *://twentypercentcooler.net/*
9// @include *://gallery.agn.ph/*
10// @exclude *://e621.net/resque/*
11// @exclude *.json
12// @exclude *.xml
13// @downloadURL h\ttps://googledrive.com/host/0BxuaxlGgDz8TTHV6dW9ESUNqdkk/files/eSix_Extend.user.js
14// @grant none
15// @icon https://www.dropbox.com/s/lzbbltnsax1i8op/e621logo.svg?raw=1
16// ==/UserScript==
17
18// Created: 03/09/12 11:23Pm
19
20/**
21 * License
22 * That one license that allows...
23 * ...redristribution of this file as long at this notice, the original metadata
24 * and original author name are not removed.
25 * ...modifications to the code, as long as the changes are notified to the original
26 * author so they can be added to the main code (if not harmful, have a real
27 * purpose or fix a bug that the author overlooked)
28 *
29 * This file is released as-is, meaning that the original, and maybe subsequent authors,
30 * are not responsible of any damage, should it happen etc, etc, etc. You probably know
31 * that already anyway. Bug reports are encouraged, though.
32 */
33
34try{
35window.eSixExtend = {
36 // Updated at each stable release
37 versionName: "3.023",
38 versionCode: 160221,
39 versionBuild: "February 9, 2016 4:52 Pm (UTC-6)",
40 changeling:"https://e621.net/forum/show/182918", // Hah, get it? "Changeling" sounds like "Change link"
41 toString: function(){return "eSixExtend V" + this.versionName + " (" + this.versionCode + ") last file update on " + this.versionBuild + ". ©2012-2015 Xch3l";}
42};
43
44(function(window, document) {
45 if(eSixExtend.isInstalled == true) return; // Prevent multiple instances
46
47 var ctype = document.contentType.split("/");
48 if(["image","application"].indexOf(ctype[0]) != -1 || ["javascript","css"].indexOf(ctype[1]) != -1) return;
49
50 var sitename = "[?]", // Name to display on the top of the page
51 cookies = parseCookies(), // GET ALL THE COOKIES!
52 pageQuery = parseQuery(), // Get the current page's query string
53 loc = window.location.pathname.split("/"), // Split the path part of the URL for easier lookup
54 defset = { // Default settings. Also used to check save versions.
55 // General settings
56 "saveVersion":160110, "ffbtnsLayout":"m1x16", "checkInterval":30, "collapseAboveLines":25, "freezeAfterDays":30, "tinyPrefix":"ta-", "tinyAlias":[],
57
58 // Hotkey preferences
59 "keyLayout":"en", "keyFavePost":70, "keyUpvote":71, "keyDownvote":68, "keyEdit":69, "poolNextKey":78, "poolPrevKey":86, "poolStopKey":66,
60
61 // Checkbox settings
62 "fixedUserBar":true, "singleline":false, "showfaves":true, "allfaves":false, "admindtext":true, "showdmails":true, "hiliteur":true, "hilitebc":true, "hiliteBlPosts":true, "unixLE":false, "submitOnCtrlEnter":false, "titleIcons":true, "allPostsInCommonTags":false, "confirmTagging":false, "allowQuickTagging":false, "enhanceNoticeError":true, "justHTTP":false, "checkOnPosts":true, "expandSauceMenu":false, "collapseLongComments":true, "docking":false, "image_effects":false, "uselog":false, "floatinglog":false,"freezePools":true, "BlackholeBlacklistedPosts":false,
63 "decorateUserRanks":true, "phoneHome":false
64 },
65 settings = {}, // Settings
66 userslist = null, // List of all users recently PM'd
67 ubLinks = [], // User custom links
68 oldTitle = document.title, postID, preview_baseurl,
69 ssym = ["\u25E2","\u25E3","\u25E4","\u25E5"] // The animated indicator glyphs
70 ;
71
72 function storageGet(name, defval) {
73 var retval = localStorage.getItem(name);
74 if(retval == null) return defval;
75
76 switch(typeof defval) {
77 case "object":
78 return JSON.parse(retval);
79
80 case "boolean":
81 return (retval === "true");
82
83 case "number":
84 if(defval.toString().indexOf(".") == -1)
85 return parseInt(retval);
86 else
87 return parseFloat(retval);
88
89 default:
90 return retval;
91 }
92 }
93
94 function storageSet(name, newval) {
95 if(newval == null) {
96 localStorage.removeItem(name);
97 return;
98 }
99
100 if(typeof newval === "object")
101 localStorage.setItem(name, asJSON(newval));
102 else
103 localStorage.setItem(name, newval);
104 }
105
106 // Determine Browser's CSS prefix
107 function cssPrefix(name, p) {
108 var uc = p || /[A-Z]/.test(name);
109
110 switch(navigator.userAgent.match(/Gecko|WebKit/)) {
111 case "Gecko": return (uc ? "Moz" + name : "-moz-" + name);
112 case "WebKit": return (uc ? "WebKit" + name : "-webkit-" + name);
113 }
114
115 return name;
116 }
117
118 // DNP Database
119 var dnp = {
120 "list":/\b(adalfyre|adiago|agalamt|airguitar|airsitar|aja_williams|aku_tojyo|akuva|allynbyrd|ammako|andrew_w\._dickman|angel27|aniutqa|aquatheohiokitty|atlasfield|audiovideomeow|awdplace|backlash91|bad-collie|baka_sukonku|bazaarbobby|besonik|bicdente|binky|blickfen|brian_harp|buizelmaniac|buizilla|cassius_\(artist\)|cbh|cheezyweapon|cigarscigarettes|clcoon|cobalta|commissionsbyj|coyote-solitarius|creams|cudacore|daniel_harris|darkrockman|darkrokkuman|demonwerewolf110|dipper|dobescrusher|doffa|dogbone|dog-bone|dogboneartwork|dogsoul|donro|doppelgaenger|dopplegangsta|drkraest|ebonyleopard|ebonypikachu|edensky|egophiliac|elton_portilho|eltonpot|ensayne|erithacuscreations|evil_art_nazi|fallenarts|fattato|fellarts|ferniio|fishbones|fishbones\.|fisk|fluffball|fortuna|foxxian|fragment-six|frogsbreath|fyuvix|gardelius|greykitty|hahul|halcyon|hear|helical|ilgrigio|inanna-nakano|inert-ren|itoril|ixerin|j-a-l-m-u|jace|jagzcat|jaleo|jalmu|jameless|jay_naylor|jayfiregrowlithe|jennadelle|jeremy_bernal|jimmuarts|john_martello|jollyjack|jooshster|jooshter|just-a-little-mixed-up|ka|kaemantis|kahei_yattsu|kahmari|kaji|kamicheetah|kefkafloyd|keihound|kelvinshadewing|kerothestrange|kii|kii-kitsune|kiirei|killveous|kim_koskamp|kipcha|kraest|kriscrash|kululu-xiao|kylontario|ladygreer|ladyshinwa|lawlzy|leatherruffian|leefuu|lemoncore|leoian|leonhusky|lilhoneypup|littlemiu|liz_day|liz_stanford|loveletterstoamonster|lukiber|lumaberry|lunacatta|luvythicus|madhattermonster|mattaku_shinzu|mattartist25|max-dragon|melissa_steben|melo666|mewyfox_|mickeyila|midi|mikachu_tuhonen|mikachutuhonen|mithril|mithril07|moodyferret|mottenfest|mrawl|msrah|mynosylexia|mysticsabreonic|myu_in_hiding|myuinhiding|naexus|namiin|nazuu-m0nster|necrophile|nekomata_neko|neogeen|nightweaver|nimrais|noben|northernscruff|ohmuu|okamiterasu|ollieosa|olven|omegaltd|omnoproxyl337|ookamithewolf1|oze|oze-jp|paddercat|paolo|pbrown|peony|peyo|phillip_m_jackson|pitkin|ponygryphonrelations|poonani|princess_rei|purplekecleon|r3drunner|rairai|randy_entinger|razzek|red-dog|red_dog|redadillio|reizakirga|remmer|reptilecynrik|rikutida|romzy|roum|rrhexis|rubyrebirth|saebira|salty_tart|sammy_stowes|sammystowes|sanae|sanji|scappo|sciggles|sefeiren|sekhmet|sephygoth|sesameseedbun|sgtrubbersoul|shadowsani|sharue|shawntae_howard|sheepdust|shocktress|sidian|silitha|silverbobcat|sixthleafclover|sonicdash|soulofadog|spacepoptart|spookeedoo|squeedgemonster|stretchycubs|strider_orion|strider-orion|strype|sugarpoultry|swaetshirt|swaetshrit|sweatshirt_\(artist\)|sweetpinkpetals|sy|sy-|sy~|taihab|tailsrulz|tamen|tani_da_real|tanidareal|tanyafoxy|tatious|tato|teekay|thaily|thecapedmanlloyd|thekitty|thelinox|thelynox|themadcatter|theponyplaypen|theramjing|thirdpotato|thornwolf|tigr3ss|timeless_error|timelesserror|tinintri|tktktk|todex|tojyo|tora-shiromaru|tremaine|tremaine_h_fox|unicornspirit|unpeeledwasp|urban-coyote|uzai|van_weasel|versiris|viral_divinity|viraldivinity|vixendra|woofmonster|xainy|xiraco|yasminachan|yedg|z\.o\.e|zeara|zerwolf|zevtibull|zody|zoe|zoey03)\b/gi,
121 "commissioned_work":/\b(jameless|lemoncore|omiart)\b/gi,
122 "commercial_work":/\b(jollyjack|phillip_m_jackson|sefeiren)\b/gi,
123 "conditional_dnp":[
124 {name:/\b(backlash91)\b/gi, special:"Only official edits allowed"},
125 {name:/\b(bad-collie)\b/gi, rating:"e"},
126 {name:/\b(jimmuarts)\b/gi, special:"Only collaborations by this artist are allowed"},
127 {name:/\b(mithril07|mithril)\b/gi, rating:"e"},
128 {name:/\b(moodyferret)\b/gi, rating:"eqs", special:"Hardcore, weird or fetishy posts."},
129 {name:/\b(mrawl)\b/gi, rating:"eq"},
130 {name:/\b(oze|oze-jp|z\.o\.e|zoe)\b/gi, special:"only their Twitter posts are DNP."},
131 {name:/\b(tanidareal|tani_da_real)\b/gi, rating:"e"},
132 {name:/\b(zoey03)\b/gi, special:"Only commissioners or artist are allowed to upload."},
133
134 // Suggestion
135 {name:/\b(smitty_g|smittyg)\b/gi, tags:/my_little_pony/gi, special:"Smittyg's MLP artwork should be uploaded under 'omiart' instead"}
136 ],
137 "site_specific":[
138 {name:/\b(itoril)\b/gi, allowedSources:["FurAffinity"]},
139 {name:/\b(jay_naylor|fisk)\b/gi, allowedSources:["FurAffinity"]},
140 {name:/\b(scappo)\b/gi, allowedSources:["FurAffinity", "Weasyl", "DeviantArt", "tumblr"]}
141 ],
142 "version":[380, "02/11/2016 05:13"]
143 };
144
145 function masterpanel(event) {
146 event.preventDefault();
147 var dlg = createFloatingPanel(null, null, "devpanel", "Dev control panel", {position:"fixed", minWidth:"25%", minHeight:"50%"});
148 if(dlg.isEmpty()) {
149 dlg.addCloseButton();
150 dlg.className += " contained";
151
152 createElement("h5", {innerHTML:"Dialogs"}, dlg);
153 createElement("span", {className:"option", innerHTML:"Welcome dialog", onclick:function(){showWelcome(1);}}, dlg);
154 createElement("span", {className:"option", innerHTML:"Update successful", onclick:function(){showWelcome(2);}}, dlg);
155
156 createElement("h5", {innerHTML:"Status"}, dlg);
157 var lsk = ["LastFavesCount","showCommonTags","lastSubsUpdatesCount","subscriptionsNextCheck","eeHiddenNotice"];
158 for(var i = 0; i < lsk.length; i++) {
159 var key = lsk[i];
160 createElement("div", {
161 style:{backgroundColor:(i % 2 ? "" : "rgba(0,0,0,0.1)")},
162 innerHTML:"<strong>" + key + "</strong><span style='float:right'>" + (key == "subscriptionsNextCheck" ? new Date(storageGet(key, 0)).toTimeString() : storageGet(key , "")) + "</span>"
163 }, dlg);
164 }
165 }
166 }
167
168 // Parse settings
169 var lsSettings = storageGet("settings", "");
170 if(lsSettings != "") {
171 if(lsSettings.charAt(0) == "(")
172 lsSettings = lsSettings.substr(1, lsSettings.length - 2);
173 lsSettings = lsSettings.replace(/,?"[A-Za-z]+"\:undefined/g, "");
174
175 settings = JSON.parse(lsSettings.replace(/"[A-Za-z]+"\:undefined,?/g, ""));
176 }
177
178 function getSetting(name) {
179 if(settings && settings[name] != undefined)
180 return settings[name];
181 else
182 if(defset[name] != undefined)
183 return defset[name];
184 else {
185 logIt("err_settingsError getSetting", "Settings error", 'Setting "' + name + '" does not exist');
186 return null;
187 }
188 }
189
190 // Set custom links (used from version 2.7 onwards)
191 if(storageGet("ubLinks") == null)
192 storageSet("ubLinks", '[{"title":"Replies to your comments", "url":"/comment/search?query=[userNM]", "check":true},{"title":"Comments on your posts", "url":"/comment?poster_id=[userID]", "check":false},{"title":"Upload", "url":"/post/upload"},{"title":"DNP List", "url":"/wiki/show?title=avoid_posting"},{"title":"eSix Extend home thread", "url":"/forum/show/66904", "check":true}]');
193
194 if(storageGet("subscriptionsNextCheck") == null)
195 storageSet("subscriptionsNextCheck", rightNow() + (getSetting("checkInterval")*60000));
196
197 // TinyAliases
198 var TinyAlias = getSetting("tinyAlias");
199 TinyAlias.find = function(name) {
200 for(var i = 0; i < this.length; i++) if(name == this[i].name) return this[i];
201 return null;
202 };
203
204 // Find an item inside an array
205 Array.prototype.findItem = function(what) {
206 if(this.length == 0) return -1;
207 var whatIs = (typeof what);
208
209 for(var index = 0, element; index < this.length; index++) {
210 element = this[index];
211 if(whatIs == "function") {
212 if(what(element) == true) return index;
213 } else
214 if(element == what) {
215 return index;
216 }
217 }
218
219 return -1;
220 };
221
222 /* integ: Quick integrity check for Base64 encoded data.
223 * This isn't elaborate at all, so use it for simple cases where you need to check if the incoming data is ok.
224 * cal: Calculates the sum of each character code and returns it. Shared by "enc" and "dec"
225 * enc: Encodes to Base64 the result of "cal" along with the value passed and returns it.
226 * dec: Decodes from Base64 the string provided and returns the original data. Returns null if no string is provided or when the check fails */
227 var integ = {
228 cal: function(a) {var b=0; for(var c=0;c<a.length;c++) b += a.charCodeAt(c); return b;},
229 enc: function(a) {if(!a) a=""; return btoa(this.cal(a)+"|"+a);},
230 dec: function(a) {if(a){a=atob(a).split("|");if(a[0]==this.cal(a[1])) return a[1]; else return null;} else return null;}
231 };
232
233 /* qrle: Quick RLE (Run-Length Encoding) compressor and decompressor.
234 * to: Takes an input string (preferably Base64'd), compresses it using RLE and returns the result.
235 * from: Takes an input string previously used on 'qrle.to', decompresses it and returns the expanded result */
236 var qrle = {
237 to:function(s){var r="",n=1;for(var i=1;i<s.length+1;i++){var c=s[i-1];if(c==s[i])n++;else{r+=c;if(n==2)r+=c;else{while(n>128){r+=String.fromCharCode(258-n);n-=128;}if(n>1)r+=String.fromCharCode(258-n);}n=1;}}return r;},
238 from:function(s){var r="",p=0,c,k,n;while(p<s.length){k=s.charCodeAt(p);if(k<128)r+=(c=s[p]);else for(n=0;n<(257-k);n++)r+=c;p++;}return r;}
239 };
240
241 // Donate message generator
242 var donatebtn = function() {
243 return ["<form id='ddlgform' onSubmit='notice(\"Thank you! \u2665\")' style='display:inline' action='https://www.paypal.com/cgi-bin/webscr' method='post' target='_blank'>",
244 "<input type='hidden' name='cmd' value='_s-xclick'>","<input type='hidden' name='hosted_button_id' value='3XY5BDGCUHXZJ'>",
245 "<img border='0' src='https://www.paypalobjects.com/es_XC/i/scr/pixel.gif' width='1' height='1'>",
246 "<input type='submit' class='navikey' style='color:gold;min-width:72px' value='Donate' />",
247 "</form>"].join("");
248 };
249
250 // Write LOC information
251 if(getSetting("uselog")) {
252 var locdiv = createElement("div", {innerHTML:"Loc: "}, document.body);
253 for(var i = 1; i < loc.length; i++) createElement("span", {className:"loctag", innerHTML:loc[i], title:"loc[" + i + "]"}, locdiv);
254 createElement("p", {innerHTML:new Date()}, locdiv);
255 }
256
257 // Check where are we and set the title according to it //
258 switch(window.location.hostname) {
259 case "e621.net": sitename="e621"; preview_baseurl = "https://static1.e621.net/data/preview/"; break; // We're on e621
260 case "e926.net": sitename="e926"; preview_baseurl = "http://static1.e926.net/data/preview/"; break; // or on e926? (SFW Version of e621)
261 case "twentypercentcooler.net": sitename="20% Cooler"; preview_baseurl = "/data/preview/"; break; // Ponies. What else did you thought?
262 case "gallery.agn.ph": sitename="AGNPH Gallery"; preview_baseurl = "/data/preview/"; break; // Poképorn!
263 }
264
265 // Add a listener to the theme selector for when the theme changes
266 var userCss = findElement("user_css"), themeRexp = /skin-hexagon-clean|hexagon|skin-pony|skin-bloodlust|skin-serpent|skin-hotdog|poke|pony/;
267 var lmss = document.querySelectorAll("link.main-stylesheet");
268 if(lmss.length == 0) lmss = document.querySelectorAll("link[rel=stylesheet]");
269 var linkedCSS = lmss[lmss.length-1];
270 document.body.className = linkedCSS.href.match(themeRexp);
271 if(userCss) userCss.addEventListener("change", function() {
272 document.body.className = document.body.className.replace(themeRexp, this.value.split(",")[1] || "hexagon");
273 }, false);
274
275 // Write a CSS for this UserScript //
276 var css = createElement("style", {type:"text/css", id:"eSixExtend_CSS", innerHTML:[
277 // Common Extend's CSS
278 ".link {cursor:pointer;text-decoration:none;} .blacklink {cursor:pointer;text-decoration:none;opacity:0.75}",
279 ".homelink {font-size:2em;font-weight:bold;}",
280 ".userStatus {display:block; vertical-align:middle; position:" + (getSetting("fixedUserBar") ? "fixed" : "absolute") + "; right:0px; top:0px; padding:4px 4px 0px 4px; border-radius:0px 0px 0px 6px; z-index:10}",
281 ".notifTag {margin-left:4px; padding:4px;} .userTag {font-weight:bold;}",
282 "[disabled]{opacity:0.5; color:#000}",
283 ".hotlink {margin:0px; font-weight:bold; font-variant:small-caps;}",
284 ".formattingButtons input {min-width:64px; margin-right:1ex; text-align:center;}",
285 ".formattingButtons input.fbAlt {background-image:linear-gradient(-45deg,#AAA 0%,#AAA 9%,#FFF 11%,#FFF 100%);}",
286 ".floatingPanel {position:fixed; border-radius:4px; padding:4px; font-size:80%; z-index:100; max-width:50%; max-height:50%; overflow-y:auto; opacity:1; transition:opacity 250ms ease-out 0ms;}",
287 ".floatingPanel .panelTitle {font-size:3ex; position:relative; left:-4px; top:-4px; width:100%; box-shadow:0px 0px 6px #000 !important; padding:2px 4px; overflow-x:hidden; text-overflow:ellipsis; white-space:nowrap; background-color:rgba(0,0,0,0.1);}",
288 ".floatingPanel .panelTitle:first-child * {display:none;}",
289 ".floatingPanel .floatingContent.contained {overflow:auto; position:absolute; left:4px; top:4px; right:4px; bottom:4px;}",
290 ".floatingPanel .panelTitleHandle + .floatingContent.contained {top:5ex;}",
291 ".floatingPanel .centerpadded {padding:10px; text-align:center;}",
292 ".floatingPanel .option, .floatingPanel .close {cursor:pointer; padding:2px 4px;}",
293 ".floatingPanel .option {display:block; font-weight:bold;}",
294 ".floatingPanel a.option {font-size:1.4em}",
295 ".floatingPanel .ixsysbtn {position:absolute; right:4px; top:0px; z-index:10;}",
296 ".floatingPanel .close {text-align:center; display:inline-block; min-width:1em;}",
297 ".floatingPanel .close:hover, .floatingPanel .option:hover {font-weight:bold; text-shadow:0px 0px 3px currentColor; background-color:rgba(0,0,0,0.2)}",
298 ".floatingPanel .close:active, .floatingPanel .option:active {font-weight:bold; text-shadow:0px 0px 3px #000;}",
299 ".floatingPanel .removeUserPMd {position:absolute; right:4px; top:1px}",
300 ".floatingPanel.minimized {width:10% !important; min-width:10% !important; max-width:10% !important; height:2.5ex !important; min-height:2.5ex !important; max-height:2.5ex !important; display:inline-block !important; margin-right:1ex; position:relative !important; left:auto !important; top:auto !important; bottom:0px !important; border-radius:auto auto 0px 0px !important; overflow:hidden;}",
301 ".floatingPanel.minimized:hover {box-shadow:0px 0px 3px #FFF inset;}",
302 ".floatingPanel.minimized h3.panelTitle {display:block; box-shadow:none !important; font-size:2ex; padding:0.5ex 1ex;}",
303 ".floatingPanel .panelTitleHandle {display:block !important;height:4.5ex;left:0px;position:absolute;right:0px;top:0px;}",
304 ".floatingPanel.minimized h3.panelTitle * {display:inline;}",
305 ".floatingPanel.minimized * {display:none;}",
306 ".floatingPanel.nomin .minimize {display:none}",
307 "fieldset {border:1px solid currentColor; border-radius:6px;}",
308 "fieldset legend {padding:0em 1ex; border-left:1px solid currentColor; border-right:1px solid currentColor; font-variant:small-caps; font-size:1.2em; font-weight:bold;}",
309 ".colorTile {border-radius:2px; box-shadow:1px 2px 3px #000000 inset; display:inline-block; height:14px; margin:1px; width:14px;}",
310 ".colorTile:hover {border-radius:4px; margin:0px; padding:1px; position:relative; top:0px;}",
311 "#subscriptionsPanel a.pref.subsUpdate {margin:1px 1em;display:block;color:#FFF}",
312 "#subscriptionsPanel a.pref.subsUpdated {color:gold;}",
313 ".small {font-size:75%;}",
314 ".navikey {font-weight:bold;border:1px solid currentColor;border-radius:3px;display:inline-block;min-width:28px;text-align:center;background-color:rgba(255,255,255,0.1);box-shadow:1px 1px 2px currentColor inset;}",
315 ".fbtnsHandle {border:0px dotted currentColor; position:absolute; bottom:0px; top:0px; margin:3px; width:6px; opacity:0.75} .fbtnsHandle:hover {opacity:1}",
316 ".fbtnsHandle.handle {cursor:move; border-left-width:3px; left:0px;} .fbtnsHandle.docker {cursor:e-resize; border-right-width:3px; right:0px;}",
317 ".formattingButtons[class*='1x16'] + .fbtnsHandle.docker {display:none;}",
318 ".dndtile input {display:block; text-align:center; margin:0px auto;}",
319 (getSetting("uselog") ? ".loctag {display:inline-block; background-color:#FFF; color:#000; border:1px solid #EEE; border-radius:4px; padding:2px; margin-right:4px;}" : ""),
320 "div.ixuiFrame {" + cssPrefix("transition") + ":all 250ms linear 0ms; background-color:#FFF; opacity:0.5; position:fixed; z-index:19920119;}",
321 "#devpanel.floatingContent h5 {border-bottom:1px solid;}",
322 ".placeholder {background-image: linear-gradient(-45deg, transparent 49%, currentColor 50%, currentColor 51%, transparent 52%, transparent 100%), linear-gradient(45deg, transparent 49%, currentColor 50%, currentColor 51%, transparent 52%, transparent 100%); background-size:10px 10px; border:1px solid currentColor; opacity:0.5;}",
323
324 // Script settings' CSS
325 "ul#settingsPanes {list-style:none; margin-bottom:0px; font-weight:bold; font-size:1.5ex; background:none;}",
326 "ul#settingsPanes li {display:inline-block; cursor:pointer; padding:1ex; margin:0px 1em 0px 0px; border-radius:4px 4px 0px 0px; box-shadow:none;position:relative; z-index:1;}",
327 "ul#settingsPanes li.inactive {opacity:0.75; padding-bottom:0px; top:1ex;}",
328 ".settingsPane {padding:1em;} .settingsPane fieldset {margin-bottom:1em;} .settingsPane fieldset > legend {cursor:pointer;}",
329 ".settingsPane#eSixExtendSettingsPane .newoption-label {" + (getSetting("saveVersion") < defset.saveVersion ? "margin-left:1em;" : "display:none;") + "}",
330 "#eSixExtendSettingsPane input[type=checkbox], #eSixExtendSettingsPane input[type=radio] {margin-right:1ex}",
331 "#eSixExtendSettingsPane .eeImExField {display:block; width:80%; margin-bottom:1em; white-space:nowrap;}",
332 "ol#lstLinks {border:1px solid currentColor;padding-bottom:2px;margin:2px 0px;padding-left:40px;overflow-x:hidden;overflow-y:auto;height:350px;position:relative}",
333 "ol#lstLinks:empty:after {content:'[Empty]';font-size:30px;font-weight:bold;text-align:center;display:block;margin-top:160px;opacity:0.5;}",
334 "ol#lstLinks li:nth-child(10) {border-bottom:1px dashed currentColor;}",
335 "ol#lstLinks.workspace.holding, ol#lstLinks li.ubLink .handle {cursor:ns-resize;}",
336 "li.ubLink {margin-left:0px;position:relative} .ubCnt * {display:inline-block; margin-right:1ex;} .ubCnt :nth-child(1), .ubCnt :nth-child(2) {width:40%;}",
337 "li.ubLink.floating {position:absolute;}",
338 "li.ubLink .handle {position:absolute;left:-20px;top:0px;width:40px;bottom:0px;}",
339 ".ubLink:first-child .ubCnt input:nth-child(3), .ubLink:last-child .ubCnt input:nth-child(4) {visibility:hidden}",
340 ".keyListTitle {padding-left:8px;}",
341 ".pref {padding:2px 6px; border-radius:4px; margin-bottom:1px; background-color:rgba(0,0,0,0.1)}",
342 ".pref:hover {background-color:rgba(255, 255, 255, 0.2);}",
343 ".pref label {display:inline-block;position:relative;top:-3px;}",
344 ".pref .errPref {float:right;margin-right:1em;text-align:right}",
345 "div#fpnkKeyList {height:25em;overflow-y:scroll;border:1px solid currentColor;}",
346 "div.nklKeyItem * {display:inline-block; width:10%; margin-right:1ex;text-align:center;}",
347 "div.nklKeyItem :last-child {width:70%;text-align:left;}",
348 ".exampleKey {cursor:default; display:inline-block; text-align:left; padding:4px; margin:4px; border:1px solid currentColor; border-radius:4px; background-color:rgba(255,255,255,0.1); box-shadow:1px 1px 2px currentColor inset; width:6ex; height:6ex;}",
349 ".exampleKey * {display:inline-block; opacity:0.5; width:2.5ex; height:3ex; text-align:center;}",
350 ".exampleKey span:first-child {display:block;} .exampleKey strong {opacity:1;}",
351
352 // New settings page styles
353 "div.cbitem {cursor:default; padding:4px 0px; overflow-y:hidden;border-bottom:1px dotted rgba(255,255,255,0.5);opacity:1;height:5.4ex; " + cssPrefix("transition") + ":all 250ms ease 0ms;}",
354 "div.cbitem:hover span.checkbox {border-color:rgba(0,0,0,0.2); box-shadow:0px 0px 6px #FFF inset; color:currentColor;}",
355 "div.cbitem:active span.checkbox {box-shadow:0px 0px 6px #000 inset;}",
356 "div.cbitem div {display:inline-block; vertical-align:middle;}",
357 "div.cbitem span.checkbox {height:12px; width:12px; text-align:center; background-color:rgba(255, 255, 255, 0.5); border:2px solid rgba(0,0,0,0.5); border-radius:4px; color:#070; margin:1ex; padding:4px; vertical-align:middle; display:inline-block;}",
358 "div.cbitem span.checkbox span.checkmark {display:none; font-size:150%; line-height:12px; width:12px; height:12px;}",
359 "div.cbitem span.checkbox.checked span.checkmark {display:block;}",
360 "div.cbitem.hasChild {background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0.25) 1%, transparent 1%);}",
361 "div.cbitem.hidden {height:0ex;margin:0px;padding:0px;opacity:0}",
362 "div.cbitem:last-child {border-bottom:none;}",
363
364 // Post's thumbnail status
365 "img.preview.flagged + span.post-score:after {color:transparent; content:'X'; width:16px; height:16px; position:absolute; bottom:0px; z-index:2; background-size:100%; background-image:url('" +
366 qrle.from("data:image/png;base64,iVBORw0KGgoAþNSUhEUgAÿBAýQBAMAÿDt3eJSAþFVBMVEUAûDlAAD/ICD/QECZmZnm5uY8K147AýXRSTlMAQObYZgAþhhY1RMAþCAýC5PYvRAþGmZjVEwAùEAþBAõPAGQBAIckpi8Aþ0SURBVHjaY2CAAUZBASACMdIEhZ0VgUzGVCFlZyUlohmKgkJgBkgrWLsA1GQ8DEEBBhQAAIq+CuCQrdBUAþGmZjVEwAþBAþEAþBAõPAGQBAWtQfG0Aþ4ZmRBVAýJ42mNggAFGQQEGQUEQI01Q0FkJyGNMFVJ2VlIiniEoCGaATAFrF4CajIchKMCAAgCKiArghXIh8gAÿBpmY1RMAýwAÿBAýQAöDwBkAQGGxq+EAþOWZkQVQAþEeNpjYIABRkEBMGZgTBMUFFZSBPJShZSdlZSIZzgKCoEYgiCTQAwBqMl4GIICDCgAAIPICuD8sXZjAþGmZjVEwAþFAþEAþBAõPAGQBAWsM3f4Aþ6ZmRBVAýZ42mNggAFGQUEGBhBmTBMSFFRSEmBgTBVSdiaeIajsLCgIYgiATAEzoCbjYQgKMKAÿIQQCuAY5uvUAþGmZjVEwAþHAþEAþBAõPAGQBAYaaDhcAþ7ZmRBVAýh42mNggAFGQUEBIAHEaUKKgkJKQEaqkLKzEtEMQWFnRZD2VAGQKQwgBtRkPAxBAQYUAACIkArgC5sx3QAÿBpmY1RMAþCQAÿBAýQAöDwBkAQFr6T9LAþPmZkQVQAþKeNpjYIABRkFBQQYGQQEGxjQhZUFBJSAjVUjZWYlohqCgsxJIe6oAA8gkMANiMh6GoAADCgAAiMYK4Nk/VEcAþaZmNUTAýsAþQAþEAõ8AZAEBhn/sogAÿDxmZEFUAþDHjaY2CAAUZBQUEBBkYgShNSdhQUAjJShZSdlZSIZQgKCispghhADDQJzICYjIchKMCAAgCPhgrgXrNmuQAÿBpmY1RMAþDQAÿBAýQAöDwBkAQFrtZ7YAþPWZkQVQAþOeNpjYIABRkEBQUEGBgEGxjRBZWdBQSAjVUjZWUmJaIagIIQBNAVkEmOqANRkPAxBAQYUAACPPgrgVxJP6wAþBJRU5ErkJgÿ==") + "');}",
367
368 // eSix's CSS fixes/tweaks
369 "div.response-list {padding-bottom:0.25em !important;}",
370 "div.response-list > div.comment {border-top:1px solid rgba(255, 255, 255, 0.25); margin-top:8px !important; margin-bottom:8px !important; padding:6px !important; display:block; word-wrap:break-word; position:relative;}",
371 "div.response-list > div.comment > div.author {text-align:right;}",
372 "div.response-list > div.comment > div.author > h6 {overflow: hidden;}",
373 "div.response-list > div.comment > div.content {margin-bottom:3ex;}",
374 "div.response-list > div.comment > div.content > div.footer, div.response-list > div.comment > div.content > span:last-child {position:absolute; bottom:4px;}",
375 "div.response-list > div:not([class]) {border-radius:4px; padding: 6px 6px 7px;}",
376 "div.response-list > div:not([class]):first-child {margin-top:3px;}",
377 ".forum-anchor:target + .comment {box-shadow:0px 0px 10px #FFF inset;}",
378 "div#preview, div#reply, div[id*='reply-'] {width:auto !important; float:none !important;}",
379 "span.thumb img.preview {border-bottom-left-radius:0px !important; border-bottom-right-radius:0px !important;}",
380 "#edit-box {background-color:#284A81 !important; border:none !important; border-radius:4px; box-shadow:2px 2px 5px #07162D; margin-bottom:10px; padding:10px;}",
381 "#edit-box input[type='submit'] {margin-right:1ex;}",
382 "div#comments form, div#comment-list form, form table {width:100%}",
383 "textarea {font-family:inherit; font-size:1em; width:100%; min-height:100px;}",
384 "div#preview, div[id*='comment-preview-'] {background-color:transparent !important; box-shadow:none !important; border:1px dashed white; position:relative; padding:3em 1em 1em !important;}",
385 "div#preview h5:first-child, div#preview h4:first-child, div[id*='comment-preview-'] h5:first-child {margin-bottom:1ex !important; border-bottom:1px dashed white; left:0px; padding:4px; position:absolute; top:0px; left:0px; right:0px;}",
386 "tr div.sourcelink-url-history, tr div.sourcelink-url-history:hover {overflow: visible !important; height:auto !important; background:none !important; border:none !important; box-shadow:none !important;}",
387 ".tooltip-thumb:hover .tooltip-thumb-inner {left:auto !important; bottom:auto !important;}",
388 ".thumbs-user {width:160px;} #recent-user {width:352px;} span.thumb a {box-shadow:0px 0px 4px #000;}",
389 "div#content {position:relative; " + cssPrefix("transition") + ":padding 250ms ease-out 0ms;} .removed-tags {opacity:0.5}",
390
391 // Site's CSS fixes by setting
392 (getSetting("enhanceNoticeError") ? "#notice, #warning, #error {display:block !important; font-weight:bold; position:fixed; top:-100%; left:0px; min-width:50%; z-index:1000; transition:top 250ms ease-out 0ms; color:#FFF} #notice {background-color:rgba(40,130,51,0.9) !important;} #warning {background-color:rgba(255,215,0,0.9) !important;} #error {background-color:rgba(130,40,40,0.9) !important;} #warning * {color:#000;} #notices.fixed ~ #content {margin-top:auto !important;}" : ""),
393 (getSetting("hiliteur") ? ".response-list > div.comment[data-author='" + fixUsername() + "'] {background-color:rgba(255,255,255,0.1)}" : ""),
394 (getSetting("hilitebc") ? ".response-list > div.comment.bad-comment {background-color:rgba(0,0,0,0.1) !important;}" : ""),
395 (getSetting("hiliteBlPosts") ? "span.thumb.blacklistedPost img {box-shadow:0px 0px 6px #F00 !important;}" : ""),
396 (getSetting("BlackholeBlacklistedPosts") ? "#blacklisted-sidebar, img.preview[src*='blacklisted-preview'] {display:none !important;}" : ""),
397 (getSetting("decorateUserRanks") ? "div.author h6 + span.level {display:inline-block; border-radius:4px; font-weight:bold; padding:2px 6px; margin:2px 0px; color:#FFF;}" : ""),
398
399
400
401 // Style declarations that are theme dependent //////////////////////////////////////////////////////////
402 // To guarantee colors being applied correctly, order is: Normal, :Visited, :active and :hover ////////
403 // Hexagon (Default): White over Blue
404 "body.hexagon .link {color:#B4C7D9 !important;} body.hexagon .link:visited {color:#B4C7D9 !important;} body.hexagon .link:active {color:#B4C7D9 !important;} body.hexagon .link:hover {color:#2E76B4 !important;}",
405 "body.hexagon .userTag {color:#FFD700 !important;} body.hexagon .userTag:hover {color:#FFFFFF !important;}",
406
407 // Hexagon clean: White over Blue (same as Hexagon but this must be defined too to match theme specific details)
408 "body.skin-hexagon-clean .link {color:#B4C7D9 !important;} body.skin-hexagon-clean .link:visited {color:#B4C7D9 !important;} body.skin-hexagon-clean .link:active {color:#B4C7D9 !important;} body.skin-hexagon-clean .link:hover {color:#2E76B4 !important;}",
409 "body.skin-hexagon-clean .userTag {color:#FFD700 !important;} body.skin-hexagon-clean .userTag:hover {color:#FFFFFF !important;}",
410
411 // Pony/20PC Theme: White over Purple
412 "body.skin-pony .link, body.pony .link {color:#E5D6FF !important;} body.skin-pony .link:visited, body.pony .link:visited {color:#E5D6FF !important;} body.skin-pony .link:active, body.pony .link:active {color:#E5D6FF !important;} body.skin-pony .link:hover, body.pony .link:hover {color:#2E76B4 !important;}",
413 "body.skin-pony .userTag, body.pony .userTag {color:#FFD700 !important;} body.skin-pony .userTag:hover, body.pony .userTag:hover {color:#FFFFFF !important;}",
414
415 // Bloodlust: Orange over Dark Gray
416 "body.skin-bloodlust .link {color:#FFF7CD !important;} body.skin-bloodlust .link:visited {color:#FFF7CD !important;} body.skin-bloodlust .link:active {color:#FFF7CD !important;} body.skin-bloodlust .link:hover {color:#F68B00 !important;}",
417 "body.skin-bloodlust .userTag {color:#F68B00 !important} body.skin-bloodlust .userTag:hover {color:#FFF !important;} body.skin-bloodlust .floatingPanel .panelTitle {background-color:rgba(255,255,255,0.1);}",
418
419 // Serpent: Black/Dark Green over Light Green
420 "body.skin-serpent .link {color:#050 !important;} body.skin-serpent .link:visited {color:#050 !important;} body.skin-serpent .link:active {color:#3A8F3A !important;} body.skin-serpent .link:hover {color:#3A8F3A !important;}",
421 "body.skin-serpent .userTag {color:#FFD700 !important;} body.skin-serpent .userTag:hover {color:#FFF !important;}",
422
423 // Hot dog: Black over Red/White/Yellow
424 "body.skin-hotdog .link {color:#000 !important;} body.skin-hotdog .link:visited {color:#000 !important;} body.skin-hotdog .link:active {color:#000 !important;} body.skin-hotdog .link:hover {color:#000 !important;}",
425 "body.skin-hotdog .userTag {color:#F00 !important} body.skin-hotdog .userTag:hover {color:#000 !important;}",
426
427 // AGNPH: Black over light blue
428 "body.poke .link {color:#006 !important;} body.poke .link:visited {color:#006 !important;} body.poke .link:active {color:#338 !important;} body.poke .link:hover {color:#338 !important;}",
429 "body.poke .userTag {color:#F00 !important} body.poke .userTag:hover {color:#000 !important;}"
430 ].join("\n")}, document.head);
431
432 // Locate the header, navigation and subnavigation bars
433 var header = findElement("header"), navbar = findElement("navbar"), subnav = findElement("subnav");
434
435 // Create the user bar
436 var xdiv = createElement("div", {style:{marginLeft:"1em"}});
437
438 // The title of the site we're on
439 if(header) createElement("a", {className:"homelink",href:"/",innerHTML:sitename}, xdiv);
440
441 // The user info space where we'll be putting... uh... the user info.
442 var userStatus = createElement("div", {className:"rounded userStatus"}, xdiv);
443
444 function showWelcome(mode) {
445 var dlgname, buildFP = function(title, content, ytxt, ntxt) {
446 if(getSetting("uselog") && mode == 0) {
447 createElement("a", {className:"rounded", style:{position:"fixed", right:"0px", top:"2em", zIndex:10, padding:"4px 8px", borderRadius:"8px 0px 0px 8px", fontWeight:"bold"}, href:"/user/edit#eSixExtendSettings", target:"_blank", innerHTML:title}, document.body);
448 } else {
449 if(mode == 1) dlgname = "dlgWelcomeUser";
450 if(mode == 2) dlgname = "dlgUpdate";
451
452 var dlg = createFloatingPanel(null, null, dlgname, title, {position:"fixed"});
453
454 if(dlg.isEmpty()) {
455 dlg.innerHTML = "<div class='centerpadded'>" + content + "<br/>" + [
456 (ytxt ? "<input type='button' value='" + ytxt + "' id='ee_okay' />" : ""),
457 (ntxt ? "<input type='button' value='" + ntxt + "' style='margin:0em 1em;' id='ee_cancel' />" : ""),
458 donatebtn()
459 ].join("") + "</div>";
460
461 window.setTimeout(function() {
462 var eok = findElement("ee_okay"), ecl = findElement("ee_cancel");
463 if(eok != null) eok.addEventListener("click", function() {
464 settings.saveVersion = defset.saveVersion;
465 storageSet("settings", settings);
466 window.location.assign("/user/edit#eSixExtendSettings");
467 }, true);
468 if(ecl != null) ecl.addEventListener("click", function() {
469 settings.saveVersion = defset.saveVersion;
470 storageSet("settings", settings);
471 dlg.close();
472 }, true);
473 }, 10);
474 }
475 }
476 };
477
478 var sv = getSetting("saveVersion");
479 if(!storageGet("settings") || mode == 1)
480 buildFP("Hi new user!", "<p>Thank you for downloading this UserScript, called \"eSix Extend\". I hope you find it useful!</p><h6>Would you like to check the settings page and set up?</h6>", "Yes", "No");
481 else
482 if(sv < defset.saveVersion || mode == 2) {
483 buildFP("Update successful!", "<h5>eSix Extend was updated to version <span class='greentext'>" + eSixExtend.versionName + "</span>!</h5>" +
484 "<h6>Check <a href='" + eSixExtend.changeling + "'>here</a> for the change log.</h6>", "", "OK");
485 }
486 }
487
488 function getUbLinkQuery(url) {
489 if(!url) return null;
490 url = url.replace(/\[userNM]/g, fixUsername()).replace(/\[userID]/g, storageGet("userID"));
491 var urlm = url.indexOf("?"), urlq = parseQuery(url.substr(urlm)), urll = url.substr(0, (urlm == -1 ? url.length : urlm)).split("/");
492
493 if(urll[1] == "comment" && urll[2] == "search" && ["user"].indexOf(urlq.query) == -1)
494 return urll.join("/") + ".json?query=" + urlq.query;
495
496 if(urll[1] == "post" && urll[2] == "index")
497 return "/" + urll[1] + "/" + urll[2] + ".json?limit=1&tags=" + (urlq.tags ? urlq.tags : urll[4].replace(/%20/g, "+"));
498
499 return null;
500 }
501
502 // Subscription updates
503 var userHasSubscriptions = storageGet("poolSubscriptions") != null || storageGet("forumSubscriptions") != null || false;
504 var subsUpdates = 0;
505
506 if(userHasSubscriptions)
507 subsUpdates = countSubsUpdates();
508
509 // These happen for every page ///////////////////////////////////////////////////////////////////////////////////////////
510 function thingsForEveryPage() {
511 // Add the forum post/comment/blip's id
512 var ss = document.querySelectorAll("div.comment > div.author > span.date");
513
514 for(var i = 0; i < ss.length; i++) {
515 var span = ss[i];
516 var ch = span.children[0] || {};
517 if(/\/(?:blip|comment|forum)\/show\/[0-9]+/.test(ch.href) && !span.classList.contains("eeid")) {
518 var href = ch.href.split("/"), commentID = createElement("span", {className:"level", innerHTML:"#" + href[href.length-1]});
519 span.parentNode.insertBefore(commentID, span.nextSibling);
520 span.parentNode.insertBefore(createElement("br"), span.nextSibling);
521 span.classList.add("eeid");
522 }
523 }
524
525 // Blackhole Blacklisted Posts
526 // When enabled, this will clean up any trace of blacklisted posts
527 if(getSetting("BlackholeBlacklistedPosts")) {
528 var blkimgs = document.querySelectorAll("img.preview[src*='blacklisted-preview']");
529 for(var i = 0; i < blkimgs.length; i++) blkimgs[i].parentNode.parentNode.style.display = "none";
530 }
531
532 // Decorate user ranks
533 if(getSetting("decorateUserRanks")) {
534 var ranks = document.querySelectorAll("div.author h6 + span.level");
535 for(var i = 0; i < ranks.length; i++) {
536 var rank = ranks[i], label = "level-" + rank.innerHTML.toLowerCase();
537
538 if(!rank.classList.contains(label))
539 rank.classList.add(label);
540 }
541 }
542
543 // Tweak all images to use HTTP instead of HTTPS
544 if(getSetting("justHTTP")) {
545 var img = document.querySelectorAll("img");
546 for(var i = 0; i < img.length; i++) {
547 img[i].src = img[i].src.replace(/https:\/\//, "http://");
548 }
549 }
550 }
551 thingsForEveryPage();
552
553 // Attach to Element.update
554 var oldElementUpdate = window.Element.update;
555 window.Element.update = function(element, content) {
556 oldElementUpdate(element, content); // Let the original function to run
557
558 // Rerun all comment related things
559 if(element.indexOf("response-list-for-") != -1)
560 thingsForEveryPage();
561
562 // Rerun the blacklist
563 Post.init_blacklisted();
564 };
565
566 // Remove "e621" and "My Account" items from the navigation bar, since we will be adding them
567 if(navbar) {
568 navbar.removeChild(navbar.childNodes[1]);
569 navbar.removeChild(navbar.childNodes[2]);
570 }
571
572 // User is logged in (because the username exists in a cookie)
573 if(cookies.login) {
574 // Check version, notify if this is a new one/welcome the user and let the him/her choose their option
575 if(!((loc[1] == "forum" && loc[3] == "66904") || (loc[1] == "user" && loc[2] == "edit")))
576 showWelcome(0);
577
578 // Get user ID and Level //
579 if(!(storageGet("userID") || storageGet("userLV", -1) == window.user_level || cookies.login == storageGet("userNM")) && window.user_level != 0)
580 updateUserData();
581
582 // User home
583 createElement("a", {href:"/user/home", title:"Home", className:"notifTag userTag", style:{padding:"4px 0px"}, innerHTML:"\u25B6"}, userStatus);
584
585 // Username/profile
586 createElement("a", {id:"userprofile", href:"/user/show/" + storageGet("userID", 1), title:"View Profile", className:"notifTag userTag", innerHTML:cookies.login, oncontextmenu:(getSetting("uselog") ? masterpanel : null)}, userStatus);
587
588 // Link to the message center
589 if(getSetting("showdmails")) {
590 query("/dmail/inbox.json", "GET", function(json, xml, text) {
591 var dmails = findElement("dmail_counter"); // The counter
592 var unread = []; // Unread PMs
593
594 // Count unread mails
595 for(var i = 0; i < json.length; i++) {
596 var pm = json[i];
597 if(pm.has_seen == false && pm.from_id != storageGet("userID")) unread.push(pm);
598 }
599
600 storageSet("LastUnreadPMsCount", unread.length);
601 dmails.innerHTML = "<b>M</b>" + (unread.length > 0 ? unread.length : "0");
602 dmails.title = (unread.length > 0 ? unread.length : "No") + " New Message" + (unread.length == 1 ? "" : "s");
603 }, "userpms");
604
605 // Total dmails
606 var unreadpms = storageGet("LastUnreadPMsCount", 0);
607 createElement("a", {id:"dmail_counter", href:"/dmail/inbox", className:"notifTag", innerHTML:"<b>M</b>" + unreadpms, title:(unreadpms > 0 ? unreadpms : "No") + " New Message" + (unreadpms == 1 ? "" : "s")}, userStatus);
608 } else
609 createElement("a", {href:"/dmail/inbox", className:"notifTag", innerHTML:"<b>M</b>\u25B6", title:"Message center"}, userStatus);
610
611 // Favorited posts
612 if(getSetting("showfaves")) {
613 createElement("a", {id:"faves_counter", href:"/post?tags=fav%3A" + fixUsername(), className:"notifTag", innerHTML:"<b>\u2665</b>" + storageGet("LastFavesCount", 0), title:storageGet("LastFavesCount") + " Posts Faved"}, userStatus);
614
615 // Get Faves count
616 query("/post/index.xml?tags=fav%3A" + fixUsername() + (getSetting("allfaves") ? "+status%3Aany" : "") + "&limit=1", "GET", function(json, xml, text) {
617 var cnt = xml.getElementsByTagName("posts")[0].getAttribute("count");
618
619 if(cnt != 10) {
620 var fav = findElement("faves_counter");
621 storageSet("LastFavesCount", cnt);
622 fav.innerHTML = "<b>\u2665</b>" + cnt;
623 fav.title = (cnt == 0 ? "No" : cnt) + " posts faved";
624 }
625 }, "userfaves");
626 } else
627 createElement("a", {href:"/post?tags=fav%3A" + fixUsername(), className:"notifTag", innerHTML:"<b>\u2665\u25B6</b>", title:"Faves list"}, userStatus);
628
629 // Subscription updates
630 if(userHasSubscriptions)
631 createElement("a", {id:"subs_updates", className:"notifTag link", innerHTML:"<b>S</b>" + subsUpdates, title:(subsUpdates == 0 ? "No" : subsUpdates) + " Subscription" + (subsUpdates == 1 ? "" : "s") + " Updated", onclick:showSubsPanel}, userStatus);
632
633 // Separator
634 userStatus.appendChild(document.createTextNode(" | "));
635
636 try {
637 ubLinks = storageGet("ubLinks", []);
638 var ubLinkHolder = createElement("span", {id:"ubLinkHolder"}, userStatus);
639
640 var checkLink = function(ele, ublink, url) {
641 if(ublink.last) ele.addEventListener("mouseover", function(event) {
642 ublink.current = ublink.last;
643 storageSet("ubLinks", ubLinks);
644 });
645
646 // Prevent checking for updates if link is not up to date already
647 if(ublink.skip == true || ublink.last != ublink.current) return;
648
649 var ublinkUpdater = function(j, x, t) {
650 if(j.length == 0) return;
651 ublink.last = j[0].id;
652 if(ublink.last != ublink.current) {
653 ele.style.fontStyle = "oblique";
654 storageSet("ubLinks", ubLinks);
655 }
656 };
657
658 var isu = getUbLinkQuery(url);
659 if(isu != null) query(isu, "GET", ublinkUpdater, "ublink updater " + url);
660 };
661
662 if(ubLinks.length != 0) {
663 var lnk, clTitle, clURL, hst = [], li, ln, ci = 0, links = ubLinks.length, ml = 10, ele;
664 for(li = 0; li < Math.min(links, ml); li++) {
665 lnk = ubLinks[li];
666 ln = lnk.title;
667 ci = 0;
668 clTitle = ln.charAt(0).toUpperCase() + ln.substr(1, ci).toLowerCase();
669
670 while(hst.indexOf(clTitle) != -1) {
671 ci++;
672 clTitle = ln.charAt(0).toUpperCase() + ln.substr(1, ci).toLowerCase();
673 }
674
675 hst.push(clTitle);
676 clURL = lnk.url.replace(/\[userNM]/g, fixUsername()).replace(/\[userID]/g, storageGet("userID", 0));
677 ele = createElement("a", {href:clURL, title:lnk.title, className:"notifTag hotlink", innerHTML:clTitle + "\u25B6"}, ubLinkHolder);
678 if(lnk.last != lnk.current) ele.style.fontStyle = "oblique";
679 checkLink(ele, lnk, clURL);
680 }
681
682 if(links > ml) {
683 createElement("a", {title:"More", className:"notifTag hotlink link", innerHTML:"\u2507", onclick:function() {
684 var fp = createFloatingPanel(userStatus, false, null, "", {position:(getSetting("fixedUserBar") ? "fixed" : "absolute"),top:(userStatus.offsetHeight + 2) + "px",left:"auto",right:"0px"});
685 for(li = ml; li < links; li++) {
686 lnk = ubLinks[li];
687 clURL = lnk.url.replace(/\[userNM]/g, fixUsername()).replace(/\[userID]/g, storageGet("userID", 0));
688 createElement("a", {className:"pref option", href:clURL, innerHTML:lnk.title}, fp);
689 }
690 }}, ubLinkHolder);
691 }
692
693 // Separator
694 userStatus.appendChild(document.createTextNode(" | "));
695 }
696 } catch(err) {
697 logIt("err_ublinks userStatus", "Parsing error: Custom links", err);
698 }
699
700 // Settings
701 var useredit = createElement("a", {href:"/user/edit", title:"Settings", className:"notifTag hotlink", innerHTML:"S\u25B6"}, userStatus);
702 useredit.addEventListener("contextmenu", function(event) {
703 event.preventDefault();
704 location.assign("/user/edit#eSixExtendSettings");
705 }, true);
706
707 // Logout
708 createElement("a", {href:"/user/logout", title:"Logout", className:"notifTag hotlink", innerHTML:"L\u25B6"}, userStatus);
709
710 } else { // User is not logged in or does not have an account already (what is it waiting for?) //
711
712 // Subscription updates
713 if(userHasSubscriptions) {
714 createElement("a", {id:"subs_updates", className:"notifTag link", innerHTML:"<b>S</b>" + subsUpdates, title:(subsUpdates == 0 ? "No" : subsUpdates) + " Subscription" + (subsUpdates == 1 ? "" : "s") + " Updated", onclick:showSubsPanel}, userStatus);
715
716 // Separator
717 userStatus.appendChild(document.createTextNode(" | "));
718 }
719
720 // Settings
721 createElement("a", {className:"notifTag hotlink link", innerHTML:"Settings"}, userStatus).onclick = function() {
722 var fp = createFloatingPanel(null, true, "E6Settings", "eSix Extend Settings", {minWidth:"90%", minHeight:"90%"});
723 writeSettingsPanel(fp);
724 var ps = findElement("eSixExtendSettingsPane").style;
725 ps.display = "block";
726 ps.fontSize = "120%";
727 fp.className += " contained";
728 fp.center();
729 };
730
731 // Separator
732 userStatus.appendChild(document.createTextNode(" | "));
733
734 // Login
735 createElement("a", {href:"/user/login", className:"notifTag hotlink", innerHTML:"Login"}, userStatus);
736
737 // Signup
738 createElement("a", {href:"/user/signup", className:"notifTag hotlink", innerHTML:"Signup"}, userStatus);
739 }
740
741 // Listen for changes on the storage
742 window.addEventListener("storage", function(event) {
743 if(!event) event = window.event;
744 var key = event.key, value = event.newValue;
745
746 switch(key) {
747 case "userID": {
748 findElement("userprofile").href = "/user/show/" + value;
749 break;
750 }
751
752 case "LastUnreadPMsCount": {
753 if(getSetting("showdmails")) {
754 value = parseInt(value, 10);
755 var dmails = findElement("dmail_counter");
756 dmails.innerHTML = "<b>M</b>" + (value > 0 ? value : "0");
757 dmails.title = (value > 0 ? value : "No") + " New Message" + (value == 1 ? "" : "s");
758 }
759 break;
760 }
761
762 case "LastFavesCount": {
763 if(getSetting("showfaves")) {
764 value = parseInt(value, 10);
765 var fav = findElement("faves_counter");
766 fav.innerHTML = "<b>\u2665</b>" + value;
767 fav.title = (value == 0 ? "No" : value) + " posts faved";
768 }
769 break;
770 }
771
772 case "poolSubscriptions":
773 case "forumSubscriptions": {
774 var subsUpdates = countSubsUpdates();
775 var supdates = findElement("subs_updates");
776 supdates.innerHTML = "<b>S</b>" + subsUpdates;
777 supdates.title = (subsUpdates == 0 ? "No" : subsUpdates) + " Subscription" + (subsUpdates == 1 ? "" : "s") + " Updated";
778 break;
779 }
780
781 case "ubLinks": {
782 ubLinks = storageGet("ubLinks", []);
783 var ulc = findElement("ubLinkHolder").children;
784 for(var i = 0; i < ubLinks.length; i++) {
785 var li = ubLinks[i];
786 for(var u = 0; u < ulc.length; u++) {
787 var ul = ulc[u], url = ul.href.toString().split(location.hostname)[1], clURL = li.url.replace(/\[userNM]/g, fixUsername()).replace(/\[userID]/g, storageGet("userID"));
788 if(url == clURL && li.skip && li.last && li.current) // Don't touch if the URL does not correspont to this item or is skipped
789 ul.style.fontStyle = (li.last == li.current ? "" : "oblique");
790 }
791 }
792 break;
793 }
794 }
795 });
796
797 // Insert the notification box before the header (it'll be at the top)
798 document.body.insertBefore(xdiv, document.body.firstChild);
799
800 function getPoolSubscriptions() {
801 var psubs = [];
802
803 try {
804 psubs = storageGet("poolSubscriptions", []);
805 } catch(ex) {
806 logIt("err_fsub getPoolSubscriptions", "Error parsing pool subscriptions", ex);
807 }
808
809 return psubs;
810 }
811
812 function getForumSubscriptions() {
813 var fsubs = [];
814
815 try {
816 fsubs = storageGet("forumSubscriptions", []);
817 } catch(ex) {
818 logIt("err_fsub getForumSubscriptions", "Error parsing forum subscriptions", ex);
819 }
820
821 return fsubs;
822 }
823
824 // Writes the settings control panel in [holder]
825 function writeSettingsPanel(holder) {
826 // Install 'VWorkspace' script
827 createElement("script", {innerHTML:atob("ZnVuY3Rpb24gVldvcmtzcGFjZShlKXt2YXIgdD17Y2hpbGRyZW46W10sZHJhZ0RhdGE6e2VsZW1lbnQ6bnVsbCxwbGFjZUhvbGRlcjpudWxsLG9mZnNldFk6MCxvcmlnaW5hbFk6MH19O2Uud29ya3NwYWNlPXQ7dmFyIG47bj0idW5kZWZpbmVkIj09dHlwZW9mIGRvY3VtZW50Lm9udG91Y2hzdGFydD9bIm1vdXNlZG93biIsIm1vdXNldXAiLCJtb3VzZW1vdmUiXTpbInRvdWNoc3RhcnQiLCJ0b3VjaGVuZCIsInRvdWNobW92ZSJdLGUuYWRkRXZlbnRMaXN0ZW5lcihuWzBdLFZXb3Jrc3BhY2Uub25Nb3VzZURvd24pLGUuYWRkRXZlbnRMaXN0ZW5lcihuWzFdLFZXb3Jrc3BhY2Uub25Nb3VzZVVwKSxlLmFkZEV2ZW50TGlzdGVuZXIoblsyXSxWV29ya3NwYWNlLm9uTW91c2VNb3ZlKX1WV29ya3NwYWNlLnNldHVwQWxsPWZ1bmN0aW9uKCl7Zm9yKHZhciBlPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoIi53b3Jrc3BhY2UiKSx0PTA7dDxlLmxlbmd0aDt0KyspVldvcmtzcGFjZShlW3RdKX0sVldvcmtzcGFjZS5vbk1vdXNlRG93bj1mdW5jdGlvbihlKXt2YXIgdD1lLnRhcmdldCxuPWUuY3VycmVudFRhcmdldCxyPW4ud29ya3NwYWNlLGE9ci5kcmFnRGF0YTtpZigtMSE9dC5jbGFzc05hbWUuaW5kZXhPZigiaGFuZGxlIikpe3ZhciBvPWUuY2xpZW50WXx8ZS50b3VjaGVzWzBdLmNsaWVudFk7ZS5wcmV2ZW50RGVmYXVsdCgpO3ZhciBsPVZXb3Jrc3BhY2UuZ2V0UmVjdCh0KTthLm9mZnNldFk9by1sLnk7ZG8gaWYodD10LnBhcmVudE5vZGUsdD09bilyZXR1cm47d2hpbGUoLTE9PXQuY2xhc3NOYW1lLmluZGV4T2YoIndvcmtzcGFjZV9lbGVtZW50IikpO3IuY2hpbGRyZW49W107Zm9yKHZhciBjPTA7YzxuLmNoaWxkcmVuLmxlbmd0aDtjKyspe3ZhciBzPW4uY2hpbGRyZW5bY107ci5jaGlsZHJlbi5wdXNoKHtyZWN0OlZXb3Jrc3BhY2UuZ2V0UmVjdChzKSxlbGVtZW50OnMsbW92ZWQ6ITF9KX1hLmVsZW1lbnQ9dCxsPVZXb3Jrc3BhY2UuZ2V0UmVjdCh0KSxhLm9yaWdpbmFsWT1vLWwueSxhLnBsYWNlSG9sZGVyPVZXb3Jrc3BhY2UuY3JlYXRlRWxlbWVudCgiZGl2Iix7c3R5bGU6e3dpZHRoOmwuY29udGVudFdpZHRoKyJweCIsaGVpZ2h0OmwuY29udGVudEhlaWdodCsicHgifX0sbiksbi5pbnNlcnRCZWZvcmUoYS5wbGFjZUhvbGRlcix0KSx0LnN0eWxlLndpZHRoPWwuY29udGVudFdpZHRoKyJweCIsdC5jbGFzc05hbWUrPSIgZmxvYXRpbmciLG4uY2xhc3NOYW1lKz0iIGhvbGRpbmciLFZXb3Jrc3BhY2Uub25Nb3VzZU1vdmUoZSl9fSxWV29ya3NwYWNlLm9uTW91c2VVcD1mdW5jdGlvbihlKXt2YXIgdD0oZS50YXJnZXQsZS5jdXJyZW50VGFyZ2V0KSxuPXQud29ya3NwYWNlLHI9bi5kcmFnRGF0YTtudWxsIT1yLmVsZW1lbnQmJihlLnByZXZlbnREZWZhdWx0KCksdC5pbnNlcnRCZWZvcmUoci5lbGVtZW50LHIucGxhY2VIb2xkZXIpLHIuZWxlbWVudC5jbGFzc05hbWU9ci5lbGVtZW50LmNsYXNzTmFtZS5yZXBsYWNlKCIgZmxvYXRpbmciLCIiKSx0LmNsYXNzTmFtZT10LmNsYXNzTmFtZS5yZXBsYWNlKCIgaG9sZGluZyIsIiIpLHIuZWxlbWVudC5zdHlsZS53aWR0aD0iIixyLmVsZW1lbnQuc3R5bGUubGVmdD0iIixyLmVsZW1lbnQuc3R5bGUudG9wPSIiLHQucmVtb3ZlQ2hpbGQoci5wbGFjZUhvbGRlciksci5wbGFjZUhvbGRlcj1udWxsLHIuZWxlbWVudD1udWxsKX0sVldvcmtzcGFjZS5vbk1vdXNlTW92ZT1mdW5jdGlvbihlKXt2YXIgdD0oZS50YXJnZXQsZS5jdXJyZW50VGFyZ2V0KSxuPXQud29ya3NwYWNlLHI9bi5kcmFnRGF0YTtpZihudWxsIT1yLmVsZW1lbnQpe3ZhciBhPWUuY2xpZW50WXx8ZS50b3VjaGVzWzBdLmNsaWVudFk7ZS5wcmV2ZW50RGVmYXVsdCgpO3ZhciBvPVZXb3Jrc3BhY2UuZ2V0UmVjdCh0KSxsPW8ucGFkZGluZ1RvcCsoYS1yLm9mZnNldFkpLW8ueTtyLmVsZW1lbnQuc3R5bGUudG9wPWwrInB4Ijtmb3IodmFyIGM9YS1vLnkscz0wO3M8bi5jaGlsZHJlbi5sZW5ndGg7cysrKXt2YXIgaT1uLmNoaWxkcmVuW3NdO2lmKHIuZWxlbWVudCE9aS5lbGVtZW50KXt2YXIgZD1pLnJlY3QueS1vLnkscD1pLnJlY3QuaC8yO2kubW92ZWQ/KGQ+Y3x8Yz5kK2kucmVjdC5oKSYmKGkubW92ZWQ9ITEpOihjPmQmJmQrcD5jJiYodC5pbnNlcnRCZWZvcmUoaS5lbGVtZW50LHIucGxhY2VIb2xkZXIpLGkucmVjdD1WV29ya3NwYWNlLmdldFJlY3QoaS5lbGVtZW50KSxpLm1vdmVkPSEwKSxjPmQrcCYmYzxkK2kucmVjdC5oJiYodC5pbnNlcnRCZWZvcmUoci5wbGFjZUhvbGRlcixpLmVsZW1lbnQpLGkucmVjdD1WV29ya3NwYWNlLmdldFJlY3QoaS5lbGVtZW50KSxpLm1vdmVkPSEwKSl9fX19LFZXb3Jrc3BhY2UuY3JlYXRlRWxlbWVudD1mdW5jdGlvbihlLHQsbil7aWYoIWUpdGhyb3cgbmV3IFN5bnRheEVycm9yKCIndGFnJyBub3QgZGVmaW5lZCIpO3ZhciByLGEsbyxsPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoZSk7aWYodClmb3IociBpbiB0KWlmKCJzdHlsZSI9PT1yKWZvcihhIGluIHQuc3R5bGUpbC5zdHlsZVthXT10LnN0eWxlW2FdO2Vsc2UgaWYoImRhdGFzZXQiPT09cilmb3IobyBpbiB0LmRhdGFzZXQpbC5kYXRhc2V0W29dPXQuZGF0YXNldFtvXTtlbHNlIGxbcl09dFtyXTtyZXR1cm4gbiYmbi5hcHBlbmRDaGlsZChsKSxsfSxWV29ya3NwYWNlLmdldFJlY3Q9ZnVuY3Rpb24oZSl7dmFyIHQ9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSksbj1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLHI9e3g6bi5sZWZ0K3BhcnNlSW50KHQubWFyZ2luTGVmdCkseTpuLnRvcCtwYXJzZUludCh0Lm1hcmdpblRvcCksdzpuLmNsaWVudFdpZHRoK3BhcnNlSW50KHQuYm9yZGVyTGVmdFdpZHRoKStwYXJzZUludCh0LmJvcmRlclJpZ2h0V2lkdGgpLGg6ZS5jbGllbnRIZWlnaHQrcGFyc2VJbnQodC5ib3JkZXJUb3BXaWR0aCkrcGFyc2VJbnQodC5ib3JkZXJCb3R0b21XaWR0aCksY29udGVudExlZnQ6bi5sZWZ0LGNvbnRlbnRUb3A6bi50b3AsY29udGVudFdpZHRoOm4ud2lkdGgsY29udGVudEhlaWdodDpuLmhlaWdodCxwYWRkaW5nTGVmdDpwYXJzZUludCh0LnBhZGRpbmdMZWZ0KSxwYWRkaW5nVG9wOnBhcnNlSW50KHQucGFkZGluZ1RvcCksbWFyZ2luTGVmdDpwYXJzZUludCh0Lm1hcmdpbkxlZnQpLG1hcmdpblRvcDpwYXJzZUludCh0Lm1hcmdpblRvcCl9O3JldHVybiByfTs=")}, document.head);
828
829 var imexGeneral = integ.enc(asJSON(settings || defset)),
830 imexSubsP = integ.enc(storageGet("poolSubscriptions", "[]")),
831 imexSubsF = integ.enc(storageGet("forumSubscriptions", "[]")),
832 imexTinyAlias = integ.enc(asJSON(getSetting("tinyAlias"))),
833 imexLinks = integ.enc(storageGet("ubLinks", "[]")),
834 imexAll = imexGeneral + "\n" + imexSubsP + "\n" + imexSubsF + "\n" + imexTinyAlias + "\n" + imexLinks;
835
836 var hiddenSections = storageGet("eeHiddenSections", {"general":false,"subscriptions":false,"hotkeys":false,"tinyalias":false,"quickaccess":false,"importexport":true,"about":true});
837
838 if(hiddenSections[0]) {
839 var o = {"general":(hiddenSections[0] || false),"subscriptions":(hiddenSections[1] ||false),"hotkeys":(hiddenSections[2] || false),"tinyalias":(hiddenSections[3] || false),"quickaccess":(hiddenSections[4] || false),"importexport":(hiddenSections[5] || true),"about":(hiddenSections[6] || true)};
840 hiddenSections = o;
841 }
842
843 var measureLS = function() {
844 var lsl = 0;
845 for(var lsi = 0; lsi < localStorage.length; lsi++) lsl += localStorage[localStorage.key(lsi)].length;
846 findElement("lsMeasure").innerHTML = (lsl >= 1024 ? (lsl / 1024).toFixed(2) + " K" : lsl + " ") + "B out of 5 MB" + (lsl >= 1024 ? " (" + lsl + " B)" : "");
847 };
848
849 // Insert the script's settings pane
850 var eePane = createElement("div", {className:"settingsPane", id:"eSixExtendSettingsPane", style:{position:"relative", display:"none"}, innerHTML:[// Version notice (storageGet("eeHiddenNotice", 0) != eSixExtend.versionCode ? "<div style='padding:5px;margin:13px;text-align:left;color:white;background-color:#288233;border:1px solid #2a9117;border-radius:4px;box-shadow:2px 2px 5px #07162D;position:relative;'><h4>News</h4><span>Wat</span><strong style='cursor:pointer; position:absolute; right:10px; top:5px;' onclick='localStorage.eeHiddenNotice=" + eSixExtend.versionCode + ";$j(this.parentNode).slideUp()'>X</strong></div>" : ""),
851
852 // Disclaimer
853 "<p><small>These settings don't affect normal usage of the entire site, just this userscript. In the extremely rare case they do, please report them as soon as possible.<br/>Most settings and fixes will take effect on the next reload.</small></p>",
854
855 // General script settings
856 "<fieldset><legend class='sec_general' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.general ? "\u25B6" : "\u25BC") + "</span> General settings</legend>",
857 "<div id='prefsList' style='display:" + (hiddenSections.general ? "none" : "block") + "'>",
858 "</div></fieldset>",
859
860 // Subscriptions
861 "<fieldset><legend class='sec_subscriptions' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.subscriptions ? "\u25B6" : "\u25BC") + "</span> Subscriptions</legend>",
862 "<div style='display:" + (hiddenSections.subscriptions ? "none" : "block") + "'>",
863 '<p style="margin-bottom:3ex;">The Pools or Forums, that you subscribed to be notified when they update, are listed below. Changes will be saved instantly.</p>',
864
865 "<div style='width:45%; float:left; margin-right:20px;'>",
866 "<label for='lstForums'>Subscriptions:</label>",
867 "<span style='float:right;'><input type='button' class='small' id='eeSubsCheckNow' value='Check for updates now' /> <input type='button' id='subsRemovePools' class='small' value='Nuke pools' /> <input type='button' id='subsRemoveForums' class='small' value='Nuke forums' /> <input type='button' id='subsRemoveSel' class='small' value='Remove selected' disabled /></span>",
868 "<select size='9' style='width:100%;' id='lstSubscriptions'><optgroup id='lstPools' label='Pools'></optgroup><optgroup id='lstForums' label='Forums'></optgroup></select></div>",
869 "<div style='width:45%; float:left; margin-right:20px;' id='subsInfo'></div><div style='clear:both'><br/>",
870
871 "<p><label for='checkInterval'>Check for updates every</label><input type='number' style='margin:0px 1ex;' size='6' id='checkInterval' min='1' max='60' value='" + getSetting("checkInterval") + "' onChange='enableSave(this); document.getElementById(\"eeMinutePlural\").style.display=(this.value==1?\"none\":\"\")' /><label for='checkInterval'>minute<span id='eeMinutePlural'>s</span></label>",
872 "</div></fieldset>",
873
874 // Hotkeys
875 "<fieldset><legend class='sec_hotkeys' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.hotkeys ? "\u25B6" : "\u25BC") + "</span> Hotkeys</legend>",
876 "<div style='display:" + (hiddenSections.hotkeys ? "none" : "block") + "'>",
877 "<p>In this section you can define the hotkeys you want to use for some in-page actions. Letter/number keys are recommended, other keys may show wrong symbols or lowercase letters (they may still work, though).</p>",
878 "<h6>Key layout</h6>",
879 "<div>In order to display the right characters for some keys (like 'Ñ' in Spanish/Latin American keyboards), it is recommended that you choose the option that best fits your language on the box below</div>"+
880 "<p><select id='keyLayout'>",
881 "<option value='en'"+(getSetting("keyLayout") == "en" ? " selected" : "")+">English keyboard</option>",
882 "<option value='es'"+(getSetting("keyLayout") == "es" ? " selected" : "")+">Spanish keyboard</option>",
883 "<option value='hu'"+(getSetting("keyLayout") == "hu" ? " selected" : "")+">Hungarian keyboard</option>",
884 "<option value='de'"+(getSetting("keyLayout") == "de" ? " selected" : "")+">German keyboard (QWERTZ)</option>",
885 (anyOf(sitename, ["e621","e926"]) ? "<option value='eh'>< Help by submitting yours! ></option>" : "") + "</select></p>",
886 "<h6 id='keyprefs1_head'>Post viewing</h6><div id='keyprefs1'></div><br/><h6>Pool navigation</h6>",
887 "<p>To navigate in a pool, click on either \"Read\" or \"Track\" buttons, then press the keys you define here to go to the previous or next post in the pool or stop reading.</p>",
888 "<div id='keyprefs2'></div></div></fieldset>",
889
890 // TinyAlias
891 "<fieldset" + (cookies.login ? "" : " style='display:none'") + "><legend class='sec_tinyalias' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.tinyalias ? "\u25B6" : "\u25BC") + "</span> TinyAlias</legend>",
892 "<div style='display:" + (hiddenSections.tinyalias ? "none" : "block") + "'>",
893 "<span>A TinyAlias is like a normal alias, but with the added benefit that it does not require admin approval, covers many, probably unrelated tags and is applied instantly while tagging.</span>",
894 '<p>For example: The tinyalias "mychar" would be replaced with the tags "gray_fur feline amber_eyes female" after pressing [Space] while tagging a post.</p>',
895 "<p>To add a new TinyAlias, fill in the name and tags in their respective boxes and then click on 'Apply' to add it to the list. If a TinyAlias exists already, it will be updated with the new tags.</p>",
896 "<div><div style='width:25%;float:left;margin-right:20px'>",
897 "<label for='taList'>TinyAliases:</label>",
898 "<select size='10' style='width:100%;' id='taList'></select>",
899 "<input type='button' id='taRemove' disabled value='Remove' />",
900 "</div><div><label for='taName'>Name:</label>",
901 "<input type='text' style='display:block;width:30%' id='taName'></textarea>",
902 "<label for='taTags'>Tags:</label>",
903 "<textarea style='display:block;width:30%;height:135px' id='taTags'></textarea>",
904 "<input type='button' id='taApply' value='Apply' disabled /></div></div>",
905 "<div style='clear:both'><br/>",
906 "<p>Note: When tagging with a TinyAlias, it must be prefixed, otherwise this will not work. This is to avoid conflicts with a TinyAlias\' name and a normal tag's name.</p>",
907 '<label for="tinyPrefix">Prefix: </label><input type="text" value="' + getSetting("tinyPrefix") + '" id="tinyPrefix" maxlength="4" size="6" />',
908 "<p>You can use just about any character, except Space, since it triggers the replacement (4 characters max).</p>",
909 "</div></div></fieldset>",
910
911 // Quick access
912 "<fieldset" + (cookies.login ? "" : " style='display:none'") + "><legend class='sec_quickaccess' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.quickaccess ? "\u25B6" : "\u25BC") + "</span> Quick Access</legend>",
913 "<div style='display:" + (hiddenSections.quickaccess ? "none" : "block") + "'>",
914 "<p>Customize the links in the UserBar. Note that their appearance on the UserBar will be the first letter followed by \u25B6 but their full title will be shown via alt text</p>",
915 "<div class='ubCnt keyListTitle' style='padding-left:40px'><strong>Title</strong><strong>URL</strong><strong>Order</strong><strong>Delete</strong><strong>Skip</strong></div>",
916 "<ol id='lstLinks'></ol>",
917 "<input type='button' id='ublAdd' value='Add new' />",
918 "<br/><br/><span>Replacement labels (applied only on URLs)</span>",
919 "<ul><li><strong>[userNM]</strong>: Your user name (" + cookies.login + ")</li>",
920 "<li><strong>[userID]</strong>: Your user ID (" + storageGet("userID") + ")</li></ul>",
921 "<span>Something wrong with them? <input type='button' class='small' value='Fix' id='eeFixUserData' /></span>",
922 "</div></fieldset>",
923
924 // Import/Export
925 "<fieldset style='position:relative;'><legend class='sec_importexport' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.importexport ? "\u25B6" : "\u25BC") + "</span> Import / Export</legend>",
926 "<div style='display:" + (hiddenSections.importexport ? "none" : "block") + "'></span>",
927 "<p>The following boxes contain your settings in a modular and easy to backup way</p>",
928 "<label for='eeImExGeneral'>General settings: </label>",
929 "<input type='text' class='eeImExField' onFocus='this.select()' id='eeImExGeneral' value='" + imexGeneral + "' onChange='' />",
930 "<label for='eeImExPoolSubscriptions'>Pool subscriptions: </label>",
931 "<input type='text' class='eeImExField' onFocus='this.select()' id='eeImExPoolSubscriptions' value='" + imexSubsP + "' />",
932 "<label for='eeImExForumSubscriptions'>Forum subscriptions: </label>",
933 "<input type='text' class='eeImExField' onFocus='this.select()' id='eeImExForumSubscriptions' value='" + imexSubsF + "' />",
934 "<label for='eeImExTinyAlias'>TinyAliases: </label>",
935 "<input type='text' class='eeImExField' onFocus='this.select()' id='eeImExTinyAlias' value='" + imexTinyAlias + "' />",
936 "<label for='eeImExCustomLinks'>Custom links: </label>",
937 "<input type='text' class='eeImExField' onFocus='this.select()' id='eeImExCustomLinks' value='" + imexLinks + "' />",
938 "<p><input type='button' value='Import' id='eeImportSettings' /> This button will import the settings into their respective areas.</p>",
939 "<p>Or if you prefer, use the buttons below:</p>",
940 "<div><input type='button' id='eeBtnSaveToFile' value='Save to file'/> <input type='button' id='eeBtnLoadFromFile' value='Load from file'/> <input type='file' id='eeFileInput' accept='text/plain' style='visibility:hidden;width:1px;height:1px;' /><a id='eeFileDownloadLink' download='eSixExtend-" + cookies.login + "_data.txt'></a></div>",
941 "</div></fieldset>",
942
943 // About
944 "<fieldset><legend class='sec_about' onClick='eeTogglePane(this); return false;'><span>" + (hiddenSections.about ? "\u25B6" : "\u25BC") + "</span> About</legend>",
945 "<div style='display:" + (hiddenSections.about ? "none" : "block") + "'>",
946 "<p>As the message at the top says, this script should not break anything in the site. If you think it did, please report it as soon as possible <a href='/forum/show/66904' target='_blank'>in this thread</a>. Feature suggestions are also welcome in the same page. Hugs, kisses and other \"services\" are welcome at <a class='link' onclick='notice(\"lol, nope! :3\");'>this address</a>.</p>",
947 "<span>A big thank you to the people who has downloaded this script, the ones who have given feedback and just about everyone! (you know who you are! \u2665 )</span>",
948 "<p><span>This script is made with the hope that it will be useful for the community and is maintained as a hobby by a single person in its free time, whom gets no payment for this. However, you can support him by using the following button. Any support will be appreciated.</span><div style='text-align:center'>" + donatebtn() + "</div></p>",
949 "<hr/><span><strong>Script version: </strong>" + eSixExtend.versionName + " (Code " + eSixExtend.versionCode + ")<br/>",
950 "<strong>Script last modification date: </strong>" + eSixExtend.versionBuild + "<br/>",
951 "<strong>DNP Database version: </strong><a href='/wiki/show?title=avoid_posting&version=" + dnp.version[0] + "' target='_blank'>" + dnp.version[0] + " (" + dnp.version[1] + ")</a><br/>",
952 "<strong>LocalStorage used: </strong><span id='lsMeasure'></span>. Approximate, your browser may report more",
953 "</div></fieldset>",
954
955 // Buttons
956 "<input type='button' value='Defaults' id='ee_btnDefaults' />",
957 "<input type='button' id='ee_btnSave' value='Save'" + (storageGet("settings") && getSetting("saveVersion") == defset.saveVersion ? " disabled" : "") + " />",
958 "<span id='ee_msgSaved' class='newoption-label' style='display:inline-block;overflow-x:hidden;white-space:nowrap;width:0px;'>Settings saved!</span>",
959 "<div>Saves settings in the LocalStorage. For these to be persistent between pages, LocalStorage must be enabled in your browser</div>",
960 ].join("\n")}, holder);
961
962 findElement("keyLayout").addEventListener("change", function() {
963 if(this.value == "eh") {
964 this.value = getSetting("keyLayout");
965 this.blur();
966
967 var body = document.body, bs = body.style, oldFlow = [bs.overflow, bs.overflowX, bs.overflowY], closeAction = function(){body.removeChild(cover);bs.overflow=oldFlow[0];bs.overflowX=oldFlow[1];bs.overflowY=oldFlow[2];};
968 var cover = createElement("div", {style:{position:"fixed", left:"0px", top:"0px", right:"0px", bottom:"0px", background:"#000", opacity:0.5, zIndex:10}}, body);
969 bs.overflow = bs.overflowX = bs.overflowY = "hidden";
970
971 var fpnkl = createFloatingPanel(null, null, null, "New keyboard layout", {overflowX:"hidden", position:"fixed", width:"80%", height:"65%", maxHeight:"65%"});
972 fpnkl.addCloseButton(closeAction);
973
974 var nklpage = 0, nklpages = [
975 createElement("div", {innerHTML:"<p><strong>First of everything, I want to thank you for helping me reach farther with compatibility for the rest of the users of this script!</strong></p><p>Alright, on the next page, you'll be presented an empty list and a box, where you will press each and every key you have available on your keyboard. Don't worry if that box doesn't display anything, what's important is when keys get recorded in the list. If you press a key and it doesn't seem to do anything, this means that it has been already recorded.</p><p>If for some reason, the key you pressed doesn't match the one presented or if something like \"[#123]\" appears, you can change it and/or describe it.</p><p>Some will look like the following, in which case you should change the symbol to the lower left character or the one that is printed on screen</p><div style='text-align:center;'><span class='exampleKey'><span>*</span><strong>+</strong><span>~</span></span><span class='exampleKey'><span>{</span><strong>[</strong></span></div><br/><p>Ready? <strong>Click <span class='navikey'> Next </span> to continue :)</strong></p>"}, fpnkl),
976 createElement("div", {innerHTML:"<p>Click here <input style='background:#FFF;' type='text' size='5' id='keyListAdder' /> and press all of your keys</p><div class='nklKeyItem keyListTitle'><strong>Code</strong><strong>Label</strong><strong>Description</strong></div><div id='fpnkKeyList'></div>", style:{display:"none"}}, fpnkl),
977 createElement("div", {innerHTML:"<p>And we're done!</p><p>Now, for what language is this key set?</p><input id='fpnkKeyLang' type='text' style='margin-left:1em;background:#FFF;' /><br/><br/><p>Anything you would like to add? Totally optional!</p><textarea style='display:block;width:95%;height:15em;margin:auto;background:#FFF;' id='nklMsg'></textarea><br/><p><strong>After clicking <span class='navikey'> Finish </span>, a message will be sent to the dev with the details you've added.</strong><br/>You can see this message and its exact contents in your <a href='/dmail/inbox' target='_blank'>message center</a>.</p>", style:{display:"none"}}, fpnkl)
978 ];
979
980 var keysUsed = [], fpnkKeyList = findElement("fpnkKeyList");
981 findElement("keyListAdder").addEventListener("keydown", function(event) {
982 var kc = getKeyCode(event);
983 event.preventDefault();
984 if(keysUsed.indexOf(kc) == -1) {
985 var keyitem = createElement("div", {className:"nklKeyItem pref"}, fpnkKeyList);
986 createElement("input", {type:"text", value:kc, disabled:true}, keyitem);
987 createElement("input", {type:"text", value:getKeySymbol(kc)}, keyitem);
988 createElement("input", {type:"text"}, keyitem);
989 fpnkKeyList.scrollTop += fpnkKeyList.offsetHeight;
990 keysUsed.push(kc);
991 }
992 if(fpnavnp.disabled == true) fpnavnp.disabled=false;
993 }, true);
994
995 var fpnav = createElement("div", {className:"panelTitle", style:{position:"absolute", top:"auto", left:"0px", bottom:"0px", right:"0px", width:"auto", padding:"4px 8px", textAlign:"right"}}, fpnkl.parentNode),
996 fpnavpp = createElement("input", {style:{marginRight:"1ex"}, disabled:true, type:"button", value:"Previous", onclick:function() {
997 for(var i=0;i<nklpages.length;i++){nklpages[i].style.display="none";}
998 if(nklpage > 0) {nklpage--;nklpages[nklpage].style.display="block";}
999 if(nklpage == 0) this.disabled = true;
1000 fpnavnp.disabled = false;
1001 fpnavnp.value="Next";
1002 }}, fpnav),
1003 fpnavnp = createElement("input", {style:{marginRight:"1em"}, type:"button", value:"Next", onclick:function() {
1004 if(this.value == "Finish") {
1005 var keys = fpnkKeyList.childNodes, output = [];
1006 for(var i = 0, key; i < keys.length; i++) {
1007 key = keys[i].childNodes;
1008 output.push(key[0].value + "\t" + key[1].value + "\t" + (key[2].value || "---"));
1009 }
1010
1011 output = output.sort(function(aa,bb){var a=Number(aa.split("\t")[0]),b=Number(bb.split("\t")[0]);if(a<b)return -1;if(a>b)return 1;return 0;});
1012 var nkmsg = findElement("nklMsg").value, nkmsgc = (nkmsg.length > 0 ? nkmsg + "\n\n" : "") + "[section=Keycodes]\nLanguage: " + (findElement("fpnkKeyLang").value || "---") + "\nBrowser: " + navigator.userAgent + "\n[code]\n" + output.join("\n") + "\n[/code][/section]";
1013 fpnkl.close();
1014 fpnkl = createFloatingPanel(null, null, null, "Sending", {position:"fixed"}); var fpnklanm = animatedIndicator(fpnkl, "h5");
1015 // [uncomment in case sending a PM requires an auth token] query("/dmail/create?authenticity_token=" + document.querySelector("input[name=authenticity_token]").value + "dmail[to_name]=Xch3l&dmail[title]=eSix Extend - New key layout&dmail[body]=" + encodeURIComponent(nkmsgc), "POST", function() {/*
1016 query("/dmail/create?dmail[to_name]=Xch3l&dmail[title]=eSix Extend - New key layout&dmail[body]=" + encodeURIComponent(nkmsgc), "POST", function() {/**/
1017 window.clearInterval(fpnklanm);
1018 notice("Message sent. Thank you very much!");
1019 fpnkl.close();
1020 closeAction();
1021 }, "nkmsgc", function(c, j) {
1022 window.clearInterval(fpnklanm);
1023 error("Error sending message" + helpIcon(j));
1024 fpnkl.close();
1025 closeAction();
1026 });
1027 } else {
1028 for(var i=0;i<nklpages.length;i++){nklpages[i].style.display="none";}
1029 if(nklpage < nklpages.length) {nklpage++;nklpages[nklpage].style.display="block";}
1030 if(nklpage == nklpages.length-1) this.value="Finish";
1031 if(fpnkKeyList.childNodes.length == 0) this.disabled = true;
1032 fpnavpp.disabled=false;
1033 }
1034 }}, fpnav);
1035 createElement("input", {type:"button", value:"Cancel", onclick:function(){closeAction();fpnkl.close();}}, fpnav);
1036 } else
1037 enableSave(this);
1038 }, true);
1039
1040 measureLS();
1041
1042 var prefsList = findElement("prefsList"), lstKeyPrefs = findElement("keyprefs1"),
1043 addCBPreference = function(key, title, summary, dependency) {
1044 var p = getSetting(key), d = (dependency ? getSetting(dependency) : true);
1045 var cbi = createElement("div", {className:"pref cbitem" + (d ? "" : " hidden"), id:key, onclick:function(){enableSave(this, switchClassName(this.firstElementChild, "checked"));}}, prefsList);
1046 var cb = createElement("span", {className:"checkbox" + (p == true?" checked":"")}, cbi);
1047 createElement("span", {className:"checkmark", innerHTML:"\u2714"}, cb);
1048 createElement("div", {innerHTML:"<strong>" + title + "</strong>"+(summary ? "<br/>"+summary : "")}, cbi);
1049 if(p == null) createElement("div", {className:"errPref redtext", innerHTML:"Setting missing!<br/>" + key}, cbi);
1050 if(dependency) {
1051 cbi.style.paddingLeft = "2em";
1052 cbi.previousElementSibling.className += " hasChild";
1053 }
1054 },
1055 addKeyPreference = function(key, title) {
1056 var p = getSetting(key);
1057 var ph = createElement("div", {className:"pref keyPref", onclick:function(){this.firstElementChild.focus();}}, lstKeyPrefs);
1058 createElement("input", {type:"text", value:getKeySymbol(p), name:p, id:key, size:4, style:{textAlign:"center",marginRight:"1em"}, onkeydown:function(event) {
1059 event.preventDefault();
1060 var kc = getKeyCode(event);
1061 if(kc != 27) {
1062 this.value = getKeySymbol(kc);
1063 this.name = kc;
1064 enableSave(this, kc);
1065 } else {
1066 this.value = getKeySymbol(p);
1067 this.name = p;
1068 enableSave(this, p);
1069 this.blur();
1070 }
1071 }, onkeyup:function(event) {
1072 event.preventDefault();
1073 }}, ph);
1074 createElement("label", {htmlFor:key, innerHTML:title}, ph);
1075 if(p == null) createElement("div", {className:"errPref redtext", innerHTML:"Setting missing! " + key}, ph);
1076 };
1077
1078 // Populate the list of preferences
1079 // Use for newest settings: <span class='newoption-label'>NEW!</span>
1080 addCBPreference("justHTTP", 'Apply the "HTTP Tweak" on every image', "Might cause double image requests. Keep it disabled if using mobile data <small title=\"Yup, you've read correctly. There are some mobile browsers, like Opera Classic for Android, that allow installation and use of UserScripts\">(?)</small>");
1081 addCBPreference("BlackholeBlacklistedPosts", "Blackhole Blacklisted Posts", "When enabled, this will clean up any trace of blacklisted posts");
1082 addCBPreference("checkOnPosts", "Check for subscription updates even when viewing posts", "Time interval can be set in the next section");
1083 addCBPreference("collapseLongComments", "Collapse long comments", "This will wrap long comments inside [section] tags to avoid even longer comments.");var sub_collapseLongComments = createElement("div", {id:"sub_collapseLongComments", className:"pref cbitem" + (getSetting("collapseLongComments") ? "" : " hidden"), innerHTML:"<span class='checkbox' style='visibility:hidden;margin-right:1em;'></span>Collapse if comment has more than <input type='number' id='collapseAboveLines' style='vertical-align:middle;margin:5px 5px 0px;' size='4' min='15' max='35' value='" + getSetting("collapseAboveLines") + "' /> lines (or <strong id='eeChars'>" + (parseInt(getSetting("collapseAboveLines"), 10) * 100) + "</strong> characters)"}, prefsList);
1084 addCBPreference("confirmTagging", "Confirm tagging", "Adds a check box on the upload page, preventing accidental submission when tagging"); addCBPreference("allowQuickTagging", "Allow quick tagging", "Tells the script to ignore the above option while editing tags", "confirmTagging");
1085 addCBPreference("freezePools", "Freeze pools", "Pools that haven't updated since several days will be frozen (set as \"kept\")"); var sub_freezePools = createElement("div", {id:"sub_freezePools", className:"pref cbitem" + (getSetting("freezeAfterDays") ? "" : " hidden"), innerHTML:"<span class='checkbox' style='visibility:hidden;margin-right:1em;'></span>Freeze after <input type='number' id='freezeAfterDays' style='vertical-align:middle;margin:5px 5px 0px;' size='4' min='15' max='90' value='" + getSetting("freezeAfterDays") + "' /> days with no update"}, prefsList);
1086 addCBPreference("decorateUserRanks", "Decorate user ranks <span class='newoption-label'>NEW!</span>", "");
1087 addCBPreference("showfaves", "Display fave count", ""); addCBPreference("allfaves", "Include deleted posts in the fave count", "Results in the same number as profile", "showfaves");
1088 addCBPreference("singleline", "Display formatting buttons in a single line", "");
1089 addCBPreference("titleIcons", "Display icons in the title bar", "");
1090 if(storageGet.userLV, 50) addCBPreference("admindtext", "Display implication/alias formatting tools (admin)", "");
1091 addCBPreference("docking", "Dock formatting buttons", "If set, the formatting buttons will be fixed to the right side of the screen, by the layout selected below"); var sub_docking = createElement("div", {id:"sub_docking", className:"pref cbitem" + (getSetting("docking") ? "" : " hidden"), innerHTML:"<span class='checkbox' style='visibility:hidden;margin-right:1em;'></span><div>Select a layout<br/>" + ["<input type='radio' id='flyt1' name='ffbtnsLayout' value='t4x4'" + (getSetting("ffbtnsLayout") == "t4x4" ? " checked" : "") + " /><label for='flyt1'>4x4 (Top)</label>","<input type='radio' id='flyt2' name='ffbtnsLayout' value='m4x4'" + (getSetting("ffbtnsLayout") == "m4x4" ? " checked" : "") + " /><label for='flyt2'>4x4 (Middle)</label>","<input type='radio' id='flyt3' name='ffbtnsLayout' value='b4x4'" + (getSetting("ffbtnsLayout") == "b4x4" ? " checked" : "") + " /><label for='flyt3'>4x4 (Bottom)</label>","<input type='radio' id='flyt4' name='ffbtnsLayout' value='t2x8'" + (getSetting("ffbtnsLayout") == "t2x8" ? " checked" : "") + " /><label for='flyt4'>2x8 (Top)</label>","<input type='radio' id='flyt5' name='ffbtnsLayout' value='m2x8'" + (getSetting("ffbtnsLayout") == "m2x8" ? " checked" : "") + " /><label for='flyt5'>2x8 (Middle)</label>","<input type='radio' id='flyt6' name='ffbtnsLayout' value='b2x8'" + (getSetting("ffbtnsLayout") == "b2x8" ? " checked" : "") + " /><label for='flyt6'>2x8 (Bottom)</label>","<input type='radio' id='flyt7' name='ffbtnsLayout' value='t1x16'" + (getSetting("ffbtnsLayout") == "t1x16" ? " checked" : "") + " /><label for='flyt7'>1x16 (Top)</label>","<input type='radio' id='flyt8' name='ffbtnsLayout' value='m1x16'" + (getSetting("ffbtnsLayout") == "m1x16" ? " checked" : "") + " /><label for='flyt8'>1x16 (Middle)</label>","<input type='radio' id='flyt9' name='ffbtnsLayout' value='b1x16'" + (getSetting("ffbtnsLayout") == "b1x16" ? " checked" : "") + " /><label for='flyt9'>1x16 (Bottom)</label>"].join("<span style='margin-right:1em;'></span>") + "</div>"}, prefsList);
1092 addCBPreference("enhanceNoticeError", "Enhanced notifications", "This setting affects <a class='link' onClick='event.stopPropagation(); notice(\"'Sup!\")'>Notice</a>, <a class='link' onClick='event.stopPropagation(); warning(\"You shall not pass!\")'>Warning</a> and <a class='link' onClick='event.stopPropagation(); error(\"Something happened. Dunno what, tho\")'>Error</a> messages");
1093 addCBPreference("expandSauceMenu", 'Expand "Find Source" menu by hovering over it', "");
1094 addCBPreference("hiliteBlPosts", "Highlight blacklisted posts", "Posts that contain tags that are in your blacklist, will have a red glow");
1095 addCBPreference("hilitebc", "Highlight hidden comments", "Comments that were hidden will be shadowed with a dark color");
1096 addCBPreference("hiliteur", "Highlight your own comments", "Comments that you've posted will be highlighted with a light color");
1097 addCBPreference("image_effects", "Image resizing effects", "Tweaks image posts to resize smoothly");
1098 addCBPreference("allPostsInCommonTags", "Include all posts in the Common Tags list when looking for tags", "Lists <em>all</em> tags from every post on the current page");
1099 addCBPreference("showdmails", "Show new DMails (PMs) received", "Requires HTTPS");
1100 addCBPreference("submitOnCtrlEnter", "Submit current form with Ctrl+Enter", "Applies on text boxes where formatting buttons are present");
1101 addCBPreference("unixLE", "Treat line endings as if they were like Windows", "Might save you a character for each line on blips <small title=\"Windows uses two characters for new lines, while pretty much any other OS doesn't\">(?)</small>");
1102 addCBPreference("fixedUserBar", "UserBar is always visible", "Sticks the UserBar at the top-right corner, making it static even when scrolling");
1103
1104 var flyt = document.querySelectorAll("#sub_docking input[name='ffbtnsLayout']");
1105 var setFFBLayout = function(){enableSave(this, this.value, "ffbtnsLayout");};
1106 for(var flytn = 0; flytn < flyt.length; flytn++) flyt[flytn].addEventListener("change", setFFBLayout);
1107
1108 sub_collapseLongComments.previousElementSibling.className += " hasChild";
1109 sub_docking.previousElementSibling.className += " hasChild";
1110 sub_freezePools.previousElementSibling.className += " hasChild";
1111
1112 findElement("collapseAboveLines").addEventListener("change", function() {
1113 enableSave(this);
1114 findElement("eeChars").innerHTML = Number(this.value) * 100;
1115 });
1116
1117 findElement("freezeAfterDays").addEventListener("change", function() {
1118 enableSave(this);
1119 });
1120
1121 createElement("h6", {style:{marginTop:"2em"},innerHTML:"Debug"}, prefsList);
1122 addCBPreference("uselog", "Log messages to console", "Posts <em>most</em> of the actions the script makes on your browser's Error Console (excludes important messages, which are always posted)");
1123 var dvg = (window.devbug ? ["Allow multiple entries", ""] : ["Show logs in-page", "Adds a box to the bottom-left corner of the screen which hold the messages logged"]);
1124 addCBPreference("floatinglog", dvg[0], dvg[1], "uselog");
1125
1126 // Populate with KeyPreferences
1127 addKeyPreference("keyFavePost", "Add to / Remove from favorites");
1128 addKeyPreference("keyEdit", "Edit tags");
1129 addKeyPreference("keyUpvote", "Vote up");
1130 addKeyPreference("keyDownvote", "Vote down");
1131 lstKeyPrefs = findElement("keyprefs2");
1132 addKeyPreference("poolNextKey", "Next post in pool");
1133 addKeyPreference("poolPrevKey", "Previous post in pool");
1134 addKeyPreference("poolStopKey", "Stop reading");
1135
1136 // Hide options not relevant for unregistered/logged out users
1137 if(!cookies.login) {
1138 var hideOptions = ["collapseLongComments", "sub_collapseLongComments", "confirmTagging", "showfaves", "allfaves", "singleline", "titleIcons", "docking", "sub_docking", "hiliteur", "showdmails", "submitOnCtrlEnter", "unixLE", "keyprefs1_head", "keyprefs1"];
1139 for(var i = 0; i < hideOptions.length; i++) findElement(hideOptions[i]).style.display = "none";
1140 }
1141
1142 // Find textboxes for Import/Export
1143 var eeSave = findElement("ee_btnSave"), eeImExGeneral = findElement("eeImExGeneral"),
1144 eeImExPoolSubscriptions = findElement("eeImExPoolSubscriptions"), eeImExForumSubscriptions = findElement("eeImExForumSubscriptions"),
1145 eeImExTinyAlias = findElement("eeImExTinyAlias"), eeImExCustomLinks = findElement("eeImExCustomLinks");
1146
1147 window.enableSave = function(ele, def, ky) {
1148 if(ele) {
1149 var val = (def != undefined ? def : (ele.className == "cbitem" ? def : (ele.type == "checkbox" ? ele.checked : ele.value))), key = ky || ele.id;
1150 var enc2 = function(v) {return integ.enc(encodeURIComponent(v))};
1151 logIt("setting", "Changed setting", key + ": " + val);
1152
1153 if(settings[key] != undefined || defset[key] != undefined)
1154 settings[key] = val;
1155 else
1156 logIt("setting", "Setting '" + key + "' not found", "Value: " + val);
1157
1158 eeImExGeneral.value = imexGeneral = enc2(asJSON(settings));
1159 eeImExPoolSubscriptions.value = imexSubsP = enc2(storageGet("poolSubscriptions"));
1160 eeImExForumSubscriptions.value = imexSubsF = enc2(storageGet("forumSubscriptions"));
1161 eeImExTinyAlias.value = imexTinyAlias = enc2(asJSON(TinyAlias));
1162 eeImExCustomLinks.value = imexLinks = enc2(storageGet("ubLinks"));
1163
1164 // Toggle child options
1165 var kn = ["showfaves","confirmTagging","collapseLongComments","docking","uselog","freezePools"].indexOf(key);
1166 var sp = ["allfaves","allowQuickTagging","sub_collapseLongComments","sub_docking","floatinglog","sub_freezePools"];
1167 if(kn != -1) switchClassName(findElement(sp[kn]), "hidden");
1168 }
1169
1170 eeSave.disabled = false;
1171 };
1172
1173 // Global function to toggle groups in settings pane
1174 window.eeTogglePane = function(title) {
1175 var arrow = title.childNodes[0], st = arrow.innerHTML.charCodeAt(0) == 9660;
1176 arrow.innerHTML = (st ? "\u25B6" : "\u25BC");
1177 title.nextElementSibling.style.display = (st ? "none" : "block");
1178 hiddenSections[title.className.substr(4)] = st;
1179 storageSet("eeHiddenSections", hiddenSections);
1180 };
1181
1182 findElement("eeFixUserData").addEventListener("click", function() {
1183 this.disabled = true;
1184 updateUserData();
1185 }, true);
1186
1187 // Populate the subscriptions lists
1188 var subsInfo = findElement("subsInfo"),
1189 subsRemPools = findElement("subsRemovePools"), subsRemForums = findElement("subsRemoveForums"), subsRemSel = findElement("subsRemoveSel"),
1190 subsLst = findElement("lstSubscriptions"), checkSubsNow = findElement("eeSubsCheckNow"),
1191 lstForums = findElement("lstForums"), lstPools = findElement("lstPools");
1192
1193 var populateSubsList = function() {
1194 var fsubs = getForumSubscriptions();
1195 var psubs = getPoolSubscriptions();
1196
1197 lstForums.label = "Forums (" + fsubs.length + ")";
1198 for(var fsi = 0; fsi < fsubs.length; fsi++) {
1199 var ob = fsubs[fsi];
1200 createElement("option", {value:asJSON(ob), type:0, innerHTML:ob.name}, lstForums);
1201 }
1202
1203 lstPools.label = "Pools (" + psubs.length + ")";
1204 for(var psi = 0; psi < psubs.length; psi++) {
1205 var ob = psubs[psi];
1206 createElement("option", {value:asJSON(ob), type:1, innerHTML:(ob.keep ? "\u25A0" : "\u25A1") + " " + ob.name.replace(/_/g, " ")}, lstPools);
1207 }
1208 };
1209
1210 var subsSelect = function() {
1211 subsInfo.innerHTML = "";
1212 subsRemSel.disabled = false;
1213 if(this.selectedIndex != -1) {
1214 var itm = this.options[this.selectedIndex], sub = JSON.parse(itm.value), anm;
1215 subsRemSel.item = itm;
1216 writeSubInfo(itm, sub);
1217 }
1218 },
1219 writeSubInfo = function(itm, sub) {
1220 if(itm.type == 0) {
1221 subsInfo.innerHTML = ["<h6>Forum: <a href='/forum/show/" + sub.id + "' target='_blank'>" + sub.name + "</a> " + (sub.pages > 1 ?"<a href='/forum/show/" + sub.id + "?page=" + sub.pages + "' target='_blank' class='last-page'>last</a>" : "") + "</h6>",
1222 "<div style='margin-left:1em;'>Last seen: " + (sub.last_id ? "<a href='/forum/show/" + sub.last_id + "' target='_blank'>" + sub.last_nm + " (forum #" + sub.last_id+")</a>" : "OP") + "<br/>",
1223 "Updated by: " + (sub.newest_id ? "<a href='/forum/show/" + sub.newest_id + "' target='_blank'>" + sub.newest_nm + " (forum #" + sub.newest_id + ")</a>" : "Nobody") + "<br/>",
1224 "<a href='/forum/show/" + sub.id+"?page=" + (sub.pages || 1) + "' target='_blank'>" + (sub.pages || 1) + " page" + (sub.pages <= 1 ? "" : "s") + "</a></div>"
1225 ].join("\n");
1226 } else {
1227 subsInfo.innerHTML = ["<div><h6>Pool: <a href='/pool/show/" + sub.id + "' target='_blank'>" + sub.name.replace(/_/g, " ") + "</a></h6>",
1228 "<div style='margin-left:1em;'><a class='preview' style='float:left; text-align:center; margin-right:1ex; width:150px; height:150px; overflow:hidden; border:1px solid currentColor; padding:2px;' target='_blank' href='/post/show/" + sub.last + "'>",
1229 "<img id='subsP_preview' class='preview' width='1' height='150' style='opacity:0;' src='https://static1.e621.net/data/preview/" + sub.thumb.url + "' />",
1230 "<strong id='plswait'>Please wait</strong></a>",
1231 "<p>Current post: " + (sub.posts.indexOf(sub.last) + 1) + " of " + sub.posts.length + "</p>",
1232 "<p><input type='checkbox' id='cbKeep'" + (sub.keep == true ? " checked" : "") + "/><label for='cbKeep'>Don't update, just keep it.</label></p></div></div>"
1233 ].join("\n");
1234
1235 var preview = findElement("subsP_preview"), plswait = findElement("plswait");
1236 findElement("cbKeep").addEventListener("change", function() {
1237 sub.keep = this.checked;
1238 itm.value = asJSON(sub);
1239 itm.innerHTML = (sub.keep ? "\u25A0" : "\u25A1") + " " + sub.name.replace(/_/g, " ");
1240 updatePoolSubscriptions(sub);
1241 }, true);
1242
1243 preview.addEventListener("load", function() {
1244 plswait.style.display = "none";
1245 window.setTimeout(function(){
1246 preview.width = sub.thumb.width;
1247 preview.height = sub.thumb.height;
1248 preview.style.marginTop = ((150 - sub.thumb.height) / 2) + "px";
1249 preview.style.opacity = 1;
1250 }, 5);
1251 }, true);
1252 }
1253
1254 var cuubtn = createElement("input", {type:"button", value:"Check for updates"}, subsInfo);
1255 cuubtn.onclick = function() {
1256 cuubtn.disabled = true;
1257 var anm = animatedIndicator(this);
1258 checkSubscriptionUpdate(sub, function() {
1259 window.clearInterval(anm);
1260 cuubtn.disabled = false;
1261 cuubtn.value = "Check for updates";
1262 writeSubInfo(itm, sub);
1263 });
1264 };
1265 };
1266
1267 subsLst.addEventListener("change", subsSelect, true);
1268 subsLst.addEventListener("keyup", function(event) {if(getKeyCode(event) == 46 && !subsRemSel.disabled) subsRemSel.click();}, true);
1269 subsRemSel.addEventListener("click", function() {
1270 var itm = this.item;
1271 itm.parentNode.removeChild(itm);
1272 subsInfo.innerHTML = "";
1273 lstForums.label = "Forums (" + lstForums.childNodes.length + ")";
1274 lstPools.label = "Pools (" + lstPools.childNodes.length + ")";
1275 this.disabled = true;
1276 enableSave();
1277 }, true);
1278 subsRemForums.addEventListener("click", function() {
1279 lstForums.label = "Forums (" + lstForums.childNodes.length + ")";
1280 lstForums.innerHTML = "";
1281 enableSave();
1282 }, true);
1283 subsRemPools.addEventListener("click", function() {
1284 lstPools.innerHTML = "";
1285 lstPools.label = "Pools (" + lstPools.childNodes.length + ")";
1286 enableSave();
1287 }, true);
1288
1289 checkSubsNow.addEventListener("click", function() {
1290 var anm = animatedIndicator(this);
1291 subsLst.disabled = subsRemSel.disabled = subsRemForums.disabled = subsRemPools.disabled = checkSubsNow.disabled = true;
1292 subsInfo.innerHTML = "";
1293 checkSubsUpdates(function() {
1294 window.clearInterval(anm);
1295 checkSubsNow.value = "Check for updates now";
1296 subsLst.disabled = subsRemSel.disabled = subsRemForums.disabled = subsRemPools.disabled = checkSubsNow.disabled = false;
1297 lstForums.innerHTML = "";
1298 lstPools.innerHTML = "";
1299 populateSubsList();
1300 });
1301 }, true);
1302 populateSubsList();
1303
1304 // Populate list of TinyAliases
1305 var taList = findElement("taList"), taName = findElement("taName"), taTags = findElement("taTags"),
1306 taPrefix = findElement("tinyPrefix"), taRemove = findElement("taRemove"), taApply = findElement("taApply"),
1307 ta, tn, op;
1308
1309 for(var i = 0; i < TinyAlias.length; i++) {
1310 ta = TinyAlias[i];
1311 tn = ta.tags.replace(/ +/g, " ").split(" ");
1312 for(var z = 0, n = 0; z < tn.length; z++) if(tn[z].length != 0) n++;
1313 op = createElement("option", {value:ta.name, innerHTML:ta.name + " (" + n + " tag" + (n==1?"":"s") + ")", id:"ta_" + ta.name}, taList);
1314 }
1315
1316 taList.addEventListener("change", function() {
1317 var ta = TinyAlias.find(this.value), itm = this.options[this.selectedIndex];
1318 taName.value = ta.name;
1319 taTags.value = ta.tags.replace(/ +/g, " ");
1320 taApply.disabled = false;
1321 taRemove.disabled = false;
1322 taRemove.item = [itm,this.selectedIndex];
1323 });
1324 taList.addEventListener("keyup", function(event) {if(getKeyCode(event) == 46 && !taRemove.disabled) taRemove.click();}, true);
1325
1326 var checkNameAndTags = function() {
1327 taApply.disabled = (taName.value.length == 0 || taTags.value.length == 0);
1328 taTags.value = taTags.value.replace(/\n/g, " ").replace(/ +/g, " ");
1329 };
1330
1331 taName.addEventListener("keyup", checkNameAndTags);
1332 taTags.addEventListener("keyup", checkNameAndTags);
1333
1334 taRemove.addEventListener("click", function() {
1335 taList.removeChild(this.item[0]);
1336 settings.tinyAlias.splice(this.item[1], 1);
1337 taName.value = "";
1338 taTags.value = "";
1339 taApply.disabled = true;
1340 this.disabled = true;
1341 enableSave();
1342 });
1343
1344 taPrefix.addEventListener("change", function() {
1345 this.value = this.value.replace(/ /gi, "");
1346 if(this.value.length == 0) this.value = "ta_";
1347 if(this.value != getSetting("tinyPrefix")) enableSave(this);
1348 });
1349
1350 taApply.addEventListener("click", function() {
1351 var name = taName.value.replace(new RegExp(taPrefix.value || "ta_", ""), ""), tags = taTags.value;
1352
1353 if(name && tags) {
1354 var ta = TinyAlias.find(name);
1355 var tn = tags.replace(/ +/g, " ").split(" ");
1356 for(var z = 0, n = 0; z < tn.length; z++) if(tn[z].length != 0) n++;
1357 var taLabel = name + " (" + n + " tag" + (n==1?"":"s") + ")";
1358
1359 if(ta == null) {
1360 createElement("option", {value:name, innerHTML:taLabel, id:"ta_" + name}, taList);
1361 if(!settings.tinyAlias) settings.tinyAlias = [];
1362 settings.tinyAlias.push({"name":name, "tags":tags});
1363 taName.value = taTags.value = "";
1364 } else {
1365 ta.tags = tags;
1366 taList.options[taList.selectedIndex].innerHTML = taLabel;
1367 TinyAlias[ta] = {"name":name, "tags":tags};
1368 }
1369 }
1370
1371 enableSave();
1372 taApply.disabled = true;
1373 });
1374
1375 // Populate list of custom links
1376 var newUbLink = function(lnk) {
1377 var itm = createElement("li", {className:"ubLink pref workspace_element"}, lst);
1378 createElement("div", {className:"handle"}, itm);
1379 var cnt = createElement("div", {className:"ubCnt"}, itm);
1380 itm.ref = {
1381 title:(lnk.title || ""), url:(lnk.url || "")
1382 };
1383
1384 if(lnk.last) itm.ref.last = lnk.last;
1385 if(lnk.current) itm.ref.current = lnk.current;
1386 if(lnk.skip) itm.ref.skip = lnk.skip;
1387
1388 // Link title
1389 var nubTitleElement = createElement("input", {type:"text", value:(lnk.title || ""), onchange:function() {
1390 var entry = this.parentNode.parentNode;
1391 entry.ref.title = this.value;
1392 enableSave();
1393 }}, cnt);
1394
1395 // Link URL
1396 createElement("input", {type:"text", value:(lnk.url || ""), onchange:function() {
1397 var entry = this.parentNode.parentNode, list = entry.parentNode;
1398 this.value = this.value.replace(window.location.protocol + "//" + window.location.hostname, "");
1399 entry.ref.url = this.value;
1400 enableSave();
1401 }}, cnt);
1402
1403 // Move up
1404 createElement("input", {type:"button", value:"\u25B2", style:{minWidth:"24px"}, title:"Move up", onclick:function() {
1405 var entry = this.parentNode.parentNode, list = entry.parentNode;
1406 list.insertBefore(list, entry.nextElementSibling);
1407 list.insertBefore(entry, entry.previousElementSibling);
1408 enableSave();
1409 }}, cnt);
1410
1411 // Move down
1412 createElement("input", {type:"button", value:"\u25BC", style:{minWidth:"24px"}, title:"Move down", onclick:function() {
1413 var entry = this.parentNode.parentNode, list = entry.parentNode;
1414 list.insertBefore(entry.nextElementSibling, entry);
1415 enableSave();
1416 }}, cnt);
1417
1418 // Remove
1419 createElement("input", {type:"button", value:"\u2716", style:{minWidth:"24px"}, title:"Remove", onclick:function() {
1420 var entry = this.parentNode.parentNode, list = entry.parentNode;
1421 list.removeChild(entry);
1422 enableSave();
1423 }}, cnt);
1424
1425 // Check for updates
1426 var notUpdateable = (getUbLinkQuery(lnk.url) == null);
1427 var nubSkip = createElement("input", {type:"button", value:(lnk.skip ? "\u25C6" : "\u25C7"), disabled:notUpdateable, style:{minWidth:"24px"}, title:(notUpdateable ? "Can't check for updates" : "Check for updates:\n\u25C6 = No\n\u25C7 = Yes"), onclick:function() {
1428 var entry = this.parentNode.parentNode, list = entry.parentNode;
1429 this.value = (this.value.charCodeAt(0) == 9670 ? "\u25C7" : "\u25C6");
1430 entry.ref.skip = (this.value.charCodeAt(0) == 9670);
1431 enableSave();
1432 }}, cnt);
1433 if(notUpdateable) lnk.skip = true;
1434
1435 if(!lnk.title) nubTitleElement.focus();
1436 };
1437
1438 var itm, lnk, inp, lst = findElement("lstLinks");
1439
1440 // Initialize VWorkspace
1441 VWorkspace(lst);
1442
1443 for(var i = 0; i < ubLinks.length; i++)
1444 newUbLink(ubLinks[i]);
1445
1446 findElement("ublAdd").addEventListener("click", function() {
1447 newUbLink({});
1448 enableSave();
1449 }, true);
1450
1451 // Import / Export
1452 var doImportSettings = function() {
1453 try {
1454 var general, subsP, subsF, tinyaliases, links, changed = false;
1455 var dec2 = function(s){return decodeURIComponent(integ.dec(s.replace(/[^A-Z0-9=\/+]/gi, "")));};
1456
1457 general = dec2(eeImExGeneral.value);
1458 subsP = dec2(eeImExPoolSubscriptions.value);
1459 subsF = dec2(eeImExForumSubscriptions.value);
1460 tinyaliases = dec2(eeImExTinyAlias.value);
1461 links = dec2(eeImExCustomLinks.value);
1462
1463 if(eeImExGeneral.value != imexGeneral && general != null) {
1464 settings = JSON.parse(general);
1465 changed = true;
1466 }
1467
1468 if(eeImExPoolSubscriptions.value != imexSubsP && subsP != null) {
1469 storageSet("poolSubscriptions", subsP);
1470 changed = true;
1471 }
1472
1473 if(eeImExForumSubscriptions.value != imexSubsF && subsF != null) {
1474 storageSet("forumSubscriptions", subsF);
1475 changed = true;
1476 }
1477
1478 if(eeImExTinyAlias.value != imexTinyAlias && tinyaliases != null) {
1479 settings.tinyAlias = JSON.parse(tinyaliases);
1480 changed = true;
1481 }
1482
1483 if(eeImExCustomLinks.value != imexLinks && links != null) {
1484 storageSet("ubLinks", links);
1485 changed = true;
1486 }
1487
1488 if(changed == true) {
1489 storageSet("settings", settings);
1490 measureLS();
1491 notice("Settings imported successfully!");
1492 } else {
1493 warning("No changes made");
1494 }
1495 } catch(ex) {
1496 error("An error occured while importing");
1497 logIt("err_eeimex", "Import error", ex);
1498 }
1499 };
1500 findElement("eeImportSettings").addEventListener("click", doImportSettings, true);
1501
1502 var fileInput = findElement("eeFileInput"), fileLink = findElement("eeFileDownloadLink"), fileHeader = "eSixExtend User Prefs";
1503 findElement("eeBtnSaveToFile").addEventListener("click", function() {
1504 var eeImExAll = [imexGeneral, imexSubsP, imexSubsF, imexTinyAlias, imexLinks].join("\n");
1505 fileLink.href = "data:application/octet-stream;base64," + btoa(fileHeader + "\n" + eeImExAll);
1506 fileLink.click();
1507 }, true);
1508 findElement("eeBtnLoadFromFile").addEventListener("click", function() {
1509 fileInput.click();
1510 }, true);
1511 fileInput.onchange = function(files) {
1512 var fr = new FileReader();
1513 fr.onloadend = function() {
1514 if(fr.result.substr(0, fileHeader.length) !== fileHeader) {
1515 error("File does not contain eSix Extend's user data");
1516 return;
1517 }
1518
1519 var dat = fr.result.split("\n");
1520 if(dat.length != 6) {
1521 error("File contains invalid data");
1522 return;
1523 }
1524
1525 eeImExGeneral.value = dat[1];
1526 eeImExPoolSubscriptions.value = dat[2];
1527 eeImExForumSubscriptions.value = dat[3];
1528 eeImExTinyAlias.value = dat[4];
1529 eeImExCustomLinks.value = dat[5];
1530
1531 doImportSettings();
1532 };
1533 fr.readAsText(fileInput.files[0]);
1534 };
1535
1536 // Assign events to 'Default' and 'Save' buttons
1537 findElement("ee_btnDefaults").addEventListener("click", function() {
1538 if(confirm('By clicking "Yes", your settings and these items will be deleted:\n\n• TinyAliases\n• Custom links\n• List of users recently PM\'d\n• Pool and Forum subscriptions\n• Visibility of sections in the settings\n• Visibility of Common Tags\n\nAre you sure?')) {
1539 var dkeys = ["settings", "ubLinks", "lastDMailed", "eeHiddenSections", "poolSubscriptions", "forumSubscriptions", "showCommonTags"];
1540 for(var dk = 0; dk < dkeys.length; dk++) storageSet(dkeys[dk], null);
1541 window.location.reload();
1542 }
1543 }, true);
1544 eeSave.addEventListener("click", function() {
1545 writeSettings();
1546 measureLS();
1547 }, true);
1548
1549 return eePane;
1550 }
1551
1552 /***********************
1553 ** General page fixes **
1554 ***********************/
1555 if(findElement("mode") == null) // Fixes "cannot convert 's' to object" when clicking on post thumbnails at some pages
1556 createElement("input", {type:"hidden", value:"view", id:"mode"}, document.body);
1557
1558 /*******************************************************************************************
1559 ** Locate the textareas that can be formatted with DText. And do some other modifications **
1560 ** loc[] array format: "https://e621.net/loc[1]/loc[2]/loc[3]/loc[n]" **
1561 ** "LOC/path" provided for ease of searchability **
1562 *******************************************************************************************/
1563
1564 if(loc[1] == "user") { // LOC/user
1565 if(loc[2] == "edit") { // We're on the user settings page (LOC/user/edit)
1566 // Temporary blacklist list and buttons
1567 if(storageGet("eeBlTagsList")) {
1568 var clearTbList = function() {storageSet("eeBlTagsList", null); lstTBL.innerHTML="";};
1569 var boxBlacklist = findElement("user_blacklisted_tags"), secBlacklist = boxBlacklist.parentNode.parentNode;
1570 var tblDiv = createElement("div", {}, secBlacklist);
1571 createElement("label", {className:"block", htmlFor:"lstTempBlacklist", innerHTML:"Temporary blacklist"}, tblDiv);
1572 createElement("p", {innerHTML:"The temporary blacklist is a list that holds the tags, posts and/or users you've blacklisted using eSix Extend's single-tag blacklist feature.<br/>The following box contains all the tags ready to be added to your main blacklist with a click of a button (literally)"}, tblDiv);
1573 var tbTags = storageGet("eeBlTagsList", "").split(" "), lstTBL = createElement("select", {id:"lstTempBlacklist", style:{width:"85%"}, size:5}, tblDiv);
1574 createElement("br", {}, tblDiv);
1575 createElement("input", {type:"button", value:"Append to main blacklist", style:{marginRight:"1em"}, onclick:function() {
1576 boxBlacklist.value += tbTags.join("\n") + "\n";
1577 clearTbList();
1578 notice("Don't forget to save!");
1579 this.disabled = true;
1580 }}, tblDiv);
1581 createElement("input", {type:"button", value:"Clear", onclick:clearTbList}, tblDiv);
1582
1583 var tl = [];
1584 for(var i = 0; i < tbTags.length; i++) if(tbTags[i].length > 0) {
1585 tl.push(tbTags[i]);
1586 createElement("option", {innerHTML:tbTags[i]}, lstTBL);
1587 }
1588 tbTags = tl;
1589 }
1590
1591 var ra = createFormattingButtons("user_about");
1592 ra.style.height = "400px";
1593
1594 var content = ra.parentNode;
1595 var previewBtn = createElement("input", {type:"button", value:"Preview",onclick:function() {
1596 // The following line was taken from the Preview button on the Forums to preview user info (Sorry guys n_n; )
1597 new Ajax.Updater('preview', '/forum/preview?forum_post[body]=' + encodeURIComponent(ra.value), {asynchronous:true, evalScripts:true, method:'get', onSuccess:function(request){$j('#preview').fadeIn('slow')}});
1598 }}, content);
1599 content.insertBefore(previewBtn, ra.nextSibling);
1600
1601 var previewDiv = createElement("div", {id:"preview", style:{margin:"1em 0", display:"none"}});
1602 content.appendChild(previewDiv);
1603 content.insertBefore(previewDiv, ra);
1604
1605 // Settings tabs
1606 var content = findElement("content");
1607 var userEdit = findElement("user-edit");
1608
1609 var paneHolder = findElement("paneHolder");
1610 if(!paneHolder) {
1611 paneHolder = createElement("div", {id:"paneHolder", className:"rounded"});
1612 content.insertBefore(paneHolder, content.firstChild);
1613 }
1614
1615 if(userEdit.className != "settingsPane")
1616 userEdit.className = "settingsPane";
1617 userEdit.style.display = "none";
1618 paneHolder.appendChild(userEdit);
1619
1620 var tabs = findElement("settingsPanes");
1621 if(!tabs) {
1622 tabs = createElement("ul", {id:"settingsPanes"});
1623 content.insertBefore(tabs, paneHolder);
1624 }
1625
1626 var switchPanes = function() {
1627 if(/inactive/.test(this.className)) {
1628 var tabs = findElement("settingsPanes").childNodes, tab, panes = findElement("paneHolder").childNodes, i = 0;
1629 for(i = 0; i < tabs.length; i++) {
1630 tab = tabs[i];
1631 if(!tab.classList.contains("inactive")) tab.classList.add("inactive");
1632 }
1633 for(i = 0; i < panes.length; i++) {
1634 panes[i].style.display = "none";
1635 }
1636 this.classList.remove("inactive");
1637 findElement(this.name).style.display = "block";
1638 }
1639 };
1640
1641 var userSettings = findElement("userSettings");
1642 if(!userSettings)
1643 userSettings = createElement("li", {id:"userSettings", innerHTML:"Account Settings", className:"rounded inactive",onclick:switchPanes, name:"user-edit"}, tabs);
1644
1645 var eSixSettings = createElement("li", {innerHTML:"<a href='#eSixExtendSettings'>eSix Extend Settings</a>", className:"rounded inactive", onclick:switchPanes, name:"eSixExtendSettingsPane"}, tabs);
1646
1647 var eePane = writeSettingsPanel(paneHolder);
1648
1649 // Activate the pane according to the hash in the URL
1650 if(window.location.hash == "#eSixExtendSettings") {
1651 userSettings.className = "rounded inactive";
1652 eSixSettings.className = "rounded";
1653 eePane.style.display = "block";
1654 } else {
1655 userSettings.className = "rounded";
1656 eSixSettings.className = "rounded inactive";
1657 userEdit.style.display = "block";
1658 }
1659 } else
1660 if(loc[2] == "show") { // LOC/user/show
1661 createFormattingButtons("blips_body");
1662
1663 if(loc[3] == storageGet("userID")) { // LOC/user/show/#
1664 var dc = findByTag("tbody")[1];
1665
1666 var countersTR = dc.insertRow(-1);
1667 var countersTD = countersTR.insertCell(0);
1668 countersTD.setAttribute("colspan", "4");
1669
1670 // Commenting score
1671 var ccs = createElement("input", {type:"button", value:"Commenting score", style:{marginRight:"1em"}, onclick:function() {
1672 this.disabled = true;
1673 var score = 0, highest = {score:0}, lowest = {score:1000}, count = 0, page = 1, stim = animatedIndicator(ccs);
1674 var cleanHTML = function(s) {
1675 return s.replace(/(\\|'|"|<|>|&|=)/g, function(m, a) {
1676 return "&#" + a.charCodeAt(0) + ";";
1677 })
1678 .replace(/\[.*?\]/g, "")
1679 .replace(/".+?":http.*?(?=\s)/gi, "");
1680 };
1681 var loadComments = function() {
1682 query("/comment/search.json?query=user%3A" + fixUsername() + "&page=" + page, "GET", function(json, xml, text) {
1683 if(json.length != 0) {
1684 count += json.length;
1685 for(var i = 0; i < json.length; i++) {
1686 var cm = json[i], cs = cm.score, o = {score:cs, id:cm.id, post:cm.post_id, body:cleanHTML(cm.body)};
1687 score += cs;
1688 if(cs >= highest.score) highest = o;
1689 if(cs <= lowest.score) lowest = o;
1690 }
1691
1692 page++;
1693 loadComments();
1694 } else
1695 countScore();
1696 }, "commentspager");
1697 };
1698
1699 var countScore = function() {
1700 var rs = createFloatingPanel(ccs, true);
1701 rs.parentNode.style.paddingRight = "2em";
1702 rs.innerHTML = "Your score is: <span" + (score==0?" style='font-weight:bold'":" class='" + (score > 0 ? "greentext" : "redtext") + "'") + ">" + score + "</span> (Avg: <span title='Out of " + count + " comment" + (count == 1 ? "" : "s") + "'>" + (score / count).toFixed(2).toString().replace(/\.00/, "") + "</span>)<br/>" +
1703 "Most rated: <a href='/comment/show/" + highest.id + "' target='_blank'" + (highest.score == 0 ? " style='font-weight:bold'" : " class='" + (highest.score > 0 ? "greentext" : "redtext") + "'") + " title=\"" + highest.body + "\">comment #" + highest.id + "</a> (<span" + (highest.score == 0 ? " style='font-weight:bold'" : " class='" + (highest.score > 0 ? "greentext" : "redtext") + "'") + ">" + highest.score + "</span>)<br/>" +
1704 "Least rated: <a href='/comment/show/" + lowest.id + "' target='_blank'" + (lowest.score == 0 ? " style='font-weight:bold'" : " class='" + (lowest.score > 0 ? "greentext" : "redtext") + "'") + " title=\"" + lowest.body + "\">comment #" + lowest.id + "</a> (<span" + (lowest.score == 0 ? " style='font-weight:bold'" : " class='" + (lowest.score > 0 ? "greentext" : "redtext") + "'") + ">" + lowest.score + "</span>)";
1705 window.clearInterval(stim);
1706 ccs.value = "Commenting score";
1707 ccs.disabled = false;
1708 }
1709
1710 loadComments();
1711 }}, countersTD);
1712
1713 // Posting score
1714 var cps = createElement("input", {type:"button", value:"Posting score", onclick:function() {
1715 this.disabled = true;
1716 var score = 0, hiScore = {score:0}, loScore = {score:1000}, count = 0, stim = animatedIndicator(cps), page = 1;
1717 var loadPosts = function() {
1718 query("/post/index.json?tags=user%3A" + fixUsername() + "&page=" + page, "GET", function(json, xml, text) {
1719 if(json.length != 0) {
1720 count += json.length;
1721 for(var i = 0; i < json.length; i++) {
1722 var post = json[i];
1723
1724 score += post.score;
1725 if(post.score >= hiScore.score) hiScore = post;
1726 if(post.score <= loScore.score) loScore = post;
1727 }
1728
1729 page++;
1730 loadPosts();
1731 } else
1732 countScore();
1733 }, "postspager");
1734 };
1735
1736 var countScore = function() {
1737 var hiScoreTitle = makeTooltip(hiScore);
1738 var loScoreTitle = makeTooltip(loScore);
1739
1740 var rs = createFloatingPanel(cps, true);
1741 rs.parentNode.style.paddingRight = "2em";
1742 rs.innerHTML = "Your score is: <a href='/post?tags=user%3A" + cookies.login + "+order%3Ascore' target='_blank'" + (score == 0 ? " style='font-weight:bold'" : " class='" + (score > 0 ? "greentext" : "redtext")) + "'>" + score + "</a> (Avg: " + (score / count).toFixed(2).toString().replace(/\.00/, "") + ")<br/>" +
1743 "Most rated: <a href='/post/show/" + hiScore.id + "' target='_blank'" + (hiScore.score == 0 ? " style='font-weight:bold'" : " class='" + (hiScore.score > 0 ? "greentext" : "redtext") + "'") + " alt='" + hiScoreTitle + "' title='" + hiScoreTitle + "'>post #" + hiScore.id + "</a> (<span" + (hiScore.score == 0 ? " style='font-weight:bold'" : " class='" + (hiScore.score > 0 ? "greentext" : "redtext") + "'") + ">" + hiScore.score + "</span>)<br/>" +
1744 "Least rated: <a href='/post/show/" + loScore.id + "' target='_blank'" + (loScore.score == 0 ? " style='font-weight:bold'" : " class='" + (loScore.score > 0 ? "greentext" : "redtext") + "'") + " alt='" + loScoreTitle + "' title='" + loScoreTitle + "'>post #" + loScore.id + "</a> (<span" + (loScore.score == 0 ? " style='font-weight:bold'" : " class='" + (loScore.score > 0 ? "greentext" : "redtext") + "'") + ">" + loScore.score + "</span>)<br/>";
1745 window.clearInterval(stim);
1746 cps.value = "Posting score";
1747 cps.disabled = false;
1748 }
1749
1750 loadPosts();
1751 }}, countersTD);
1752 } else {
1753 // Blacklist user
1754 createElement("li", {innerHTML:"<a class='link blacklink' id='blUser'>Blacklist user</a>"}, subnav.firstElementChild);
1755 findElement("blUser").addEventListener("click", function() {
1756 notice("Adding user to blacklist");
1757 query("/user/index.json?id=" + loc[3], "GET", function(j, x, t) {
1758 blacklistTag("user:" + j[0].name);
1759 });
1760 }, true);
1761 }
1762 } else
1763 if(loc[2] == "block") { // LOC/user/block
1764 createFormattingButtons("ban_reason");
1765 }
1766 } else
1767 if(loc[1] == "forum" && anyOf(loc[2], ["new","show","edit"])) { // LOC/forum/new LOC/forum/show LOC/forum/edit
1768 // We're on the forums (creating a new thread, editing a post or replying to someone)
1769 var ra = createFormattingButtons("forum_post_body");
1770 if(ra) ra.style.height = "200px";
1771
1772 if(loc[2] == "show") { // LOC/forum/show
1773 var subsF = getForumSubscriptions();
1774 var index = subsF.findItem(function(item) {return (item.id == loc[3]);});
1775
1776 if(index != -1) {
1777 var sub = subsF[index],
1778 lastId = parseInt(document.querySelector("#forum div.comment:last-child").id.substr(1)),
1779 lastNm = document.querySelector("#forum div.comment:last-child div.author h6 a").innerHTML;
1780
1781 if(sub.last_id < parseInt(lastId, 10)) {
1782 sub.name = oldTitle;
1783 sub.pages = parseInt(pageQuery.page) || 1;
1784 sub.newest_id = sub.last_id = lastId;
1785 sub.newest_nm = sub.last_nm = lastNm;
1786 saveForumSubscriptions(subsF);
1787 }
1788 } else {
1789 var subsForum = createElement("li", {className:"link", innerHTML:"<a>Subscribe to this forum</a>"}, subnav.firstElementChild);
1790 subsForum.addEventListener("click", function() {
1791 notice("Subscribing to this forum");
1792 var anm = animatedIndicator(subsForum, "span");
1793 query("/forum/show.json?id=" + loc[3], "GET", function(j1, x1, t1) {
1794 var forumName = j1.title, count = 0, page = 1, current = [0, ""];
1795 var saveForum = function() {
1796 notice("Subscribed to this forum");
1797 updateForumSubscriptions({"id":loc[3], "name":forumName, "pages":(Math.ceil(count / 30) || 1),"newest_id":current[0],"last_id":current[0],"newest_nm":current[1],"last_nm":current[1]});
1798 window.clearInterval(anm);
1799 subsForum.style.display = "none";
1800 };
1801
1802 var getForumPosts = function() {
1803 query("/forum/index.json?parent_id=" + loc[3] + "&page=" + page, "GET", function(j2, x2, t2) {
1804 if(j2.length == 0) {
1805 saveForum();
1806 } else {
1807 count += j2.length;
1808 if(page == 1) current = [j2[0].id, j2[0].creator];
1809 page++;
1810 getForumPosts();
1811 }
1812 }, "subsforum");
1813 };
1814 getForumPosts();
1815 }, "subsforum");
1816 }, true);
1817 }
1818 }
1819 } else
1820 if(loc[1] == "blip" && loc[2] != "search") { // LOC/blip LOC/blip/index
1821 // We're on the blips or creating a new blip
1822 var ra = createFormattingButtons("blips_body") || createFormattingButtons("blip_body");
1823
1824 // Create the counter element
1825 var charcounter = createElement("span", {id:"blipChars", style:{textAlign:"right", cssFloat:"right"}, innerHTML:"\u2711 <strong>0</strong>"});
1826
1827 var fchange = function() {
1828 var chars = ra.value.replace(/\n/gm, (getSetting("unixLE") ? "." : "..")).length;
1829
1830 if(chars >= 255) charcounter.className = "greytext"; else
1831 if(chars >= 240 && chars < 255) charcounter.className = "redtext"; else
1832 if(chars >= 200 && chars < 240) charcounter.className = "greentext"; else
1833 if(chars < 200) charcounter.className = "";
1834
1835 findElement("blipChars").innerHTML = "\u2711 <strong>" + chars + "</strong>";
1836 };
1837
1838 // Count remaining characters on each keypress
1839 ra.addEventListener("keydown", fchange, true);
1840 ra.addEventListener("keyup", fchange, true);
1841 ra.addEventListener("focus", fchange, true);
1842 ra.addEventListener("blur", fchange, true);
1843
1844 // Insert the counter
1845 ra.parentNode.insertBefore(charcounter, findElement("fbtns_blips_body"));
1846 } else
1847 if(loc[1] == "post" && !anyOf(loc[2], ["error","deleted_index","popular_by_day","popular_by_week","popular_by_month"])) { // LOC/post LOC/post/index
1848 // We're on the post listing
1849
1850 // Formatting buttons for "Post Description"
1851 var postDesc = createFormattingButtons("post_description");
1852
1853 // Open wiki entries from the tag search box
1854 var tagSearch = findElement("tags");
1855 if(tagSearch) {
1856 tagSearch.addEventListener("keyup", function(event) {
1857 if(event.ctrlKey && event.keyCode == 13) {
1858 event.preventDefault();
1859 var tags = this.value.replace(/ +/g, " ").split(" ");
1860 if(tags.length == 1)
1861 window.location.assign("/wiki/show?title=" + tags[0]);
1862 else
1863 if(tags.length > 1) {
1864 for(var i = 0; i < tags.length; i++) {
1865 window.open("/wiki/show?title=" + encodeURIComponent(tags[i]));
1866 }
1867 }
1868 }
1869 }, false);
1870 }
1871
1872 var tagsBox = findElement("post_tags"), tbParent = tagsBox.parentNode;
1873 if(tbParent.id == "quick-edit") tbParent = tbParent.parentNode.parentNode; // Scale up three levels (when we're in the post index)
1874 var extend = createElement("div", {style:{margin:"1ex 0ex"}}, tbParent); // "Sort" and "Tags count" holder
1875
1876 var tagNotif = createElement("a", {style:{display:"block"}, innerHTML:""}, extend);
1877 var tagCount = createElement("span", {innerHTML:"Tags: 0"}, extend);
1878 var taPrefix = getSetting("tinyPrefix");
1879
1880 var addTaTags = function(alias) {
1881 var tags = tagsBox.value.replace(/ +/g, " ").split(" "), taTags = alias.tags.replace(/ +/g, " ").split(" "), tt = [];
1882 for(var tai = 0; tai < taTags.length; tai++) if(tags.indexOf(taTags[tai]) == -1) tt.push(taTags[tai]);
1883 tagsBox.value = tagsBox.value.replace(taPrefix + alias.name, tt.join(" "));
1884 };
1885
1886 tagsBox.addEventListener("keyup", function(event) {
1887 var taCount = TinyAlias.length;
1888
1889 var text = tagsBox.value.substr(0, tagsBox.selectionEnd).replace(/ +/g, " ").split(" ");
1890 var tag = text[text.length - 1], taTag = tag.substr(taPrefix.length), tagss = 0;
1891
1892 var taList = createFloatingPanel(tagsBox, false, "taList", "", {minWidth:"1px", minHeight:"1px", left:(tagsBox.offsetLeft + tagsBox.offsetWidth + 12) + "px"});
1893 if(tag.indexOf(taPrefix) == 0) {
1894 taList.innerHTML = "";
1895
1896 for(var i = 0; i < taCount; i++) {
1897 var ta = TinyAlias[i];
1898 if(ta.name.indexOf(taTag) == 0) {
1899 var tagsnum = ta.tags.split(" ").length;
1900 createElement("div", {className:"option", title:ta.tags, innerHTML:ta.name + "<span style='float:right'>(" + tagsnum + " tag" + (tagsnum == 1 ? "" : "s") + ")</span>", dataset:{tinyAliasIndex:i}, onclick:function() {
1901 var ta = TinyAlias[this.dataset.tinyAliasIndex];
1902 tagsBox.value = tagsBox.value.replace(taPrefix + taTag, taPrefix + ta.name);
1903 addTaTags(ta);
1904 tagsBox.focus();
1905 }}, taList);
1906 tagss++;
1907 }
1908 }
1909 if(taList && tagss == 0) taList.close();
1910 } else
1911 if(taList)
1912 taList.close();
1913
1914 if(event.keyCode == 32) // Check for TinyAliases at every press of the spacebar
1915 for(var i = 0; i < taCount; i++) addTaTags(TinyAlias[i]);
1916
1917 countTags(); // Count number of tags, removing leading, trailing and duplicate spaces
1918 checkDNP(); // Check if an artist tag is on the DNP List
1919 });
1920
1921 // Count tags
1922 var countTags = function() {
1923 var tn = tagsBox.value.replace(/ +/g, " ").split(" ");
1924 for(var z = 0, n = 0; z < tn.length; z++) if(tn[z].length != 0) n++;
1925 tagCount.innerHTML = "Tags: " + n;
1926 };
1927 countTags();
1928
1929 var re = findElement("post_rating_explicit"), rq = findElement("post_rating_questionable"), rs = findElement("post_rating_safe");
1930
1931 var getRating = function() {
1932 if(re && rq && rs) return (re.checked ? "e" : "") + (rq.checked ? "q" : "") + (rs.checked ? "s" : "");
1933 var ma = tagsBox.value.match(/rating:([sqe])/gi);
1934 return (ma == null ? "" : ma[0]);
1935 };
1936
1937 var checkDNP = function() {
1938 var rating = getRating(), tags = tagsBox.value, dnp_tag = tags.match(dnp.list);
1939 var sources = findElement("post_source");
1940 sources = (sources == null ? "" : sources.value);
1941
1942 if(dnp_tag == null) tagNotif.innerHTML = ""; else {
1943 dnp_tag = dnp_tag[0];
1944
1945 // Commissioned work
1946 if(tags.match(dnp.commissioned_work) != null) {
1947 tagNotif.className = "greytext";
1948 tagNotif.innerHTML = "Commissioned works from " + dnp_tag + " are not allowed by the artist. Make sure this isn't commissioned before uploading.";
1949 } else
1950
1951 // Commercial work
1952 if(tags.match(dnp.commercial_work) != null) {
1953 tagNotif.className = "redtext";
1954 tagNotif.innerHTML = "Commercial work by " + dnp_tag + " is DNP, even if it's over two years old.";
1955 } else {
1956 /// Examine all other cases
1957 var message = "", artist, cdnp, ssdnp;
1958
1959 // Conditional DNP
1960 for(cdnp = 0; cdnp < dnp.conditional_dnp.length; cdnp++) {
1961 artist = dnp.conditional_dnp[cdnp];
1962
1963 if(tags.match(artist.name) != null) {
1964 // Special case
1965 if(artist.rating && artist.rating.indexOf(rating) != -1) {
1966 switch(rating) {
1967 case "e": message = "Explicit"; break;
1968 case "q": message = "Questionable"; break;
1969 case "s": message = "Safe"; break;
1970 }
1971
1972 message += " artwork by " + dnp_tag + " is DNP" + (artist.special ? ": " + artist.special : "");
1973 } else
1974 if(artist.tags) {
1975 var dtags = tags.match(artist.tags);
1976 if(dtags != null) message = 'Artwork made by ' + dnp_tag + ', tagged with "' + dtags + '" is DNP';
1977 } else {
1978 message = "Artwork by " + dnp_tag + " is DNP" + (artist.special ? ": " + artist.special : "");
1979 }
1980
1981 tagNotif.className = "redtext";
1982 tagNotif.innerHTML = message;
1983 break;
1984 }
1985 }
1986
1987 if(cdnp == dnp.conditional_dnp.length) {
1988 // Site specific DNP
1989 for(ssdnp = 0; ssdnp < dnp.site_specific.length; ssdnp++) {
1990 artist = dnp.site_specific[ssdnp];
1991 var sourcesComposed = artist.allowedSources;
1992
1993 if(artist.allowedSources.length >= 2)
1994 sourcesComposed = sourcesComposed.slice(0, sourcesComposed.length - 1).join(", ") + " or " + sourcesComposed[sourcesComposed.length - 1];
1995
1996 var asrc = new RegExp(artist.allowedSources.join("|"), "gi").test(sources);
1997 if(tags.match(artist.name) != null && !asrc) {
1998 tagNotif.className = "tag-type-1";
1999 tagNotif.innerHTML = "Artwork by " + dnp_tag + " that is not from " + sourcesComposed + " is DNP";
2000 break;
2001 }
2002 }
2003
2004 if(ssdnp == dnp.site_specific.length) {
2005 tagNotif.className = "redtext";
2006 tagNotif.innerHTML = dnp_tag + " is on the <a class='link' href='/wiki/show?title=avoid_posting' target='_blank'>Avoid Posting</a> list. Uploading will get you in trouble";
2007 }
2008 }
2009 }
2010 }
2011 };
2012 checkDNP();
2013
2014 if(re && rq && rs) {
2015 re.addEventListener("change", checkDNP);
2016 rq.addEventListener("change", checkDNP);
2017 rs.addEventListener("change", checkDNP);
2018 }
2019
2020 createElement("input", {type:"button", className:"small", value:"Sort", style:{marginLeft:"1em"}, onclick:function() {
2021 var tags = tagsBox.value.replace(/ +/g, " ").split(" ");
2022 for(var i = 0; i < tags.length; i++) if(tags[i].length == 0) tags.splice(i, 1);
2023 tags = tags.sort();
2024 var tags2 = [tags[0]];
2025 for(var i = 1; i < tags.length; i++) if(tags[i-1] != tags[i]) tags2.push(tags[i]);
2026 tagsBox.value = tags2.join(" ");
2027 countTags();
2028 }}, extend);
2029
2030 var popoutbtn = createElement("input", {type:"button", className:"small", value:"Pop out", style:{marginLeft:"1em"}, onclick:function() {
2031 popoutbtn.style.display = "none";
2032 var placeholder = createElement("div", {className:"placeholder", style:{width:tagsBox.offsetWidth + "px", height:tagsBox.offsetHeight + "px"}, innerHTML:" "}, tbParent);
2033 tbParent.insertBefore(placeholder, tagsBox);
2034 var fp = createFloatingPanel(tagsBox, null, "floatingTagsBox", "Tags", {position:"fixed"});
2035 fp.addCloseButton(function() {
2036 tbParent.insertBefore(tagsBox, placeholder);
2037 fp.appendChild(placeholder);
2038 popoutbtn.style.display = "";
2039 });
2040 fp.appendChild(tagsBox);
2041 fp.parentNode.className += " nomin";
2042 tbParent.appendChild(fp.parentNode);
2043 }}, extend);
2044
2045 createCheckTag(tbParent);
2046
2047 var createReadybox = function() {
2048 var editBox = document.querySelector("#edit-form div.section");
2049
2050 if(editBox) {
2051 var postForm = findElement("edit-form");
2052 var ctBox = createElement("input", {type:"checkbox", id:"ctbox"}, editBox);
2053 createElement("label", {htmlFor:"ctbox", innerHTML:"Ready"}, editBox);
2054
2055 postForm.addEventListener("submit", function(event) {
2056 if(!ctBox.checked) {
2057 event.stopPropagation();
2058 event.preventDefault();
2059 }
2060 }, true);
2061 tagsBox.stopObserving("keydown"); // Sorry tony/spight! But it had to be done... :/
2062 tagsBox.addEventListener("keydown", function(event) {
2063 if(event.keyCode == 13) {
2064 event.stopPropagation();
2065 event.preventDefault();
2066 if(ctBox.checked)
2067 postForm.submit();
2068 } else {
2069 ctBox.checked = false;
2070 }
2071 }, true);
2072 }
2073 };
2074
2075 var postImage = findElement("image"), postFlash = document.querySelector("object param[name='movie']"), postVideo = document.querySelector("video source[type='video/webm']");
2076 if(getSetting("confirmTagging") && !(getSetting("allowQuickTagging") && loc[2] == "show")) createReadybox();
2077
2078 // Formatting buttons for comment box
2079 if(loc[2] == "show" && (postImage || postFlash || postVideo)) { // LOC/post/show
2080 var stch = findElement("stats").children[1].children, postID;
2081
2082 // Find post ID
2083 for(var i = 0; i < stch.length; i++) {
2084 var idele = stch[i].innerHTML.split(": ");
2085 if(idele[0].trim() === "ID") {
2086 postID = parseInt(idele[1], 10);
2087 createFormattingButtons("reply-text-" + idele[1]);
2088 break;
2089 }
2090 }
2091
2092 // Posts's file URL and MD5
2093 var postSource;
2094
2095 if(postFlash) postSource = postFlash.value; else
2096 if(postVideo) postSource = postVideo.src;
2097 else postSource = postImage.src;
2098
2099 var sozMD5 = postSource.match(/\/([a-f0-9]+)\..*$/)[1], // File's name is the MD5 hash
2100 postType = postSource.substr(postSource.lastIndexOf("."));
2101
2102 // Link for each tag to blacklist
2103 var tags = document.querySelectorAll("#tag-sidebar a:nth-child(2)");
2104 for(var i = 0; i < tags.length; i++) {
2105 var tag = tags[i], tagName = tag.href.match(/\/post\/(search|index)\?tags=(.*)$/)[2].replace(/ /g, "_");
2106 var blTag = createElement("a", {innerHTML:"X", name:decodeURIComponent(tagName), title:'Blacklist "' + decodeURIComponent(tagName) + '"', className:"blacklink", onclick:function() {blacklistTag(this.name);}});
2107 tag.parentNode.insertBefore(blTag, tag);
2108 tag.parentNode.insertBefore(document.createTextNode(" "), tag);
2109 }
2110
2111 // Link that blacklists the currently viewed post
2112 var postOptions = findElement("stats").nextElementSibling.childNodes[3];
2113 createElement("li", {innerHTML:"<a class='link blacklink' onclick='blacklistTag()'>Blacklist post</a>"}, postOptions);
2114
2115 // Link that reloads the image over HTTP. Available only on image posts
2116 if(postImage) {
2117 var linkHTTP = createElement("a", {className:"link", innerHTML:"Not loading?", style:{textDecoration:(postImage ? "" : "line-through")}, href:"#image", onclick:function() {
2118 loadImageOverHTTP({shiftKey:true});
2119 }}, createElement("li", {}, postOptions));
2120 }
2121
2122 var postCommands, rightcol = findElement("right-col").childNodes;
2123 for(var i = 0; i < rightcol.length; i++) {
2124 var el = rightcol[i];
2125 if(el.childNodes[1] && el.childNodes[1].tagName == "H4") {
2126 postCommands = el.childNodes[1];
2127 break;
2128 }
2129 }
2130
2131 var descBox = postCommands.parentNode.previousSibling.previousSibling;
2132 if(descBox.childNodes[1].className == "section collapse-container")
2133 descBox.parentNode.insertBefore(postCommands, descBox);
2134
2135 // Find Source
2136 postCommands.appendChild(document.createTextNode(" | "));
2137 var sozLink = createElement(postImage ? "a" : "span", {innerHTML:"Find Source"}, postCommands);
2138 sozLink.className = "link";
2139 sozLink.addEventListener((getSetting("expandSauceMenu") ? "mouseover" : "click"), function() {
2140 var sozURL = "http" + (getSetting("justHTTP") ? "" : "s") + "://" + (["e621","e926"].indexOf(sitename) != -1 ? "static1.e621.net" : location.hostname) + "/data/" + (postImage && (parseInt(postImage.dataset.orig_height, 10) > 1000 || parseInt(postImage.dataset.orig_width, 10) > 1000) && postType != ".gif" ? "sample/" : "") + sozMD5.substr(0, 2) + "/" + sozMD5.substr(2, 2) + "/" + sozMD5 + postType;
2141 var sozLnks = document.querySelectorAll("div#stats ul li.sourcelink")[0], sauceFA, sauceDA, sozPixiv, sozDerpi;
2142
2143 if(sozLnks) {
2144 var urls = sozLnks.firstElementChild.childNodes;
2145 for(var i = 0; i < urls.length; i++) {
2146 var s = urls[i];
2147 if(s.tagName == "A") {
2148 var href = s.href;
2149
2150 // Direct url to image in FurAffinity
2151 if(anyOf(href, ["d.facdn.net", "d.furaffinity.net", "data.furaffinity.net"])) {
2152 var sozLnk = href.split("/"), sozName = sozLnk[sozLnk.length-1], dots = [sozName.indexOf(".") + 1, sozName.lastIndexOf(".")];
2153 sauceFA = "search?q=" + sozName.substr(dots[0], dots[1]-dots[0]).replace(/[~&|!()@\/]/g, "").replace(/_/g, "%20");
2154 } else
2155
2156 // Direct url to preview image in FurAffinity
2157 if(href.indexOf("t.facdn.net") != -1) {
2158 var sozLnk = href.split("/"), sozName = sozLnk[sozLnk.length-1];
2159 sauceFA = "view/" + sozName.match(/(\d+)/g)[0] + "/";
2160 } else
2161
2162 // Direct url to image in DeviantArt
2163 if(/[a-z0-9]{4}\.deviantart\.net/.test(href)) {
2164 var sozLnk = href.split("/"), sozName = sozLnk[sozLnk.length-1];
2165 sauceDA = sozName.match(/-([a-z0-9]+)\./)[1];
2166 } else
2167
2168 // Direct url to image in Pixiv
2169 if(href.indexOf("i1.pixiv.net") != -1) {
2170 var sozLnk = href.split("/"), sozName = sozLnk[sozLnk.length-1];
2171 sozPixiv = sozName.split(".")[0];
2172 } else
2173
2174 // Direct url to image in Derpibooru
2175 var derpiMatch = href.match(/derpicdn.*\/([0-9]+)__/);
2176 if(derpiMatch) sozDerpi = derpiMatch[1];
2177 }
2178 }
2179 }
2180
2181 var sozLinks = [];
2182 if(postImage) {
2183 sozLinks.push(["://www.google.com/searchbyimage?image_url=" + sozURL, "Google"]);
2184 sozLinks.push(["://www.tineye.com/search?url=" + sozURL, "TinEye"]);
2185 sozLinks.push(["://saucenao.com/search.php?url=" + sozURL, "SauceNAO"]);
2186 sozLinks.push(["://iqdb.org/?url=" + sozURL, "IQDB"]);
2187 sozLinks.push(["://iqdb.harry.lu/?url=" + sozURL, "Harry.lu"]);
2188 }
2189
2190 sozLinks.push(["s://inkbunny.net/submissionsviewall.php?mode=search&text=" + sozMD5 + "&md5=yes", "InkBunny"]);
2191 if(sauceFA) sozLinks.push(["://www.furaffinity.net/" + sauceFA, "FurAffinity"]);
2192 if(sauceDA) sozLinks.push(["://fav.me/" + sauceDA, "DeviantArt"]);
2193 if(sozPixiv) sozLinks.push(["://www.pixiv.net/member_illust.php?mode=medium&illust_id=" + sozPixiv, "Pixiv"]);
2194
2195 if(sozDerpi) sozLinks.push(["s://derpibooru.org/" + sozDerpi, "Derpibooru ⇥", "I've detected a valid direct image URL that leads to an actual post"]); else
2196 //if(postImage && (tagsBox.value.split(" ").indexOf("my_little_pony") != -1 || sitename == "20% Cooler")) sozLinks.push(["s://derpibooru.org/search/reverse?url=" + sozURL + "&fuzziness=0.4", "Derpibooru"]);
2197
2198 var sozMenu = createFloatingPanel(this, false);
2199 for(var sl = 0, lk; sl < sozLinks.length; sl++) {
2200 lk = sozLinks[sl];
2201 createElement("a", {className:"option", target:"_blank", href:"http" + lk[0], innerHTML:lk[1], title:(lk[2] || "")}, sozMenu);
2202 }
2203 }, true);
2204
2205 setPoolSpace();
2206
2207 var pools = [], subs = getPoolSubscriptions();
2208 for(var i = 0; i < subs.length; i++)
2209 pools.push(subs[i].id);
2210
2211 var readOrTrack = function() {
2212 var dis = this, oldName = this.value;
2213 initPool(this.poolID, this, (this.value == "Keep" ? 1 : 0), function() {
2214 dis.parentNode.style.display = "none";
2215 dis.disabled = false;
2216 dis.value = oldName;
2217 });
2218 };
2219
2220 var divs = findElement("post-view").getElementsByTagName("div");
2221 for(i = 0; i < divs.length; i++) {
2222 var dp = divs[i], dpid;
2223 if(dp.id) {
2224 dpid = dp.id.match(/pool([0-9]+)/);
2225 if(dpid != null) {
2226 dp.parentNode.style.position = "relative";
2227 var rp = createElement("div", {style:{position:"absolute", right:"4px", top:"0px", display:(pools.indexOf(Number(dpid[1])) == -1 ? "block" : "none")}, id:"track-pool-" + dpid[1]}, dp);
2228 createElement("input", {type:"button", value:"Read", poolID:Number(dpid[1]), className:"small", style:{marginRight:"1ex"}, onclick:readOrTrack}, rp);
2229 createElement("input", {type:"button", value:"Keep", poolID:Number(dpid[1]), className:"small", onclick:readOrTrack}, rp);
2230 }
2231 }
2232 }
2233
2234 if(postImage) {
2235 var loadImageOverHTTP = function(event) {
2236 if(event.shiftKey) {
2237 postImage.src = postImage.src.replace("https", "http");
2238 postImage.style.opacity = "0";
2239 indi = createElement("h6", {}, postImage.parentNode);
2240 itim = animatedIndicator(indi, "span");
2241 }
2242 };
2243
2244 // Workaround for images not loading over HTTPS
2245 var oSrc, indi, itim;
2246 postImage.addEventListener("click", loadImageOverHTTP, false);
2247 postImage.addEventListener("load", function(event) {
2248 if(this.style.opacity == "0") {
2249 this.style.opacity = "";
2250 window.clearInterval(itim);
2251 indi.parentNode.removeChild(indi);
2252 }
2253 }, false);
2254
2255 if(getSetting("image_effects")) {
2256 var imgStyle = postImage.style, imgData = postImage.dataset;
2257 postImage.onload = function() {
2258 imgStyle.width = postImage.width + "px";
2259 imgStyle.height = postImage.height + "px";
2260 postImage.removeAttribute("width");
2261 postImage.removeAttribute("height");
2262 imgStyle[cssPrefix("transitionProperty")] = "width, height";
2263 imgStyle[cssPrefix("transitionDuration")] = "500ms";
2264 postImage.onload = null;
2265 };
2266
2267 Post.toggle_size = function() {
2268 jQuery("#resized_notice").hide();
2269 var resize = (imgData.resize == "true");
2270 if(window.Note) window.Note.all.forEach(function(n) {n.adjustScale();});
2271
2272 switch(parseInt(imgData.resize_mode)) {
2273 case 0: case 1:
2274 imgStyle.height = "100%";
2275 if(resize) {
2276 imgStyle.width = imgData.orig_width + "px";
2277 } else {
2278 var width = jQuery("#content").width() - jQuery(".sidebar").width();
2279 if (width > imgData.orig_width) width = imgData.orig_width;
2280 imgStyle.width = width + "px";
2281 }
2282 break;
2283
2284 case 2:
2285 if(resize) {
2286 imgStyle.width = imgData.orig_width + "px";
2287 imgStyle.height= imgData.orig_height + "px";
2288 postImage.src = findElement("highres").href;
2289 } else {
2290 imgStyle.width = (imgData.sample_width || 800) + "px";
2291 imgStyle.height= (imgData.sample_height || 800) + "px";
2292 postImage.src = imgData.sample_url;
2293 }
2294 break;
2295 }
2296
2297 imgData.resize = !resize;
2298 };
2299 }
2300 }
2301
2302 placeTitleIcons();
2303
2304 document.body.addEventListener("keydown", function(event) {
2305 var tag = event.target.tagName, kc = getKeyCode(event), ishk = false;
2306
2307 if(["INPUT","TEXTAREA","OBJECT"].indexOf(tag) == -1 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
2308 if(kc == getSetting("keyFavePost")) {
2309 if(findElement("add-to-favs").style.display != "none")
2310 Favorite.create(postID);
2311 else
2312 Favorite.destroy(postID);
2313 ishk = true;
2314 }
2315 if(kc == getSetting("keyEdit")) {
2316 if ($('comments')) $('comments').hide();
2317 $j('#edit').fadeIn();
2318 $('post_tags').focus();
2319 ishk = true;
2320 }
2321 if(kc == getSetting("keyUpvote")) {
2322 Post.vote(1, postID);
2323 ishk = true;
2324 }
2325 if(kc == getSetting("keyDownvote")) {
2326 Post.vote(-1, postID);
2327 ishk = true;
2328 }
2329
2330 if(ishk) event.preventDefault();
2331 }
2332 }, false);
2333 } else
2334 if(!anyOf(loc[2], ["show","upload"])) { // LOC/post/index
2335 var posts = Post.posts.entries();
2336 var sidebar = document.querySelector("#post-list .sidebar");
2337 var commonTags = createElement("div", {}, sidebar);
2338
2339 var h5_commonTags = createElement("h5", {}, commonTags);
2340 createElement("a", {className:"link", innerHTML:"Common tags"}, h5_commonTags).onclick = function() {
2341 $j("#commonTags").toggle();
2342 storageSet("showCommonTags", !storageGet("showCommonTags", false));
2343 };
2344 createElement("span", {className:"searchhelp", style:{marginLeft:"1ex", cursor:"help"}, innerHTML:"(?)"}, h5_commonTags)
2345 .title = "This section lists 30 tags that are common among the current list";
2346
2347 var thumbs = document.querySelectorAll("div.content-post div span.thumb a img.preview");
2348 var makeSearchTerms = function(post) {
2349 var t = post.title.toLowerCase().replace(/: /g, ":")
2350
2351 .replace(/date:([a-z]+) ([0-9]+), ([0-9]+)/, function(s, m, dd, aa) {
2352 var mm = ["","january","february","march","april","may","june","july","august","september","october","november","december"];
2353 return [
2354 "date:" + mm.indexOf(m) + "/" + dd + "/" + aa, "date:" + mm[mm.indexOf(m)] + "/" + dd + "/" + aa,
2355 "day:" + dd,
2356 "month:" + mm.indexOf(m), "month:" + mm[mm.indexOf(m)],
2357 "year:" + aa,
2358 ].join(" ");
2359 })
2360 .replace(/(rating:[sqe])/, "$1 $1")
2361 .replace(/\n/g, " ").replace(/ +/g, " ");
2362
2363 return t;
2364 };
2365
2366 // Search refiner
2367 var sidebar = document.querySelector("div.sidebar");
2368 var refine = function() {
2369 var ttags = refbox.value.toLowerCase().split(" "), rtags = [];
2370 for(var i = 0; i < ttags.length; i++) if(ttags[i].length > 0 && ttags[i] != "-") rtags.push(ttags[i]);
2371 if("almonds" == rtags[0]) if(confirm("I have. Want some?")) {window.location.assign("http://e926.net/post/show/195545"); return;}
2372
2373 var thumbs = document.querySelectorAll("div.content-post div span.thumb a img.preview"), results = 0, tlen = thumbs.length;
2374
2375 for(var t = 0; t < tlen; t++) { // For all posts
2376 var thumb = thumbs[t], tile = thumb.parentNode.parentNode.style, hits = 0;
2377 var ptags = makeSearchTerms(thumb); // Get tags from a post as search terms
2378 var tags = ptags.split(" ");
2379
2380 for(var rti = 0; rti < rtags.length; rti++) { // For all refiner tags
2381 var mt = rtags[rti], rtag = mt.replace(/^-+/, ""), negated = (mt.charAt(0) === "-"), wild = (mt.indexOf("*") != -1);
2382
2383 if(wild) {
2384 if(new RegExp(rtag.replace(/(\(|\)|\.|\|)/g, "\\$1").replace(/\*/g, ".*")).test(ptags)) hits++;
2385 } else {
2386 if(tags.indexOf(rtag) != -1) hits++;
2387 }
2388 }
2389
2390 if(hits == rtags.length || rtags.length == 0) {
2391 tile.display = (negated ? "none" : "");
2392 results++;
2393 } else
2394 tile.display = (negated ? "" : "none");
2395 }
2396
2397 refcount.innerHTML = (results == tlen ? " " : results + " / " + tlen + " post" + (results == 1 ? "" : "s"));
2398 };
2399
2400 var secRefiner = createElement("div", {style:{marginBottom:"1em"}});
2401 var hedRefiner = createElement("h5", {innerHTML:"Refine"}, secRefiner);
2402 var refbox = createElement("input", {type:"text", style:{width:"183px", display:"block"}, onkeyup:refine}, secRefiner);
2403 var refcount = createElement("div", {innerHTML:" "}, secRefiner);
2404 sidebar.insertBefore(secRefiner, sidebar.children[1]);
2405
2406 createElement("span", {className:"searchhelp", style:{marginLeft:"1ex"}, title:"Help", innerHTML:"<a class='link'>?</a>"}, hedRefiner).onclick = function() {
2407 var pa = this.parentNode.parentNode.parentNode;
2408 createFloatingPanel(this, true, "refhelp", "Search Refiner help", {left:(pa.offsetWidth + 4) + "px"}).innerHTML = [
2409 "You can use normal tags and combinations to further discard posts:",
2410 "<ul><li>" + [
2411 "Refine by date: <span class='dtext-mono'>day:[1-31]</span>, <span class='dtext-mono'>month:[1-12]</span> / <span class='dtext-mono'>month:[January-December]</span> and <span class='dtext-mono'>year:[####]</span>",
2412 "Refine by specific date: <span class='dtext-mono'>date:[1-12]/day/year</span> or <span class='dtext-mono'>date:[January-December]/day/year</span>",
2413 "Rating: <span class='dtext-mono'>rating:[s, q, e]</span> or <span class='dtext-mono'>rating:[safe, questionable, explicit]</span>",
2414 "Score: <span class='dtext-mono'>score:##</span>",
2415 "Uploader: <span class='dtext-mono'>user:[user name]</span>"
2416 ].join("</li><li>")
2417 ].join("<br/>");
2418 };
2419
2420 createElement("span", {className:"searchhelp", style:{marginLeft:"1ex"}, title:"Unsupported metatags", innerHTML:"<a class='link'>!</a>"}, hedRefiner).onclick = function() {
2421 var pa = this.parentNode.parentNode.parentNode;
2422 createFloatingPanel(this, true, "refunsupp", "Search Refiner unsupported metatags", {left:(pa.offsetWidth + 4) + "px"}).innerHTML = [
2423 "These metatags: <span class='dtext-mono'>" + ["type", "source", "description</span>/<span class='dtext-mono'>desc", "pool", "set", "notes", "hassource</span>/<span class='dtext-mono'>hasdescription", "width", "height", "favcount", "views", "mpixels", "ratio", "filesize", "comments", "gentags</span>/<span class='dtext-mono'>arttags</span>/<span class='dtext-mono'>chartags</span>/<span class='dtext-mono'>speciestags", "status", "parent", "ischild</span>/<span class='dtext-mono'>isparent", "inpool", "ratinglocked</span>/<span class='dtext-mono'>notelocked", "hideanon</span>/<span class='dtext-mono'>hidegoogle"].join("</span>, <span class='dtext-mono'>") + "</span>",,
2424 "And the following are not supported:<ul><li>" + [
2425 "Range syntax",
2426 "Has either/or tag: <span class='dtext-mono'>~cat ~dog</span>",
2427 "Who favorited a post: <span class='dtext-mono'>fav:username</span>",
2428 "Who voted on a post: <span class='dtext-mono'>voted:Username</span>, <span class='dtext-mono'>votedup:Username</span> and <span class='dtext-mono'>voteddown:Username</span>",
2429 "Janitor, Mod or Admin actions: <span class='dtext-mono'>approvedby:Admin</span> and <span class='dtext-mono'>deletedby:Admin</span>",
2430 "Tag subscription: <span class='dtext-mono'>sub:Username</span> or <span class='dtext-mono'>sub:Username:SubscriptionName</span>",
2431 "Sorting: <span class='dtext-mono'>order</span>",
2432 "Tag types: <span class='dtext-mono'>artist</span>, <span class='dtext-mono'>character</span>, <span class='dtext-mono'>copyright</span> and <span class='dtext-mono'>species</span> (due to limitations, all are treated as general)"
2433 ].join("</li><li>") + "</li></ul>",
2434 ].join("<br/>");
2435 };
2436
2437 // Highlight blacklisted posts
2438 if(getSetting("hiliteBlPosts")) {
2439 var postThumbs = document.getElementById("quick-edit").nextElementSibling.getElementsByTagName("span"), thumb;
2440
2441 for(var i = 0; i < postThumbs.length; i++) {
2442 thumb = postThumbs[i];
2443 if(thumb.classList.contains("blacklisted"))
2444 thumb.classList.add("blacklistedPost");
2445 }
2446 }
2447
2448 // List of common tags
2449 var ctList = createElement("div", {id:"commonTags", style:{display:(storageGet("showCommonTags", true) ? "block" : "none")}}, commonTags);
2450
2451 if(posts.length == 0) createElement("p", {innerHTML:"Uh..."}, ctList); else
2452 if(posts.length == 1) createElement("p", {innerHTML:"Really?"}, ctList);
2453 else {
2454 var tags = [];
2455 tags.find = function(what) {
2456 for(var i = 0; i < this.length; i++) {
2457 if(this[i].name == what) return i;
2458 }
2459
2460 return -1;
2461 };
2462
2463 var searchTerms = tagSearch.value.replace(/ +/g, " ").split(" "), p, t;
2464
2465 for(p = 0; p < posts.length; p++) {
2466 var postTags = posts[p].value.tags;
2467 for(t = 0; t < postTags.length; t++) {
2468 var i = tags.find(postTags[t]);
2469 if(searchTerms.indexOf(postTags[t]) == -1) {
2470 if(i == -1)
2471 tags.push({name:postTags[t], count:1});
2472 else
2473 tags[i].count++;
2474 }
2475 }
2476 }
2477
2478 var li;
2479
2480 tags.sort(function(a, b) {
2481 if(a.count < b.count) return 1;
2482 if(a.count > b.count) return -1;
2483 return 0;
2484 });
2485
2486 for(var i = 0; i < tags.length; i++) {
2487 t = tags[i];
2488
2489 if(i < 30 && t.count != (getSetting("allPostsInCommonTags") ? posts.length : -1)) {
2490 createElement("div", {innerHTML:'<a href="/wiki/show?title=' + t.name + '">?</a> <a href="/post/index?tags=' + t.name + '+' + tagSearch.value + '">+</a> <a href="/post/index?tags=-' + t.name + '+' + tagSearch.value + '">–</a> <a class="link" onClick="blacklistTag(\'' + t.name + '\')">X</a> <a href="/post/index?tags=' + t.name + '">' + t.name + '</a><span class="post-count" style="float:right;">' + t.count + '</span>'}, ctList);
2491 }
2492 }
2493 }
2494 }
2495
2496 } else
2497 if(loc[1] == "pool") { // LOC/pool
2498 if(loc[2] == "create") { // LOC/pool/create
2499 // We're creating a new pool
2500 createFormattingButtons("pool_description");
2501 } else
2502 if(loc[2] == "show") { // LOC/pool/show
2503 var cnt = createElement("div", {style:{cssFloat:"right"}});
2504 var pool = findElement("pool-show");
2505 pool.insertBefore(cnt, pool.firstChild);
2506 createElement("span", {innerHTML:"Read pool and: "}, cnt);
2507 createElement("input", {type:"button", value:"Track", style:{marginRight:"1ex"}, onclick:function() {initPool(loc[3], this, 0);}}, cnt);
2508 createElement("input", {type:"button", value:"Keep", onclick:function() {initPool(loc[3], this, 1);}}, cnt);
2509 createElement("span", {innerHTML:" or "}, cnt);
2510 createElement("input", {type:"button", value:"Skip to last", onclick:function() {initPool(loc[3], this, 2);}}, cnt);
2511 } else
2512 if(loc[2] == "order") { // LOC/pool/order
2513 var cont = findElement("content"), tbody = document.querySelector("div#content form table tbody");
2514 var styl = createElement("style", {type:"text/css", innerHTML:"tr, td {display:inline-block} tr td:last-child {display:block} tr {border:1px solid rgba(0,0,0,0);}"}, cont);
2515 cont.insertBefore(styl, cont.firstChild);
2516 cont.ondragstart = dndStart;
2517 window.dragend = function() {
2518 if(dragEle) dragEle.style.opacity = "";
2519 var inps = document.querySelectorAll("div#content form table tbody input");
2520 logIt("reo", "Reordering...", inps.length + " tiles");
2521 for(var i = 0; i < inps.length; i++) {
2522 inps[i].value = i;
2523 tbody.appendChild(inps[i].parentNode.parentNode);
2524 }
2525 };
2526
2527 window.orderAutoFill = function() {
2528 var i = 0;
2529
2530 $$(".pp").each(function(x) {
2531 x.value = i++;
2532 });
2533 }
2534
2535 window.orderReverse = function() {
2536 var posts = $$(".pp");
2537 var i = posts.length - 1;
2538 posts.each(function(x) {
2539 x.value = i--;
2540 tbody.insertBefore(x.parentNode.parentNode, tbody.firstChild);
2541 });
2542 }
2543
2544 cont.children[2].innerHTML += " You can also drag and drop thumbnails to reorder them.";
2545
2546 var tiles = document.querySelectorAll("form table tr");
2547 for(var i = 2; i < tiles.length; i++) {
2548 var tile = tiles[i];
2549 tile.draggable = true;
2550 tile.dropzone = "move string:text/plain";
2551 tile.className = "dndtile";
2552 tile.ondrop = dndDrop;
2553 tile.ondragover = dndOver;
2554 tile.ondragleave = dndLeave;
2555 }
2556 }
2557 } else
2558 if(loc[1] == "dmail") { // LOC/dmail
2559 var namebox = findElement("dmail_to_name") || findElement("to_name");
2560 var lastDMailed = storageGet("lastDMailed", "");
2561 userslist = (lastDMailed ? lastDMailed.split(",") : []);
2562
2563 var showNamesList = function() {
2564 var disRect = getRect(this);
2565 var fp = createFloatingPanel(this, false, null, "", {top:(disRect.t + disRect.h) + "px", minHeight:"50%"});
2566 fp.className += " contained";
2567 if(userslist.length == 0) {
2568 fp.style.textAlign = "center";
2569 if(getSetting("showdmails")) {
2570 fp.innerHTML = "<h5 style='color:#AAA' id='ee_check'><No users recently PM'd></h5><small>Click to fetch from your sent PMs</small>";
2571 fp.addEventListener("click", findUsersPMd, true);
2572 } else
2573 fp.innerHTML = "<h5 style='color:#AAA' id='ee_check'><No users recently PM'd></h5>";
2574 } else {
2575 var dis = this;
2576 for(var i = 0; i < userslist.length; i++) {
2577 var userItem = createElement("h5", {style:{"position":"relative"}, className:"option"}, fp);
2578
2579 var userName = createElement("div", {innerHTML:userslist[i], onclick:function() {
2580 dis.value = this.innerHTML;
2581 if(loc[2] != "inbox") findElement("dmail_title").focus();
2582 }}, userItem);
2583
2584 createElement("span", {className:"removeUserPMd", innerHTML:"X", name:userslist[i], title:'Remove "' + userslist[i] + '"', onmouseup:function(event) {
2585 event.stopPropagation();
2586 userslist.splice(userslist.indexOf(this.name), 1);
2587 storageSet("lastDMailed", userslist.join(","));
2588 var parent = this.parentNode;
2589 parent.parentNode.removeChild(parent);
2590 }}, userItem);
2591 }
2592 }
2593 };
2594
2595 namebox.addEventListener("click", showNamesList, true);
2596 var fromname = findElement("from_name");
2597 if(fromname != null) fromname.addEventListener("click", showNamesList, true);
2598
2599 if(anyOf(loc[2], ["show","compose"])) { // LOC/dmail/show LOC/dmail/compose
2600 // We're responding to a PM
2601 createFormattingButtons("dmail_body");
2602 document.forms[0].addEventListener("submit", function() {
2603 var sendTo = namebox.value.replace(/ /g, "_");
2604 if(sendTo != "") {
2605 var usrpos = userslist.findItem(sendTo);
2606 if(usrpos != -1) userslist.splice(usrpos, 1);
2607 userslist.unshift(sendTo);
2608 storageSet("lastDMailed", userslist.join(","));
2609 }
2610 }, true);
2611 }
2612 } else
2613 if(loc[1] == "comment") { // LOC/comment
2614 var commentBoxes = findByTag("textarea");
2615 for(i = 0; i < commentBoxes.length; i++){
2616 var ta = commentBoxes[i];
2617 if(/reply-text-/.test(ta.id) || ta.id == "comment_body") {
2618 createFormattingButtons(ta.id);
2619 }
2620 }
2621 } else
2622 if(anyOf(loc[1], ["take_down","takedown"]) && loc[2] == "show") { // LOC/take_down/show LOC/takedown/show
2623 var list = findElement("content").firstElementChild.lastElementChild;
2624 var posts = list.querySelectorAll("a");
2625 var postIDs = [];
2626 var postNum = 0;
2627
2628 postIDs.indexOf = function(o) {
2629 var index = -1;
2630
2631 for(var i = 0; i < this.length; i++) {
2632 if(this[i].url == o.url) {
2633 index = i;
2634 break;
2635 }
2636 }
2637
2638 return index;
2639 };
2640
2641 for(var i = 0; i < posts.length; i++) {
2642 var a = posts[i];
2643 var id = a.innerHTML.replace(/[^0-9]+/g, "");
2644 var newo = {"url":a.href, "id":parseInt(id)};
2645 if(id && postIDs.indexOf(newo) == -1) postIDs.push(newo);
2646 }
2647
2648 if(postIDs.length != 0) {
2649 var thumbs = createElement("div", {}, list);
2650
2651 var getTakedownPosts = function() {
2652 var item = postIDs[postNum];
2653
2654 query("/post/show.json?id=" + item.id, "GET", function(post, x, t) {
2655 logIt("takedown#" + loc[3], "Checking post #" + post.id, (post.status?"Status: " + post.status:"Probably deleted"));
2656
2657 if(post.status && post.status != "deleted") {
2658 var space = createElement("span", {className:"thumb", style:{display:"inline-block"}}, thumbs);
2659
2660 createElement("a", {href:item.url, target:"_blank", className:"tooltip-thumb", onmouseover:function() {borosimageswitch("si" + post.id);}, innerHTML:
2661 '<img class="preview" id="i' + post.id + '" alt="' + makeTooltip(post) + '" title="' + makeTooltip(post) + '" src="' + post.preview_url + '" width="' + post.preview_width + '" height="' + post.preview_height + '" />' +
2662 '<span class="post-score" style="display:block">#' + post.id + '</span>'
2663 }, space);
2664
2665 thumbs.appendChild(document.createTextNode(" "));
2666 }
2667
2668 postNum++;
2669 if(postNum < postIDs.length)
2670 getTakedownPosts();
2671 }, "takedown", function(code) {
2672 postNum++;
2673 if(postNum < postIDs.length)
2674 getTakedownPosts();
2675 });
2676 };
2677
2678 getTakedownPosts();
2679 }
2680 } else
2681 if(loc[1] == "set" && loc[2] == "new") { // LOC/set/new
2682 // We're creating a new set
2683 createFormattingButtons("set_description");
2684 } else
2685 if(loc[1] == "set" && loc[2] == "post_list") { // LOC/set/post_list
2686 var holdr = findElement("content");
2687 createElement("p", {innerHTML:"Drag and drop the thumbnails to reorder them"}, holdr);
2688 createElement("style", {innerHTML:"#content .section {display:none;} span.thumb {border:1px solid rgba(0,0,0,0);}"}, holdr);
2689 var cont = createElement("div", {style:{textAlign:"center"}, ondragstart:dndStart}, holdr);
2690 createElement("input", {type:"button", value:"Save", onclick:function() {
2691 var list = findElement("set_posts"), thumbs = cont.childNodes, reslt = [];
2692 for(var i = thumbs.length-1; i >= 0; i--) {
2693 var t = thumbs[i];
2694 if(t.nodeName == "SPAN" && t.className == "thumb") reslt.push(t.id.substr(1));
2695 }
2696 list.value = reslt.join("\n") + "\n";
2697 list.parentNode.submit();
2698 }}, holdr);
2699 window.dragend = function() {if(dragEle) dragEle.style.opacity = "";};
2700 var anm = animatedIndicator(cont, "h1");
2701
2702 var page = 1, posts = [], getSetPosts = function() {
2703 query("/post/index.json?tags=set%3A" + loc[3] + "&page=" + page, "GET", function(j, x, t) {
2704 if(j.length == 0) {
2705 fillList();
2706 } else {
2707 posts = posts.concat(j);
2708 page++;
2709 getSetPosts();
2710 }
2711 }, "setPosts");
2712 },
2713 fillList = function() {
2714 window.clearInterval(anm);
2715 cont.innerHTML = "";
2716 for(var i = 0; i < posts.length; i++) {
2717 var p = posts[i], tile = createElement("span", {draggable:true, dropzone:"move string:text/plain", className:"thumb", id:"p" + p.id, ondrop:dndDrop, ondragover:dndOver,ondragleave:dndLeave}, cont),
2718 span = createElement("a", {className:"tooltip-thumb", href:"/post/show/" + p.id, target:"_blank"}, tile), tt = makeTooltip(p);
2719 createElement("img", {id:"i" + p.id, className:"preview", src:p.preview_url, width:p.preview_width, height:p.preview_height, alt:tt, title:tt}, span);
2720 createElement("span", {className:"post-score link", style:{display:"block"}, innerHTML:"[Remove]", onclick:function(event){event.preventDefault();cont.removeChild(this.parentNode.parentNode);}}, span);
2721 }
2722 };
2723 getSetPosts();
2724 } else
2725 if(loc[1] == "wiki" && anyOf(loc[2], ["add", "new", "edit"])) { // LOC/wiki/add LOC/wiki/edit
2726 // We're editing a page on the wiki
2727 createFormattingButtons("wiki_page_body");
2728 } else
2729 if(loc[1] == "tag_alias" && loc[2] == "new") { // LOC/tag_alias/new
2730 // We're suggesting a tag alias
2731 var ra = createFormattingButtons("tag_alias_reason");
2732 createCheckTag(ra.parentNode);
2733 } else
2734 if(loc[1] == "tag_implication" && loc[2] == "new") { // LOC/tag_implication/new
2735 // We're suggesting an implication
2736 var ra = createFormattingButtons("tag_implication_reason");
2737 createCheckTag(ra.parentNode);
2738 } else
2739 if(loc[1] == "ticket" && loc[2] == "new") { // LOC/ticket/new
2740 // We're filing a report
2741 createFormattingButtons("ticket_reason");
2742 } else
2743 if(loc[1] == "artist" && anyOf(loc[2], ["create","update"])) { // LOC/artist/create LOC/artist/update
2744 // We're creating a new entry for an artist
2745 createFormattingButtons("artist_notes");
2746 document.forms[0].addEventListener("submit", function() {
2747 var urls = findElement("artist_urls"), lines = urls.value.split("\n");
2748 if(lines.length == 1) return; // Don't "sort" one liners
2749 var sortedUrls = [], su = lines.sort(function(a, b) {
2750 if(a < b) return -1;
2751 if(a > b) return 1;
2752 return 0;
2753 });
2754
2755 for(var sa = 0; sa < su.length; sa++) for(var sb = 0; sb < su.length; sb++)
2756 if(su[sa] != su[sb] && sortedUrls.indexOf(su[sa]) == -1) sortedUrls.push(su[sa]);
2757
2758 urls.value = sortedUrls.join("\n");
2759 });
2760 } else
2761 if(loc[1] == "user_record") { // LOC/user_record
2762 createFormattingButtons("user_record_body");
2763 }
2764
2765 /****************************************************************************************************************
2766 ****************************************************************************************************************/
2767 function createCheckTag(tbParent) {
2768 var tagsBox = findElement("tag_alias_name") || findElement("tag_implication_predicate") || findElement("post_tags");
2769 // Check tag
2770 var checkTheTag = function() {
2771 if(tfTag.value != "") {
2772 tfTag.value = tfTag.value.toLowerCase();
2773 this.disabled = true;
2774 var tim = animatedIndicator(tagFindInfo, "span");
2775 query("/tag/index.json?name=" + encodeURIComponent(tfTag.value), "GET", function(json, xml, text) {
2776 window.clearInterval(tim);
2777 searchTag.disabled = false;
2778 if(json.length == 0) {
2779 tagFindInfo.innerHTML = "<div>No posts use this tag</div><div id='ee_isaliased'></div>";
2780 var eeIsAliased = findElement("ee_isaliased");
2781 var tim2 = animatedIndicator(eeIsAliased, "span", "Looking up if it's aliased");
2782
2783 // Look up if tag is aliased
2784 query("/tag_alias/index.json?order=tagcount&query=" + encodeURIComponent(tfTag.value), "GET", function(j1, x1, t1) {
2785 window.clearTimeout(tim2);
2786
2787 var tagIndex = 0;
2788 for(; tagIndex < j1.length; tagIndex++)
2789 if(j1[tagIndex].name === tfTag.value)
2790 break;
2791
2792 if(tagIndex == j1.length) eeIsAliased.innerHTML = "Nope! Tag does not exist."; else {
2793 tim2 = animatedIndicator(eeIsAliased, "span", "One moment, please");
2794 query("/tag/show.json?id=" + j1[tagIndex].alias_id, "GET", function(j2, x2, t2) {
2795 window.clearTimeout(tim2);
2796
2797 var postCount = j2.count, tagName = j2.name;
2798 tagFindInfo.innerHTML = "<p>" + tfTag.value + " is aliased to "" + tagName + "" and is present on " + postCount + " post" + (postCount == 1 ? " is" : "s") + "</p>" +
2799 "<div><input type='button' style='margin-right:1ex;' onClick='window.open(\"/post/index?tags=" + tagName + "\")' value='Posts' />" +
2800 "<input type='button' onClick='window.open(\"/wiki/show?title=" + tagName + "\")' value='Wiki' /></div>";
2801 }, "tagsearch_alias_step2");
2802 }
2803 }, "tagsearch_alias");
2804 } else {
2805 var num = json[0].count;
2806 tagFindInfo.innerHTML = "<p>" + (num == 0 ? "No" : num) + " post" + (num==1?" is":"s are") + " tagged with "" + tfTag.value + ""</p>" +
2807 "<div><input type='button' style='margin-right:1ex;' onClick='window.open(\"/post/index?tags=" + tfTag.value + "\")' value='Posts' />" +
2808 "<input type='button' onClick='window.open(\"/wiki/show?title=" + tfTag.value + "\")' value='Wiki' /></div>";
2809 }
2810 }, "tagsearch");
2811 } else {
2812 tagFindInfo.innerHTML = "<em>Please provide a tag to search for</em>";
2813 }
2814 },
2815 messageButton = function(msg) {
2816 addTag.value = msg;
2817 addTag.disabled = true;
2818 window.setTimeout(function() {
2819 addTag.value = "Add";
2820 addTag.disabled = false;
2821 }, 5000);
2822 },
2823 addTheTag = function() {
2824 var t = tagsBox.value;
2825 var tags = t.replace(/ +/g, " ").split(" ");
2826 var tag = tfTag.value;
2827 if(tag.length == 0) {
2828 messageButton("What tag?");
2829 } else
2830 if(tags.indexOf(tag) == -1) {
2831 if(tagsBox.id == "tag_alias_name") {
2832 tagsBox.value = tag;
2833 tagsBox = findElement("tag_alias_alias");
2834 } else
2835 if(tagsBox.id == "tag_alias_alias") {
2836 tagsBox.value = tag;
2837 tagsBox = findElement("tag_alias_name");
2838 } else
2839 if(tagsBox.id == "tag_implication_predicate") {
2840 tagsBox.value = tag;
2841 tagsBox = findElement("tag_implication_consequent");
2842 } else
2843 if(tagsBox.id == "tag_implication_consequent") {
2844 tagsBox.value = tag;
2845 tagsBox = findElement("tag_implication_predicate");
2846 } else {
2847 tagsBox.value += (t.charAt(t.length-1) == " " ? tag : " " + tag);
2848 tagsBox.focus();
2849 countTags();
2850 }
2851 tfTag.value = "";
2852 } else {
2853 messageButton("Tag already exists");
2854 }
2855 };
2856
2857 var lastTag = "", grpTagFind = createElement("fieldset", {}, tbParent);
2858 createElement("legend", {innerHTML:"<label for='ee_tagSearch'>Check tag</label>"}, grpTagFind);
2859 createElement("div", {innerHTML:"Use this field to look for a tag if you're not sure whether it is used or not"}, grpTagFind);
2860
2861 var tfTag = createElement("input", {id:"ee_tagSearch", type:"text", size:"24", style:{marginRight:"1ex"}, onkeydown:function(event) {
2862 if(event.keyCode == 13) {
2863 event.preventDefault();
2864 if(lastTag != this.value) {checkTheTag();} else {addTheTag();}
2865 lastTag = this.value;
2866 }
2867 }, onkeyup:function(event) {
2868 var oldpos = this.selectionStart;
2869 if(event.keyCode == 32) this.value = this.value.replace(/ +/g, "_");
2870 if(event.keyCode == 13) event.preventDefault();
2871 this.selectionStart = oldpos;
2872 }}, grpTagFind);
2873
2874 var searchTag = createElement("input", {type:"button", value:"Check", style:{marginRight:"1ex"}, onclick:checkTheTag}, grpTagFind);
2875 var addTag = createElement("input", {type:"button", value:"Add", style:{marginRight:"1ex"}, onclick:addTheTag}, grpTagFind);
2876 var tagFindInfo = createElement("div", {}, grpTagFind);
2877 }
2878
2879 // Run the subscriptions updater
2880 if((rightNow() > storageGet("subscriptionsNextCheck", 0) && (!(loc[1] == "post" && loc[2] == "show") || getSetting("checkOnPosts"))))
2881 window.setTimeout(function(){checkSubsUpdates();}, 50);
2882
2883 // Formatting buttons
2884 function createFormattingButtons(textareaID) {
2885 var responseArea = findElement(textareaID);
2886
2887 if(responseArea) {
2888 var raHolder = responseArea.parentNode;
2889 while(raHolder.nodeName != "BODY" && ["section"].indexOf(raHolder.className) == -1) {
2890 if(raHolder.style.width != "") raHolder.style.width = "";
2891 raHolder = raHolder.parentNode;
2892 }
2893
2894 // A tad higher if it doesn't have any size defined
2895 if(!responseArea.style.height) responseArea.style.height = Math.max(responseArea.offsetHeight, 200) + "px";
2896 responseArea.removeAttribute("rows"); // Remove cols and rows attributes
2897 responseArea.removeAttribute("cols");
2898 responseArea.style.width = ""; // Clear the width so the box fits the entire container
2899
2900 var newButton, tbDiv = createElement("div");
2901 var fbtns = createElement("div", {className:"formattingButtons", id:"fbtns_" + textareaID});
2902
2903 createElement("input", {type:"button", value:"B", title:"Bold", style:{fontWeight:"bold"}, onclick:function(){responseArea.add("[b]", "[/b]", "");}}, tbDiv);
2904 createElement("input", {type:"button", value:"I", title:"Italic", style:{fontStyle:"italic"}, onclick:function(){responseArea.add("[i]", "[/i]", "");}}, tbDiv);
2905 createElement("input", {type:"button", value:"S", title:"Strike", style:{textDecoration:"line-through"}, onclick:function(){responseArea.add("[s]", "[/s]", "");}}, tbDiv);
2906 createElement("input", {type:"button", value:"U", title:"Underline", className:"dtext-underline", onclick:function(){responseArea.add("[u]", "[/u]", "");}}, tbDiv);
2907 createElement("input", {type:"button", value:"O", title:"Overline", className:"dtext-overline", onclick:function(){responseArea.add("[o]", "[/o]", "");}}, tbDiv);
2908 createElement("input", {type:"button", value:"Spoiler", title:"Spoil something", onclick:function(){responseArea.add("[spoiler]", "[/spoiler]", "");}}, tbDiv);
2909
2910 createElement("input", {type:"button", value:"Link", title:"Add a link to another page", onclick:function() {
2911 var selection = responseArea.getSelection();
2912 var fp = createFloatingPanel(this, true);
2913 fp.innerHTML = '<label for="url">Enter the URL of the page' +
2914 (selection.text != null ? ' for<div style="box-shadow:1px 2px 4px #000 inset; border-radius:4px; padding:4px;">' + selection.text + '</div>' : ':') + '</label><br/>' +
2915 '<input type="url" id="url" value="" size="50" /><br/>' +
2916 '<div id="grpTitle"><label for="title">Title</label><br/>' +
2917 '<input type="text" id="title" value="" size="50" /></div>' +
2918 '<div style="text-align:center; margin-top:1em"><input type="button" id="okay" value="OK" /></div>';
2919
2920 findElement("grpTitle").style.display = (selection.hasSelection ? "block" : "none");
2921 var burl = findElement("url"), btitle = findElement("title"), okbtn = findElement("okay");
2922 burl.addEventListener("keyup", function(e){if(e.keyCode == 13) btitle.focus();}, true);
2923 btitle.addEventListener("keyup", function(e){if(e.keyCode == 13) okbtn.click();}, true);
2924 okbtn.addEventListener("click", function() {
2925 var url = findElement("url").value;
2926 var title = findElement("title").value;
2927 if(url != "") responseArea.add('"', '":' + url, title);
2928 fp.close();
2929 }, true);
2930
2931 fp.adjust();
2932 findElement("url").focus();
2933 }}, tbDiv);
2934
2935 // Just Priv+ users (above level 20) can enjoy this. Unless you're editing or creating a set
2936 if(storageGet("userLV", 0) > 20 || (loc[1] == "set" && anyOf(loc[2], ["new","edit"]))) {
2937 createElement("input", {type:"button", value:"Color", title:"Colorize text", onclick:function() {
2938 var c = ["#000","#808080","#800000","#808000","#008000","#008080","#000080","#800080","#808040","#004040","#0080FF","#004080","#8000FF","#804000","#FFF","#CCC","#F00","#FF0","#0F0","#0FF","#00F","#F0F","#FFFF80","#00FF80","#80FFFF","#8080FF","#FF0080","#FF8040"];
2939 var cb = createFloatingPanel(this, false);
2940 cb.style.lineHeight = "1px";
2941
2942 for(var i = 0; i < c.length; i++) {
2943 createElement("span", {className:"colorTile", style:{backgroundColor:c[i]}, color:c[i], onclick:function() {
2944 responseArea.add("[color=" + (this.color) + "]", "[/color]", "");
2945 }}, cb);
2946
2947 if(i + 1 == c.length/2) createElement("br", {}, cb);
2948 }
2949 }}, tbDiv);
2950 }
2951
2952 if(!getSetting("singleline")) createElement("br", {}, tbDiv);
2953
2954 createElement("input", {type:"button", value:"Sup", title:"Superscript", onclick:function(){responseArea.add("[sup]", "[/sup]", "");}}, tbDiv);
2955 createElement("input", {type:"button", value:"Sub", title:"Subscript", onclick:function(){responseArea.add("[sub]", "[/sub]", "");}}, tbDiv);
2956 createElement("input", {type:"button", value:"Heading", title:"Add a heading", onclick:function() {
2957 var cb = createFloatingPanel(this, false);
2958 cb.style.textAlign = "center";
2959
2960 for(var i = 0; i < 6; i++) {
2961 createElement("h" + (i + 1), {className:"option", innerHTML:"H" + (i + 1), val:i + 1, onclick:function() {
2962 responseArea.add("h" + this.val + ".", "", "");
2963 }}, cb);
2964 }
2965 cb.adjust();
2966 }}, tbDiv);
2967
2968 createElement("input", {type:"button", value:"Wiki", title:"Add a link to an entry page on the wiki", onclick:function() {
2969 var selection = responseArea.getSelection();
2970 var fp = createFloatingPanel(this, true);
2971 fp.innerHTML = '<div id="grpWikipage"><label for="wikipage">Wiki Page:</label><br/>' +
2972 '<input type="text" id="wikipage" value="" /><br/></div>' +
2973 '<label for="wikititle">Title:</label><br/>' +
2974 '<input type="text" id="wikititle" value="" placeholder="(Optional)" />' +
2975 '<div style="text-align:center; margin-top:1em"><input type="button" id="okay" value="OK" /></div>';
2976
2977 var wikipage = findElement("wikipage"), wikititle = findElement("wikititle"), okbtn = findElement("okay");
2978 wikipage.addEventListener("keyup", function(e){if(e.keyCode == 13) okbtn.click();}, true);
2979 wikititle.addEventListener("keyup", function(e){if(e.keyCode == 13) okbtn.click();}, true);
2980 okbtn.addEventListener("click", function() {
2981 var wpage = findElement("wikipage").value || selection.text;
2982 var wtitle = findElement("wikititle").value;
2983 if(wpage != "" || wtitle != "") responseArea.add("[[", "]]", wpage + (wtitle == "" ? "" : "|" + wtitle));
2984 fp.close();
2985 }, true);
2986
2987 fp.adjust();
2988 findElement("grpWikipage").style.display = (selection.hasSelection ? "none" : "block");
2989 findElement(selection.hasSelection ? "wikititle" : "wikipage").focus();
2990 }}, tbDiv);
2991
2992 createElement("input", {type:"button", value:"Tag", title:"Add a link to a tag search", onclick:function(){responseArea.add("{{", "}}", "");}}, tbDiv);
2993
2994 createElement("input", {type:"button", className:"fbAlt", value:"Quote", title:"Insert a quote block",
2995 onclick:function(){responseArea.add("[quote]", "[/quote]", "");},
2996 oncontextmenu:function(e){e.preventDefault();responseArea.add("[/quote]", "[quote]", "");}
2997 }, tbDiv);
2998
2999 createElement("input", {type:"button", className:"fbAlt", value:"Code", title:"Insert a piece of code", onclick:function(){responseArea.add("[code]", "[/code]", "");}, oncontextmenu:function(){responseArea.add("`", "`", "");return false;}}, tbDiv);
3000
3001 createElement("input", {type:"button", value:"Section", title:"Separate with a section", onclick:function() {
3002 var fp = createFloatingPanel(this, true);
3003 fp.innerHTML = '<label for="stitle">Section title:</label><br/>' +
3004 '<input type="text" id="stitle" value="" size="30" placeholder="(Optional)" />' +
3005 '<br/><input type="checkbox" id="expand" /><label for="expand">Expand by default</label>' +
3006 '<div style="text-align:center; margin-top:1em"><input type="button" id="okay" value="OK" /></div>';
3007
3008 var stitle = findElement("stitle"), okbtn = findElement("okay");
3009 stitle.addEventListener("keyup", function(e) {
3010 if(e.keyCode == 13) okbtn.click();
3011 }, true);
3012
3013 okbtn.addEventListener("click", function() {
3014 var stitle = findElement("stitle").value;
3015 var expand = findElement("expand").checked;
3016 responseArea.add("[section" + (expand ? ",expanded" : "") + (stitle != "" ? "=" + stitle : "") + "]", "[/section]", "");
3017 fp.close();
3018 }, true);
3019 fp.adjust();
3020 okbtn.focus();
3021 }}, tbDiv);
3022
3023 if((storageGet.userLV, 50) && getSetting("admindtext") && (loc[1] == "forum")) {
3024 createElement("br", {}, tbDiv);
3025 createElement("input", {type:"button", value:"Alias:", title:"Create an alias template (this would be replaced with a simple text label if I knew how :X)", style:{fontWeight:"bold"}, onclick:function(){responseArea.add("I've made the following changes:\n\n* Alias: [[one]] -> [[two]] ([color=#3E9E49]approved[/color])\n", "", "");}}, tbDiv);
3026 createElement("input", {type:"button", value:"+app", title:"Add an approval line", style:{color:"white", background:"#288233"}, onclick:function(){responseArea.add("* Alias: [[one]] -> [[two]] ([color=#3e9e49]approved[/color])\n", "", "");}}, tbDiv);
3027 createElement("input", {type:"button", value:"+pend", title:"Add a pending line", style:{color:"white", background:"#808080"}, onclick:function(){responseArea.add("* Alias: [[one]] -> [[two]] ([color=#e4e150]pending[/color])\n", "", "");}}, tbDiv);
3028 createElement("input", {type:"button", value:"+deny", title:"Add a denied line", style:{color:"white", background:"red"}, onclick:function(){responseArea.add("* Alias: [[one]] -> [[two]] ([color=#e45f5f]denied[/color])\n", "", "");}}, tbDiv);
3029 createElement("input", {type:"button", value:"+del", title:"Add a deleted line", style:{color:"white", background:"#822828"}, onclick:function(){responseArea.add("* Alias: [[one]] -> [[two]] ([color=#e45f5f]deleted[/color])\n", "", "");}}, tbDiv);
3030
3031 if(!getSetting("singleline")) createElement("br", {}, tbDiv);
3032 createElement("input", {type:"button", value:"Implication:", title:"Create an implication template (this would be replaced with a simple text label if I knew how :X)", style:{fontWeight:"bold"}, onclick:function(){responseArea.add("I've made the following changes:\n\n* Implication: [[one]] -> [[two]] ([color=#3E9E49]approved[/color])\n", "", "");}}, tbDiv);
3033 createElement("input", {type:"button", value:"+app", title:"Add an approval line", style:{color:"white", background:"#288233"}, onclick:function(){responseArea.add("* Implication: [[one]] -> [[two]] ([color=#3e9e49]approved[/color])\n", "", "");}}, tbDiv);
3034 createElement("input", {type:"button", value:"+pend", title:"Add a pending line", style:{color:"white", background:"#808080"}, onclick:function(){responseArea.add("* Implication: [[one]] -> [[two]] ([color=#e4e150]pending[/color])\n", "", "");}}, tbDiv);
3035 createElement("input", {type:"button", value:"+deny", title:"Add a denied line", style:{color:"white", background:"red"}, onclick:function(){responseArea.add("* Implication: [[one]] -> [[two]] ([color=#e45f5f]denied[/color])\n", "", "");}}, tbDiv);
3036 createElement("input", {type:"button", value:"+del", title:"Add a deleted line", style:{color:"white", background:"#822828"}, onclick:function(){responseArea.add("* Implication: [[one]] -> [[two]] ([color=#e45f5f]deleted[/color])\n", "", "");}}, tbDiv);
3037
3038 if(!getSetting("singleline")) createElement("br", {}, tbDiv);
3039 createElement("input", {type:"button", value:"approve", title:"Approve something", style:{color:"white", background:"#288233", fontWeight:"bold"}, onclick:function(){responseArea.add("[color=#3e9e49]Approved[/color]", "", "");}}, tbDiv);
3040 createElement("input", {type:"button", value:"deny", title:"Deny something", style:{color:"white", background:"red", fontWeight:"bold"}, onclick:function(){responseArea.add("[color=#e45f5f]Denied[/color]", "", "");}}, tbDiv);
3041 createElement("input", {type:"button", value:"delete", title:"Delete something", style:{color:"white", background:"#822828", fontWeight:"bold"}, onclick:function(){responseArea.add("[color=#e45f5f]deleted[/color]", "", "");}}, tbDiv);
3042 }
3043
3044 if(getSetting("submitOnCtrlEnter")) {
3045 var raform = responseArea.form, fsub = raform[0], fsn = 0;
3046 while(!(fsub.nodeName == "INPUT" && fsub.type == "submit") && fsn < raform.length) fsub = raform[fsn++];
3047
3048 responseArea.addEventListener("keydown", function(event) {
3049 if(event.ctrlKey && event.keyCode == 13) this.dataset.submitOnRelease = "yah";
3050 });
3051
3052 responseArea.addEventListener("keyup", function(event) {
3053 if(event.keyCode == 17 && this.dataset.submitOnRelease) {
3054 if(fsub) fsub.click(); else raform.submit();
3055 delete this.dataset.submitOnRelease;
3056 }
3057 });
3058 }
3059
3060 fbtns.appendChild(tbDiv);
3061 responseArea.parentNode.insertBefore(fbtns,responseArea);
3062
3063 // Formatting buttons' docking functions
3064 var dock = [window.innerWidth - 315, window.innerWidth - 173, window.innerWidth - 94], dm = 72;
3065 var popout = function(manualDocking) {
3066 var btop = fbtns.firstChild.getBoundingClientRect().top - 4;
3067 var fpBtns = createFloatingPanel(fbtns.firstChild.firstChild, undefined, null, "", {position:"fixed", top:btop + "px", maxHeight:"100%", overflow:"hidden"});
3068 var fpp = fpBtns.parentNode;
3069 fpBtns.className = "formattingButtons 8x2";
3070 fbtns.style.height = fbtns.style.offsetHeight + "px";
3071 fpp.style.display = "block";
3072 fpp.ignore = true;
3073
3074 var setDocking = function(dl) {
3075 if(fbHandle.dl == dl.toString()) return;
3076 fpBtns.className = fpBtns.className.replace(/8x2|4x4|2x8|1x16/, ["4x4","2x8","1x16"][dl[0]] || "8x2");
3077
3078 var orect = getRect(tbDiv);
3079 fpp.style.left = dock[dl[0]] + "px";
3080 fpp.style.top = ((window.innerHeight - fpp.offsetHeight) / dl[1]) + "px";
3081 if(manualDocking) makeFrame(orect, getRect(fpp));
3082 fbHandle.dl = dl.toString();
3083 };
3084
3085 var fbHandle = createElement("div", {className:"fbtnsHandle handle"});
3086 fpp.insertBefore(fbHandle, fpBtns);
3087
3088 createElement("div", {style:{marginLeft:"6px"}}, fpBtns).appendChild(fbtns.firstChild);
3089 var fbDock = createElement("div", {className:"fbtnsHandle docker", onclick:function() {
3090 var gs = getSetting("ffbtnsLayout").substr(1), ps = gs.substr(0, 1), dl = [2,2];
3091 if(fpBtns.classList.contains("8x2")) dl[0] = 0; else
3092 if(fpBtns.classList.contains("4x4")) dl[0] = 1;
3093
3094 if(ps == "t") dl[1] = 4; else if(ps == "b") dl[1] = 1;
3095 setDocking(dl);
3096 }}, fpp);
3097
3098 if(getSetting("docking")) {
3099 var gs = getSetting("ffbtnsLayout"), ly = gs.substr(1), ps = gs.substr(0, 1), dl = [2,2];
3100 if(ly == "4x4") dl[0] = 0; else if(ly == "2x8") dl[0] = 1;
3101 if(ps == "t") dl[1] = 4; else if(ps == "b") dl[1] = 1;
3102 setDocking(dl);
3103 }
3104
3105 var dragMove = function(event) {
3106 dock = [window.innerWidth - 315, window.innerWidth - 173, window.innerWidth - 98]
3107 var th = window.innerHeight / 3;
3108 if(fbHandle.dragging) {
3109 var pos = {"x":(event.clientX - fbHandle.click.x), "y":(event.clientY-fbHandle.click.y)}, dl = [-1,2];
3110
3111 if(pos.x > dock[0]-dm) dl[0] = 0;
3112 if(pos.x > dock[1]-dm) dl[0] = 1;
3113 if(pos.x > dock[2]-dm) dl[0] = 2;
3114
3115 if(dl[0] == -1) {
3116 fpp.style.left = pos.x + "px";
3117 fpp.style.top = Math.min(pos.y, window.innerHeight - fpp.offsetHeight) + "px";
3118 fpBtns.className = fpBtns.className.replace(/8x2|4x4|2x8|1x16/, "8x2");
3119 } else {
3120 if(pos.y < th) dl[1] = 4;
3121 if(pos.y > th * 2) dl[1] = 1;
3122 setDocking(dl);
3123 }
3124 }
3125 },
3126 dragStop = function(event) {
3127 fbHandle.dragging = false;
3128 };
3129
3130 fbHandle.click = {"x":0, "y":0};
3131 fpp.ondblclick = function() {
3132 var orect = getRect(fpp);
3133 fpp.style.display = "none";
3134 fpBtns.close();
3135 fbtns.appendChild(fpBtns.firstChild.firstChild);
3136 document.body.removeEventListener("mousemove", dragMove, false);
3137 document.body.removeEventListener("mouseup", dragStop, false);
3138 makeFrame(orect, getRect(tbDiv));
3139 };
3140 fbHandle.onmousedown = function(event) {
3141 event.preventDefault();
3142 fbHandle.click = {"x":(event.clientX - fpp.offsetLeft), "y":(event.clientY - fpp.offsetTop)};
3143 fbHandle.dragging = true;
3144 return false;
3145 };
3146 document.body.addEventListener("mousemove", dragMove, false);
3147 document.body.addEventListener("mouseup", dragStop, false);
3148 };
3149
3150 fbtns.addEventListener("dblclick", function(){popout(true);});
3151 if(getSetting("docking")) popout(false);
3152
3153 logIt("fbtns", "Created formatting buttons for: \"" + textareaID + "\"", "");
3154 return responseArea;
3155 } else {
3156 logIt("fbtns", "Couldn't find textbox \"" + textareaID + "\" to add formatting buttons", "");
3157 return null;
3158 }
3159 }
3160
3161 /* add(open, close, preset)
3162 Adds 'open' at the end of the text that is inside the defined text area (before the selection,
3163 if it's any). Same for 'close', except that it will be after 'open' (after the selected text,
3164 again, if it was any).
3165
3166 Parameters:
3167 open The opening tag to be added before the selected text
3168 close The closing tag to be added after the selected text
3169 preset The text that has been inserted using a pop-up box. Currently, this is only used
3170 for the "Wiki" button. */
3171 HTMLTextAreaElement.prototype.add = function(open, close, preset) {
3172 var selection = this.getSelection();
3173 var scroll = this.scrollTop;
3174 var left = selection.before + open;
3175 var right = close + selection.after;
3176
3177 if(selection.hasSelection) {
3178 var ins = (preset ? selection.text + preset : selection.text);
3179 this.value = left + ins + right;
3180 this.selectionStart = left.length;
3181 this.selectionEnd = left.length + ins.length;
3182 } else {
3183 this.value = left + preset + right;
3184 this.selectionEnd = this.selectionStart = left.length;
3185 }
3186
3187 this.scrollTop = scroll;
3188 this.focus();
3189 };
3190
3191 /* getSelection()
3192 Returns an object with the selection start and end, the extracted text and if nothing is selected on the textarea element */
3193 HTMLTextAreaElement.prototype.getSelection = function() {
3194 var o = {start: this.selectionStart, end: this.selectionEnd};
3195 o.hasSelection = (o.start != o.end);
3196 o.text = (o.start != o.end ? this.value.substr(o.start, o.end - o.start) : "");
3197 o.before = this.value.substr(0, o.start);
3198 o.after = this.value.substr(o.end);
3199 return o;
3200 };
3201
3202 /* blacklistTag(tag)
3203 Adds to the user blacklist the tag defined or the current post ID if there is no tag */
3204 window.blacklistTag = function(tag) {
3205 if(confirm("Blacklist " + (tag ? "'" + tag.replace(/user:/g, "") + "'" : "post #" + postID) + "?") == true) {
3206 if(!tag) tag = "id:" + postID;
3207 var tags = storageGet("eeBlTagsList", "").split(" "), t2 = [];
3208 for(var i = 0; i < tags.length; i++) if(tags[i].length > 0) t2.push(tags[i]);
3209 tags = t2;
3210 if(tags.indexOf(tag) == -1) {
3211 tags.push(tag);
3212 storageSet("eeBlTagsList", tags.join(" "));
3213 var tn = tags.length, n = 0;
3214 for(var itn = 0; itn < tn; itn++) if(tags[itn].length > 0) n++;
3215 notice("Tag '" + tag + "' added to temporary blacklist" + helpIcon({reason:"The temporary blacklist contains a list of the tags to be added later to your blacklist.\n\nRight now, it holds " + n + " tag" + (n == 1 ? "" : "s")}));
3216 } else {
3217 error("Tag '" + tag + "' is already on the list");
3218 }/* --- Old tag blacklist code. Frozen ---
3219 var blTags = decodeURIComponent(cookies.blacklisted_tags).replace(/&/g, "\n");
3220 blTags = encodeURIComponent(blTags.replace(/\+/g, " ") + "\n" + tag).replace(/%0A/gi, "%0D%0A");
3221
3222 notice("Adding " + (tag ? "'" + tag.replace(/user:/g, "") + "'" : "post #" + postID) + " to blacklist");
3223 query("/user/update.json?user[blacklisted_tags]=" + blTags, "POST", function(j, x, t) {
3224 if(j.success == true) {
3225 notice((tag ? "'" + tag.replace(/user:/g, "") + "'" : "Post #" + postID) + " blacklisted");
3226 } else {
3227 error("Error blacklisting " + (tag ? "'" + tag.replace(/user:/g, "") + "'" : "post #" + postID) + (j.reason ? ": " + j.reason : ""));
3228 }
3229 }, "blacklistTag", function(code, j) {
3230 error("Error blacklisting " + (tag ? "'" + tag.replace(/user:/g, "") + "'" : "post #" + postID) + helpIcon(j));
3231 });/**/
3232 }
3233 };
3234
3235 /* fixUsername()
3236 Fixes the username (stored in cookies.login) by replacing spaces with an undescore and returns it */
3237 function fixUsername() {
3238 return (cookies.login ? cookies.login.toLowerCase().replace(" ","_") : "");
3239 }
3240
3241 /* writeSettings() - Writes the settings into the localStorage */
3242 function writeSettings() {
3243 if(window.localStorage) {
3244 var setts = {};
3245 for(var s in defset) if(defset[s] != undefined && settings[s] != undefined) setts[s] = settings[s]; // Remove unused/undefined entries
3246
3247 // Store the settings
3248 settings = setts;
3249 settings.saveVersion = defset.saveVersion;
3250 storageSet("settings", settings);
3251
3252 // NOTICE: Lines commented with "[**]" should'nt be changed. Doing so will cause screwups when saving settings!
3253
3254 // Save the custom links
3255 var arr = [], itm, ob, lst = findElement("lstLinks").childNodes;
3256
3257 for(var i = 0; i < lst.length; i++) if(lst[i].classList.contains("ubLink")) arr.push(asJSON(lst[i].ref));
3258 storageSet("ubLinks", "[" + arr + "]"); // [**]
3259
3260 // Save subscriptions
3261 arr = []; lst = findElement("lstForums").childNodes;
3262 for(var i = 0; i < lst.length; i++) arr.push(lst[i].value);
3263 storageSet("forumSubscriptions", "[" + arr + "]"); // [**]
3264
3265 arr = []; lst = findElement("lstPools").childNodes;
3266 for(var i = 0; i < lst.length; i++) arr.push(lst[i].value);
3267 storageSet("poolSubscriptions", "[" + arr + "]"); // [**]
3268
3269 // Let the user know that the settings have been saved
3270 var i = findElement("ee_msgSaved"), m = i.style;
3271 m[cssPrefix("transition", true)] = "";
3272 m.width = "auto";
3273 m.width = i.offsetWidth + "px";
3274 window.setTimeout(function() {
3275 m[cssPrefix("transition", true)] = "width 250ms ease 0ms";
3276 m.width = "0px";
3277 }, 1000);
3278 } else {
3279 window.alert("WebStorage is disabled in your browser. Your settings will not be saved");
3280 }
3281 findElement("ee_btnSave").disabled = true;
3282 }
3283
3284 /* createFloaingPanel(parent, closeButton, fpid, fpTitle, fpStyle)
3285 Creates a new floating panel with a close button (if specified) and a DIV and returns the DIV
3286 to add a content.
3287
3288 Parameters:
3289 parent The parent object from which retrieve its position and size
3290 closeButton Indicates if a close button should be used (true) instead of closing the panel
3291 by clicking anywhere else (false)
3292 fpid An identifier to avoid spawining several instances of the same panel
3293 fpTitle Text to use as title for the panel
3294 fpStyle Style override for the panel. Used mainly to position the panel itself */
3295 function createFloatingPanel(parent, closeButton, fpid, fpTitle, fpStyle) {
3296 var floatingPanel = findElement(fpid);
3297 if(floatingPanel) {
3298 maximizePanel(floatingPanel.parentNode);
3299 return floatingPanel;
3300 }
3301
3302 var rect = {x:0, y:0, w:64, h:16};
3303
3304 if(parent != null) {
3305 rect = getRect(parent);
3306 rect.l -= 6;
3307 rect.t -= 6;
3308 }
3309
3310 var getDatecode = function() {
3311 var d = new Date();
3312 var cs = Math.floor(d.getTime() / 100);
3313 var datecode = (cs % 100) | (d.getSeconds() << 8) | (d.getMinutes() << 14) | (d.getHours() << 20);
3314 return datecode;
3315 };
3316
3317 floatingPanel = createElement("div", {className:"floatingPanel rounded", style:{display:(closeButton != null ? "block" : "none"), left:rect.l + "px", top:rect.t + "px", minWidth:rect.w + "px", minHeight:rect.h + "px", zIndex:getDatecode()}, onmousedown:function() {
3318 this.style.zIndex = getDatecode();
3319 }}, document.body);
3320 if(fpStyle) for(var p in fpStyle) {floatingPanel.style[p] = fpStyle[p];}
3321
3322 var ixsysbtns = createElement("small", {className:"ixsysbtn"}), thnd;
3323
3324 if(fpTitle) {
3325 createElement("small", {className:"close minimize", innerHTML:"_", title:"Minimize", onclick:function() {
3326 var p = floatingPanel;
3327 p.dataset.old_x = p.offsetLeft + "px";
3328 p.dataset.old_y = p.offsetTop + "px";
3329 minimizePanel(p);
3330 }}, ixsysbtns);
3331 createElement("h3", {className:"panelTitle", innerHTML:fpTitle}, floatingPanel);
3332 thnd = createElement("div", {className:"panelTitleHandle", onmousedown:dndStart, onclick:function(){if(floatingPanel.classList.contains("minimized")) maximizePanel(floatingPanel)}});
3333 }
3334
3335 floatingPanel.appendChild(ixsysbtns);
3336 if(thnd) floatingPanel.appendChild(thnd);
3337
3338 var floatingContent = createElement("div", {className:"floatingContent"}, floatingPanel);
3339 if(fpid) floatingContent.id = fpid;
3340
3341 // Add utility functions
3342 floatingContent.center = function() {
3343 var dis = this.parentNode;
3344 window.setTimeout(function() {
3345 dis.style.display = "block";
3346 dis.style.left = ((window.innerWidth/2)-(dis.offsetWidth/2)) + "px";
3347 dis.style.top = ((window.innerHeight/2)-(dis.offsetHeight/2)) + "px";
3348 }, 1);
3349 };
3350
3351 floatingContent.addCloseButton = function(beforeClose) {
3352 createElement("small", {className:"close", innerHTML:"✖", title:"Close", onclick:function() {if(beforeClose) beforeClose(); floatingContent.close(null);}}, ixsysbtns);
3353 floatingContent.ignore = true;
3354 };
3355
3356 floatingContent.close = function(beforeClose) {
3357 if(floatingPanel) {
3358 if(beforeClose) beforeClose();
3359 floatingPanel.style.opacity = 0;
3360 window.setTimeout(function() {if(floatingPanel) {floatingPanel.parentNode.removeChild(floatingPanel); floatingPanel = null;}}, 250);
3361 }
3362 };
3363
3364 floatingContent.isEmpty = function() {
3365 return (floatingContent.innerHTML.length == 0);
3366 };
3367
3368 floatingContent.adjust = function() {
3369 var minx = window.innerWidth - floatingPanel.offsetWidth - 20, miny = window.innerHeight - floatingPanel.offsetHeight - 20, fs = floatingPanel.style;
3370 if(floatingPanel.offsetLeft > minx) fs.left = minx + "px";
3371 if(floatingPanel.offsetTop > miny) fs.top = miny + "px";
3372 };
3373
3374 switch(closeButton) {
3375 case true: floatingContent.addCloseButton(null); break;
3376 case false: document.body.addEventListener("click", hidePanels, true); break;
3377 case null: floatingContent.center(); break;
3378 }
3379
3380 return floatingContent;
3381 }
3382
3383 /* hidePanels()
3384 Hides (removes from the document, actually) all floating panels that are in the page */
3385 function hidePanels() {
3386 var i, fps = document.querySelectorAll(".floatingPanel .floatingContent");
3387
3388 for(i = 0; i < fps.length; i++) {
3389 var fp = fps[i];
3390 if(fp && !fp.ignore) fp.close();
3391 }
3392
3393 document.body.removeEventListener("click", hidePanels, true);
3394 }
3395
3396 function animatedIndicator(elem, tag, msg) {
3397 if(!elem) return -1;
3398
3399 var sstp = 0;
3400
3401 if(tag) elem.innerHTML = "<" + tag + ">" + ssym[0] + " " + (msg || "Please wait...") + "</" + tag + ">";
3402 else elem.value = ssym[0] + " " + (msg || "Please wait...");
3403
3404 return window.setInterval(function() {
3405 sstp = (sstp == 3 ? 0 : sstp + 1);
3406 if(tag) elem.innerHTML = "<" + tag + ">" + ssym[sstp] + " " + (msg || "Please wait...") + "</" + tag + ">";
3407 else elem.value = ssym[sstp] + " " + (msg || "Please wait...");
3408 }, 250);
3409 }
3410
3411 function initPool(id, elem, mode, onLoad) {
3412 if(elem) elem.disabled = true;
3413
3414 id = parseInt(id);
3415 var stim = animatedIndicator(elem, null), page = 1, poolPostIDs = [], poolPosts = [], poolName = "",
3416 loadPool = function() {
3417 query("/pool/show.json?id=" + id + "&page=" + page, "GET", function(json, xml, text) {
3418 if(json.posts.length != 0) {
3419 for(var i = 0; i < json.posts.length; i++) {
3420 poolPostIDs.push(json.posts[i].id);
3421 poolPosts.push(json.posts[i]);
3422 }
3423
3424 page++;
3425 loadPool();
3426 } else {
3427 poolName = json.name;
3428 poolReady();
3429 }
3430 }, "poolpager");
3431 },
3432
3433 poolReady = function() {
3434 window.clearInterval(stim);
3435 if(elem)
3436 if(elem.id == "ee_btnStop") {
3437 elem.value = "Stop reading";
3438 elem.disabled = false;
3439 findElement("totalPosts").innerHTML = poolPosts.length;
3440 } else
3441 elem.value = "Ready";
3442
3443 var postID = (loc[1] == "post" && loc[2] == "show" ? loc[3] : poolPosts[0].id), targetPost = (mode == 2 ? poolPostIDs[poolPostIDs.length - 1] : postID);
3444 var postIdx = poolPosts.findItem(function(item) {if(item.id == postID) return true;});
3445 updatePoolSubscriptions({
3446 "id":id,
3447 "name":poolName,
3448 "posts":poolPostIDs,
3449 "last":targetPost,
3450 "keep":mode == 1,
3451 "thumb":{
3452 "url":poolPosts[postIdx].preview_url.match(/\/[0-9a-f]{2}\/[0-9a-f]{2}\/[0-9a-f]{32}.*$/),
3453 "width":poolPosts[postIdx].preview_width,
3454 "height":poolPosts[postIdx].preview_height
3455 }
3456 });
3457
3458 if(onLoad != 0) {
3459 if(loc[1] == "post" && loc[2] == "show") {
3460 setPoolSpace();
3461 if(onLoad && onLoad != 0) onLoad();
3462 } else
3463 location.assign("/post/show/" + targetPost);
3464 }
3465 };
3466
3467 loadPool();
3468 }
3469
3470 function setPoolSpace() {
3471 var subs = getPoolSubscriptions();
3472 var poolSpc = null, active, pool = subs.findItem(function(item) {poolSpc = findElement("pool" + item.id);return (poolSpc != null);});
3473
3474 if(poolSpc) {
3475 var su = subs[pool], posts = su.posts, index = posts.indexOf(Number(loc[3])), pk = getKeySymbol(getSetting("poolPrevKey")), nk = getKeySymbol(getSetting("poolNextKey"));
3476 var poolNav = createElement("div", {dataset:{ref:asJSON(su)}, style:{textAlign:"center"}}, poolSpc),
3477 active = createElement("div", {innerHTML:
3478 (index > 0 ? "<span style='float:left' id='ee_poolPrevPage' title='Press \"" + pk + "\" to go to the previous page'>◄<span class='navikey'>" + pk + "</span></span>" : "") +
3479 "Page " + (index == -1 ? "?" : index + 1) + " of <span id='totalPosts'>" + posts.length + "</span>" +
3480 (index<posts.length-1 ? "<span style='float:right' id='ee_poolNextPage' title='Press \"" + nk + "\" to go to the next page'><span class='navikey'>" + nk + "</span>►</span>" : "")
3481 }, poolNav);
3482 var btnStop = createElement("input", {type:"button",className:"small",value:"Stop Reading",id:"ee_btnStop",style:{display:"block",margin:"1em auto 0px"}}, active);
3483
3484 if(index == posts.length-2 && su.keep != true) initPool(su.id, btnStop, 0, 0);
3485
3486 var stopReading = function() {
3487 updatePoolSubscriptions(su, true);
3488 $j(active).slideUp(function(){poolNav.parentNode.removeChild(poolNav);});
3489 $j("#track-pool-" + su.id).slideDown();
3490 };
3491
3492 btnStop.addEventListener("click", stopReading, true);
3493
3494 document.body.addEventListener("keydown", function(event) {
3495 var tag = event.target.tagName, nextPost, kc = getKeyCode(event);
3496
3497 if(["INPUT","TEXTAREA","OBJECT"].indexOf(tag) == -1 && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
3498 if(kc == getSetting("poolPrevKey")) {
3499 if(index == 0) {
3500 error("You're in the first post of the pool");
3501 } else {
3502 notice("Switching to previous post");
3503 nextPost = posts[index-1]; subs[pool].last = nextPost;
3504 savePoolSubscriptions(subs); // Update position in the pool
3505 findElement("ee_poolPrevPage").className = "greentext";
3506 location.assign("/post/show/" + nextPost);
3507 }
3508 } else
3509 if(kc == getSetting("poolStopKey")) stopReading(); else
3510 if(kc == getSetting("poolNextKey")) {
3511 if(index == posts.length-1) {
3512 error("You're in the last post of the pool");
3513 } else {
3514 notice("Switching to next post");
3515 nextPost = posts[index + 1]; subs[pool].last = nextPost;
3516 savePoolSubscriptions(subs); // Update position in the pool
3517 findElement("ee_poolNextPage").className = "greentext";
3518 location.assign("/post/show/" + nextPost);
3519 }
3520 }
3521 }
3522 }, false);
3523 }
3524 }
3525
3526 function findUsersPMd() {
3527 var page = 1, userIDs = [], userIDindex = 0, fp2 = createFloatingPanel(null, null, null),
3528 fpTitle = createElement("div", {}, fp2), fpMessage = createElement("div", {}, fp2);
3529 fp2.style.textAlign = "center";
3530
3531 var getPMs = function() {
3532 logIt("pms", "Getting PMs", "");
3533 query("/dmail/inbox.json?page=" + page + "&visibility=all", "GET", function(json, xml, text) {
3534 if(json.length > 0) {
3535 for(var i = 0; i < json.length; i++) {
3536 var uid = json[i].to_id;
3537 if(userIDs.indexOf(uid) == -1) userIDs.push(uid);
3538 }
3539 page++;
3540 getPMs();
3541 } else {
3542 fpMessage.innerHTML = "Finding users";
3543 findUser();
3544 }
3545 }, "pmdpager");
3546 };
3547
3548 var findUser = function() {
3549 if(userIDs.length == 0) {
3550 logIt("usrs", "Finding users", "No users found");
3551 window.clearInterval(stim);
3552 fp2.center();
3553 fp2.innerHTML = "<h3 class='panelTitle'>No users found</h3><small>Have you PM'd somebody lately?</small>";
3554 var tth = window.setTimeout(hidePanels, 2000);
3555 document.body.addEventListener("click", function() {
3556 if(tth) window.clearTimeout(tth);
3557 hidePanels();
3558 }, true);
3559 } else {
3560 logIt("usrs", "Finding user", "User ID: " + userIDs[userIDindex]);
3561 query("/user/index.json?id=" + userIDs[userIDindex], "GET", function(json, xml, text) {
3562 var user = json[0];
3563 if(userIDs[userIDindex] == user.id && userslist.indexOf(user.name) == -1 && user.id != storageGet("userID")) userslist.push(user.name);
3564 userIDindex++;
3565
3566 if(userIDindex < userIDs.length) {
3567 findUser();
3568 } else {
3569 window.clearInterval(stim);
3570 fp2.center();
3571 fpTitle.innerHTML = "<h2>Done</h2>";
3572 fpMessage.innerHTML = userslist.length + " users found";
3573 var tth = window.setTimeout(hidePanels, 2000);
3574 document.body.addEventListener("click", function() {
3575 if(tth) window.clearTimeout(tth);
3576 hidePanels();
3577 }, true);
3578 logIt("usrs", "Users list", userslist.length + " users");
3579 storageSet("lastDMailed", userslist);
3580 }
3581 }, "userspager");
3582 }
3583 };
3584
3585 var stim = animatedIndicator(fpTitle, "h2");
3586 getPMs();
3587 fpMessage.innerHTML = "Checking PMs";
3588 }
3589
3590 function checkSubsUpdates(onEnd) {
3591 var pIndex = 0, fIndex = 0, url, page = 1, subsP = [], subsF = [];
3592 var previousTitle = document.title, sstim, sub, totalSubs, aok = 0;
3593
3594 var next = function() {
3595 var forumsMode = (pIndex == subsP.length), fstr = (forumsMode ? "forum" : "pool");
3596
3597 // We're done when subscription indexes are the same as their numbers
3598 if(pIndex == subsP.length && fIndex == subsF.length) {
3599 var upds = countSubsUpdates(), sulnk = findElement("subs_updates");
3600 if(sulnk) sulnk.innerHTML = "<b>S</b>" + upds;
3601 if(onEnd) onEnd();
3602 storageSet("subscriptionsNextCheck", rightNow() + (getSetting("checkInterval") * 60000)); // Refresh the date
3603 window.clearInterval(sstim);
3604 document.title = previousTitle;
3605
3606 var msg = "Finished checking for updates. " + (aok != totalSubs ? aok + " / " + totalSubs : "");
3607 if(upds != 0) msg += " <a class='link' id='eeShowUpdates'>Click to view</a>";
3608 notice(msg);
3609 var eeShowUpdates = findElement("eeShowUpdates");
3610 if(eeShowUpdates != null) eeShowUpdates.onclick = showSubsPanel;
3611
3612 return;
3613 }
3614
3615 if(forumsMode) sub = subsF[fIndex]; else sub = subsP[pIndex];
3616 logIt("chksub_" + fstr, "Subscription updater (P: " + (pIndex + 1) + "/" + subsP.length + "; F: " + (fIndex + 1) + "/" + subsF.length + ")", "Checking " + fstr + ' "' + sub.name.replace(/_/g, " ") + '"');
3617 checkSubscriptionUpdate(sub, onCheckEnd);
3618 },
3619 onCheckEnd = function(isError) {
3620 if(!isError) aok++;
3621 if(pIndex == subsP.length) fIndex++; else pIndex++;
3622 next();
3623 };
3624
3625 // Load all subscriptions
3626 subsP = getPoolSubscriptions();
3627 subsF = getForumSubscriptions();
3628
3629 if(subsP.length > 0 || subsF.length > 0) {
3630 // Prevent other tabs or windows from checking for updates
3631 storageSet("subscriptionsNextCheck", rightNow() + 600000);
3632 totalSubs = subsP.length + subsF.length;
3633
3634 var sstp = 0;
3635 solt = document.title;
3636 sstim = window.setInterval(function() {
3637 sstp = (sstp == 3 ? 0 : sstp + 1);
3638 document.title = ssym[sstp] + " " + solt;
3639 }, 250);
3640
3641 if(!subsP[0]) mode = 1;
3642 warning("Checking subscriptions");
3643 next();
3644 }
3645 }
3646
3647 function checkSubscriptionUpdate(sub, onEnd) {
3648 var isPool = !!sub.thumb, url, page = 1, tmp, timeSinceLastUpdate = sub.last_updated || rightNow();
3649
3650 if(isPool) {
3651 if(sub.keep == true) {
3652 if(onEnd) onEnd();
3653 return;
3654 }
3655
3656 url = "/pool/show.json?id=" + sub.id;
3657 sub.posts = [];
3658 } else {
3659 url = "/forum/index.json?parent_id=" + sub.id;
3660 tmp = [];
3661 }
3662
3663 var check = function() {
3664 query(url + "&page=" + page, "GET", function(j, x, t) {
3665 var list = (isPool ? j.posts : j);
3666
3667 if(list.length == 0) {
3668 sub.id = parseInt(sub.id);
3669
3670 if(isPool) {
3671 sub.last = parseInt(sub.last);
3672 sub.last_updated = j.updated_at.s;
3673 if(settings.freezePools) {
3674 var updated_at = j.updated_at.s * 1000;
3675 var daysSinceUpdate = Math.floor((rightNow() - updated_at) / 86400000); // There are 86'400,000 seconds in a day. Neat, huh?
3676 sub.keep = (daysSinceUpdate > settings.freezeAfterDays);
3677 }
3678 updatePoolSubscriptions(sub);
3679 } else {
3680 if(tmp.length == 0) {// Thread has no messages
3681 sub.newest_nm = "Nobody";
3682 sub.newest_id = sub.id;
3683 sub.pages = 1;
3684 sub.last_id = sub.id;
3685 } else {
3686 lastItem = tmp[0];
3687 sub.newest_nm = lastItem.creator;
3688 sub.newest_id = parseInt(lastItem.id);
3689 sub.pages = Math.ceil(tmp.length / 30) || 1;
3690 sub.last_id = parseInt(sub.last_id);
3691 }
3692
3693 updateForumSubscriptions(sub);
3694 }
3695
3696 if(onEnd) onEnd(false);
3697 } else {
3698 if(isPool) {
3699 for(var i = 0; i < list.length; i++) sub.posts.push(list[i].id);
3700 lastItem = list[list.length - 1];
3701 sub.thumb = {
3702 url: lastItem.preview_url.replace(preview_baseurl, ""),
3703 width: parseInt(lastItem.preview_width),
3704 height:parseInt(lastItem.preview_height)
3705 };
3706 } else {
3707 for(var i = 0; i < list.length; i++)
3708 tmp.push({creator:list[i].creator, id:list[i].id});
3709 }
3710
3711 page++;
3712 check();
3713 }
3714 }, "chk_" + (isPool ? "pool" : "forum"), function(code, j) {
3715 if(onEnd) onEnd(true);
3716 });
3717 };
3718
3719 check();
3720 }
3721
3722 function updatePoolSubscriptions(ns, remove) {
3723 var subs, index;
3724
3725 if(storageGet("poolSubscriptions", "[]") != "[]") {
3726 subs = getPoolSubscriptions();
3727 index = subs.findItem(function(idx) {return (idx.id == ns.id);});
3728
3729 if(remove) subs.splice(index, 1); else
3730 if(index != -1) subs[index] = ns; else
3731 subs.push(ns);
3732 } else {
3733 subs = [ns];
3734 }
3735
3736 savePoolSubscriptions(subs);
3737 }
3738
3739 function updateForumSubscriptions(ns, remove) {
3740 var subs, index;
3741
3742 if(storageGet("forumSubscriptions", "[]") != "[]") {
3743 subs = getForumSubscriptions();
3744 index = subs.findItem(function(idx) {return (idx.id == ns.id);});
3745
3746 if(remove) subs.splice(index, 1); else
3747 if(index != -1) subs[index] = ns; else
3748 subs.push(ns);
3749 } else {
3750 subs = [ns];
3751 }
3752
3753 saveForumSubscriptions(subs);
3754 }
3755
3756 function savePoolSubscriptions(subs) {
3757 storageSet("poolSubscriptions", subs);
3758 }
3759
3760 function saveForumSubscriptions(subs) {
3761 storageSet("forumSubscriptions", subs);
3762 }
3763
3764 function isSubUpdated(sub) {
3765 if(sub.posts && sub.last != sub.posts[sub.posts.length-1])
3766 return true;
3767 else
3768 if((sub.newest_nm != cookies.login) && (sub.last_id < sub.newest_id))
3769 return true;
3770
3771 return false;
3772 }
3773
3774 function countSubsUpdates() {
3775 var cnt = 0, i, itm, sub;
3776
3777 sub = getPoolSubscriptions();
3778 for(i = 0; i < sub.length; i++) {
3779 itm = sub[i];
3780 if(isSubUpdated(itm) && itm.keep != true) cnt++;
3781 }
3782
3783 sub = getForumSubscriptions();
3784 for(i = 0; i < sub.length; i++) {
3785 itm = sub[i];
3786 if(isSubUpdated(itm)) cnt++;
3787 }
3788
3789 return cnt;
3790 }
3791
3792 function showSubsPanel() {
3793 var snum = countSubsUpdates();
3794 var lst = createFloatingPanel(null, null, "subscriptionsPanel", "Subscriptions" + (snum == 0 ? "" : "<span id='supd8'> (" + snum + ")</span>"), {position:"fixed", width:"45%", height:"50%"});
3795 if(lst.isEmpty()) {
3796 // Prepare the subscriptions
3797 var subsP = getPoolSubscriptions();
3798 var subsF = getForumSubscriptions();
3799
3800 var sfunc = function(a,b){a=a.name.toLowerCase();b=b.name.toLowerCase();if(a<b)return -1;if(a>b)return 1;return 0;};
3801 subsP.sort(sfunc);subsF.sort(sfunc); // Sort subscriptions alphabetically
3802 subsP.sort(function(a, b) { // Sort pool subscriptions: active ones first
3803 if(a.keep && !b.keep) return 1;
3804 if(!a.keep && b.keep || a.keep == b.keep) return -1;
3805 return 0;
3806 });
3807 var supd8 = findElement("supd8");
3808
3809 var dp = findElement("notice").style;
3810 if(getSetting("enhanceNoticeError")) dp.top = "-100%"; else dp.display = "none";
3811
3812 lst.addCloseButton();
3813 lst.className += " contained";
3814
3815 var lstUpdates = createElement("div", {innerHTML:"<h6 style='margin-top:1ex'>Updated:</h6>"}, lst);
3816
3817 var ups, sub, idx, upd8, itm;
3818 var lstPools = createElement("div", {innerHTML:"<h6 style='margin-top:1ex'>Pools:</h6>"}, lst);
3819 for(ups = 0; ups < subsP.length; ups++) {
3820 sub = subsP[ups];
3821 idx = sub.posts.findItem(sub.last);
3822 upd8 = isSubUpdated(sub) && !sub.keep;
3823 itm = createElement("a", {className:"pref subsUpdate" + (upd8 ? " subsUpdated" : ""), href:"/post/show/" + sub.last, target:"_blank", onmouseup:function() {
3824 this.className = this.className.replace(/ ?subsUpdated/g, "");
3825 if(supd8) {
3826 snum--;
3827 supd8.innerHTML = (snum == 0 ? "" : " (" + snum + ")");
3828 }
3829 }, innerHTML:sub.name.replace(/_/g, " ")}, upd8 ? lstUpdates : lstPools);
3830 createElement("small", {style:{cssFloat:"right",margin:"0ex 1ex"}, innerHTML:(sub.keep ? "<em>(Kept)</em> " : "") + "Post: " + (idx == -1 ? "?" : idx + 1) + "/" + sub.posts.length}, itm);
3831 }
3832
3833 var lstForums = createElement("div", {innerHTML:"<h6 style='margin-top:1ex'>Forums:</h6>"}, lst);
3834 upds = 0;
3835 for(ups = 0; ups < subsF.length; ups++) {
3836 sub = subsF[ups];
3837 upd8 = isSubUpdated(sub);
3838 itm = createElement("a", {className:"pref subsUpdate" + (upd8 ? " subsUpdated" : ""), href:"/forum/show/" + sub.id + "?page=" + sub.pages + "#post-" + sub.last_id, target:"_blank", onmouseup:function() {
3839 this.className = this.className.replace(/ ?subsUpdated/g, "");
3840 if(supd8) {
3841 snum--;
3842 supd8.innerHTML = (snum == 0 ? "" : " (" + snum + ")");
3843 }
3844 }, innerHTML:sub.name}, upd8 ? lstUpdates : lstForums);
3845 createElement("small", {style:{display:"block",margin:"1ex 1ex 0px 0px",textAlign:"right"}, innerHTML:"Updated by: " + (sub.newest_nm ? sub.newest_nm : "Nobody")}, itm);
3846 }
3847
3848 // Hide empty lists
3849 if(lstPools.children.length == 1) lstPools.style.display = "none";
3850 if(lstForums.children.length == 1) lstForums.style.display = "none";
3851 if(lstUpdates.children.length == 1) lstUpdates.style.display = "none";
3852 }
3853 }
3854
3855 function makeTooltip(post) {
3856 return post.tags +
3857 "\n\nRating: " + (post.rating == "s" ? "Safe" : (post.rating == "q" ? "Questionable" : "Explicit")) +
3858 (typeof post.score == "number" ? "\nScore: " + post.score : "") +
3859 "\nUser: " + post.author +
3860 (post.created_at ? "\nDate: " + parse_eSix_time(post.created_at) : "");
3861 }
3862
3863 function parse_eSix_time(time) {
3864 var d = new Date(time.s*1000), dd = d.getDate();
3865 var mm = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3866 return mm[d.getMonth()] + " " + (dd < 10 ? "0" + dd : dd) + ", " + d.getFullYear();
3867 }
3868
3869 // Update user data (Name, level and ID)
3870 function updateUserData() {
3871 // If the UserID and UserLevel were not saved already or if it's a different username...
3872 query("/user/index.json?name=" + fixUsername(), "GET", function(json, xml, text) { // ...retrieve the new data and store it.
3873 for(var i = 0; i < json.length; i++) {
3874 var id = json[i].id;
3875 var lv = json[i].level;
3876
3877 if(json[i].name == cookies.login) {
3878 storageSet("userNM", cookies.login);
3879 storageSet("userID", id);
3880 storageSet("userLV", lv);
3881
3882 notice("User ID, level and name stored");
3883 }
3884 }
3885 }, "userident");
3886 }
3887
3888 /**************************************
3889 ** Override site's default functions **
3890 **************************************/
3891 var collapseLongComments = getSetting("collapseLongComments");
3892
3893 if(collapseLongComments) {
3894 var maxlines = getSetting("collapseAboveLines");
3895
3896 function appendComment(box, comment) {
3897 var strippedBody = comment.body.replace(/\[quote\](?:.|\n|\r)+?\[\/quote\][\n\r]*/gm, "");
3898 var lines = strippedBody.split("\n").length, chars = strippedBody.length;
3899
3900 if(collapseLongComments && (lines > maxlines || chars > maxlines * 100))
3901 strippedBody = "[section]" + strippedBody + "[/section]";
3902
3903 box.value += '[quote]"' + comment.creator + '":/user/show/' + comment.creator_id + ' said:\n' + strippedBody + '\n[/quote]\n\n';
3904 }
3905
3906 Forum.quote = function(id) {
3907 query("/forum/show.json?id=" + id, "GET", function(j, x, t) {
3908 var rbox = findElement("forum_post_body");
3909 appendComment(rbox, j);
3910
3911 $j("#respond-link").fadeOut("fast");
3912 $j("#reply").fadeIn();
3913 rbox.focus();
3914 }, "forum.quote", function() {error("Error quoting forum post");});
3915 };
3916
3917 Comment.quote = function(id) {
3918 query("/comment/show.json?id=" + id, "GET", function(j, x, t) {
3919 var rbox = findElement("reply-text-" + j.post_id);
3920 appendComment(rbox, j);
3921
3922 $j("#reply-" + j.post_id).fadeIn();
3923 $j("#respond-link-" + j.post_id).fadeOut();
3924 rbox.focus();
3925 }, "comment.quote", function() {error("Error quoting comment");});
3926 }
3927 }
3928
3929 // Override default "notice()", "warning()" and "error()" functions, if allowed to
3930 if(getSetting("enhanceNoticeError")) {
3931 var divNotice = findElement("notice"), divWarning = findElement("warning"), divError = findElement("error");
3932
3933 if(divNotice) {
3934 window.notice = function(msg) {
3935 divNotice.innerHTML = "<div style='margin-right:2em'>" + msg + "</div><div style='cursor:pointer; position:absolute; right:10px; top:5px;' onclick='this.parentNode.style.top=\"-100%\"'>X</div>";
3936 if(divNotice.style.top == "0%") {
3937 divNotice.style.borderColor = "#0F0";
3938 divNotice.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms";
3939 window.setTimeout(function() {
3940 divNotice.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms, border-color 500ms ease 0ms";
3941 divNotice.style.borderColor = "";
3942 }, 1);
3943 } else {
3944 divNotice.style.top = "0%";
3945 }
3946 divError.style.top = "-100%";
3947 if(divWarning) divWarning.style.top = "-100%";
3948
3949 window.setTimeout(placeTitleIcons, 5);
3950 };
3951
3952 if(divNotice.style.display == "") {
3953 notice(divNotice.querySelector("p").innerHTML);
3954 }
3955 }
3956
3957 if(divWarning) {
3958 window.warning = function(msg) {
3959 divWarning.innerHTML = "<div style='margin-right:2em'>" + msg + "</div><div style='cursor:pointer; position:absolute; right:10px; top:5px;' onclick='this.parentNode.style.top=\"-100%\"'>X</div>";
3960 if(divWarning.style.top == "0%") {
3961 divWarning.style.borderColor = "#FF0";
3962 divWarning.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms";
3963 window.setTimeout(function() {
3964 divWarning.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms, border-color 500ms ease 0ms";
3965 divWarning.style.borderColor = "";
3966 }, 1);
3967 } else {
3968 divWarning.style.top = "0%";
3969 }
3970 divNotice.style.top = "-100%";
3971 divError.style.top = "-100%";
3972 };
3973
3974 if(divWarning.style.display == "") {
3975 warning(divWarning.querySelector("p").innerHTML);
3976 }
3977 }
3978
3979 if(divError) {
3980 window.error = function(msg) {
3981 divError.innerHTML = "<div style='margin-right:2em'>" + msg + "</div><div style='cursor:pointer; position:absolute; right:10px; top:5px;' onclick='this.parentNode.style.top=\"-100%\"'>X</div>";
3982 if(divError.style.top == "0%") {
3983 divError.style.borderColor = "#F00";
3984 divError.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms";
3985 window.setTimeout(function() {
3986 divError.style[cssPrefix("transition", true)] = "top 250ms ease-out 0ms, border-color 500ms ease 0ms";
3987 divError.style.borderColor = "";
3988 }, 1);
3989 } else {
3990 divError.style.top = "0%";
3991 }
3992 divNotice.style.top = "-100%";
3993 if(divWarning) divWarning.style.top = "-100%";
3994 };
3995
3996 if(divError.style.display == "") {
3997 error(divError.querySelector("p").innerHTML);
3998 }
3999 }
4000 }
4001
4002 /****************************
4003 ** Drag and Drop functions **
4004 ****************************/
4005 var dragEle = null, dblTime = null;
4006
4007 function minimizePanel(p) {
4008 var strip = findElement("minimizedPanels");
4009 if(strip == null)
4010 strip = createElement("div", {id:"minimizedPanels", style:{position:"fixed", left:"0px", bottom:"0px", right:"0px", zIndex:15}}, document.body);
4011
4012 var r = getRect(p);
4013 switchClassName(p, "minimized", true);
4014 strip.appendChild(p);
4015 makeFrame(r, getRect(p));
4016 }
4017
4018 function maximizePanel(p) {
4019 if(!p.classList.contains("minimized")) return;
4020 var r = getRect(p);
4021 switchClassName(p, "minimized", false);
4022 p.style.left = p.dataset.old_x;
4023 delete p.dataset.old_x;
4024 p.style.top = p.dataset.old_y;
4025 delete p.dataset.old_y;
4026 document.body.appendChild(p);
4027 makeFrame(r, getRect(p));
4028 }
4029
4030 function dndStart(event) {
4031 var t = event.target;
4032 if(t.className == "panelTitleHandle") {
4033 event.preventDefault();
4034 var p = t.parentNode;
4035
4036 var xp = p.offsetLeft, yp = p.offsetTop;
4037 if(!p.classList.contains("minimized")) {
4038 p.dataset.old_x = xp + "px";
4039 p.dataset.old_y = yp + "px";
4040 }
4041 dragEle = {panel:p,ox:(xp - event.clientX), oy:(yp - event.clientY)};
4042
4043 if(dblTime) {
4044 window.clearTimeout(dblTime);
4045 if(!p.classList.contains("minimized")) minimizePanel(p);
4046 dblTime = null;
4047 } else {
4048 dblTime = window.setTimeout(function() {dblTime = null}, 300);
4049 }
4050
4051 if(!window.onmousemove) window.onmousemove = function(event) {
4052 dblTime = null;
4053 if(dragEle && dragEle.panel && !dragEle.panel.classList.contains("minimized")) {
4054 var panel = dragEle.panel, ps = panel.style, xx = (event.clientX + dragEle.ox), yy = (event.clientY + dragEle.oy);
4055
4056 if(yy < 8) yy = 8; // Limit to minimum top position
4057
4058 if(panel.classList.contains("nomin")) {
4059 var ymax = window.innerHeight - panel.offsetHeight - 8;
4060 if(yy > ymax) yy = ymax;
4061 }
4062
4063 if(yy >= window.innerHeight - 24) {
4064 minimizePanel(panel);
4065 } else {
4066 ps.left = xx + "px";
4067 ps.top = yy + "px";
4068 }
4069 }
4070 };
4071
4072 if(!window.onmouseup) window.onmouseup = function() {dragEle = null;};
4073 } else {
4074 while(t && t.nodeName != "SPAN" && t.className != "thumb") t = t.parentNode;
4075 if(t && t.nodeName == "SPAN" && t.className == "thumb") {
4076 var dt = event.dataTransfer;
4077 dt.setData("text/plain", "thumb #" + t.id.substr(1));
4078 dragEle = (loc[1] == "pool" && loc[2] == "order" ? t.parentNode.parentNode : t);
4079 dragEle.style.opacity = 0.5;
4080 }
4081 }
4082 }
4083
4084 function dndDrop(event) {
4085 event.preventDefault();
4086 var data = event.dataTransfer.getData("text/plain");
4087 if(data.indexOf("thumb #") != -1) {
4088 if(loc[1] == "pool" && loc[2] == "order") cont = this.parentNode;
4089 dragEle.style.opacity = "";
4090 var ww = this.offsetWidth, mx = event.clientX, xx = this.offsetLeft, sp = ww / 2;
4091 if(mx < xx + sp) cont.insertBefore(dragEle, this);
4092 if(mx > xx + ww - sp) cont.insertBefore(dragEle, this.nextElementSibling);
4093 dragEle.style.outline = "";
4094 dragEle = null;
4095 }
4096 this.style.borderColor = "";
4097 if(window.dragend) window.dragend();
4098 }
4099
4100 function dndOver(event) {
4101 if(dragEle) {
4102 event.preventDefault();
4103 var ww = this.offsetWidth, mx = event.clientX, xx = this.offsetLeft, sp = ww / 2, st = this.style;
4104 if(dragEle == this) st.borderColor = "currentColor"; else
4105 if(mx < xx + sp) st.borderColor = "rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0) currentColor"; else
4106 if(mx > xx + ww - sp) st.borderColor = "rgba(0,0,0,0) currentColor rgba(0,0,0,0) rgba(0,0,0,0)";
4107 }
4108 }
4109
4110 function dndLeave() {this.style.borderColor = "rgba(0,0,0,0)";}
4111
4112 /****************************
4113 ** Miscellaneous functions **
4114 ****************************/
4115
4116 /* parseCookies()
4117 Parses all of the current website cookies and returns a JSON object based off of them for
4118 easier access to each cookie.
4119
4120 Returns:
4121 A JSON Object with all of the cookies' names as attributes of that object. Note that
4122 changing the value of an attribute of this object will not change the actual cookie.
4123 Unfortunately. For that, [document.cookie = "{cookie name}={value}";] is still an
4124 option. */
4125 function parseCookies() {
4126 var q = document.cookie.split("; "); // Open cookie jar and separate cookies
4127 var o = {}, m, i;
4128
4129 // Divide cookies
4130 for(i = 0; i < q.length; i++) {
4131 m = q[i].replace(/=/, ";").split(";");
4132 o[m[0]] = m[1];
4133 }
4134
4135 // Share cookies
4136 return o;
4137 }
4138
4139 function parseQuery(q) {
4140 var s = q || location.search, o = {}, p, i;
4141 s = s.substr(1).split("&")
4142
4143 for(i = 0; i < s.length; i++) {
4144 p = s[i].replace(/=/, "/").split("/");
4145 o[p[0]] = p[1];
4146 }
4147
4148 return o;
4149 }
4150
4151 /* query(url, method, callback, grouptag, onError)
4152 Makes a request for a resource on a website and executes the callback with the responseText
4153 and responseXML as a JSON Object and a DOM Object (respectively) as parameters.
4154
4155 Parameters:
4156 url The url of the resource to request
4157 method Method of request (GET / POST)
4158 callback The callback to execute when the request is received (State=200 and Status=4)
4159 grouptag Tag to group similar requests (to minimize DevBug entries)
4160 onError Callback to run when an error occurs */
4161 function query(url, method, callback, grouptag, onError) {
4162 if(!url || !callback) throw "Query error: URL or Callback are missing";
4163
4164 var req = new window.XMLHttpRequest(), reqto;
4165 var gtag = grouptag || "urlquery" + url;
4166
4167 var evalJSONstr = function(text) {
4168 try {return JSON.parse(text);}
4169 catch (except) {return null;}
4170 };
4171
4172 logIt(gtag, "Query: " + url + " (" + method + ")", "Request initiated");
4173 req.onreadystatechange = function() {
4174 if(req.readyState == 4) {
4175 window.clearTimeout(reqto);
4176
4177 switch(req.status) {
4178 case 200: {
4179 var text = req.responseText;
4180 logIt(gtag, "Query: " + url + " (" + method + ")", "Request OK");
4181
4182 try {
4183 callback(evalJSONstr(text), req.responseXML, text);
4184 } catch(except) {
4185 logIt("err_" + gtag, "Query: " + url, except);
4186 }
4187 break;
4188 }
4189
4190 case 403: logIt("er403." + gtag, "Query: " + url + " [Forbidden]", "Access denied"); if(onError) {onError(403, evalJSONstr(req.responseText))}; break;
4191 case 404: logIt("er404." + gtag, "Query: " + url + " [Not Found]", "Not found"); if(onError) {onError(404, evalJSONstr(req.responseText))}; break;
4192 case 420: logIt("er420." + gtag, "Query: " + url + " [Invalid Record]", "Record could not be saved"); if(onError) {onError(420, evalJSONstr(req.responseText))}; break;
4193 case 421: logIt("er421." + gtag, "Query: " + url + " [User Throttled]", "User is throttled, try again later"); if(onError) {onError(421, evalJSONstr(req.responseText))}; break;
4194 case 422: logIt("er422." + gtag, "Query: " + url + " [Locked]", "The resource is locked and cannot be modified"); if(onError) {onError(422, evalJSONstr(req.responseText))}; break;
4195 case 423: logIt("er423." + gtag, "Query: " + url + " [Already Exists]", "Resource already exists"); if(onError) {onError(423, evalJSONstr(req.responseText))}; break;
4196 case 424: logIt("er424." + gtag, "Query: " + url + " [Invalid Parameters]", "The given parameters were invalid"); if(onError) {onError(424, evalJSONstr(req.responseText))}; break;
4197 case 500: logIt("er500." + gtag, "Query: " + url + " [Internal Server Error]", "Some unknown error occurred on the server"); if(onError) {onError(500, evalJSONstr(req.responseText))}; break;
4198 case 503: logIt("er503." + gtag, "Query: " + url + " [Service Unavailable]", "Server cannot currently handle the request, try again later"); if(onError) {onError(503, evalJSONstr(req.responseText))}; break;
4199 }
4200 }
4201 };
4202
4203 try {
4204 req.open(method, url, true);
4205 req.setRequestHeader("X-Requested-With", "Query / eSixExtend V" + eSixExtend.versionName + " (" + eSixExtend.versionCode + ")");
4206 req.send();
4207 reqto = window.setTimeout(function(){
4208 req.abort();
4209 logIt("erTO." + gtag, "Query: " + url + " [Time out]", "Request timed out");
4210 if(onError) onError(0, {reason:"Query: Request timed out (20s)"});
4211 }, 15000);
4212 } catch(except) {
4213 window.clearTimeout(reqto);
4214 logIt("err_" + gtag, "Query: " + url, except);
4215 }
4216 }
4217
4218 // Creates a '?' link to be used at the end of notices to provide more information
4219 function helpIcon(o) {
4220 return o ? " <span style='cursor:pointer;float:right' onclick='alert(\"" + o.reason.replace(/\n/g, "\\n") + "\")'>?</span>" : "";
4221 }
4222
4223 function getKeyCode(event) {
4224 if(!(event instanceof KeyboardEvent)) throw "Event given is not a KeyboardEvent";
4225 return event.keyCode || event.key.toUpperCase().charCodeAt(0);
4226 }
4227
4228 function switchClassName(e, c, f) {
4229 var r = new RegExp(" ?" + c + " ?");
4230 var m = e.className.match(r);
4231 if(m == null || f == true) {
4232 if(e.className.indexOf(c) == -1) e.className += " " + c;
4233 return true;
4234 } else
4235 if(m != null || f == false) {
4236 e.className = e.className.replace(r, "");
4237 return false;
4238 }
4239 return false;
4240 }
4241
4242 function getRect(e) {
4243 var r = e.getBoundingClientRect();
4244 return {l:r.left,t:r.top,w:(r.right - r.left),h:(r.bottom - r.top)};
4245 }
4246
4247 function makeFrame(r1, r2) {
4248 if(r2.l == 0 && r2.t == 0) r2 = r1;
4249 var fr = createElement("div", {className:"ixuiFrame", style:{left:r1.l + "px", top:r1.t + "px", width:r1.w + "px", height:r1.h + "px"}}, document.body);
4250 var s = fr.style;
4251
4252 window.setTimeout(function() {
4253 s.left = r2.l + "px";
4254 s.top = r2.t + "px";
4255 s.width = r2.w + "px";
4256 s.height = r2.h + "px";
4257 s.opacity = 0;
4258 window.setTimeout(function() {
4259 fr.parentNode.removeChild(fr);
4260 }, 250);
4261 }, 10);
4262 }
4263
4264 /* logIt(tag, title, message)
4265 Adds a new log to the error console with the title and the message arranged in separate lines.
4266 If the DevBug extension exists, it will create a new DevLog entry with the title and the
4267 message specified. Get it from
4268 https://55ce109755f831e73d324b0cc7fa3fe010e80bfb.googledrive.com/host/0BxuaxlGgDz8TTHV6dW9ESUNqdkk/devbug.html */
4269 function logIt(tag, title, message) {
4270 var iserr = (tag.indexOf("err_") == 0);
4271 if(getSetting("uselog") || iserr) {
4272 if(iserr) title = title + " (" + tag.substr(4) + loc + ")";
4273 if(window.devbug) { // Check for DevBug's presence
4274 devbug("{eSixExtend} " + tag + (getSetting("floatinglog") ? "_" + Math.floor(Math.random()*4096) : ""), "{eSixExtend} " + title.replace(/\n/gm, "<br/>"), message, false);
4275 } else {
4276 // Log messages to the console
4277 if(message instanceof Error || message.message)
4278 message = message.name + "\n" + message.message + (message.lineNumber || message.columnNumber?"\nAt" + (message.lineNumber?" line " + message.lineNumber + ",":"") + (message.columnNumber?" column " + message.columnNumber:""):"") + (message.stack?"\nStack:\n" + message.stack:"") + (message.stackTrace?"\nStack trace:\n" + message.stacktrace:"");
4279 else
4280 message = message.replace(/\n/gm, "\n\t");
4281
4282 console.log("{eSixExtend} " + title + (message ? "\n\t" + message : ""));
4283
4284 // *sigh* Do all the work of creating a new box to log all of the messages
4285 if(getSetting("floatinglog")) {
4286 var dbg = findElement("dbgTextarea");
4287 if(!dbg) {
4288 dbg = createElement("pre", {id:"dbgTextarea", style:{display:"block", border:"1px dashed #000", color:"#000", backgroundColor:"#FFF", overflow:"auto", position:"fixed", left:"1em", bottom:"1em", right:"auto", top:"auto", width:"80%", fontSize:"1em", lineHeight:"1em", height:"2em", zIndex:"10", fontFamily:"Monospace"}, innerHTML:"", onclick:function() {
4289 this.style.height = (this.style.height == "25%" ? "2em" : "25%");
4290 }}, document.body);
4291 }
4292
4293 var d = new Date(), p = function(a) {return (a < 10 ? "0" + a : a);};
4294 dbg.innerHTML += (dbg.innerHTML.length>0?"\n\n":"") + "[" + p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds()) + "] {eSixExtend} " + title + (message?"\n\t" + message.replace(/\n/gm, "\n\t"):"");
4295 dbg.scrollTop = dbg.scrollHeight;
4296 }
4297 }
4298 }
4299 }
4300
4301 function rightNow() {
4302 return new Date().getTime();
4303 }
4304
4305 // Helper for document.createElement
4306 function createElement(tag, attrs, appendTo) {
4307 if(!tag) throw new SyntaxError("'tag' not defined");
4308
4309 var ele = document.createElement(tag), attrName, styleName, dataName;
4310 if(attrs) for(attrName in attrs) {
4311 if(attrName === "style") for(styleName in attrs.style) {ele.style[styleName] = attrs.style[styleName];} else
4312 if(attrName === "dataset") for(dataName in attrs.dataset) {ele.dataset[dataName] = attrs.dataset[dataName];} else
4313 ele[attrName] = attrs[attrName];
4314 }
4315
4316 if(appendTo) appendTo.appendChild(ele);
4317 return ele;
4318 }
4319
4320 // Shortcut for document.getElementById
4321 function findElement(id) {
4322 return document.getElementById(id);
4323 }
4324
4325 // Shortcut for document.getElementsByTagName
4326 function findByTag(tag) {
4327 return document.getElementsByTagName(tag);
4328 }
4329
4330 function getKeySymbol(kc) {
4331 var ksym = {
4332 en:{186:";", 187:"=", 191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"'"}, // English Layout (en-US)
4333 es:{172:"|", 186:"´", 187:"+", 191:"}", 192:"Ñ", 209:"Ñ", 219:"'", 220:"|", 221:"¿", 222:"{", 226:"<"}, // Spanish Layout (es-MX)
4334 hu:{192:"Ö", 191:"Ü", 187:"Ó", 219:"Å", 221:"Ú", 186:"É", 222:"Ã", 220:"Ű", 226:"Ã"}, // Hungarian layout (hu-HU)
4335 de:{36:"Pos1", 60:"<", 63:"ß", 144:"Num-Lock", 160:"^", 163:"#", 171:"+", 192:"`"}, // German layout (de, QWERTZ)
4336
4337 // Other symbols
4338 8:"B.Sp.", 9:"Tab", 13:"Enter", 16:"Shift", 17:"Ctrl", 18:"Alt", 19:"Pause", 20:"Caps", 27:"Esc", 32:"Space", 33:"PgUp", 34:"PgDn", 35:"End", 36:"Home", 37:"\u21E6", 38:"\u21E7", 39:"\u21E8", 40:"\u21E9", 45:"Ins", 46:"Del", 59:";", 91:"OS", 93:"Context", 96:"KP 0", 97:"KP 1", 98:"KP 2", 99:"KP 3", 100:"KP 4", 101:"KP 5", 102:"KP 6", 103:"KP 7", 104:"KP 8", 105:"KP 9", 106:"*", 107:"+", 109:"-", 110:".", 111:"/", 112:"F1", 113:"F2", 114:"F3", 115:"F4", 116:"F5", 117:"F6", 118:"F7", 119:"F8", 120:"F9", 121:"F10", 122:"F11", 123:"F12", 145:"Scr. Lk.", 173:"Mute", 174:"Vol-", 175:"Vol+", 176:"Next", 177:"Prev.", 179:"Pl / Ps", 188:",", 189:"-", 190:"."
4339 };
4340
4341 if(kc == null) return "[???]"; else {
4342 var dignum = (kc >= 48 && kc <= 57) || (kc >= 65 && kc <= 90);
4343 return ksym[getSetting("keyLayout")][kc] || ksym[kc] || (dignum ? String.fromCharCode(kc) : "[#" + kc + "]");
4344 }
4345 }
4346
4347 function placeTitleIcons() {
4348 if(getSetting("titleIcons")) {
4349 var titleIcons = "", unfav = findElement("remove-from-favs"), voteup = findElement("voteup"), votedown = findElement("votedown");
4350 if(unfav && unfav.style.display == "") titleIcons += "\u2665 ";
4351 if(voteup && voteup.className == "greentext") titleIcons += "\u2191 ";
4352 if(votedown && votedown.className == "redtext") titleIcons += "\u2193 ";
4353 document.title = titleIcons + oldTitle;
4354 }
4355 }
4356
4357 // Helper for asJSON
4358 function convertValue(value) {
4359 type = typeof value;
4360 if(type == "string") return '"' + value.replace(/(\\|")/g, "\\$1") + '"';
4361 if(type == "object") return (value == null ? "null" : asJSON(value));
4362
4363 return value;
4364 }
4365
4366 // Helper to convert an object to a JSON string (because I didn't like how JSON.stringify converted objects)
4367 function asJSON(object) {
4368 var kv = [], key, value, type;
4369
4370 if(object instanceof Array) { // Test if the object is an array
4371 for(key = 0; key < object.length; key++) kv.push(convertValue(object[key]));
4372 return "[" + kv.join(",") + "]";
4373 } else {
4374 for(key in object) kv.push('"' + key + '":' + convertValue(object[key]));
4375 return "{" + kv.join(",") + "}";
4376 }
4377 }
4378
4379 // Returns true if any of the items in 'what' are in 'on'
4380 function anyOf(on, what) {
4381 return new RegExp(what.join("|").replace(/(\/|\.|\\)/, "\\$1")).test(on);
4382 }
4383
4384 eSixExtend.isInstalled = true;
4385 Object.freeze(window.eSixExtend);
4386})(window, document);}catch(ex) {
4387 var e = ["{eSixExtend} " + ex.name + ": " + ex.message];
4388 if(ex.stack) e.push("-= Stack =---------------------------------------------------------------------\n" + ex.stack);
4389 if(ex.stacktrace) e.push("-= Stack trace =---------------------------------------------------------------\n" + ex.stacktrace);
4390 e.push("\n\n" + eSixExtend.toString());
4391 console.error(e.join("\n\n"));
4392 if(devbug) devbug("caterror", e[0], ex);
4393}