').addClass('tb-action-button tb-insert-debug').text('Insert debug info');
69 | if ($tbUsertextButtons.length) {
70 | $tbUsertextButtons.before($debugInsertButton);
71 | } else {
72 | $saveButton.parent().find('.status').before($('
').addClass('tb-usertext-buttons').append($debugInsertButton));
73 | }
74 |
75 | $('body').on('click', 'div.tb-insert-debug', function (e) {
76 | self.log('Insert debug clicked!');
77 | let $commentTextArea = $(this).closest('.usertext-edit.md-container').find('.md textarea');
78 | let currentComment = $commentTextArea.val();
79 |
80 | $commentTextArea.val(currentComment + submissionAddition);
81 |
82 | });
83 |
84 | }
85 | };
86 |
87 | TB.register_module(self);
88 | }
89 |
90 | (function () {
91 | window.addEventListener("TBModuleLoaded", function () {
92 | support();
93 | });
94 | })();
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://forthebadge.com) [](http://forthebadge.com) [](http://forthebadge.com)
2 |
3 | [](http://forthebadge.com) [](http://forthebadge.com) [](http://forthebadge.com)
4 |
5 | toolbox for reddit
6 | ========================
7 |
8 | Bundled extension of the /r/toolbox moderator tools for reddit.com
9 |
10 | Containing:
11 | - Comments Module: Highlight keywords and hide removed comments.
12 | - Mod button: Adds a button to submissions and comments that allows you to Ban, unban, mod, unmod, approve, unapprove a user from that spot. If a user is banned it will also load the ban reason on the spot. Very handy when someone modmails you asking why they are banned!
13 | - Mod Mail Pro: Filter your modmail, easily compose new modmail for your fellow mods, hide invite spam, much more!
14 | - Moderation log matrix: See who does what in your team, analyze the modlog and output nice statistics.
15 | - Removal Reasons: When removing a submission have a selection of predefined reasons you can select from. Supports removal comments and flairs!
16 | - Toolbar Shortcuts: Put handy shortcuts to your often used pages in the toolbar.
17 | - User Notes: Leave notes about users that other mods can see as well!
18 | - Domain Tagger: A shared feature allowing mod teams to tag domains with colors. Makes it easier to spot spammy domains, approve approved domains, etc.
19 | - Notifications of new (mod)mails, queue items, etc.
20 | - Toolbar with queue counters
21 | - Banlist live search: If you have a big ban list this is a awesome feature, it basically turn the banlist search bar in a live search bar that automatically updates with matchers.
22 | - Trouble Shooter (beta): Highlights and sorts comments in subreddits you moderate to help guide you to potential sources of trouble i.e. controversial and negative score comments.
23 |
24 | Documentation: https://www.reddit.com/r/toolbox/w/docs
25 |
26 |
27 | # Building
28 |
29 | Building is relatively easy through [nodejs](https://nodejs.org/) with gulp.
30 |
31 | Install gulp globally.
32 |
33 | ```sh
34 | $ npm install --global gulp
35 | ```
36 |
37 | Then navigate to the root of the toolbox folder and install the dependencies
38 |
39 | ```sh
40 | $ npm install
41 | ```
42 |
43 | To build toolbox now simply run
44 |
45 | ```sh
46 | $ gulp
47 | ```
48 |
49 | Or if you have followed these steps before and are on windows click the build.bat file.
50 |
51 | This will create a zip file which can be used in both Chrome as well as Firefox versions that support web extensions.
52 |
53 | # Development
54 |
55 | ## Chrome
56 |
57 | - Go to `chrome://extensions`.
58 | - Check the "Developer mode" checkbox if it's not already checked.
59 | - Click the "Load unpacked extension..." button.
60 | - Load the `extension` directory.
61 |
62 | Reload the extension when needed.
63 |
64 | ## Firefox (developer or nightly edition)
65 |
66 | - Go to `about:debugging`.
67 | - Click the "Load Temporary Add-on" button.
68 | - Point to `extension/manifest.json`.
69 |
70 | Reload the addon when needed.
71 |
72 | ### Third party support
73 |
74 | All shared features settings and data are stored in subreddit wikis through versioned json. Third party applications can use this data to hook into toolbox features like usernotes.
75 |
76 | Examples:
77 |
78 | - https://github.com/creesch/reddit-moderator-toolbox/wiki/JSON:-usernotes
79 | - https://github.com/creesch/reddit-moderator-toolbox/wiki/JSON:-toolbox-config
80 |
--------------------------------------------------------------------------------
/extension/data/styles/modmatrix.css:
--------------------------------------------------------------------------------
1 | #mod-matrix-wrapper {
2 | position: absolute;
3 | background-color: #fff;
4 | margin-top: 0;
5 | left: 0;
6 | right: 0;
7 | padding-bottom: 100px;
8 | }
9 |
10 | #mod-matrix-wrapper a {
11 | cursor: pointer;
12 | }
13 |
14 | #mod-matrix thead th, #mod-matrix tfoot td, #mod-matrix tbody td.action-total, #mod-matrix tbody td.action-percentage {
15 | font-weight: bold;
16 | }
17 |
18 | #mod-matrix tr {
19 | border-bottom: 1px solid #eee;
20 | }
21 |
22 | #mod-matrix tfoot tr {
23 | border-top: 1px solid #eee;
24 | }
25 |
26 | #mod-matrix td, #mod-matrix th {
27 | padding: 5px;
28 | border-right: 1px solid #eee;
29 | text-align: center;
30 | font-size: x-small;
31 | }
32 |
33 | #mod-matrix td:first-child, #mod-matrix th:first-child {
34 | text-align: left;
35 | }
36 |
37 | #mod-matrix a.modactions {
38 | margin-right: 0;
39 | display: inline-block;
40 | float: none;
41 | }
42 |
43 | #mod-matrix tbody tr:nth-of-type(even) {
44 | background: #fafafa;
45 | }
46 |
47 | #mod-matrix tr:hover, #mod-matrix td.hover {
48 | background: #ffc!important;
49 | }
50 |
51 | #mod-matrix thead th {
52 | position: relative;
53 | cursor: s-resize;
54 | }
55 |
56 | #mod-matrix thead th {
57 | cursor: pointer;
58 | }
59 |
60 | #mod-matrix thead th .sorting-icon {
61 | position: absolute;
62 | left: 50%;
63 | margin-left: -8px;
64 | bottom: -8px;
65 | }
66 |
67 | #mod-matrix .action-number {
68 | color: #000;
69 | }
70 |
71 | #mod-matrix .zero a, #mod-matrix .zero span {
72 | color: #c0c0c0;
73 | }
74 |
75 | #mod-matrix .highlight a, #mod-matrix .highlight span {
76 | color: #f00;
77 | }
78 |
79 | #mod-matrix tr.filtered, #mod-matrix td.filtered, #mod-matrix th.filtered, #mod-matrix tr.hide-zero, #mod-matrix td.hide-zero, #mod-matrix th.hide-zero {
80 | display: none;
81 | }
82 |
83 | #mod-matrix-settings td {
84 | padding: 2px;
85 | vertical-align: top;
86 | line-height: 25px;
87 | }
88 |
89 | #mod-matrix-settings td:first-child {
90 | width: 200px;
91 | }
92 |
93 | #mod-matrix-settings div {
94 | line-height: 1em;
95 | }
96 |
97 | #mod-matrix-settings input[type=submit] {
98 | padding: 1px 6px;
99 | }
100 |
101 | #mod-matrix-statistics {
102 | margin-top: 1em;
103 | }
104 |
105 | #mod-matrix-statistics {
106 | border-top: 1px dotted #000;
107 | padding: 7px;
108 | }
109 |
110 | #mod-matrix-statistics strong {
111 | font-weight: bold;
112 | }
113 |
114 | /* show header labels (thanks to /u/ashishtiwari) */
115 | #mod-matrix.labels th {
116 | vertical-align: text-bottom;
117 | text-align: center;
118 | text-overflow: ellipsis;
119 | overflow: hidden;
120 | padding-top: 150px;
121 | }
122 |
123 | #mod-matrix.labels a.modactions {
124 | vertical-align: bottom;
125 | width: 16px!important;
126 | height: 16px!important;
127 | }
128 |
129 | #mod-matrix.labels a.modactions:before {
130 | content: attr(title);
131 | position: relative;
132 | bottom: 20px;
133 | display: block;
134 | font-size: 11px;
135 | white-space: nowrap;
136 | font-weight: normal;
137 | color: #000;
138 | -webkit-transform: rotate(-90.0deg);
139 | transform: rotate(-90.0deg);
140 | }
141 |
142 | /* Comment text */
143 |
144 | .modactionlisting .removed_comment_text {
145 | padding: 6px;
146 | margin: 2px 0;
147 | background-color: rgba(250, 250, 250, 0.8);
148 | border: 1px solid lightgray;
149 | border-radius: 5px;
150 | }
151 |
152 | .modactionlisting .removed_comment_text p:first-child {
153 | margin-top: 0;
154 | }
155 |
156 | .modactionlisting .removed_comment_text p:last-child {
157 | margin-bottom: 0;
158 | }
159 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/matchesonscrollbar.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
15 | if (typeof options == "string") options = {className: options};
16 | if (!options) options = {};
17 | return new SearchAnnotation(this, query, caseFold, options);
18 | });
19 |
20 | function SearchAnnotation(cm, query, caseFold, options) {
21 | this.cm = cm;
22 | this.options = options;
23 | var annotateOptions = {listenForChanges: false};
24 | for (var prop in options) annotateOptions[prop] = options[prop];
25 | if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
26 | this.annotation = cm.annotateScrollbar(annotateOptions);
27 | this.query = query;
28 | this.caseFold = caseFold;
29 | this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
30 | this.matches = [];
31 | this.update = null;
32 |
33 | this.findMatches();
34 | this.annotation.update(this.matches);
35 |
36 | var self = this;
37 | cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
38 | }
39 |
40 | var MAX_MATCHES = 1000;
41 |
42 | SearchAnnotation.prototype.findMatches = function() {
43 | if (!this.gap) return;
44 | for (var i = 0; i < this.matches.length; i++) {
45 | var match = this.matches[i];
46 | if (match.from.line >= this.gap.to) break;
47 | if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
48 | }
49 | var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
50 | var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
51 | while (cursor.findNext()) {
52 | var match = {from: cursor.from(), to: cursor.to()};
53 | if (match.from.line >= this.gap.to) break;
54 | this.matches.splice(i++, 0, match);
55 | if (this.matches.length > maxMatches) break;
56 | }
57 | this.gap = null;
58 | };
59 |
60 | function offsetLine(line, changeStart, sizeChange) {
61 | if (line <= changeStart) return line;
62 | return Math.max(changeStart, line + sizeChange);
63 | }
64 |
65 | SearchAnnotation.prototype.onChange = function(change) {
66 | var startLine = change.from.line;
67 | var endLine = CodeMirror.changeEnd(change).line;
68 | var sizeChange = endLine - change.to.line;
69 | if (this.gap) {
70 | this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
71 | this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
72 | } else {
73 | this.gap = {from: change.from.line, to: endLine + 1};
74 | }
75 |
76 | if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
77 | var match = this.matches[i];
78 | var newFrom = offsetLine(match.from.line, startLine, sizeChange);
79 | if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
80 | var newTo = offsetLine(match.to.line, startLine, sizeChange);
81 | if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
82 | }
83 | clearTimeout(this.update);
84 | var self = this;
85 | this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
86 | };
87 |
88 | SearchAnnotation.prototype.updateAfterChange = function() {
89 | this.findMatches();
90 | this.annotation.update(this.matches);
91 | };
92 |
93 | SearchAnnotation.prototype.clear = function() {
94 | this.cm.off("change", this.changeHandler);
95 | this.annotation.clear();
96 | };
97 | });
98 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/mode/yaml.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("yaml", function() {
15 |
16 | var cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
17 | var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i');
18 |
19 | return {
20 | token: function(stream, state) {
21 | var ch = stream.peek();
22 | var esc = state.escaped;
23 | state.escaped = false;
24 | /* comments */
25 | if (ch == "#" && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) {
26 | stream.skipToEnd();
27 | return "comment";
28 | }
29 |
30 | if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))
31 | return "string";
32 |
33 | if (state.literal && stream.indentation() > state.keyCol) {
34 | stream.skipToEnd(); return "string";
35 | } else if (state.literal) { state.literal = false; }
36 | if (stream.sol()) {
37 | state.keyCol = 0;
38 | state.pair = false;
39 | state.pairStart = false;
40 | /* document start */
41 | if(stream.match(/---/)) { return "def"; }
42 | /* document end */
43 | if (stream.match(/\.\.\./)) { return "def"; }
44 | /* array list item */
45 | if (stream.match(/\s*-\s+/)) { return 'meta'; }
46 | }
47 | /* inline pairs/lists */
48 | if (stream.match(/^(\{|\}|\[|\])/)) {
49 | if (ch == '{')
50 | state.inlinePairs++;
51 | else if (ch == '}')
52 | state.inlinePairs--;
53 | else if (ch == '[')
54 | state.inlineList++;
55 | else
56 | state.inlineList--;
57 | return 'meta';
58 | }
59 |
60 | /* list seperator */
61 | if (state.inlineList > 0 && !esc && ch == ',') {
62 | stream.next();
63 | return 'meta';
64 | }
65 | /* pairs seperator */
66 | if (state.inlinePairs > 0 && !esc && ch == ',') {
67 | state.keyCol = 0;
68 | state.pair = false;
69 | state.pairStart = false;
70 | stream.next();
71 | return 'meta';
72 | }
73 |
74 | /* start of value of a pair */
75 | if (state.pairStart) {
76 | /* block literals */
77 | if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; };
78 | /* references */
79 | if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; }
80 | /* numbers */
81 | if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; }
82 | if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; }
83 | /* keywords */
84 | if (stream.match(keywordRegex)) { return 'keyword'; }
85 | }
86 |
87 | /* pairs (associative arrays) -> key */
88 | if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) {
89 | state.pair = true;
90 | state.keyCol = stream.indentation();
91 | return "atom";
92 | }
93 | if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; }
94 |
95 | /* nothing found, continue */
96 | state.pairStart = false;
97 | state.escaped = (ch == '\\');
98 | stream.next();
99 | return null;
100 | },
101 | startState: function() {
102 | return {
103 | pair: false,
104 | pairStart: false,
105 | keyCol: 0,
106 | inlinePairs: 0,
107 | inlineList: 0,
108 | literal: false,
109 | escaped: false
110 | };
111 | }
112 | };
113 | });
114 |
115 | CodeMirror.defineMIME("text/x-yaml", "yaml");
116 |
117 | });
118 |
--------------------------------------------------------------------------------
/extension/data/modules/metricstab.js:
--------------------------------------------------------------------------------
1 | function metricstab() {
2 | var self = new TB.Module('Metrics Tab');
3 | self.shortname = 'Metrics';
4 |
5 | self.settings['enabled']['default'] = true;
6 |
7 | self.getSectionFromUrl = function getSectionFromUrl(url) {
8 | var regex = new RegExp(/^(http|https):\/\/([a-z]+\.)?reddit\.com\/(user|r)\/([^\/]+)(\/|$)/g);
9 | var matches = regex.exec(url);
10 |
11 | if (matches != null) {
12 | return {section: matches[3], subSection: matches[4]};
13 | } else {
14 | return null;
15 | }
16 | };
17 |
18 | self.init = function () {
19 | var page = this.getSectionFromUrl(window.location.href),
20 | $body = $('body');
21 |
22 | if (page == null) {
23 | return false;
24 | }
25 |
26 | var metrics = {
27 | user: {
28 | //'Observatory': 'http://0bservat0ry.com/reddit/u/{subSection}.html', //Currently offline
29 | 'MetaReddit': 'http://metareddit.com/stalk?user={subSection}',
30 | 'Redective': 'http://www.redective.com/?r=e&a=search&s=user&t=redective&q={subSection}',
31 | 'Hivemind': 'http://www.hivemind.cc/rank/u/{subSection}',
32 | 'Karmawhores': 'http://www.karmawhores.net/user/{subSection}',
33 | //'Karmalb': 'http://www.karmalb.com/user/{subSection}', //Currently offline
34 | 'Karmastats': 'http://reddit.dataoverload.de/karmastats/#{subSection}',
35 | 'RateRedditors': 'http://rateredditors.com/{subSection}',
36 | 'SnoopSnoo': 'http://www.snoopsnoo.com/u/{subSection}',
37 | 'RedditInvestigator': 'http://www.redditinvestigator.com/{subSection}',
38 | 'redditgraphs': 'http://www.roadtolarissa.com/redditgraphs/?{subSection}&PieChart&Number&Submissions'
39 | //'RedditInsight': 'http://www.redditinsight.com/#trackuser', // donno if we want to add ones that don't propagate the user name.
40 | },
41 |
42 | r: {
43 | //'Observatory': 'http://0bservat0ry.com/reddit/r/{subSection}.html', // Currently offline
44 | 'MetaReddit': 'http://metareddit.com/r/{subSection}',
45 | 'Redective': 'http://www.redective.com/?r=e&a=search&s=subreddit&t=redective&q={subSection}',
46 | 'Hivemind': 'http://www.hivemind.cc/rank/r/{subSection}',
47 | 'RedditMetrics': 'http://redditmetrics.com/r/{subSection}',
48 | 'ExploreReddit': 'http://paulrosenzweig.com/explore-reddit/r/{subSection}'
49 | }
50 | };
51 |
52 | var header = document.getElementById("header-bottom-left");
53 | var tabList = header.getElementsByTagName("ul")[0];
54 |
55 | if (tabList == null) {
56 | return false;
57 | }
58 | $body.append('
');
59 |
60 | var $tabList = $(tabList),
61 | $metricsDropDown = $body.find('#tb-metrics-expand-list ul');
62 |
63 | $tabList.css('overflow', 'visible');
64 |
65 | var $listItem = $("
metrics"),
66 | $tbMetricsList = $body.find('#tb-metrics-expand-list');
67 |
68 |
69 | $(tabList).append($listItem);
70 |
71 | var links = metrics[page.section];
72 | for (var i in links) {
73 | var url = links[i];
74 | url = url.replace(/\{subSection\}/g, page.subSection);
75 | $metricsDropDown.append('
' + i + '');
76 | }
77 |
78 | $listItem.on('click', function () {
79 | self.log('metrics tab opened');
80 | var offset = $(this).offset(),
81 | offsetLeft = offset.left,
82 | offsetTop = (offset.top + 20);
83 |
84 | $body.find('#tb-metrics-expand-list').css({
85 | "left": offsetLeft + 'px',
86 | "top": offsetTop + 'px'
87 | });
88 | $tbMetricsList.toggle();
89 | });
90 |
91 | $(document).on('click', function (event) {
92 | if (!$(event.target).closest('.tb-metrics').length) {
93 | $tbMetricsList.hide();
94 | }
95 | });
96 |
97 | };
98 |
99 | TB.register_module(self);
100 | }
101 |
102 | (function () {
103 | window.addEventListener("TBModuleLoaded", function () {
104 | metricstab();
105 | });
106 | })();
107 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/panel.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | CodeMirror.defineExtension("addPanel", function(node, options) {
13 | options = options || {};
14 |
15 | if (!this.state.panels) initPanels(this);
16 |
17 | var info = this.state.panels;
18 | var wrapper = info.wrapper;
19 | var cmWrapper = this.getWrapperElement();
20 |
21 | if (options.after instanceof Panel && !options.after.cleared) {
22 | wrapper.insertBefore(node, options.before.node.nextSibling);
23 | } else if (options.before instanceof Panel && !options.before.cleared) {
24 | wrapper.insertBefore(node, options.before.node);
25 | } else if (options.replace instanceof Panel && !options.replace.cleared) {
26 | wrapper.insertBefore(node, options.replace.node);
27 | options.replace.clear();
28 | } else if (options.position == "bottom") {
29 | wrapper.appendChild(node);
30 | } else if (options.position == "before-bottom") {
31 | wrapper.insertBefore(node, cmWrapper.nextSibling);
32 | } else if (options.position == "after-top") {
33 | wrapper.insertBefore(node, cmWrapper);
34 | } else {
35 | wrapper.insertBefore(node, wrapper.firstChild);
36 | }
37 |
38 | var height = (options && options.height) || node.offsetHeight;
39 | this._setSize(null, info.heightLeft -= height);
40 | info.panels++;
41 | return new Panel(this, node, options, height);
42 | });
43 |
44 | function Panel(cm, node, options, height) {
45 | this.cm = cm;
46 | this.node = node;
47 | this.options = options;
48 | this.height = height;
49 | this.cleared = false;
50 | }
51 |
52 | Panel.prototype.clear = function() {
53 | if (this.cleared) return;
54 | this.cleared = true;
55 | var info = this.cm.state.panels;
56 | this.cm._setSize(null, info.heightLeft += this.height);
57 | info.wrapper.removeChild(this.node);
58 | if (--info.panels == 0) removePanels(this.cm);
59 | };
60 |
61 | Panel.prototype.changed = function(height) {
62 | var newHeight = height == null ? this.node.offsetHeight : height;
63 | var info = this.cm.state.panels;
64 | this.cm._setSize(null, info.height += (newHeight - this.height));
65 | this.height = newHeight;
66 | };
67 |
68 | function initPanels(cm) {
69 | var wrap = cm.getWrapperElement();
70 | var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
71 | var height = parseInt(style.height);
72 | var info = cm.state.panels = {
73 | setHeight: wrap.style.height,
74 | heightLeft: height,
75 | panels: 0,
76 | wrapper: document.createElement("div")
77 | };
78 | wrap.parentNode.insertBefore(info.wrapper, wrap);
79 | var hasFocus = cm.hasFocus();
80 | info.wrapper.appendChild(wrap);
81 | if (hasFocus) cm.focus();
82 |
83 | cm._setSize = cm.setSize;
84 | if (height != null) cm.setSize = function(width, newHeight) {
85 | if (newHeight == null) return this._setSize(width, newHeight);
86 | info.setHeight = newHeight;
87 | if (typeof newHeight != "number") {
88 | var px = /^(\d+\.?\d*)px$/.exec(newHeight);
89 | if (px) {
90 | newHeight = Number(px[1]);
91 | } else {
92 | info.wrapper.style.height = newHeight;
93 | newHeight = info.wrapper.offsetHeight;
94 | info.wrapper.style.height = "";
95 | }
96 | }
97 | cm._setSize(width, info.heightLeft += (newHeight - height));
98 | height = newHeight;
99 | };
100 | }
101 |
102 | function removePanels(cm) {
103 | var info = cm.state.panels;
104 | cm.state.panels = null;
105 |
106 | var wrap = cm.getWrapperElement();
107 | info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
108 | wrap.style.height = info.setHeight;
109 | cm.setSize = cm._setSize;
110 | cm.setSize();
111 | }
112 | });
113 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/annotatescrollbar.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineExtension("annotateScrollbar", function(options) {
15 | if (typeof options == "string") options = {className: options};
16 | return new Annotation(this, options);
17 | });
18 |
19 | CodeMirror.defineOption("scrollButtonHeight", 0);
20 |
21 | function Annotation(cm, options) {
22 | this.cm = cm;
23 | this.options = options;
24 | this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
25 | this.annotations = [];
26 | this.doRedraw = this.doUpdate = null;
27 | this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
28 | this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
29 | this.computeScale();
30 |
31 | function scheduleRedraw(delay) {
32 | clearTimeout(self.doRedraw);
33 | self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
34 | }
35 |
36 | var self = this;
37 | cm.on("refresh", this.resizeHandler = function() {
38 | clearTimeout(self.doUpdate);
39 | self.doUpdate = setTimeout(function() {
40 | if (self.computeScale()) scheduleRedraw(20);
41 | }, 100);
42 | });
43 | cm.on("markerAdded", this.resizeHandler);
44 | cm.on("markerCleared", this.resizeHandler);
45 | if (options.listenForChanges !== false)
46 | cm.on("change", this.changeHandler = function() {
47 | scheduleRedraw(250);
48 | });
49 | }
50 |
51 | Annotation.prototype.computeScale = function() {
52 | var cm = this.cm;
53 | var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
54 | cm.getScrollerElement().scrollHeight
55 | if (hScale != this.hScale) {
56 | this.hScale = hScale;
57 | return true;
58 | }
59 | };
60 |
61 | Annotation.prototype.update = function(annotations) {
62 | this.annotations = annotations;
63 | this.redraw();
64 | };
65 |
66 | Annotation.prototype.redraw = function(compute) {
67 | if (compute !== false) this.computeScale();
68 | var cm = this.cm, hScale = this.hScale;
69 |
70 | var frag = document.createDocumentFragment(), anns = this.annotations;
71 |
72 | var wrapping = cm.getOption("lineWrapping");
73 | var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
74 | var curLine = null, curLineObj = null;
75 | function getY(pos, top) {
76 | if (curLine != pos.line) {
77 | curLine = pos.line;
78 | curLineObj = cm.getLineHandle(curLine);
79 | }
80 | if (wrapping && curLineObj.height > singleLineH)
81 | return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
82 | var topY = cm.heightAtLine(curLineObj, "local");
83 | return topY + (top ? 0 : curLineObj.height);
84 | }
85 |
86 | if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
87 | var ann = anns[i];
88 | var top = nextTop || getY(ann.from, true) * hScale;
89 | var bottom = getY(ann.to, false) * hScale;
90 | while (i < anns.length - 1) {
91 | nextTop = getY(anns[i + 1].from, true) * hScale;
92 | if (nextTop > bottom + .9) break;
93 | ann = anns[++i];
94 | bottom = getY(ann.to, false) * hScale;
95 | }
96 | if (bottom == top) continue;
97 | var height = Math.max(bottom - top, 3);
98 |
99 | var elt = frag.appendChild(document.createElement("div"));
100 | elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
101 | + (top + this.buttonHeight) + "px; height: " + height + "px";
102 | elt.className = this.options.className;
103 | if (ann.id) {
104 | elt.setAttribute("annotation-id", ann.id);
105 | }
106 | }
107 | this.div.textContent = "";
108 | this.div.appendChild(frag);
109 | };
110 |
111 | Annotation.prototype.clear = function() {
112 | this.cm.off("refresh", this.resizeHandler);
113 | this.cm.off("markerAdded", this.resizeHandler);
114 | this.cm.off("markerCleared", this.resizeHandler);
115 | if (this.changeHandler) this.cm.off("change", this.changeHandler);
116 | this.div.parentNode.removeChild(this.div);
117 | };
118 | });
119 |
--------------------------------------------------------------------------------
/extension/data/styles/queuetools.css:
--------------------------------------------------------------------------------
1 | .mod-toolbox .modtools-active {
2 | padding-top: 40px !important;
3 | }
4 |
5 | .mod-toolbox .menuarea.modtools {
6 | background-color: rgba(237, 241, 245, 0.8);
7 | border: 1px solid #B6C9DD;
8 | z-index: 500;
9 | overflow: visible;
10 | }
11 |
12 | .drop-choices.lightdrop {
13 | z-index: 501;
14 | }
15 |
16 |
17 | #modtab-threshold {
18 | width: 20px;
19 | }
20 |
21 |
22 | .mod-toolbox .tb-kitteh {
23 | display: block;
24 | min-width: 330px;
25 | min-height: 351px;
26 | background-image: url("https://b.thumbs.redditmedia.com/7uBqTsML9Y-sksmfvCrKrHvoUANP6ZCOw9UOVIV6aXQ.png");
27 | background-repeat: no-repeat;
28 | background-position: center center;
29 | text-indent: -1000px;
30 | overflow: hidden;
31 | }
32 |
33 | .mod-toolbox .tb-puppy {
34 | display: block;
35 | min-width: 360px;
36 | min-height: 460px;
37 | background-image: url("https://a.thumbs.redditmedia.com/L8eKqhvo2mcjB0E8t87nOlu1_oMoSQ8F8LzqbFHumK8.png");
38 | background-repeat: no-repeat;
39 | background-position: center center;
40 | text-indent: -1000px;
41 | overflow: hidden;
42 | }
43 |
44 | .mod-toolbox .tb-begifs {
45 | display: block;
46 | min-width: 360px;
47 | min-height: 460px;
48 | background-image: url("https://creesch.github.io/reddit-moderator-toolbox/hosted_images/begifs.gif");
49 | background-repeat: no-repeat;
50 | background-position: center center;
51 | text-indent: -1000px;
52 | overflow: hidden;
53 | }
54 |
55 | .mod-toolbox .tb-spiders {
56 | display: block;
57 | min-width: 360px;
58 | min-height: 460px;
59 | background-image: url("https://b.thumbs.redditmedia.com/AgVm8q7yNxjcklDntkpH4hc6UoYrBUcwU8SXcZWgBJA.png");
60 | background-repeat: no-repeat;
61 | background-position: center center;
62 | text-indent: -1000px;
63 | overflow: hidden;
64 | }
65 |
66 | .mod-toolbox .tb-begifs:after {
67 | content: "baby elephant is pleased!";
68 | }
69 |
70 |
71 | /* Action reason stuff */
72 |
73 | .action-reason {
74 | padding: 5px;
75 | font-size: 12px;
76 | background-color: rgba(255, 255, 255, 0.59);
77 | }
78 |
79 | .action-reason a {
80 | font-size: 10px;
81 | }
82 |
83 |
84 | /* Color borders */
85 | .comment.tb-subreddit-color {
86 | padding-left: 10px;
87 | }
88 |
89 |
90 | /* queue sort counter */
91 |
92 | a.tb-subreddit-item-count {
93 | background-color: #D56F18;
94 | color: #FFF;
95 | font-size: 11px;
96 | font-weight: bold;
97 | padding: 0px 3px 3px 3px;
98 | margin: 3px;
99 | border-radius: 2px;
100 | display: inline-block;
101 | }
102 |
103 | .subscription-box li {
104 | display: -webkit-flex;
105 | display: -ms-flexbox;
106 | display: flex;
107 | -webkit-flex-direction: row;
108 | -ms-flex-direction: row;
109 | flex-direction: row;
110 | -webkit-flex-wrap: wrap;
111 | -ms-flex-wrap: wrap;
112 | flex-wrap: wrap;
113 | }
114 |
115 | a.tb-subreddit-item-count {
116 | -ms-flex-order: -1;
117 | order: -1;
118 | }
119 |
120 | .mod-toolbox .tb-sort-subs {
121 | float: right;
122 | margin: 4px;
123 | border: 1px solid #B6C9DD;
124 | border-left: 2px solid #B6C9DD;
125 | padding: 3px;
126 | background-color: white;
127 | font-size: 13px;
128 | }
129 |
130 | .mod-toolbox .tb-sort-subs:hover {
131 | background-color: #E2EEFA;
132 | }
133 |
134 | .mod-toolbox .tb-sort-subs img {
135 | margin-bottom: -3px;
136 | margin-right: 3px;
137 | }
138 |
139 |
140 | /* pretty buttons */
141 | .menuarea.modtools a.pretty-button {
142 | border-radius: 0 !important;
143 | border-width: 1px 1px 1px 2px;
144 | border-color: #778696;
145 | border-style: solid;
146 | padding: 1px 5px;
147 | text-align: center;
148 | font-size: inherit;
149 | }
150 |
151 | .menuarea.modtools .pretty-button.negative {
152 | background: none;
153 | background-color: #EDBDBE;
154 |
155 | }
156 | .menuarea.modtools .pretty-button.positive {
157 | background: none;
158 | background-color: #D1EABF;
159 | }
160 |
161 | .menuarea.modtools .pretty-button.neutral {
162 | background-image: none;
163 | background-color: #D5D5D5;
164 | }
165 |
166 | .menuarea.modtools .pretty-button.negative.pressed {
167 | background: none;
168 | background-color: #886B6C;
169 |
170 | }
171 | .menuarea.modtools .pretty-button.positive.pressed {
172 | background: none;
173 | background-color: #849479;
174 | }
175 |
176 | .menuarea.modtools .pretty-button.neutral.pressed {
177 | background-image: none;
178 | background-color: #9E9C9C;
179 | }
180 |
181 |
182 | /* Highlight for zero-scored posts */
183 | .tb-zero-highlight {
184 | background: #FFF1C4;
185 | }
186 |
--------------------------------------------------------------------------------
/extension/data/modules/realtime.js:
--------------------------------------------------------------------------------
1 | function realtime() {
2 | // ===============
3 | // http://userscripts-mirror.org/scripts/show/129928
4 | // By: /u/DEADBEEF
5 | // ===============
6 |
7 | var self = new TB.Module('Realtime Reddit');
8 | self.shortname = 'Realtime';
9 |
10 | self.settings['enabled']['default'] = false;
11 | self.config['betamode'] = true;
12 |
13 | self.init = function () {
14 |
15 | // Don't run if the page we're viewing is paginated or a threaded comments page... or page restrictions.
16 | if (location.search.match(/before|after/) || $('body.comments-page').length || !(TBUtils.isModpage || TBUtils.isCommentsPage || TBUtils.isNewPage || TBUtils.isUserPage)) return;
17 |
18 | // Add checkbox;
19 | $('.tabmenu:first-of-type').append('
');
20 |
21 | var timeout, delay = 5000,
22 | $checkbox = $('.tb-realtime-checkbox');
23 |
24 | // Add new things
25 | function getNewThings() {
26 | self.log("realtime gettingnewthings");
27 |
28 | if (!$('#realtime:checked').length) return;
29 | timeout = setTimeout(getNewThings, delay);
30 |
31 | // Don't run when window not visible
32 | if (document.hidden) return;
33 |
34 | // Get first thing
35 | var before = $('#siteTable div.thing:first').attr('data-fullname'),
36 | html = [];
37 |
38 | // Get new things, prepend to page on success
39 | $.get(location.pathname + '.json-html?before=' + before).done(function (response) {
40 |
41 | // Compress the HTML of each returned thing
42 | for (i in response.data) html.push(compressHTML(response.data[i].data.content));
43 | if (!html.length) return;
44 |
45 | insertHTML(html);
46 |
47 | // Update Ranks on link listings (if applicable)
48 | var n = 1;
49 | $('.rank').each(function () {
50 | this.innerHTML = n++;
51 | this.style.width = '3.30ex';
52 | this.nextSibling.style.width = '3ex'
53 | });
54 |
55 | });
56 | }
57 |
58 | // Insert new things into sitetable.
59 | function insertHTML(html) {
60 |
61 | var height = $sitetable.css('top').slice(0, -2),
62 | things = $(html.join(''))
63 | .find('.child').remove().end()
64 | .prependTo($sitetable)
65 | .each(function () {
66 | height -= this.offsetHeight
67 | });
68 |
69 | // Scroll new items into view.
70 | $sitetable.stop().css('top', height).animate({top: 0}, 5000);
71 | things.css({opacity: 0.2}).animate({opacity: 1}, 2000, 'linear');
72 |
73 | // Trim items
74 | $('#siteTable>div.thing:gt(99),#siteTable>.clearleft:gt(99),#siteTable tr.modactions:gt(200)').remove();
75 |
76 | // Run flowwit callbacks on new things.
77 | if (window.flowwit) for (i in window.flowwit) window.flowwit[i](things.filter('.thing'))
78 |
79 | // Run callbacks for new things
80 | $(document).trigger('new_things_inserted');
81 |
82 | // Run callbacks for toolbox
83 | setTimeout(function () {
84 | var event = new CustomEvent("TBNewThings");
85 | window.dispatchEvent(event);
86 | }, 1000);
87 | }
88 |
89 | // Toggle realtime view on/off
90 | $checkbox.on('click', function () {
91 | var $body = $('body'),
92 | siteTableMargin = $body.find('.side').outerWidth() + 10,
93 | $sitetable = $('#siteTable').css({
94 | 'top': 0,
95 | 'margin-right': siteTableMargin + 'px'
96 | }),
97 | initialPosition = $sitetable.css('position');
98 |
99 | self.log("realtime checked: " + $checkbox.is(':checked'));
100 |
101 | clearTimeout(timeout);
102 | if ($checkbox.is(':checked')) getNewThings();
103 |
104 | // Toggle promo box
105 | $('#siteTable_organic,.content>.infobar').css('display', (this.checked ? 'none' : 'block'));
106 | $sitetable.css('position', (this.checked ? 'relative' : initialPosition));
107 | });
108 |
109 | // .json-html returns uncompressed html, so we have to compress it manually and replace HTML entities.
110 | function compressHTML(src) {
111 | return src.replace(/(\n+|\s+)?</g, '<')
112 | .replace(/>(\n+|\s+)?/g, '>')
113 | .replace(/&/g, '&')
114 | .replace(/\n/g, '')
115 | .replace(/child" > False/, 'child">');
116 | }
117 | };
118 |
119 | TB.register_module(self);
120 | }
121 |
122 | (function() {
123 | window.addEventListener("TBModuleLoaded", function () {
124 | realtime();
125 | });
126 | })();
127 |
--------------------------------------------------------------------------------
/extension/data/modules/flyingsnoo.js:
--------------------------------------------------------------------------------
1 | function flyingsnoo() {
2 | // @name Flying Snoo
3 | // @namespace http://reddit.com/user/LowSociety
4 | // @copyright 2014+, LowSociety
5 |
6 | var self = new TB.Module('Userpage');
7 | self.shortname = 'Userpage';
8 |
9 | ////Default settings
10 | self.settings['enabled']['default'] = true;
11 | self.settings['enabled']['hidden'] = true; // it's an easter egg.
12 |
13 | self.init = function () {
14 | if (!TB.utils.isUserPage) return;
15 |
16 | $(".profile-page .footer-parent").click(function () {
17 | var background = $(this).css("background");
18 |
19 | var width = 87,
20 | height = 145;
21 |
22 | // unlock achievement
23 | TB.utils.sendEvent(TB.utils.events.TB_FLY_SNOO);
24 |
25 | var floater = $("
").css({
26 | height: height + "px",
27 | width: width + "px",
28 | background: "url(//c.thumbs.redditmedia.com/7U0yjjycFy9MC5ht.png)",
29 | position: "absolute",
30 | top: $(this).offset().top + "px",
31 | left: (($(window).width() * 0.49) - (width / 2)) + "px",
32 | zIndex: 999
33 | }).appendTo("body");
34 |
35 | var documentHeight = $(document).height(),
36 | documentWidth = $(document).width(),
37 | iterations = 0,
38 | wind = 0,
39 | oldTop = floater.position().top,
40 | oldLeft = floater.position().left;
41 |
42 | var interval = null;
43 |
44 | var startInterval = function () {
45 | interval = window.setInterval(function () {
46 | var newTop = Math.max(0, (oldTop - ((documentHeight) * 0.001)));
47 |
48 | if (iterations % 50 == 0) {
49 | wind = ((Math.random() * 200) - 100) * 0.02;
50 | }
51 |
52 | newLeft = Math.min(documentWidth - width, Math.max(0, oldLeft + wind));
53 |
54 | floater.css({
55 | top: newTop + "px",
56 | left: newLeft + "px"
57 | });
58 |
59 | iterations++;
60 | if (newTop <= 0) {
61 | window.clearInterval(interval);
62 | interval = null;
63 | }
64 | oldTop = newTop;
65 | oldLeft = newLeft;
66 | }, 50);
67 | };
68 |
69 | floater.mousedown(function (e) {
70 | if (interval != null) {
71 | window.clearInterval(interval);
72 | interval = null;
73 | }
74 | floater.data("offsetX", e.offsetX);
75 | floater.data("offsetY", e.offsetY);
76 |
77 | var dragEvent = function (e) {
78 | var offsetX = floater.data("offsetX") || 0,
79 | offsetY = floater.data("offsetY") || 0;
80 | oldLeft = (e.pageX - offsetX);
81 | oldTop = (e.pageY - offsetY);
82 | floater.css({
83 | "left": oldLeft + "px",
84 | "top": oldTop + "px"
85 | });
86 | };
87 |
88 | var releaseEvent = function () {
89 | if (interval == null)
90 | startInterval();
91 | $(document).unbind("mousemove", dragEvent);
92 | $(document).unbind("mouseup", releaseEvent);
93 | };
94 |
95 | $(document).bind("mousemove", dragEvent).bind("mouseup", releaseEvent);
96 |
97 | }).dblclick(function () {
98 | if (interval != null) {
99 | window.clearInterval(interval);
100 | interval = null;
101 | }
102 | $(this).unbind("mousedown");
103 | $(this).css("background", "url(//a.thumbs.redditmedia.com/P0tVLpH52smMNXPN.png)");
104 | interval = window.setInterval(function () {
105 | var newTop = oldTop + Math.max(50, Math.min(10, oldTop * 0.05));
106 | floater.css({
107 | "top": newTop + "px"
108 | });
109 | oldTop = newTop;
110 | if (oldTop + height >= documentHeight) {
111 | window.clearInterval(interval);
112 | interval = null;
113 | floater.css({
114 | "top": (documentHeight - height) + "px"
115 | });
116 | floater.css("background", "url(//d.thumbs.redditmedia.com/tIFOjQQ5pPahJKP-.png)");
117 |
118 | // unlock achievement
119 | TB.utils.sendEvent(TB.utils.events.TB_KILL_SNOO);
120 | }
121 | }, 50);
122 | });
123 |
124 | $(this).css("background", "transparent");
125 | startInterval();
126 | });
127 | };
128 |
129 | TB.register_module(self);
130 | }
131 |
132 | (function() {
133 | window.addEventListener("TBModuleLoaded", function () {
134 | flyingsnoo();
135 | });
136 | })();
137 |
--------------------------------------------------------------------------------
/extension/data/styles/usernotes.css:
--------------------------------------------------------------------------------
1 | body.mod-toolbox.mod-toolbox-new-modmail .InfoBar__recents.tb-recents .InfoBar__recent {
2 | margin-bottom: 3px;
3 | }
4 |
5 | body.mod-toolbox.mod-toolbox-new-modmail .usernote-button {
6 | vertical-align: middle;
7 | margin-right: 3px;
8 | }
9 |
10 | body.mod-toolbox.mod-toolbox-new-modmail .tb-general-button,
11 | body.mod-toolbox.mod-toolbox-new-modmail li a.tb-general-button,
12 | body.mod-toolbox.mod-toolbox-new-modmail .tb-bracket-button {
13 | display: inline-block;
14 | height: 12px;
15 | line-height: 12px;
16 | font-size: 9px;
17 | padding: 0 2px;
18 | border-width: 0 0 0 3px;
19 | }
20 | body.mod-toolbox.mod-toolbox-new-modmail .tb-popup a {
21 | text-decoration: none;
22 | }
23 |
24 | body.mod-toolbox.mod-toolbox-new-modmail .tb-recents .tb-bracket-button {
25 | border-width: 0 0 0 3px;
26 | padding: 3px;
27 | font-size: 1.2em;
28 | }
29 |
30 | /* Note styling */
31 |
32 | .usernote-button {
33 | margin-left: 2px;
34 |
35 | font-size: x-small;
36 | color: #888888;
37 | }
38 |
39 | .usernote-button #add-user-tag {
40 | cursor: pointer;
41 | }
42 |
43 | .usernote-button .add-user-tag:hover {
44 | text-decoration: none;
45 | }
46 |
47 | /* Popup styling */
48 |
49 | .utagger-popup .right {
50 | float: right;
51 | }
52 |
53 | .utagger-popup .left {
54 | float: left;
55 | }
56 |
57 | .utagger-popup .utagger-date {
58 | font-size: 80%;
59 | }
60 |
61 | .utagger-popup .utagger-notes {
62 | width: 100%;
63 | }
64 |
65 | .utagger-popup .utagger-notes td {
66 | padding: 2px !important;
67 | border: solid 1px #C1BFBF;
68 | vertical-align: top;
69 | }
70 |
71 | .utagger-popup .utagger-notes-td1 {
72 | width: 65px;
73 | }
74 |
75 | .utagger-popup .utagger-notes-td3 {
76 | width: 5px;
77 | border: 0;
78 | }
79 |
80 | .utagger-popup .utagger-notes-td3 a {
81 | color: #C92A2A;
82 | font-weight: bold;
83 | }
84 |
85 | .utagger-popup .utagger-note .note-type {
86 | margin-right: 2px;
87 | }
88 |
89 | .utagger-popup .utagger-remove-note {
90 | cursor: pointer;
91 | }
92 |
93 | .utagger-popup .utagger-types {
94 | display: inline-block;
95 | width: 100%;
96 | margin-top: 5px;
97 | margin-bottom: 6px;
98 | border-top: 0;
99 | padding-top: 4px;
100 | }
101 |
102 | .utagger-popup .utagger-type input {
103 | display: none;
104 | }
105 |
106 | .utagger-popup .utagger-type span {
107 | padding: 3px;
108 | border: 1px solid rgb(114, 134, 154);
109 | transition: backgroun-color 0.15s ease-out;
110 | }
111 |
112 | .utagger-popup td:not(:last-child) .utagger-type span {
113 | border-right: none;
114 | }
115 |
116 | .utagger-popup .utagger-type span:hover {
117 | background-color: #e2eefa;
118 | cursor: pointer;
119 | }
120 | .utagger-popup .utagger-type input:checked ~ span {
121 | background-color: #e2eefa;
122 | }
123 |
124 | .utagger-popup .utagger-type span:before {
125 | content: '\2713';
126 | margin-right: 2px;
127 | opacity: 0.5;}
128 |
129 | .utagger-popup .utagger-type span:hover:before {
130 | opacity: 1;
131 | }
132 |
133 | .utagger-popup .utagger-type input:checked ~ span:before {
134 | opacity: 1;
135 | font-weight: bold;
136 | }
137 |
138 | .utagger-popup .utagger-user-note {
139 | width: 98%;
140 | margin-bottom: 2px;
141 | padding: 2px;
142 | }
143 |
144 | .utagger-popup #utagger-user-note-input.error {
145 | border-color: red;
146 | }
147 |
148 | /*.utagger-popup .utagger-type input,*/
149 | .utagger-popup .utagger-include-link input {
150 | margin-right: 1px;
151 | vertical-align: sub;
152 | }
153 |
154 | .utagger-popup .utagger-include-link input[disabled] ~ span {
155 | color: slategray;
156 | }
157 |
158 | /* Usernote manager */
159 |
160 | .mod-toolbox a.tb-un-notedelete img {
161 | width: 13px;
162 | opacity: 0.8;
163 | margin-bottom: -3px;
164 | }
165 |
166 | .mod-toolbox .tb-un-user:nth-child(even) {
167 | background: #F6F9FC;
168 | }
169 |
170 | .mod-toolbox .tb-un-user {
171 | padding: 5px;
172 | margin-bottom: 3px;
173 | border: solid 1px #FAFAFA;
174 | }
175 |
176 | .mod-toolbox .tb-un-note-details {
177 | padding: 3px;
178 | }
179 |
180 | .mod-toolbox .tb-un-user-header {
181 | border-bottom: solid 1px #E7E7E7;
182 | margin-bottom: 3px;
183 | }
184 |
185 | .mod-toolbox .tb-un-user-header .tb-un-delete {
186 | margin-right: 4px;
187 | }
188 |
189 | .mod-toolbox .tb-un-user-header .user {
190 | font-size: 12px;
191 | }
192 |
193 | .mod-toolbox #tb-un-note-content-wrap {
194 | max-width: 115em;
195 | margin: 30px;
196 | }
197 |
198 | .mod-toolbox .tb-un-note-details .note {
199 | margin-left: 4px;
200 | }
201 |
202 | .mod-toolbox .tb-un-note-details .note .note-type {
203 | margin-right: 2px;
204 | }
205 |
206 | .mod-toolbox .tb-un-user-header .mod,
207 | .mod-toolbox .tb-un-note-details .date {
208 | color: #A8A8A8;
209 | }
210 |
211 | .mod-toolbox .tb-un-user-header .tb-un-notedelete {
212 | margin-right: 4px;
213 | }
214 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/dialog.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | // Open simple dialogs on top of an editor. Relies on dialog.css.
5 |
6 | (function(mod) {
7 | if (typeof exports == "object" && typeof module == "object") // CommonJS
8 | mod(require("../../lib/codemirror"));
9 | else if (typeof define == "function" && define.amd) // AMD
10 | define(["../../lib/codemirror"], mod);
11 | else // Plain browser env
12 | mod(CodeMirror);
13 | })(function(CodeMirror) {
14 | function dialogDiv(cm, template, bottom) {
15 | var wrap = cm.getWrapperElement();
16 | var dialog;
17 | dialog = wrap.appendChild(document.createElement("div"));
18 | if (bottom)
19 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
20 | else
21 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
22 |
23 | if (typeof template == "string") {
24 | dialog.innerHTML = template;
25 | } else { // Assuming it's a detached DOM element.
26 | dialog.appendChild(template);
27 | }
28 | return dialog;
29 | }
30 |
31 | function closeNotification(cm, newVal) {
32 | if (cm.state.currentNotificationClose)
33 | cm.state.currentNotificationClose();
34 | cm.state.currentNotificationClose = newVal;
35 | }
36 |
37 | CodeMirror.defineExtension("openDialog", function(template, callback, options) {
38 | if (!options) options = {};
39 |
40 | closeNotification(this, null);
41 |
42 | var dialog = dialogDiv(this, template, options.bottom);
43 | var closed = false, me = this;
44 | function close(newVal) {
45 | if (typeof newVal == 'string') {
46 | inp.value = newVal;
47 | } else {
48 | if (closed) return;
49 | closed = true;
50 | dialog.parentNode.removeChild(dialog);
51 | me.focus();
52 |
53 | if (options.onClose) options.onClose(dialog);
54 | }
55 | }
56 |
57 | var inp = dialog.getElementsByTagName("input")[0], button;
58 | if (inp) {
59 | inp.focus();
60 |
61 | if (options.value) {
62 | inp.value = options.value;
63 | if (options.selectValueOnOpen !== false) {
64 | inp.select();
65 | }
66 | }
67 |
68 | if (options.onInput)
69 | CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
70 | if (options.onKeyUp)
71 | CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
72 |
73 | CodeMirror.on(inp, "keydown", function(e) {
74 | if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
75 | if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
76 | inp.blur();
77 | CodeMirror.e_stop(e);
78 | close();
79 | }
80 | if (e.keyCode == 13) callback(inp.value, e);
81 | });
82 |
83 | if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
84 | } else if (button = dialog.getElementsByTagName("button")[0]) {
85 | CodeMirror.on(button, "click", function() {
86 | close();
87 | me.focus();
88 | });
89 |
90 | if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
91 |
92 | button.focus();
93 | }
94 | return close;
95 | });
96 |
97 | CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
98 | closeNotification(this, null);
99 | var dialog = dialogDiv(this, template, options && options.bottom);
100 | var buttons = dialog.getElementsByTagName("button");
101 | var closed = false, me = this, blurring = 1;
102 | function close() {
103 | if (closed) return;
104 | closed = true;
105 | dialog.parentNode.removeChild(dialog);
106 | me.focus();
107 | }
108 | buttons[0].focus();
109 | for (var i = 0; i < buttons.length; ++i) {
110 | var b = buttons[i];
111 | (function(callback) {
112 | CodeMirror.on(b, "click", function(e) {
113 | CodeMirror.e_preventDefault(e);
114 | close();
115 | if (callback) callback(me);
116 | });
117 | })(callbacks[i]);
118 | CodeMirror.on(b, "blur", function() {
119 | --blurring;
120 | setTimeout(function() { if (blurring <= 0) close(); }, 200);
121 | });
122 | CodeMirror.on(b, "focus", function() { ++blurring; });
123 | }
124 | });
125 |
126 | /*
127 | * openNotification
128 | * Opens a notification, that can be closed with an optional timer
129 | * (default 5000ms timer) and always closes on click.
130 | *
131 | * If a notification is opened while another is opened, it will close the
132 | * currently opened one and open the new one immediately.
133 | */
134 | CodeMirror.defineExtension("openNotification", function(template, options) {
135 | closeNotification(this, close);
136 | var dialog = dialogDiv(this, template, options && options.bottom);
137 | var closed = false, doneTimer;
138 | var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
139 |
140 | function close() {
141 | if (closed) return;
142 | closed = true;
143 | clearTimeout(doneTimer);
144 | dialog.parentNode.removeChild(dialog);
145 | }
146 |
147 | CodeMirror.on(dialog, 'click', function(e) {
148 | CodeMirror.e_preventDefault(e);
149 | close();
150 | });
151 |
152 | if (duration)
153 | doneTimer = setTimeout(close, duration);
154 |
155 | return close;
156 | });
157 | });
158 |
--------------------------------------------------------------------------------
/extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Moderator toolbox for reddit",
4 | "author": "toolbox team",
5 | "short_name": "toolbox",
6 | "description": "A set of tools to be used by moderators on reddit in order to make their jobs easier.",
7 | "version": "3.6.4",
8 | "options_page": "data/background/options.html",
9 | "applications": {
10 | "gecko": {
11 | "id": "yes@jetpack",
12 | "strict_min_version": "46.0"
13 | }
14 | },
15 | "permissions": [
16 | "https://*.reddit.com/",
17 | "http://*.reddit.com/",
18 | "cookies",
19 | "tabs",
20 | "storage",
21 | "unlimitedStorage"
22 | ],
23 | "icons": {
24 | "16": "data/images/icon16.png",
25 | "48": "data/images/icon48.png",
26 | "128": "data/images/icon128.png"
27 | },
28 | "background": {
29 | "scripts": [
30 | "data/libs/jquery-3.1.1.js",
31 | "data/background/chrome.js"
32 | ],
33 | "persistent": false
34 | },
35 | "content_scripts": [
36 | {
37 | "run_at": "document_end",
38 | "matches": [
39 | "http://*.reddit.com/*",
40 | "https://*.reddit.com/*"
41 | ],
42 | "css": [
43 | "data/styles/spectrum.css",
44 | "data/styles/toolbox.css",
45 | "data/styles/support.css",
46 | "data/styles/comment.css",
47 | "data/styles/modmatrix.css",
48 | "data/styles/removalreasons.css",
49 | "data/styles/personalnotes.css",
50 | "data/styles/queuetools.css",
51 | "data/styles/modmailpro.css",
52 | "data/styles/achievements.css",
53 | "data/styles/modbar.css",
54 | "data/styles/historybutton.css",
55 | "data/styles/trouble.css",
56 | "data/styles/notifier.css",
57 | "data/styles/domaintagger.css",
58 | "data/styles/usernotes.css",
59 | "data/styles/config.css",
60 | "data/styles/codemirror/codemirror.css",
61 | "data/styles/codemirror/dialog.css",
62 | "data/styles/codemirror/fullscreen.css",
63 | "data/styles/codemirror/matchesonscrollbar.css",
64 | "data/styles/codemirror/show-hint.css",
65 | "data/styles/codemirror/themes.css"
66 | ],
67 | "js": [
68 | "data/libs/jquery-3.1.1.js",
69 | "data/libs/jquery_migrate.js",
70 | "data/libs/tbplugins.js",
71 | "data/libs/snuownd.js",
72 | "data/libs/codemirror/codemirror.js",
73 | "data/libs/codemirror/addon/closebrackets.js",
74 | "data/libs/codemirror/addon/css-hint.js",
75 | "data/libs/codemirror/addon/dialog.js",
76 | "data/libs/codemirror/addon/fullscreen.js",
77 | "data/libs/codemirror/addon/jump-to-line.js",
78 | "data/libs/codemirror/addon/matchesonscrollbar.js",
79 | "data/libs/codemirror/addon/match-highlighter.js",
80 | "data/libs/codemirror/addon/annotatescrollbar.js",
81 | "data/libs/codemirror/addon/panel.js",
82 | "data/libs/codemirror/addon/rulers.js",
83 | "data/libs/codemirror/addon/search.js",
84 | "data/libs/codemirror/addon/searchcursor.js",
85 | "data/libs/codemirror/addon/show-hint.js",
86 | "data/libs/codemirror/mode/css.js",
87 | "data/libs/codemirror/mode/markdown.js",
88 | "data/libs/codemirror/mode/yaml.js",
89 | "data/libs/codemirror/mode/javascript.js",
90 | "data/libs/pako.js",
91 | "data/libs/redditapi.js",
92 | "data/libs/spectrum.js",
93 | "data/tbstorage.js",
94 | "data/tbui.js",
95 | "data/tbutils.js",
96 | "data/tbmodule.js",
97 | "data/modules/support.js",
98 | "data/modules/modbar.js",
99 | "data/modules/modbutton.js",
100 | "data/modules/notifier.js",
101 | "data/modules/modmailpro.js",
102 | "data/modules/newmodmailpro.js",
103 | "data/modules/domaintagger.js",
104 | "data/modules/usernotes.js",
105 | "data/modules/metricstab.js",
106 | "data/modules/comment.js",
107 | "data/modules/config.js",
108 | "data/modules/modmatrix.js",
109 | "data/modules/banlist.js",
110 | "data/modules/removalreasons.js",
111 | "data/modules/historybutton.js",
112 | "data/modules/syntax.js",
113 | "data/modules/macros.js",
114 | "data/modules/realtime.js",
115 | "data/modules/nukecomments.js",
116 | "data/modules/personalnotes.js",
117 | "data/modules/betterbuttons.js",
118 | "data/modules/bagel.js",
119 | "data/modules/flyingsnoo.js",
120 | "data/modules/achievements.js",
121 | "data/modules/trouble.js",
122 | "data/modules/queuetools.js",
123 | "data/tbmoduleinit.js"
124 | ]
125 | }
126 | ],
127 | "web_accessible_resources": [
128 | "data/libs/jquery-2.2.4.js",
129 | "data/libs/snuownd.js",
130 | "data/libs/worker-css.js"
131 | ]
132 | }
133 |
--------------------------------------------------------------------------------
/extension/data/modules/newmodmailpro.js:
--------------------------------------------------------------------------------
1 | function newmodmailpro() {
2 | var self = new TB.Module('New Mod Mail Pro');
3 | self.shortname = 'NewModMail';
4 |
5 | ////Default settings
6 | self.settings['enabled']['default'] = false;
7 | self.config['betamode'] = false;
8 |
9 | self.register_setting('modmaillink', {
10 | 'type': 'selector',
11 | 'values': ['All modmail', 'New', 'In Progress', 'Archived', 'Highlighted', 'Mod Discussions', 'Notifications'],
12 | 'default': 'all_modmail',
13 | 'title': 'Change the modmail link to open a different modmail view by default.'
14 | });
15 |
16 | self.register_setting('openmailtab', {
17 | 'type': 'boolean',
18 | 'default': true,
19 | 'title': 'Open modmail in a new tab.'
20 | });
21 |
22 | self.register_setting('lastreplytypecheck', {
23 | 'type': 'boolean',
24 | 'default': true,
25 | 'title': 'Warns you if you reply as yourself but the last reply type is a private mod note or a "as subreddit" reply. '
26 | });
27 |
28 | self.register_setting('modmailnightmode', {
29 | 'type': 'boolean',
30 | 'default': false,
31 | 'title': 'Open modmail in nightmode'
32 | });
33 |
34 | // All stuff we want to do when we are on new modmail
35 | if (TBUtils.isNewModmail) {
36 | // Add a class to body
37 | var $body = $('body');
38 |
39 | $body.addClass('tb-new-modmail');
40 |
41 | // ready some variables.
42 | var modMailNightmode = self.setting('modmailnightmode'),
43 | lastReplyTypeCheck = self.setting('lastreplytypecheck');
44 |
45 |
46 | if (lastReplyTypeCheck && TBUtils.isNewMMThread) {
47 | $body.on('click', '.ThreadViewerReplyForm__replyButton', function(event) {
48 |
49 | // Get all mod replies and see if they are something we need to warn the user about.
50 | let $lastReply = $body.find('.Thread__messages .Thread__message:has(.m-mod)').last();
51 | const replyTypeMyself = $body.find('.FancySelect__valueText').text() === 'Reply as myself';
52 |
53 | // if it finds this the last mod that replied did so with "as subreddit".
54 | if ($lastReply.find('.icon-profile-slash').length && replyTypeMyself) {
55 | if (confirm('The last mod that replied did so as the subreddit, are you sure you want to reply as yourself?')) {
56 | // Ok, do nothing and let the message be posted.
57 | } else {
58 | // Not ok, prevent the button from being clicked.
59 | event.preventDefault();
60 | }
61 |
62 | }
63 |
64 | // If it finds this class it means the last reply was a private mod note.
65 | if ($lastReply.find('.Thread__messageIsMod').length && replyTypeMyself) {
66 | if (confirm('The last mod that replied did so with a private mod note, are you sure you want to reply as yourself?')) {
67 | // Ok, do nothing and let the message be posted.
68 | } else {
69 | // Not ok, prevent the button from being clicked.
70 | event.preventDefault();
71 | }
72 | }
73 |
74 | });
75 | }
76 |
77 | if (modMailNightmode) {
78 | // Let's make sure RES nightmode doesn't mess things up.
79 | $('html, body').removeClass('res-nightmode');
80 |
81 | // Now enable toolbox nightmode.
82 | // Firefox can't do simple nightmode so we do it like this
83 | if(TBUtils.browser === 'firefox') {
84 | $('html').addClass('tb-nightmode-firefox');
85 | $('body').addClass('tb-nightmode-firefox');
86 | } else {
87 | $('html').addClass('tb-nightmode');
88 | }
89 | }
90 | }
91 |
92 | // Below all stuff we do when we are NOT on new modmail.
93 | if (!TBUtils.isNewModmail) {
94 |
95 | // ready some variables.
96 | var modmailLink = self.setting('modmaillink'),
97 | openMailTab = self.setting('openmailtab');
98 |
99 |
100 | // Let's mess around with the link to modmail.
101 | var $newModmailLinkElement = $('#new_modmail'),
102 | newModmailBaseUrl = 'https://mod.reddit.com/mail/';
103 |
104 | // Open modmail in a new tab if the option is selected
105 | if (openMailTab) {
106 | $newModmailLinkElement.attr('target', '_blank');
107 | }
108 |
109 | // let's replace urls.
110 | switch(modmailLink) {
111 | case 'all_modmail':
112 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'all');
113 |
114 | break;
115 | case 'new':
116 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'new');
117 |
118 | break;
119 | case 'in_progress':
120 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'inprogress');
121 |
122 | break;
123 | case 'archived':
124 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'archived');
125 |
126 | break;
127 | case 'highlighted':
128 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'highlighted');
129 |
130 | break;
131 | case 'mod_discussions':
132 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'mod');
133 |
134 | break;
135 | case 'notifications':
136 | $newModmailLinkElement.attr('href', newModmailBaseUrl + 'notifications');
137 |
138 | }
139 |
140 | }
141 |
142 | TB.register_module(self);
143 | }
144 |
145 | (function () {
146 | window.addEventListener("TBModuleLoaded", function () {
147 | newmodmailpro();
148 | });
149 | })();
150 |
--------------------------------------------------------------------------------
/extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Author
6 | toolbox Team
7 | Builder Version
8 | 10600.8.9
9 | CFBundleDisplayName
10 | Moderator toolbox for reddit
11 | CFBundleIdentifier
12 | com.reddit.toolbox
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleShortVersionString
16 | 3.6.4
17 | CFBundleVersion
18 | 370
19 | Chrome
20 |
21 | Global Page
22 | data/background/safari.html
23 |
24 | Content
25 |
26 | Scripts
27 |
28 | End
29 |
30 | data/libs/jquery-3.1.1.js
31 | data/libs/tbplugins.js
32 | data/libs/snuownd.js
33 | data/libs/codemirror/codemirror.js
34 | data/libs/codemirror/addon/closebrackets.js
35 | data/libs/codemirror/addon/css-hint.js
36 | data/libs/codemirror/addon/dialog.js
37 | data/libs/codemirror/addon/fullscreen.js
38 | data/libs/codemirror/addon/jump-to-line.js
39 | data/libs/codemirror/addon/matchesonscrollbar.js
40 | data/libs/codemirror/addon/match-highlighter.js
41 | data/libs/codemirror/addon/annotatescrollbar.js
42 | data/libs/codemirror/addon/panel.js
43 | data/libs/codemirror/addon/rulers.js
44 | data/libs/codemirror/addon/search.js
45 | data/libs/codemirror/addon/searchcursor.js
46 | data/libs/codemirror/addon/show-hint.js
47 | data/libs/codemirror/mode/css.js
48 | data/libs/codemirror/mode/markdown.js
49 | data/libs/codemirror/mode/yaml.js
50 | data/libs/codemirror/mode/javascript.js
51 | data/libs/pako.js
52 | data/libs/redditapi.js
53 | data/libs/spectrum.js
54 | data/tbstorage.js
55 | data/tbui.js
56 | data/tbutils.js
57 | data/tbmodule.js
58 | data/modules/support.js
59 | data/modules/modbar.js
60 | data/modules/modbutton.js
61 | data/modules/notifier.js
62 | data/modules/modmailpro.js
63 | data/modules/newmodmailpro.js
64 | data/modules/domaintagger.js
65 | data/modules/usernotes.js
66 | data/modules/metricstab.js
67 | data/modules/comment.js
68 | data/modules/config.js
69 | data/modules/modmatrix.js
70 | data/modules/banlist.js
71 | data/modules/removalreasons.js
72 | data/modules/historybutton.js
73 | data/modules/syntax.js
74 | data/modules/macros.js
75 | data/modules/realtime.js
76 | data/modules/nukecomments.js
77 | data/modules/personalnotes.js
78 | data/modules/betterbuttons.js
79 | data/modules/bagel.js
80 | data/modules/flyingsnoo.js
81 | data/modules/achievements.js
82 | data/modules/trouble.js
83 | data/modules/queuetools.js
84 | data/tbmoduleinit.js
85 |
86 |
87 | Stylesheets
88 |
89 | data/styles/spectrum.css
90 | data/styles/toolbox.css
91 | data/styles/support.css
92 | data/styles/comment.css
93 | data/styles/modmatrix.css
94 | data/styles/removalreasons.css
95 | data/styles/personalnotes.css
96 | data/styles/queuetools.css
97 | data/styles/modmailpro.css
98 | data/styles/achievements.css
99 | data/styles/modbar.css
100 | data/styles/historybutton.css
101 | data/styles/trouble.css
102 | data/styles/notifier.css
103 | data/styles/domaintagger.css
104 | data/styles/usernotes.css
105 | data/styles/config.css
106 | data/styles/codemirror/codemirror.css
107 | data/styles/codemirror/dialog.css
108 | data/styles/codemirror/fullscreen.css
109 | data/styles/codemirror/matchesonscrollbar.css
110 | data/styles/codemirror/show-hint.css
111 | data/styles/codemirror/themes.css
112 |
113 |
114 | Description
115 | A set of tools to be used by moderators on reddit in order to make their jobs easier.
116 | DeveloperIdentifier
117 | 42X34WDLP5
118 | ExtensionInfoDictionaryVersion
119 | 1.0
120 | Permissions
121 |
122 | Website Access
123 |
124 | Allowed Domains
125 |
126 | *.reddit.com
127 |
128 | Include Secure Pages
129 |
130 | Level
131 | Some
132 |
133 |
134 | Update Manifest URL
135 | http://creesch.github.io/reddit-moderator-toolbox/update/UpdateManifest.plist
136 | Website
137 | http://reddit.com/r/toolbox
138 |
139 |
140 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/match-highlighter.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | // Highlighting text that matches the selection
5 | //
6 | // Defines an option highlightSelectionMatches, which, when enabled,
7 | // will style strings that match the selection throughout the
8 | // document.
9 | //
10 | // The option can be set to true to simply enable it, or to a
11 | // {minChars, style, wordsOnly, showToken, delay} object to explicitly
12 | // configure it. minChars is the minimum amount of characters that should be
13 | // selected for the behavior to occur, and style is the token style to
14 | // apply to the matches. This will be prefixed by "cm-" to create an
15 | // actual CSS class name. If wordsOnly is enabled, the matches will be
16 | // highlighted only if the selected text is a word. showToken, when enabled,
17 | // will cause the current token to be highlighted when nothing is selected.
18 | // delay is used to specify how much time to wait, in milliseconds, before
19 | // highlighting the matches. If annotateScrollbar is enabled, the occurences
20 | // will be highlighted on the scrollbar via the matchesonscrollbar addon.
21 |
22 | (function(mod) {
23 | if (typeof exports == "object" && typeof module == "object") // CommonJS
24 | mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
25 | else if (typeof define == "function" && define.amd) // AMD
26 | define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
27 | else // Plain browser env
28 | mod(CodeMirror);
29 | })(function(CodeMirror) {
30 | "use strict";
31 |
32 | var defaults = {
33 | style: "matchhighlight",
34 | minChars: 2,
35 | delay: 100,
36 | wordsOnly: false,
37 | annotateScrollbar: false,
38 | showToken: false,
39 | trim: true
40 | }
41 |
42 | function State(options) {
43 | this.options = {}
44 | for (var name in defaults)
45 | this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
46 | this.overlay = this.timeout = null;
47 | this.matchesonscroll = null;
48 | this.active = false;
49 | }
50 |
51 | CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
52 | if (old && old != CodeMirror.Init) {
53 | removeOverlay(cm);
54 | clearTimeout(cm.state.matchHighlighter.timeout);
55 | cm.state.matchHighlighter = null;
56 | cm.off("cursorActivity", cursorActivity);
57 | cm.off("focus", onFocus)
58 | }
59 | if (val) {
60 | var state = cm.state.matchHighlighter = new State(val);
61 | if (cm.hasFocus()) {
62 | state.active = true
63 | highlightMatches(cm)
64 | } else {
65 | cm.on("focus", onFocus)
66 | }
67 | cm.on("cursorActivity", cursorActivity);
68 | }
69 | });
70 |
71 | function cursorActivity(cm) {
72 | var state = cm.state.matchHighlighter;
73 | if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
74 | }
75 |
76 | function onFocus(cm) {
77 | var state = cm.state.matchHighlighter
78 | if (!state.active) {
79 | state.active = true
80 | scheduleHighlight(cm, state)
81 | }
82 | }
83 |
84 | function scheduleHighlight(cm, state) {
85 | clearTimeout(state.timeout);
86 | state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
87 | }
88 |
89 | function addOverlay(cm, query, hasBoundary, style) {
90 | var state = cm.state.matchHighlighter;
91 | cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
92 | if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
93 | var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
94 | state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
95 | {className: "CodeMirror-selection-highlight-scrollbar"});
96 | }
97 | }
98 |
99 | function removeOverlay(cm) {
100 | var state = cm.state.matchHighlighter;
101 | if (state.overlay) {
102 | cm.removeOverlay(state.overlay);
103 | state.overlay = null;
104 | if (state.matchesonscroll) {
105 | state.matchesonscroll.clear();
106 | state.matchesonscroll = null;
107 | }
108 | }
109 | }
110 |
111 | function highlightMatches(cm) {
112 | cm.operation(function() {
113 | var state = cm.state.matchHighlighter;
114 | removeOverlay(cm);
115 | if (!cm.somethingSelected() && state.options.showToken) {
116 | var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken;
117 | var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
118 | while (start && re.test(line.charAt(start - 1))) --start;
119 | while (end < line.length && re.test(line.charAt(end))) ++end;
120 | if (start < end)
121 | addOverlay(cm, line.slice(start, end), re, state.options.style);
122 | return;
123 | }
124 | var from = cm.getCursor("from"), to = cm.getCursor("to");
125 | if (from.line != to.line) return;
126 | if (state.options.wordsOnly && !isWord(cm, from, to)) return;
127 | var selection = cm.getRange(from, to)
128 | if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "")
129 | if (selection.length >= state.options.minChars)
130 | addOverlay(cm, selection, false, state.options.style);
131 | });
132 | }
133 |
134 | function isWord(cm, from, to) {
135 | var str = cm.getRange(from, to);
136 | if (str.match(/^\w+$/) !== null) {
137 | if (from.ch > 0) {
138 | var pos = {line: from.line, ch: from.ch - 1};
139 | var chr = cm.getRange(pos, from);
140 | if (chr.match(/\W/) === null) return false;
141 | }
142 | if (to.ch < cm.getLine(from.line).length) {
143 | var pos = {line: to.line, ch: to.ch + 1};
144 | var chr = cm.getRange(to, pos);
145 | if (chr.match(/\W/) === null) return false;
146 | }
147 | return true;
148 | } else return false;
149 | }
150 |
151 | function boundariesAround(stream, re) {
152 | return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&
153 | (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));
154 | }
155 |
156 | function makeOverlay(query, hasBoundary, style) {
157 | return {token: function(stream) {
158 | if (stream.match(query) &&
159 | (!hasBoundary || boundariesAround(stream, hasBoundary)))
160 | return style;
161 | stream.next();
162 | stream.skipTo(query.charAt(0)) || stream.skipToEnd();
163 | }};
164 | }
165 | });
166 |
--------------------------------------------------------------------------------
/extension/data/modules/nukecomments.js:
--------------------------------------------------------------------------------
1 | function nukecomments() {
2 | // Adapted from:
3 | // @name Reddit Mod Nuke Userscript
4 | // @author djimbob (dr jimbob)
5 |
6 | var self = new TB.Module('Comment Nuke');
7 | self.shortname = 'Nuke';
8 |
9 | ////Default settings
10 | self.settings['enabled']['default'] = false;
11 | self.config['betamode'] = false;
12 |
13 | self.register_setting('hideAfterNuke', {
14 | 'type': 'boolean',
15 | 'default': false,
16 | 'title': 'Hide nuked comments after they are removed'
17 | });
18 |
19 | self.register_setting('ignoreMods', {
20 | 'type': 'boolean',
21 | 'default': true,
22 | 'title': 'Ignore moderators\' comments when removing'
23 | });
24 |
25 | self.register_setting('confirmNuke', {
26 | 'type': 'boolean',
27 | 'default': true,
28 | 'title': 'Show a confirmation window before nuking a comment chain'
29 | });
30 |
31 | // self.register_setting('useImage', {
32 | // 'type': 'boolean',
33 | // 'default': true,
34 | // 'title': 'Use an image button instead of [R]'
35 | // });
36 |
37 |
38 | self.init = function () {
39 | // // Image or text?
40 | // if (self.setting('useImage')) {
41 | // self.button = $('
![]()
')
42 | // .attr('title', 'Nuke!')
43 | // .attr('src', TB.ui.iconNuke)
44 | // .prop('outerHTML');
45 | // } else {
46 | // self.button = '[R]';
47 | // }
48 |
49 | self.button = 'R';
50 |
51 | // Mod button clicked
52 | $('body').on('click', '.nuke-button', function (event) {
53 | var $nukeButton = $(event.target);
54 | var $comment = $nukeButton.closest('.comment');
55 |
56 | var $continue_thread = $comment.find('span.morecomments>a');
57 |
58 |
59 | var confirmMessage = "Are you sure you want to nuke the following ";
60 | var $delete_button = $comment.find('form.remove-button input[name="spam"][value="False"]~span.option.error a.yes,a[onclick^="return big_mod_action($(this), -1)"]');
61 | // form input[value="removed"]~span.option.error a.yes -- finds the yes for normal deleting comments.
62 | // a.pretty-button.neutral finds the 'remove' button for flagged comments
63 | confirmMessage += $delete_button.length;
64 | if ($continue_thread.length > 0) {
65 | confirmMessage += "+ comments (more after expanding collapsed threads; there will be a pause before the first deletion to retrieve more comments)?";
66 | } else {
67 | confirmMessage += " comments?";
68 | }
69 |
70 | if (!self.setting('confirmNuke')
71 | || confirm(confirmMessage)
72 | ) {
73 | $continue_thread.each(function (idx, $continue_button) {
74 | // wait a bit before each ajax call
75 | setTimeout(function () {
76 | $continue_button.click();
77 | }, 2000 * idx);
78 | });
79 |
80 | // wait a bit after last ajax call before deleting
81 | setTimeout(function () {
82 | self.deleteThreadFromComment($comment);
83 | }, 2000 * ($continue_thread.length + ($continue_thread.length ? 1 : 0)));
84 | }
85 |
86 |
87 | return false; // necessary?
88 | });
89 |
90 | // https://github.com/reddit/reddit/blob/master/r2/r2/public/static/js/jquery.reddit.js#L531
91 | // $(document).on('new_thing', function(e, thing) {
92 | // // This could be useful...
93 | // });
94 |
95 | // https://github.com/reddit/reddit/blob/master/r2/r2/public/static/js/jquery.reddit.js#L531
96 | // $(document).on('new_things_inserted', function(e, thing) {
97 | // // eh?
98 | // });
99 |
100 |
101 | // NER support.
102 | window.addEventListener("TBNewThings", function () {
103 | self.run();
104 | });
105 |
106 | self.run();
107 | };
108 |
109 | self.deleteThreadFromComment = function ($thread_root) {
110 | var ignoreMods = self.setting('ignoreMods');
111 |
112 | var $removeButtons = $thread_root.find('form input[value="removed"]~span.option.error a.yes,a[onclick^="return big_mod_action($(this), -1)"]');
113 | TB.ui.longLoadSpinner(true, 'removing comments', 'neutral');
114 | self.log("Nuking " + $removeButtons.length + " comments");
115 |
116 | // we need a delay between every single click of >1sec
117 | // this should be re-written to use the API
118 | TB.utils.forEachChunked($removeButtons, 1, 1500, function remove_comment(button, num) {
119 | var msg = 'removing comment ' + (num + 1) + '/' + $removeButtons.length;
120 | TB.ui.textFeedback(msg, 'neutral');
121 |
122 | if (ignoreMods) {
123 | var $entry = $(button).parents('.entry'),
124 | $author = $entry.find('a.author');
125 |
126 | if ($author.hasClass('moderator')) {
127 | self.log(" " + (num + 1) + "... ignored");
128 | return;
129 | }
130 | }
131 |
132 | self.log(" " + (num + 1) + "... removed");
133 | button.click();
134 | }, function complete() {
135 | if (self.setting('hideAfterNuke')) {
136 | $thread_root.hide(750);
137 | }
138 | self.log("kill spinner");
139 | TB.ui.longLoadSpinner(false);
140 | TB.ui.textFeedback('all comments removed', 'positive');
141 | });
142 | };
143 |
144 |
145 | // Add nuke button to all comments
146 | self.processComment = function (comment, num) {
147 | var $comment = $(comment);
148 | if (!$comment.hasClass('nuke-processed')) {
149 | // Add the class so we don't add buttons twice.
150 | $comment.addClass('nuke-processed');
151 |
152 |
153 |
154 | // Defer info gathering until button is clicked.
155 | // the report button is always visible, so we don't have to do anything special for the big mod action buttons
156 | $comment.find('.userattrs:first')
157 | .after('
' + self.button + '');
158 | }
159 | };
160 |
161 | // need this for RES NER support
162 | self.run = function () {
163 | // Not a mod, don't bother.
164 | if (!TB.utils.isMod || TB.utils.isModQueuePage) {
165 | //self.log('Not a mod of the sub, d\'oh!');
166 | return;
167 | }
168 |
169 | var $comments = $('div.comment:not(.nuke-processed)');
170 | TB.utils.forEachChunked($comments, 15, 650, self.processComment);
171 | };
172 |
173 | TB.register_module(self);
174 | } // nukecomments() wrapper
175 |
176 | (function() {
177 | window.addEventListener("TBModuleLoaded", function () {
178 | nukecomments();
179 | });
180 | })();
181 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/closebrackets.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | var defaults = {
13 | pairs: "()[]{}''\"\"",
14 | triples: "",
15 | explode: "[]{}"
16 | };
17 |
18 | var Pos = CodeMirror.Pos;
19 |
20 | CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
21 | if (old && old != CodeMirror.Init) {
22 | cm.removeKeyMap(keyMap);
23 | cm.state.closeBrackets = null;
24 | }
25 | if (val) {
26 | cm.state.closeBrackets = val;
27 | cm.addKeyMap(keyMap);
28 | }
29 | });
30 |
31 | function getOption(conf, name) {
32 | if (name == "pairs" && typeof conf == "string") return conf;
33 | if (typeof conf == "object" && conf[name] != null) return conf[name];
34 | return defaults[name];
35 | }
36 |
37 | var bind = defaults.pairs + "`";
38 | var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
39 | for (var i = 0; i < bind.length; i++)
40 | keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
41 |
42 | function handler(ch) {
43 | return function(cm) { return handleChar(cm, ch); };
44 | }
45 |
46 | function getConfig(cm) {
47 | var deflt = cm.state.closeBrackets;
48 | if (!deflt) return null;
49 | var mode = cm.getModeAt(cm.getCursor());
50 | return mode.closeBrackets || deflt;
51 | }
52 |
53 | function handleBackspace(cm) {
54 | var conf = getConfig(cm);
55 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
56 |
57 | var pairs = getOption(conf, "pairs");
58 | var ranges = cm.listSelections();
59 | for (var i = 0; i < ranges.length; i++) {
60 | if (!ranges[i].empty()) return CodeMirror.Pass;
61 | var around = charsAround(cm, ranges[i].head);
62 | if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
63 | }
64 | for (var i = ranges.length - 1; i >= 0; i--) {
65 | var cur = ranges[i].head;
66 | cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
67 | }
68 | }
69 |
70 | function handleEnter(cm) {
71 | var conf = getConfig(cm);
72 | var explode = conf && getOption(conf, "explode");
73 | if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
74 |
75 | var ranges = cm.listSelections();
76 | for (var i = 0; i < ranges.length; i++) {
77 | if (!ranges[i].empty()) return CodeMirror.Pass;
78 | var around = charsAround(cm, ranges[i].head);
79 | if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
80 | }
81 | cm.operation(function() {
82 | cm.replaceSelection("\n\n", null);
83 | cm.execCommand("goCharLeft");
84 | ranges = cm.listSelections();
85 | for (var i = 0; i < ranges.length; i++) {
86 | var line = ranges[i].head.line;
87 | cm.indentLine(line, null, true);
88 | cm.indentLine(line + 1, null, true);
89 | }
90 | });
91 | }
92 |
93 | function contractSelection(sel) {
94 | var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
95 | return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
96 | head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
97 | }
98 |
99 | function handleChar(cm, ch) {
100 | var conf = getConfig(cm);
101 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
102 |
103 | var pairs = getOption(conf, "pairs");
104 | var pos = pairs.indexOf(ch);
105 | if (pos == -1) return CodeMirror.Pass;
106 | var triples = getOption(conf, "triples");
107 |
108 | var identical = pairs.charAt(pos + 1) == ch;
109 | var ranges = cm.listSelections();
110 | var opening = pos % 2 == 0;
111 |
112 | var type;
113 | for (var i = 0; i < ranges.length; i++) {
114 | var range = ranges[i], cur = range.head, curType;
115 | var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
116 | if (opening && !range.empty()) {
117 | curType = "surround";
118 | } else if ((identical || !opening) && next == ch) {
119 | if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
120 | curType = "skipThree";
121 | else
122 | curType = "skip";
123 | } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
124 | cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
125 | (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
126 | curType = "addFour";
127 | } else if (identical) {
128 | if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
129 | else return CodeMirror.Pass;
130 | } else if (opening && (cm.getLine(cur.line).length == cur.ch ||
131 | isClosingBracket(next, pairs) ||
132 | /\s/.test(next))) {
133 | curType = "both";
134 | } else {
135 | return CodeMirror.Pass;
136 | }
137 | if (!type) type = curType;
138 | else if (type != curType) return CodeMirror.Pass;
139 | }
140 |
141 | var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
142 | var right = pos % 2 ? ch : pairs.charAt(pos + 1);
143 | cm.operation(function() {
144 | if (type == "skip") {
145 | cm.execCommand("goCharRight");
146 | } else if (type == "skipThree") {
147 | for (var i = 0; i < 3; i++)
148 | cm.execCommand("goCharRight");
149 | } else if (type == "surround") {
150 | var sels = cm.getSelections();
151 | for (var i = 0; i < sels.length; i++)
152 | sels[i] = left + sels[i] + right;
153 | cm.replaceSelections(sels, "around");
154 | sels = cm.listSelections().slice();
155 | for (var i = 0; i < sels.length; i++)
156 | sels[i] = contractSelection(sels[i]);
157 | cm.setSelections(sels);
158 | } else if (type == "both") {
159 | cm.replaceSelection(left + right, null);
160 | cm.triggerElectric(left + right);
161 | cm.execCommand("goCharLeft");
162 | } else if (type == "addFour") {
163 | cm.replaceSelection(left + left + left + left, "before");
164 | cm.execCommand("goCharRight");
165 | }
166 | });
167 | }
168 |
169 | function isClosingBracket(ch, pairs) {
170 | var pos = pairs.lastIndexOf(ch);
171 | return pos > -1 && pos % 2 == 1;
172 | }
173 |
174 | function charsAround(cm, pos) {
175 | var str = cm.getRange(Pos(pos.line, pos.ch - 1),
176 | Pos(pos.line, pos.ch + 1));
177 | return str.length == 2 ? str : null;
178 | }
179 |
180 | // Project the token type that will exists after the given char is
181 | // typed, and use it to determine whether it would cause the start
182 | // of a string token.
183 | function enteringString(cm, pos, ch) {
184 | var line = cm.getLine(pos.line);
185 | var token = cm.getTokenAt(pos);
186 | if (/\bstring2?\b/.test(token.type)) return false;
187 | var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
188 | stream.pos = stream.start = token.start;
189 | for (;;) {
190 | var type1 = cm.getMode().token(stream, token.state);
191 | if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
192 | stream.start = stream.pos;
193 | }
194 | }
195 | });
196 |
--------------------------------------------------------------------------------
/extension/data/modules/trouble.js:
--------------------------------------------------------------------------------
1 | function trouble() {
2 | var self = new TB.Module('Trouble Shooter');
3 | self.shortname = 'Trouble';
4 |
5 | //Default settings
6 | self.settings['enabled']['default'] = false;
7 | self.config['betamode'] = true;
8 |
9 | self.register_setting('highlightAuto', {
10 | 'type': 'boolean',
11 | 'default': false,
12 | 'title': 'Highlight comments automatically'
13 | });
14 |
15 | self.register_setting('negHighlightThreshold', {
16 | 'type': 'number',
17 | 'default': 0,
18 | 'title': 'Negative comment highlight score threshold'
19 | });
20 |
21 | self.register_setting('highlightControversy', {
22 | 'type': 'boolean',
23 | 'default': true,
24 | 'title': 'Highlight controversial comments'
25 | });
26 |
27 | self.register_setting('expandOnLoad', {
28 | 'type': 'boolean',
29 | 'default': false,
30 | 'title': 'Expand all downvoted/controversial comments on page load'
31 | });
32 |
33 | self.register_setting('sortOnMoreChildren', {
34 | 'type': 'boolean',
35 | 'default': false,
36 | 'title': 'Continue to sort children on "load more comments"'
37 | });
38 |
39 | self.register_setting('displayNChildren', {
40 | 'type': 'boolean',
41 | 'default': true,
42 | 'title': 'Always display the number of children a comment has.'
43 | });
44 |
45 | self.register_setting('displayNChildrenTop', {
46 | 'type': 'boolean',
47 | 'default': false,
48 | 'advanced': true,
49 | 'title': 'Display the number of children a comment has in the upper left. This may change the normal flow of the comments page slightly.'
50 | });
51 |
52 | self.sorted = false;
53 | self.pending = [];
54 |
55 | self.init = function() {
56 | var neg_thresh_pref = self.setting('negHighlightThreshold'),
57 | highlightControversy = self.setting('highlightControversy'),
58 | expand = self.setting('expandOnLoad'),
59 | auto = self.setting('highlightAuto'),
60 | sortOnMoreChildren = self.setting('sortOnMoreChildren'),
61 | nChildren = self.setting('displayNChildren'),
62 | nChildrenTop = self.setting('displayNChildrenTop'),
63 | $body = $('body'),
64 | $buttons = $('
'),
65 | $init_btn = $('
').click(start),
66 | $sitetable;
67 |
68 | if(!TBUtils.isMod) return;
69 |
70 | if(!TBUtils.isCommentsPage) return;
71 |
72 | if($body.hasClass('listing-page')){
73 | $sitetable = $('.content').children('.sitetable');
74 | } else {
75 | $sitetable = $('.commentarea').children('.sitetable');
76 | }
77 |
78 | $sitetable.before($buttons);
79 |
80 | if(auto){
81 | start();
82 | } else {
83 | $buttons.append($init_btn);
84 | }
85 |
86 | function start(){
87 |
88 | $init_btn.remove();
89 |
90 | $body.addClass('tb-trouble');
91 | if(highlightControversy) $body.addClass('tb-controversy-hl');
92 | if(nChildren) $body.addClass('tb-nchildren');
93 | if(nChildrenTop) $body.addClass('tb-nchildrentop');
94 |
95 | $buttons.append($('
').click(sortChildren))
96 | .append($('
').click(collapseNonDrama));
97 |
98 | if(sortOnMoreChildren){
99 | $('.commentarea').on('click', '.morecomments', function(){
100 | if(self.sorted) self.pending.push(sortMe.bind($(this).closest('.sitetable')));
101 | });
102 | }
103 | window.addEventListener("TBNewThings", function () {
104 | run();
105 | });
106 |
107 | run();
108 |
109 | if (expand) $('.thing.tb-controversy, .thing.tb-ncontroversy').each(uncollapseThing);
110 | }
111 |
112 | function run() {
113 | var $things = $('.thing.comment').not('.tb-pc-proc');
114 |
115 | highlightComments($things);
116 |
117 | while (self.pending.length) self.pending.pop()();
118 |
119 | if (expand) $('.thing.tb-controversy, .thing.tb-ncontroversy').not('.tb-pc-proc').each(uncollapseThing);
120 |
121 | markProcessedThings();
122 | }
123 |
124 | function highlightComments($things){
125 | var controversial = new RegExp(/\bcontroversial\b/);
126 |
127 | $things.find('.numchildren').each(numChildren);
128 |
129 | $things.find('.score.unvoted').each(score);
130 |
131 | if(highlightControversy){
132 | $things.filter(function(){ return controversial.test(this.className); })
133 | .children('.entry').addClass('tb-controversy')
134 | .parents('.thing').addClass('tb-controversy');
135 | }
136 | }
137 |
138 | function score(){
139 | var $this = $(this),
140 | $thing = $this.closest('.thing'),
141 | neg_thresh = neg_thresh_pref;
142 |
143 | //lower the threashold by one for user's comments
144 | if(RegExp("\/"+TBUtils.logged+"\\b").test($thing.children('.entry').find('.author')[0].href)) --neg_thresh;
145 |
146 | //highlighting here to avoid another .each() iteration
147 | if( ($thing[0].dataset.score = $this.text().match(/^(-)?\d+/)[0]) <= neg_thresh ){
148 | $thing.addClass('tb-neg tb-ncontroversy')
149 | .parents('.thing').addClass('tb-ncontroversy');
150 | }
151 | }
152 |
153 | function numChildren(){
154 | var $this = $(this);
155 |
156 | $this.closest('.thing')[0].dataset.nchildren = $this.text().match(/\d+/)[0];
157 | }
158 |
159 | function sortChildren(){
160 |
161 | self.sorted = true;
162 |
163 | sortMe.call($(this).closest('.sitetable, .commentarea, .content').children('.sitetable'));
164 | }
165 |
166 | function fixFlatNER($this){
167 | var $NERs = $this.find('.linklisting');
168 | if(!$NERs.length) return;
169 |
170 | $this.append($NERs.children('.thing'));
171 | $('.NERPageMarker, .clearleft + .clearleft').remove();
172 | }
173 |
174 | function sortMe(){
175 | var $this = $(this),
176 | $things;
177 |
178 | fixFlatNER($this);
179 |
180 | $things = $this.children('.thing').not('.morechildren')
181 | .sort(function(a, b){
182 | return (b.dataset.nchildren - a.dataset.nchildren);
183 | });
184 |
185 | $this.prepend($things)
186 | .prepend($this.children('.thing.tb-controversy'))
187 | .prepend($this.children('.thing.tb-ncontroversy'));
188 |
189 | $things.children('.child').children('.sitetable').each(sortMe);
190 | }
191 |
192 | function collapseThing(){
193 | $(this).addClass('collapsed').children('.entry').find('.expand').text('[+]');
194 | }
195 |
196 | function uncollapseThing(){
197 | $(this).removeClass('collapsed').children('.entry').find('.expand').text('[–]');
198 | }
199 |
200 | function markProcessedThings(){
201 | $('.thing').not('.tb-pc-proc').addClass('tb-pc-proc');
202 | }
203 |
204 | function collapseNonDrama(){
205 |
206 | $('.thing.tb-controversy, .thing.tb-ncontroversy').each(uncollapseThing);
207 |
208 | $('.commentarea').add($('.thing.tb-controversy, .thing.tb-ncontroversy').children('.child'))
209 | .children('.sitetable').children('.thing').not('.tb-controversy, .tb-ncontroversy')
210 | .each(collapseThing);//collapsing only top-level-most comment children of drama
211 | }
212 | /* TODO
213 |
214 | Include below threshold comments when the score is hidden?
215 |
216 | */
217 | };
218 |
219 | TB.register_module(self);
220 | }
221 |
222 | (function() {
223 | window.addEventListener("TBModuleLoaded", function () {
224 | trouble(); //run
225 | });
226 | })();
227 |
--------------------------------------------------------------------------------
/extension/data/styles/removalreasons.css:
--------------------------------------------------------------------------------
1 | .mod-toolbox .reason-popup {
2 | padding: 10px 15px !important;
3 | background-color: rgba(250, 250, 250, 0.84);
4 | border: 0;
5 | border-radius: 0;
6 | position: fixed;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | bottom: 0;
11 | overflow: auto;
12 | z-index: 2147483647 !important;
13 | font-size: 11px;
14 | }
15 |
16 | .reason-popup-header {
17 | right: 0;
18 | left: 0;
19 | display: block;
20 | background-color: #CEE3F8;
21 | text-align: center;
22 | font-weight: bold;
23 | padding: 4px !important;
24 | font-size: small;
25 | }
26 |
27 | .reason-popup-innercontent {
28 | overflow: auto;
29 | left: 0;
30 | right: 0;
31 | padding: 5px !important;
32 | }
33 |
34 | .reason-popup-footer {
35 | width: 100%;
36 | height: 30px;
37 | line-height: 30px;
38 | display: block;
39 | background-color: #CEE3F8;
40 | text-align: center;
41 | }
42 |
43 | .reason-popup input,.reason-popup select,.reason-popup textarea {
44 | font-size: x-small;
45 | border: 1px solid #ccc;
46 | background-color: #fafafa;
47 | }
48 |
49 | .reason-popup input[type="checkbox"],.reason-popup input[type="radio"] {
50 | /* CENTER DAT SHIT */
51 | vertical-align: text-bottom;
52 | margin-right: 2px;
53 | margin-top: 0;
54 | }
55 |
56 | .reason-popup textarea {
57 | min-width: 400px;
58 | }
59 |
60 | .reason-popup #reason-table {
61 | border-left: solid 1px transparent;
62 | border-right: solid 1px transparent;
63 | }
64 |
65 | .reason-popup #reason-table.error-highlight {
66 | border: solid 1px red;
67 | }
68 |
69 | .reason-popup thead th {
70 | padding: 3px;
71 | white-space: nowrap;
72 | font-weight: bold;
73 | border-bottom: 1px dotted rgb(187, 187, 187);
74 | }
75 |
76 | .reason-popup tbody {
77 | border-bottom: 1px dotted #BBBBBB;
78 | }
79 |
80 | .reason-popup tbody tr {
81 | margin-top: 2px;
82 | vertical-align: top;
83 | }
84 |
85 | .reason-popup tbody td p {
86 | margin-top: 2px;
87 | }
88 |
89 | .reason-popup .reason-check {
90 | margin: 4px 6px 6px 6px !important;
91 | }
92 |
93 | .reason-popup .reason-num {
94 | width: 15px;
95 | height: 15px;
96 | margin-left: 2px;
97 | margin-bottom: 6px;
98 | display: inline-block;
99 | text-align: center;
100 | font-size: 10px;
101 | color: #D3D3D3;
102 | }
103 |
104 | .reason-popup .selectable-reason.reason-selected .reason-num {
105 | color: black;
106 | }
107 |
108 | .reason-popup select {
109 | max-width: 200px;
110 | }
111 |
112 | .reason-popup .styled-reason {
113 | margin-top: 4px;
114 | padding: 1px 3px 3px 3px;
115 | border: 0;
116 | }
117 |
118 | .reason-popup .selectable-reason:last-child .styled-reason {
119 | margin-bottom: 4px;
120 | }
121 |
122 | .mod-toolbox tr.removal-reason {
123 | border-bottom: solid 5px #F0F0F0;
124 | }
125 |
126 | .reason-popup tr.selectable-reason {
127 | border-bottom: solid 1px #D3D3D3;
128 | }
129 |
130 | .reason-popup tr.selectable-reason td:nth-child(1) {
131 | padding-top: 5px !important;
132 | }
133 |
134 | .reason-popup .selectable-reason.reason-selected .styled-reason {
135 | background: #B1DDAA;
136 | }
137 |
138 | .mod-toolbox .removal-reason-title {
139 | margin-top: 9px;
140 | margin-left: 6px;
141 | font-size: 14px;
142 | font-family: "Lucida Console", Monaco, monospace;
143 | }
144 |
145 | .mod-toolbox .reason-popup #buttons ul {
146 | list-style-type: none;
147 | margin: 5px;
148 | }
149 |
150 | .mod-toolbox .reason-popup #buttons ul li {
151 | margin-top: 5px;
152 | }
153 |
154 | .mod-toolbox .reason-popup #buttons ul li ul {
155 | margin-left: 15px;
156 | margin-bottom: 5px;
157 | }
158 |
159 | .reason-popup .reason-content {
160 | padding-left: 8px;
161 | }
162 |
163 | .reason-popup #header-reason, .reason-popup #footer-reason {
164 | border: 0;
165 | padding: 3px;
166 | }
167 |
168 | .reason-popup #buttons {
169 | margin-top: 2px;
170 | padding: 6px 0 6px 2px;
171 | vertical-align: middle;
172 | border: solid 1px transparent;
173 | }
174 |
175 | .reason-popup #buttons.error-highlight {
176 | border-color: red;
177 | }
178 |
179 | .reason-popup #log-reason {
180 | border-top: 1px dotted #BBBBBB;
181 | padding-top: 6px;
182 | }
183 |
184 | .reason-popup #log-reason-input {
185 | padding-left: 1px;
186 | min-width: 365px;
187 | }
188 |
189 | .reason-popup #log-reason-input.error-highlight {
190 | border-color: red;
191 | }
192 |
193 | .reason-popup #log-reason p {
194 | margin-bottom: 4px;
195 | }
196 |
197 | .reason-popup .reason-popup-content {
198 | position: relative;
199 | top: 40px;
200 | max-width: 1000px;
201 | padding: 0 !important;
202 | background-color: #FFF;
203 | box-shadow: 0 1px 3px 1px #D6D6D6;
204 | margin-left: auto !important;
205 | margin-right: auto !important;
206 | margin-bottom: 50px;
207 | font-size: 11px;
208 | }
209 |
210 | .reason-popup .reason-popup-content span p {
211 | padding: 3px !important;
212 | }
213 |
214 | .reason-popup strong {
215 | font-weight: bold;
216 | }
217 |
218 | .reason-popup em {
219 | font-style: oblique;
220 | }
221 |
222 | /* Nightmode */
223 |
224 | .mod-toolbox.res-nightmode .reason-popup {
225 | background-color: rgba(250, 250, 250, 0.84);
226 | color: black;
227 | }
228 |
229 | .res-nightmode .reason-popup-header {
230 | background-color: #6e7a86;
231 | border-bottom: solid 1px #39526b;
232 | }
233 |
234 | .res-nightmode .reason-popup-footer {
235 | background-color: #6e7a86;
236 | border-top: solid 1px #39526b;
237 | }
238 |
239 | .res-nightmode .reason-popup input,.reason-popup select,.reason-popup textarea {
240 | border: 1px solid #ccc;
241 | background-color: #fafafa;
242 | }
243 |
244 | .res-nightmode .reason-popup #reason-table.error-highlight {
245 | border: solid 1px red;
246 | }
247 |
248 | .res-nightmode .reason-popup thead th {
249 | border-bottom: 1px dotted rgb(187, 187, 187);
250 | }
251 |
252 | .res-nightmode .reason-popup tbody {
253 | border-bottom: 1px dotted #BBBBBB;
254 | }
255 |
256 | .res-nightmode .reason-popup .reason-num {
257 | color: #D3D3D3;
258 | }
259 |
260 | .res-nightmode .reason-popup .selectable-reason.reason-selected .reason-num {
261 | color: black;
262 | }
263 |
264 | .res-nightmode .reason-popup .styled-reason {
265 | border: 0 !important;
266 | padding: 1px 3px 3px 3px !important;
267 | }
268 |
269 | .res-nightmode .reason-popup .selectable-reason.reason-selected .styled-reason {
270 | background: #B1DDAA;
271 | }
272 |
273 | .res-nightmode .reason-popup #header-reason, .reason-popup #footer-reason {
274 | border-color: #E9E9E9 !important;
275 | }
276 |
277 | .res-nightmode .reason-popup #buttons.error-highlight {
278 | border-color: red;
279 | }
280 |
281 | .res-nightmode .reason-popup #log-reason {
282 | border-top: 1px dotted #BBBBBB;
283 | padding-top: 6px;
284 | }
285 |
286 | .res-nightmode .reason-popup #log-reason-input.error-highlight {
287 | border-color: red;
288 | }
289 |
290 | .res-nightmode .reason-popup .reason-popup-content {
291 | background-color: #878787;
292 | border: solid 1px #808080;
293 | box-shadow: 0 1px 3px 1px rgba(83, 83, 83, 0.61);
294 | }
295 |
296 | .mod-toolbox.res-nightmode tr.removal-reason {
297 | border-bottom: solid 5px #7A7A7A;
298 | }
299 |
300 | /* 3.0 fixes */
301 |
302 | .mod-toolbox #removal-reasons-table {
303 | width: 99%;
304 | }
305 |
306 | .mod-toolbox .removal-toggle {
307 | width: 40px;
308 | }
309 |
310 | .mod-toolbox .flair-text {
311 | width: 75px;
312 | }
313 |
314 | .mod-toolbox .flair-css {
315 | width: 50px;
316 | overflow: hidden;
317 | }
318 |
319 | .mod-toolbox td.flair-css,
320 | .mod-toolbox td.flair-text {
321 | padding-top: 8px;
322 | }
323 |
324 | .mod-toolbox .reason-popup .reason-check {
325 | margin: 4px 0 0 3px !important;
326 | display: inline;
327 | }
328 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/searchcursor.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 | var Pos = CodeMirror.Pos;
14 |
15 | function SearchCursor(doc, query, pos, caseFold) {
16 | this.atOccurrence = false; this.doc = doc;
17 | if (caseFold == null && typeof query == "string") caseFold = false;
18 |
19 | pos = pos ? doc.clipPos(pos) : Pos(0, 0);
20 | this.pos = {from: pos, to: pos};
21 |
22 | // The matches method is filled in based on the type of query.
23 | // It takes a position and a direction, and returns an object
24 | // describing the next occurrence of the query, or null if no
25 | // more matches were found.
26 | if (typeof query != "string") { // Regexp match
27 | if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
28 | this.matches = function(reverse, pos) {
29 | if (reverse) {
30 | query.lastIndex = 0;
31 | var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
32 | for (;;) {
33 | query.lastIndex = cutOff;
34 | var newMatch = query.exec(line);
35 | if (!newMatch) break;
36 | match = newMatch;
37 | start = match.index;
38 | cutOff = match.index + (match[0].length || 1);
39 | if (cutOff == line.length) break;
40 | }
41 | var matchLen = (match && match[0].length) || 0;
42 | if (!matchLen) {
43 | if (start == 0 && line.length == 0) {match = undefined;}
44 | else if (start != doc.getLine(pos.line).length) {
45 | matchLen++;
46 | }
47 | }
48 | } else {
49 | query.lastIndex = pos.ch;
50 | var line = doc.getLine(pos.line), match = query.exec(line);
51 | var matchLen = (match && match[0].length) || 0;
52 | var start = match && match.index;
53 | if (start + matchLen != line.length && !matchLen) matchLen = 1;
54 | }
55 | if (match && matchLen)
56 | return {from: Pos(pos.line, start),
57 | to: Pos(pos.line, start + matchLen),
58 | match: match};
59 | };
60 | } else { // String query
61 | var origQuery = query;
62 | if (caseFold) query = query.toLowerCase();
63 | var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
64 | var target = query.split("\n");
65 | // Different methods for single-line and multi-line queries
66 | if (target.length == 1) {
67 | if (!query.length) {
68 | // Empty string would match anything and never progress, so
69 | // we define it to match nothing instead.
70 | this.matches = function() {};
71 | } else {
72 | this.matches = function(reverse, pos) {
73 | if (reverse) {
74 | var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
75 | var match = line.lastIndexOf(query);
76 | if (match > -1) {
77 | match = adjustPos(orig, line, match);
78 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
79 | }
80 | } else {
81 | var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
82 | var match = line.indexOf(query);
83 | if (match > -1) {
84 | match = adjustPos(orig, line, match) + pos.ch;
85 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
86 | }
87 | }
88 | };
89 | }
90 | } else {
91 | var origTarget = origQuery.split("\n");
92 | this.matches = function(reverse, pos) {
93 | var last = target.length - 1;
94 | if (reverse) {
95 | if (pos.line - (target.length - 1) < doc.firstLine()) return;
96 | if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
97 | var to = Pos(pos.line, origTarget[last].length);
98 | for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
99 | if (target[i] != fold(doc.getLine(ln))) return;
100 | var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
101 | if (fold(line.slice(cut)) != target[0]) return;
102 | return {from: Pos(ln, cut), to: to};
103 | } else {
104 | if (pos.line + (target.length - 1) > doc.lastLine()) return;
105 | var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
106 | if (fold(line.slice(cut)) != target[0]) return;
107 | var from = Pos(pos.line, cut);
108 | for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
109 | if (target[i] != fold(doc.getLine(ln))) return;
110 | if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
111 | return {from: from, to: Pos(ln, origTarget[last].length)};
112 | }
113 | };
114 | }
115 | }
116 | }
117 |
118 | SearchCursor.prototype = {
119 | findNext: function() {return this.find(false);},
120 | findPrevious: function() {return this.find(true);},
121 |
122 | find: function(reverse) {
123 | var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
124 | function savePosAndFail(line) {
125 | var pos = Pos(line, 0);
126 | self.pos = {from: pos, to: pos};
127 | self.atOccurrence = false;
128 | return false;
129 | }
130 |
131 | for (;;) {
132 | if (this.pos = this.matches(reverse, pos)) {
133 | this.atOccurrence = true;
134 | return this.pos.match || true;
135 | }
136 | if (reverse) {
137 | if (!pos.line) return savePosAndFail(0);
138 | pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
139 | }
140 | else {
141 | var maxLine = this.doc.lineCount();
142 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
143 | pos = Pos(pos.line + 1, 0);
144 | }
145 | }
146 | },
147 |
148 | from: function() {if (this.atOccurrence) return this.pos.from;},
149 | to: function() {if (this.atOccurrence) return this.pos.to;},
150 |
151 | replace: function(newText, origin) {
152 | if (!this.atOccurrence) return;
153 | var lines = CodeMirror.splitLines(newText);
154 | this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
155 | this.pos.to = Pos(this.pos.from.line + lines.length - 1,
156 | lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
157 | }
158 | };
159 |
160 | // Maps a position in a case-folded line back to a position in the original line
161 | // (compensating for codepoints increasing in number during folding)
162 | function adjustPos(orig, folded, pos) {
163 | if (orig.length == folded.length) return pos;
164 | for (var pos1 = Math.min(pos, orig.length);;) {
165 | var len1 = orig.slice(0, pos1).toLowerCase().length;
166 | if (len1 < pos) ++pos1;
167 | else if (len1 > pos) --pos1;
168 | else return pos1;
169 | }
170 | }
171 |
172 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
173 | return new SearchCursor(this.doc, query, pos, caseFold);
174 | });
175 | CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
176 | return new SearchCursor(this, query, pos, caseFold);
177 | });
178 |
179 | CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
180 | var ranges = [];
181 | var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
182 | while (cur.findNext()) {
183 | if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
184 | ranges.push({anchor: cur.from(), head: cur.to()});
185 | }
186 | if (ranges.length)
187 | this.setSelections(ranges, 0);
188 | });
189 | });
190 |
--------------------------------------------------------------------------------
/extension/data/modules/banlist.js:
--------------------------------------------------------------------------------
1 | function banlist() {
2 | var self = new TB.Module('Ban List');
3 | self.shortname = 'BanList';
4 |
5 | self.settings['enabled']['default'] = true;
6 |
7 | self.register_setting('automatic', {
8 | 'type': 'boolean',
9 | 'default': false,
10 | 'title': 'Automatically pre-load the whole ban list for live filtering.'
11 | });
12 |
13 | // extracts a url parameter value from a URL string
14 | // from http://stackoverflow.com/a/15780907/362042
15 | // TODO: move to tbutils
16 | self.getURLParameter = function getURLParameter(url, name) {
17 | return (new RegExp(name + '=' + '(.+?)(&|$)').exec(url) || [, null])[1];
18 | };
19 |
20 | self.init = function () {
21 | if (!TB.utils.isEditUserPage) return;
22 |
23 | var banlist_updating = false,
24 | banlist_last_update = 0,
25 | last_request = 0,
26 | time_to_update = 1000 * 60 * 5, // in milliseconds (last value is minutes)
27 | pages_back = 0,
28 | $num_bans = $('
');
29 |
30 | function _get_next_ban_page(after, pages_back) {
31 |
32 | // default parameter value handling
33 | after = typeof after !== 'undefined' ? after : '';
34 | pages_back = typeof pages_back !== 'undefined' ? pages_back : 0;
35 |
36 | self.log("_get_next_ban_page(" + after + ")");
37 |
38 | var parameters = {'limit': 1000, 'after': after};
39 |
40 | after = null;
41 | last_request = Date.now();
42 |
43 | $.ajax({
44 | url: document.location.href,
45 | data: parameters,
46 | type: 'get',
47 | dataType: 'html',
48 | async: true,
49 | done: function (data) {
50 | self.log(" success!");
51 | self.log(" " + pages_back + " pages back");
52 | var response_page = $(data);
53 | // append to the list, using clever jQuery context parameter to create jQuery object to parse out the HTML response
54 | // var $new_banlist = $('.usertable', response_page);
55 | self.log($('.usertable table tbody tr', response_page).length);
56 | if ($('.usertable table tbody tr', response_page).length > 0) {
57 | $('.usertable table tbody tr', response_page).each(function () {
58 | // workaround for known bug in listings where "next" button is available on last page
59 | if (this.className == 'notfound') {
60 | return;
61 | }
62 |
63 | var t = $(this).find('.user a').text().toLowerCase(); // username
64 | if ($(this).find('input[name="note"]').length > 0) {
65 | t += ' ' + $(this).find('input[name="note"]').val().toLowerCase(); // ban note text, if available
66 | }
67 | $("
| ").hide().text(t).appendTo(this);
68 | $(this).addClass('visible');
69 | });
70 | var value = $('input#user').val().toLowerCase();
71 | filter_banlist($('.usertable', response_page), value, true);
72 | $('.usertable table tbody').append($('.usertable table tbody tr', response_page));
73 | // update the results counter
74 | $num_bans.html($('.usertable tr:visible').length);
75 | } else {
76 | return;
77 | }
78 |
79 | after_url = $('.nextprev a[rel~="next"]', response_page).prop('href');
80 | self.log(after_url);
81 | after = self.getURLParameter(after_url, 'after');
82 | self.log(after);
83 | if (after) {
84 | // hit the API hard the first 10, to make it more responsive on small subs
85 | if (pages_back < 10) {
86 | pages_back++;
87 | _get_next_ban_page(after, pages_back);
88 | } else {
89 | sleep = last_request + 2000 - Date.now();
90 | setTimeout(_get_next_ban_page, sleep, after, pages_back);
91 | }
92 | } else {
93 | self.log(" last page");
94 | banlist_updating = false;
95 | banlist_last_update = Date.now();
96 | TB.ui.longLoadSpinner(false);
97 | }
98 | },
99 | fail: function (data) {
100 | self.log(" failed");
101 | self.log(data.status);
102 | if (data.status == 504) {
103 | // "504, post some more"
104 | this.done(data);
105 | } else {
106 | // Did we get logged out during the process, or some other error?
107 | banlist_updating = false;
108 | TB.ui.longLoadSpinner(false);
109 | $num_bans.html("Something went wrong while fetching the banlist. You should reload this page.");
110 | }
111 | }
112 | });
113 |
114 | }
115 |
116 | function filter_banlist(banlist, value, ignore_last) {
117 | self.log('filter(' + value + ')');
118 | var last_value = typeof last_value !== 'undefined' ? last_value : '';
119 | ignore_last = typeof ignore_last !== 'undefined' ? ignore_last : false;
120 |
121 | if (value == '') {
122 | self.log('empty');
123 | // empty search? show all
124 | $('tr', banlist).show().addClass('visible');
125 | } else if (!ignore_last && last_value && value.indexOf(last_value) > -1) {
126 | self.log('subset');
127 | // is this query a subset of the last query?
128 | // filter *out* non-matching
129 | $("tr.visible .indexColumn:not(:contains('" + value + "'))", banlist).parent().hide().removeClass('visible');
130 | } else {
131 | self.log('full search');
132 | $('tr', banlist).hide().removeClass('visible');
133 | // combine and use a single selector for increased performance
134 | // credit: http://kobikobi.wordpress.com/2008/09/15/using-jquery-to-filter-table-rows/
135 | $("tr .indexColumn:contains('" + value + "')", banlist).parent().show().addClass('visible');
136 | }
137 | $("tr", banlist).removeClass('even');
138 | $("tr.visible:even", banlist).addClass('even');
139 |
140 | // update last value
141 | last_value = value;
142 | }
143 |
144 | function liveFilter() {
145 | var $user = $('#user');
146 |
147 | // counter for number of bans
148 | $num_bans.appendTo($user.parent());
149 |
150 | $user.prop('placeholder', 'Begin typing to live filter the list.');
151 |
152 | $('.usertable').addClass('filtered');
153 |
154 | $(".usertable tr").each(function () {
155 | var t = $(this).text().toLowerCase(); //all row text
156 | $("
| ").hide().text(t).appendTo(this);
157 | });//each tr
158 |
159 |
160 | function _filter(value) {
161 | if (!banlist_updating // don't trigger an update if we're still running
162 | && (banlist_last_update === 0 // catch the first run, before last_update has been set
163 | || (banlist_last_update + time_to_update) <= Date.now())
164 | ) {
165 | banlist_updating = true;
166 | TB.ui.longLoadSpinner(true);
167 |
168 | self.log("Updating now");
169 | // clean up
170 | $('.usertable table tbody').empty();
171 | pages_back = 0;
172 | _get_next_ban_page();
173 | }
174 |
175 | filter_banlist($('.usertable'), value);
176 | // update the results counter
177 | $num_bans.html($('.usertable tr:visible').length);
178 | }
179 |
180 | // text input trigger
181 | var $userInput = $('input#user');
182 | $userInput.keyup(function () {
183 | if ($('.usertable tr').length > 1000) {
184 | return;
185 | } // don't live filter
186 | var value = $(this).val().toLowerCase();
187 | _filter(value);
188 | });
189 |
190 | $userInput.parent().submit(function (e) {
191 | _filter($('input#user').val().toLowerCase());
192 | e.preventDefault();
193 | });
194 |
195 | // we want to populate the table immediately on load.
196 | $userInput.keyup();
197 | }
198 |
199 | if (self.setting('automatic')) {
200 | liveFilter();
201 | } else {
202 | var $tb_liveFilter = $('
');
203 | $tb_liveFilter.insertAfter($('input#user').next());
204 | $tb_liveFilter.click(function () {
205 | liveFilter();
206 | $(this).remove();
207 | });
208 | }
209 | };
210 |
211 | TB.register_module(self);
212 | }
213 |
214 | (function() {
215 | window.addEventListener("TBModuleLoaded", function () {
216 | banlist();
217 | });
218 | })();
219 |
--------------------------------------------------------------------------------
/extension/data/styles/codemirror/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .mod-toolbox .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
6 | height: 600px;
7 | color: black;
8 | }
9 |
10 | .mod-toolbox .CodeMirror-fullscreen {
11 | z-index: 2147483646;
12 | bottom: 25px;
13 |
14 | }
15 |
16 | .tb-syntax-keyboard i {
17 | font-size: medium;
18 | }
19 |
20 | .mod-toolbox .tb-syntax-keyboard {
21 | position: absolute;
22 | width: 150px;
23 | background-color: rgba(206, 227, 248, 0.55);
24 | color: black;
25 | right: 0;
26 | top: 0;
27 | z-index: 99999999999;
28 | padding: 3px;
29 | transition: width 0.3s;
30 | }
31 |
32 | .mod-toolbox .tb-syntax-keyboard:hover {
33 | width: 500px;
34 | }
35 |
36 | .mod-toolbox .tb-syntax-keyboard ul {
37 | display: none;
38 | }
39 |
40 |
41 |
42 | .tb-syntax-keyboard:hover ul {
43 | display: block;
44 | }
45 | /* PADDING */
46 |
47 | .CodeMirror-lines {
48 | padding: 4px 0; /* Vertical padding around content */
49 | }
50 | .CodeMirror pre {
51 | padding: 0 4px; /* Horizontal padding of content */
52 | }
53 |
54 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
55 | background-color: white; /* The little square between H and V scrollbars */
56 | }
57 |
58 | /* GUTTER */
59 |
60 | .CodeMirror-gutters {
61 | border-right: 1px solid #ddd;
62 | background-color: #f7f7f7;
63 | white-space: nowrap;
64 | }
65 | .CodeMirror-linenumbers {}
66 | .CodeMirror-linenumber {
67 | padding: 0 3px 0 5px;
68 | min-width: 20px;
69 | text-align: right;
70 | color: #999;
71 | white-space: nowrap;
72 | }
73 |
74 | .CodeMirror-guttermarker { color: black; }
75 | .CodeMirror-guttermarker-subtle { color: #999; }
76 |
77 | /* CURSOR */
78 |
79 | .CodeMirror-cursor {
80 | border-left: 1px solid black;
81 | border-right: none;
82 | width: 0;
83 | }
84 | /* Shown when moving in bi-directional text */
85 | .CodeMirror div.CodeMirror-secondarycursor {
86 | border-left: 1px solid silver;
87 | }
88 | .cm-fat-cursor .CodeMirror-cursor {
89 | width: auto;
90 | border: 0 !important;
91 | background: #7e7;
92 | }
93 | .cm-fat-cursor div.CodeMirror-cursors {
94 | z-index: 1;
95 | }
96 |
97 | .cm-animate-fat-cursor {
98 | width: auto;
99 | border: 0;
100 | -webkit-animation: blink 1.06s steps(1) infinite;
101 | -moz-animation: blink 1.06s steps(1) infinite;
102 | animation: blink 1.06s steps(1) infinite;
103 | background-color: #7e7;
104 | }
105 | @-moz-keyframes blink {
106 | 0% {}
107 | 50% { background-color: transparent; }
108 | 100% {}
109 | }
110 | @-webkit-keyframes blink {
111 | 0% {}
112 | 50% { background-color: transparent; }
113 | 100% {}
114 | }
115 | @keyframes blink {
116 | 0% {}
117 | 50% { background-color: transparent; }
118 | 100% {}
119 | }
120 |
121 | /* Can style cursor different in overwrite (non-insert) mode */
122 | .CodeMirror-overwrite .CodeMirror-cursor {}
123 |
124 | .cm-tab { display: inline-block; text-decoration: inherit; }
125 |
126 | .CodeMirror-rulers {
127 | position: absolute;
128 | left: 0; right: 0; top: -50px; bottom: -20px;
129 | overflow: hidden;
130 | }
131 | .CodeMirror-ruler {
132 | border-left: 1px solid #ccc;
133 | top: 0; bottom: 0;
134 | position: absolute;
135 | }
136 |
137 | /* DEFAULT THEME */
138 |
139 | .cm-s-default .cm-header {color: blue;}
140 | .cm-s-default .cm-quote {color: #090;}
141 | .cm-negative {color: #d44;}
142 | .cm-positive {color: #292;}
143 | .cm-header, .cm-strong {font-weight: bold;}
144 | .cm-em {font-style: italic;}
145 | .cm-link {text-decoration: underline;}
146 | .cm-strikethrough {text-decoration: line-through;}
147 |
148 | .cm-s-default .cm-keyword {color: #708;}
149 | .cm-s-default .cm-atom {color: #219;}
150 | .cm-s-default .cm-number {color: #164;}
151 | .cm-s-default .cm-def {color: #00f;}
152 | .cm-s-default .cm-variable,
153 | .cm-s-default .cm-punctuation,
154 | .cm-s-default .cm-property,
155 | .cm-s-default .cm-operator {}
156 | .cm-s-default .cm-variable-2 {color: #05a;}
157 | .cm-s-default .cm-variable-3 {color: #085;}
158 | .cm-s-default .cm-comment {color: #a50;}
159 | .cm-s-default .cm-string {color: #a11;}
160 | .cm-s-default .cm-string-2 {color: #f50;}
161 | .cm-s-default .cm-meta {color: #555;}
162 | .cm-s-default .cm-qualifier {color: #555;}
163 | .cm-s-default .cm-builtin {color: #30a;}
164 | .cm-s-default .cm-bracket {color: #997;}
165 | .cm-s-default .cm-tag {color: #170;}
166 | .cm-s-default .cm-attribute {color: #00c;}
167 | .cm-s-default .cm-hr {color: #999;}
168 | .cm-s-default .cm-link {color: #00c;}
169 |
170 | .cm-s-default .cm-error {color: #f00;}
171 | .cm-invalidchar {color: #f00;}
172 |
173 | .CodeMirror-composing { border-bottom: 2px solid; }
174 |
175 | /* Default styles for common addons */
176 |
177 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
178 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
179 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
180 | .CodeMirror-activeline-background {background: #e8f2ff;}
181 |
182 | /* STOP */
183 |
184 | /* The rest of this file contains styles related to the mechanics of
185 | the editor. You probably shouldn't touch them. */
186 |
187 | .CodeMirror {
188 | position: relative;
189 | overflow: hidden;
190 | background: white;
191 | }
192 |
193 | .CodeMirror-scroll {
194 | overflow: scroll !important; /* Things will break if this is overridden */
195 | /* 30px is the magic margin used to hide the element's real scrollbars */
196 | /* See overflow: hidden in .CodeMirror */
197 | margin-bottom: -30px; margin-right: -30px;
198 | padding-bottom: 30px;
199 | height: 100%;
200 | outline: none; /* Prevent dragging from highlighting the element */
201 | position: relative;
202 | }
203 | .CodeMirror-sizer {
204 | position: relative;
205 | border-right: 30px solid transparent;
206 | }
207 |
208 | /* The fake, visible scrollbars. Used to force redraw during scrolling
209 | before actual scrolling happens, thus preventing shaking and
210 | flickering artifacts. */
211 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
212 | position: absolute;
213 | z-index: 6;
214 | display: none;
215 | }
216 | .CodeMirror-vscrollbar {
217 | right: 0; top: 0;
218 | overflow-x: hidden;
219 | overflow-y: scroll;
220 | }
221 | .CodeMirror-hscrollbar {
222 | bottom: 0; left: 0;
223 | overflow-y: hidden;
224 | overflow-x: scroll;
225 | }
226 | .CodeMirror-scrollbar-filler {
227 | right: 0; bottom: 0;
228 | }
229 | .CodeMirror-gutter-filler {
230 | left: 0; bottom: 0;
231 | }
232 |
233 | .CodeMirror-gutters {
234 | position: absolute; left: 0; top: 0;
235 | min-height: 100%;
236 | z-index: 3;
237 | }
238 | .CodeMirror-gutter {
239 | white-space: normal;
240 | height: 100%;
241 | display: inline-block;
242 | vertical-align: top;
243 | margin-bottom: -30px;
244 | /* Hack to make IE7 behave */
245 | *zoom:1;
246 | *display:inline;
247 | }
248 | .CodeMirror-gutter-wrapper {
249 | position: absolute;
250 | z-index: 4;
251 | background: none !important;
252 | border: none !important;
253 | }
254 | .CodeMirror-gutter-background {
255 | position: absolute;
256 | top: 0; bottom: 0;
257 | z-index: 4;
258 | }
259 | .CodeMirror-gutter-elt {
260 | position: absolute;
261 | cursor: default;
262 | z-index: 4;
263 | }
264 | .CodeMirror-gutter-wrapper {
265 | -webkit-user-select: none;
266 | -moz-user-select: none;
267 | user-select: none;
268 | }
269 |
270 | .CodeMirror-lines {
271 | cursor: text;
272 | min-height: 1px; /* prevents collapsing before first draw */
273 | }
274 | .CodeMirror pre {
275 | /* Reset some styles that the rest of the page might have set */
276 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
277 | border-width: 0;
278 | background: transparent;
279 | font-family: inherit;
280 | font-size: inherit;
281 | margin: 0;
282 | white-space: pre;
283 | word-wrap: normal;
284 | line-height: inherit;
285 | color: inherit;
286 | z-index: 2;
287 | position: relative;
288 | overflow: visible;
289 | -webkit-tap-highlight-color: transparent;
290 | -webkit-font-variant-ligatures: none;
291 | font-variant-ligatures: none;
292 | }
293 | .CodeMirror-wrap pre {
294 | word-wrap: break-word;
295 | white-space: pre-wrap;
296 | word-break: normal;
297 | }
298 |
299 | .CodeMirror-linebackground {
300 | position: absolute;
301 | left: 0; right: 0; top: 0; bottom: 0;
302 | z-index: 0;
303 | }
304 |
305 | .CodeMirror-linewidget {
306 | position: relative;
307 | z-index: 2;
308 | overflow: auto;
309 | }
310 |
311 | .CodeMirror-widget {}
312 |
313 | .CodeMirror-code {
314 | outline: none;
315 | }
316 |
317 | /* Force content-box sizing for the elements where we expect it */
318 | .CodeMirror-scroll,
319 | .CodeMirror-sizer,
320 | .CodeMirror-gutter,
321 | .CodeMirror-gutters,
322 | .CodeMirror-linenumber {
323 | -moz-box-sizing: content-box;
324 | box-sizing: content-box;
325 | }
326 |
327 | .CodeMirror-measure {
328 | position: absolute;
329 | width: 100%;
330 | height: 0;
331 | overflow: hidden;
332 | visibility: hidden;
333 | }
334 |
335 | .CodeMirror-cursor {
336 | position: absolute;
337 | pointer-events: none;
338 | }
339 | .CodeMirror-measure pre { position: static; }
340 |
341 | div.CodeMirror-cursors {
342 | visibility: hidden;
343 | position: relative;
344 | z-index: 3;
345 | }
346 | div.CodeMirror-dragcursors {
347 | visibility: visible;
348 | }
349 |
350 | .CodeMirror-focused div.CodeMirror-cursors {
351 | visibility: visible;
352 | }
353 |
354 | .CodeMirror-selected { background: #d9d9d9; }
355 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
356 | .CodeMirror-crosshair { cursor: crosshair; }
357 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
358 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
359 |
360 | .cm-searching {
361 | background: #ffa;
362 | background: rgba(255, 255, 0, .4);
363 | }
364 |
365 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
366 | .CodeMirror span { *vertical-align: text-bottom; }
367 |
368 | /* Used to force a border model for a node */
369 | .cm-force-border { padding-right: .1px; }
370 |
371 | @media print {
372 | /* Hide the cursor when printing */
373 | .CodeMirror div.CodeMirror-cursors {
374 | visibility: hidden;
375 | }
376 | }
377 |
378 | /* See issue #2901 */
379 | .cm-tab-wrap-hack:after { content: ''; }
380 |
381 | /* Help users use markselection to safely style text background */
382 | span.CodeMirror-selectedtext { background: none; }
383 |
--------------------------------------------------------------------------------
/extension/data/libs/codemirror/addon/search.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | // Define search commands. Depends on dialog.js or another
5 | // implementation of the openDialog method.
6 |
7 | // Replace works a little oddly -- it will do the replace on the next
8 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a
9 | // replace by making sure the match is no longer selected when hitting
10 | // Ctrl-G.
11 |
12 | (function(mod) {
13 | if (typeof exports == "object" && typeof module == "object") // CommonJS
14 | mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
15 | else if (typeof define == "function" && define.amd) // AMD
16 | define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
17 | else // Plain browser env
18 | mod(CodeMirror);
19 | })(function(CodeMirror) {
20 | "use strict";
21 |
22 | function searchOverlay(query, caseInsensitive) {
23 | if (typeof query == "string")
24 | query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
25 | else if (!query.global)
26 | query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
27 |
28 | return {token: function(stream) {
29 | query.lastIndex = stream.pos;
30 | var match = query.exec(stream.string);
31 | if (match && match.index == stream.pos) {
32 | stream.pos += match[0].length || 1;
33 | return "searching";
34 | } else if (match) {
35 | stream.pos = match.index;
36 | } else {
37 | stream.skipToEnd();
38 | }
39 | }};
40 | }
41 |
42 | function SearchState() {
43 | this.posFrom = this.posTo = this.lastQuery = this.query = null;
44 | this.overlay = null;
45 | }
46 |
47 | function getSearchState(cm) {
48 | return cm.state.search || (cm.state.search = new SearchState());
49 | }
50 |
51 | function queryCaseInsensitive(query) {
52 | return typeof query == "string" && query == query.toLowerCase();
53 | }
54 |
55 | function getSearchCursor(cm, query, pos) {
56 | // Heuristic: if the query string is all lowercase, do a case insensitive search.
57 | return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
58 | }
59 |
60 | function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
61 | cm.openDialog(text, onEnter, {
62 | value: deflt,
63 | selectValueOnOpen: true,
64 | closeOnEnter: false,
65 | onClose: function() { clearSearch(cm); },
66 | onKeyDown: onKeyDown
67 | });
68 | }
69 |
70 | function dialog(cm, text, shortText, deflt, f) {
71 | if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
72 | else f(prompt(shortText, deflt));
73 | }
74 |
75 | function confirmDialog(cm, text, shortText, fs) {
76 | if (cm.openConfirm) cm.openConfirm(text, fs);
77 | else if (confirm(shortText)) fs[0]();
78 | }
79 |
80 | function parseString(string) {
81 | return string.replace(/\\(.)/g, function(_, ch) {
82 | if (ch == "n") return "\n"
83 | if (ch == "r") return "\r"
84 | return ch
85 | })
86 | }
87 |
88 | function parseQuery(query) {
89 | var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
90 | if (isRE) {
91 | try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
92 | catch(e) {} // Not a regular expression after all, do a string search
93 | } else {
94 | query = parseString(query)
95 | }
96 | if (typeof query == "string" ? query == "" : query.test(""))
97 | query = /x^/;
98 | return query;
99 | }
100 |
101 | var queryDialog =
102 | 'Search:
(Use /re/ syntax for regexp search)';
103 |
104 | function startSearch(cm, state, query) {
105 | state.queryText = query;
106 | state.query = parseQuery(query);
107 | cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
108 | state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
109 | cm.addOverlay(state.overlay);
110 | if (cm.showMatchesOnScrollbar) {
111 | if (state.annotate) { state.annotate.clear(); state.annotate = null; }
112 | state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
113 | }
114 | }
115 |
116 | function doSearch(cm, rev, persistent, immediate) {
117 | var state = getSearchState(cm);
118 | if (state.query) return findNext(cm, rev);
119 | var q = cm.getSelection() || state.lastQuery;
120 | if (persistent && cm.openDialog) {
121 | var hiding = null
122 | var searchNext = function(query, event) {
123 | CodeMirror.e_stop(event);
124 | if (!query) return;
125 | if (query != state.queryText) {
126 | startSearch(cm, state, query);
127 | state.posFrom = state.posTo = cm.getCursor();
128 | }
129 | if (hiding) hiding.style.opacity = 1
130 | findNext(cm, event.shiftKey, function(_, to) {
131 | var dialog
132 | if (to.line < 3 && document.querySelector &&
133 | (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
134 | dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
135 | (hiding = dialog).style.opacity = .4
136 | })
137 | };
138 | persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
139 | var keyName = CodeMirror.keyName(event)
140 | var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
141 | if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
142 | if (cmd == "findNext" || cmd == "findPrev" ||
143 | cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
144 | CodeMirror.e_stop(event);
145 | startSearch(cm, getSearchState(cm), query);
146 | cm.execCommand(cmd);
147 | } else if (cmd == "find" || cmd == "findPersistent") {
148 | CodeMirror.e_stop(event);
149 | searchNext(query, event);
150 | }
151 | });
152 | if (immediate && q) {
153 | startSearch(cm, state, q);
154 | findNext(cm, rev);
155 | }
156 | } else {
157 | dialog(cm, queryDialog, "Search for:", q, function(query) {
158 | if (query && !state.query) cm.operation(function() {
159 | startSearch(cm, state, query);
160 | state.posFrom = state.posTo = cm.getCursor();
161 | findNext(cm, rev);
162 | });
163 | });
164 | }
165 | }
166 |
167 | function findNext(cm, rev, callback) {cm.operation(function() {
168 | var state = getSearchState(cm);
169 | var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
170 | if (!cursor.find(rev)) {
171 | cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
172 | if (!cursor.find(rev)) return;
173 | }
174 | cm.setSelection(cursor.from(), cursor.to());
175 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
176 | state.posFrom = cursor.from(); state.posTo = cursor.to();
177 | if (callback) callback(cursor.from(), cursor.to())
178 | });}
179 |
180 | function clearSearch(cm) {cm.operation(function() {
181 | var state = getSearchState(cm);
182 | state.lastQuery = state.query;
183 | if (!state.query) return;
184 | state.query = state.queryText = null;
185 | cm.removeOverlay(state.overlay);
186 | if (state.annotate) { state.annotate.clear(); state.annotate = null; }
187 | });}
188 |
189 | var replaceQueryDialog =
190 | '
(Use /re/ syntax for regexp search)';
191 | var replacementQueryDialog = 'With:
';
192 | var doReplaceConfirm = "Replace?
";
193 |
194 | function replaceAll(cm, query, text) {
195 | cm.operation(function() {
196 | for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
197 | if (typeof query != "string") {
198 | var match = cm.getRange(cursor.from(), cursor.to()).match(query);
199 | cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
200 | } else cursor.replace(text);
201 | }
202 | });
203 | }
204 |
205 | function replace(cm, all) {
206 | if (cm.getOption("readOnly")) return;
207 | var query = cm.getSelection() || getSearchState(cm).lastQuery;
208 | var dialogText = all ? "Replace all:" : "Replace:"
209 | dialog(cm, dialogText + replaceQueryDialog, dialogText, query, function(query) {
210 | if (!query) return;
211 | query = parseQuery(query);
212 | dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
213 | text = parseString(text)
214 | if (all) {
215 | replaceAll(cm, query, text)
216 | } else {
217 | clearSearch(cm);
218 | var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
219 | var advance = function() {
220 | var start = cursor.from(), match;
221 | if (!(match = cursor.findNext())) {
222 | cursor = getSearchCursor(cm, query);
223 | if (!(match = cursor.findNext()) ||
224 | (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
225 | }
226 | cm.setSelection(cursor.from(), cursor.to());
227 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
228 | confirmDialog(cm, doReplaceConfirm, "Replace?",
229 | [function() {doReplace(match);}, advance,
230 | function() {replaceAll(cm, query, text)}]);
231 | };
232 | var doReplace = function(match) {
233 | cursor.replace(typeof query == "string" ? text :
234 | text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
235 | advance();
236 | };
237 | advance();
238 | }
239 | });
240 | });
241 | }
242 |
243 | CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
244 | CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
245 | CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
246 | CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
247 | CodeMirror.commands.findNext = doSearch;
248 | CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
249 | CodeMirror.commands.clearSearch = clearSearch;
250 | CodeMirror.commands.replace = replace;
251 | CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
252 | });
253 |
--------------------------------------------------------------------------------
/extension/data/modules/achievements.js:
--------------------------------------------------------------------------------
1 | function achievements() {
2 | var self = new TB.Module('Achievements');
3 | self.shortname = 'Achievements';
4 |
5 | // Default settings
6 | self.settings['enabled']['default'] = true;
7 |
8 | self.register_setting('save', {
9 | 'type': 'achievement_save',
10 | 'default': ''
11 | });
12 |
13 | self.register_setting('lastSeen', {
14 | 'type': 'number',
15 | 'default': TBUtils.getTime(),
16 | 'hidden': true
17 | });
18 |
19 | // Saves
20 | function Manager() {
21 | var saves = [],
22 | saveIndex = 0,
23 |
24 | achievements = [];
25 |
26 | this.init = function () {
27 | var save = self.setting('save');
28 | if (save.length > 0) {
29 | saves = this.decodeSave(save);
30 | }
31 | };
32 |
33 | this.register = function (title, description, achievement) {
34 | this.registerTarget(title, description, 1, achievement);
35 | };
36 |
37 | this.registerTarget = function (title, description, target, achievement) {
38 | this.registerSeries([title], description, [target], achievement);
39 | };
40 |
41 | this.registerSeries = function (titles, description, maxValues, achievement) {
42 | if (saveIndex >= saves.length) {
43 | saves.push(0);
44 | }
45 |
46 | var achievementsBlock = [];
47 | for (var i = 0; i < maxValues.length; i++) {
48 | var title = titles[i],
49 | maxValue = maxValues[i];
50 |
51 | self.log('Registering Achievement');
52 | if (TB.utils.devMode) self.log(' name=' + title); // spoilers
53 | self.log(' maxValue=' + maxValue);
54 | self.log(' saveIndex=' + saveIndex);
55 |
56 | achievementsBlock.push({
57 | title: title,
58 | descr: description.format(maxValue),
59 | maxValue: maxValue,
60 | saveIndex: saveIndex
61 | });
62 | }
63 | achievements.push(achievementsBlock);
64 |
65 | achievement(saveIndex);
66 | saveIndex++;
67 | };
68 |
69 | this.unlock = function (saveIndex, value) {
70 | if (value === undefined) {
71 | value = 1;
72 | }
73 | self.log('Unlocking achievement block: index=' + saveIndex + ', value=' + value);
74 |
75 | var old = saves[saveIndex];
76 | self.log(' Old value: ' + saves[saveIndex]);
77 | saves[saveIndex] += value;
78 | self.log(' New value: ' + saves[saveIndex]);
79 |
80 | var achievementsBlock = achievements[saveIndex];
81 | for (var index = 0; index < achievementsBlock.length; index++) {
82 | self.log(' Checking achievement ' + index);
83 | var achievement = achievementsBlock[index];
84 | self.log(' Comparing to max value: ' + achievement.maxValue);
85 | if (saves[saveIndex] >= achievement.maxValue && old < achievement.maxValue) {
86 | var title = achievement.title;
87 |
88 | // eh, close enough.
89 | // any better solution for links requires re-writing all the rewriting register functions
90 | // to support another prop. If someone want to do that, go for it.
91 | try {
92 | title = $(achievement.title).text() ? $(achievement.title).text() : achievement.title;
93 | } catch(e) {}
94 |
95 | self.log(' '+ title +' Unlocked!');
96 | TBUtils.notification('Mod achievement unlocked!', title, window.location + '#?tbsettings=' + self.shortname);
97 | }
98 | }
99 |
100 | if (saves[saveIndex] > achievement.maxValue) {
101 | saves[saveIndex] = achievement.maxValue
102 | }
103 | this.save();
104 | };
105 |
106 | this.save = function () {
107 | var save = '';
108 | saves.forEach(function (saveValue, saveIndex) {
109 | save += saveValue;
110 | if (saveIndex < saves.length - 1) {
111 | save += ';';
112 | }
113 | });
114 | save = btoa(save);
115 | self.setting('save', save);
116 | };
117 |
118 | // Utilities
119 |
120 | this.decodeSave = function (save) {
121 | var vals = atob(self.setting('save')).split(';');
122 | // Because '2' + 1 = 21
123 | if (vals && vals.length > 0) {
124 | for (var i = 0; i < vals.length; i++) {
125 | vals[i] = parseInt(vals[i]);
126 | }
127 | }
128 | return vals;
129 | };
130 |
131 | this.getAchievementBlockCount = function () {
132 | return achievements.length;
133 | };
134 |
135 | this.getAchievementCount = function (saveIndex) {
136 | return achievements[saveIndex].length;
137 | };
138 |
139 | this.getAchievementTotal = function () {
140 | var total = 0;
141 | for (var saveIndex = 0; saveIndex < achievements.length; saveIndex++) {
142 | total += this.getAchievementCount(saveIndex);
143 | }
144 | return total;
145 | };
146 |
147 | this.getUnlockedCount = function () {
148 | var count = 0;
149 | for (var saveIndex = 0; saveIndex < achievements.length; saveIndex++) {
150 | var achievementsBlock = achievements[saveIndex];
151 | for (var index = 0; index < achievementsBlock.length; index++) {
152 | if (this.isUnlocked(saveIndex, index, saves)) {
153 | count++;
154 | }
155 | }
156 | }
157 | return count;
158 | };
159 |
160 | this.getAchievement = function (saveIndex, index) {
161 | return achievements[saveIndex][index];
162 | };
163 |
164 | this.isUnlocked = function (saveIndex, index, saves) {
165 | var a = this.getAchievement(saveIndex, index);
166 | if (!(saves instanceof Array) || a.saveIndex >= saves.length) {
167 | return false;
168 | }
169 |
170 | return saves[a.saveIndex] >= a.maxValue;
171 | };
172 | }
173 |
174 | // Always load the manager so achievements can still be viewed if the module is disabled
175 | self.manager = new Manager();
176 | self.manager.init();
177 |
178 | // Init module
179 | self.init = function () {
180 | var $body = $('body');
181 |
182 | // Individual achievement stuff
183 | var lastSeen = self.setting('lastSeen');
184 |
185 | // Achievement definitions
186 | self.log('Registering achievements');
187 |
188 |
189 | // Random awesome
190 | self.manager.register('
being awesome', "toolbox just feels like you're awesome today", function (saveIndex) {
191 | var awesome = 7,
192 | chanceOfBeingAwesome = TB.utils.getRandomNumber(10000);
193 |
194 | self.log('You rolled a: ' + chanceOfBeingAwesome);
195 | if (awesome == chanceOfBeingAwesome) {
196 | self.manager.unlock(saveIndex);
197 | }
198 | });
199 |
200 | // Still Alive (TODO: can we make links work?)
201 | self.manager.register('
not dead yet', 'Spent a week away from reddit', function (saveIndex) {
202 | // BUG: this one keeps firing on default no value for lastSeen.
203 | // I tried defaulting to now but it's still wonky.
204 | var now = TBUtils.getTime(),
205 | timeSince = now - lastSeen,
206 | daysSince = TBUtils.millisecondsToDays(timeSince);
207 | self.log('daysSince: ' + daysSince);
208 |
209 | if (daysSince >= 7) {
210 | //self.log("you've got an award!");
211 | self.manager.unlock(saveIndex);
212 | }
213 |
214 | self.setting('lastSeen', now);
215 | });
216 |
217 | //toolbox Loves You: Look at the about page
218 | self.manager.register('
toolbox loves you', 'Looked at the about page. <3', function (saveIndex) {
219 | TB.utils.catchEvent(TB.utils.events.TB_ABOUT_PAGE, function () {
220 | self.manager.unlock(saveIndex);
221 | });
222 | });
223 |
224 | // Beta testers
225 | self.manager.register('bug hunter', 'Beta testing toolbox', function (saveIndex) {
226 | if (TB.utils.betaRelease) {
227 | self.manager.unlock(saveIndex, 1);
228 | }
229 | });
230 |
231 | // Judas
232 | self.manager.register('Judas', "Why do you hate toolbox devs? :'( ", function (saveIndex) {
233 | $body.on('click', 'form.remove-button, a.pretty-button.negative, a.pretty-button.neutral', function () {
234 | var $this = $(this);
235 | var auth = TB.utils.getThingInfo($this).author;
236 |
237 | if (TB.utils.tbDevs.indexOf(auth) != -1) {
238 | self.manager.unlock(saveIndex, 1);
239 | }
240 | // TODO: wait for 'yes' click.
241 | //$body.on('click', '.yes', function(){
242 | // self.log('yes clicked');
243 | //});
244 | });
245 | });
246 |
247 | // approving stuff
248 | self.manager.registerSeries(['too nice', 'way too nice', 'big softie', 'approvening master', 'the kinda mod reddit deserves'], 'Approved {0} things', [50, 200, 1000, 10000, 20000], function (saveIndex) {
249 |
250 | // If just the button is used.
251 | $body.on('click', '.pretty-button, .approve-button', function () {
252 | var $this = $(this);
253 | if ($this.hasClass('positive') || $this.hasClass('approve-button')) {
254 | self.manager.unlock(saveIndex, 1);
255 | }
256 | });
257 |
258 | // If the API is used
259 | TB.utils.catchEvent(TB.utils.events.TB_APPROVE_THING, function () {
260 | self.manager.unlock(saveIndex, 1);
261 | });
262 | });
263 |
264 | // Mod mail
265 | self.manager.registerSeries(['hic sunt dracones', "just checkin' the mail", '
Mr. Postman', "You've got mail!"], 'Checked mod mail {0} times!', [1, 100, 1000, 10000], function (saveIndex) {
266 | if (TB.utils.isModmail) {
267 | self.manager.unlock(saveIndex, 1);
268 | }
269 | });
270 |
271 | // Empty queue
272 | self.manager.registerSeries(['kitteh get!', 'puppy power!','
Dr. Jan Itor', '/u/Kylde'], 'Cleared your queues {0} times!', [10, 50, 100, 700], function (saveIndex) {
273 | if (TBUtils.isModpage && $body.find('p#noresults').length > 0) {
274 | self.manager.unlock(saveIndex, 1);
275 | }
276 | });
277 |
278 | // Found flying Snoo
279 | self.manager.register('Cadbury Bunny', 'Found flying Snoo.', function (saveIndex) {
280 | TB.utils.catchEvent(TB.utils.events.TB_FLY_SNOO, function () {
281 | self.manager.unlock(saveIndex);
282 | });
283 | });
284 |
285 | // Killed Snoo
286 | self.manager.register('you bastard!', 'Killed Snoo.', function (saveIndex) {
287 | TB.utils.catchEvent(TB.utils.events.TB_KILL_SNOO, function () {
288 | self.manager.unlock(saveIndex);
289 | });
290 | });
291 |
292 | // New modmail access
293 | self.manager.register('mannomail', 'No one knows, send hate mail to /u/thatastronautguy', function (saveIndex) {
294 | if (window.location.href.startsWith("https://mod.reddit.com/mail")) {
295 | self.manager.unlock(saveIndex);
296 | }
297 | });
298 | };
299 |
300 | TB.register_module(self);
301 | }
302 |
303 | (function() {
304 | window.addEventListener('TBModuleLoaded', function () {
305 | achievements();
306 | });
307 | })();
308 |
--------------------------------------------------------------------------------
/extension/data/modules/syntax.js:
--------------------------------------------------------------------------------
1 | function syntax() {
2 |
3 |
4 | var self = new TB.Module('Syntax Highlighter');
5 | self.shortname = 'Syntax';
6 |
7 | self.settings['enabled']['default'] = true;
8 |
9 | self.register_setting('enableWordWrap', {
10 | 'type': 'boolean',
11 | 'default': true,
12 | 'title': 'Enable word wrap in editor'
13 | });
14 | self.register_setting('selectedTheme', {
15 | 'type': 'syntaxTheme',
16 | 'default': 'dracula',
17 | 'title': 'Syntax highlight theme selection'
18 | });
19 |
20 | self.settings['enabled']['default'] = true; // on by default
21 |
22 | // we reference this from tbobject for settings generation
23 | self.themeSelect = '\
24 |
\
72 | ';
73 |
74 | self.init = function () {
75 | var $body = $('body'),
76 | selectedTheme = this.setting('selectedTheme'),
77 | enableWordWrap = this.setting('enableWordWrap'),
78 | editor, session, textarea;
79 |
80 | // This makes sure codemirror behaves and uses spaces instead of tabs.
81 | function betterTab(cm) {
82 | if (cm.somethingSelected()) {
83 | cm.indentSelection("add");
84 | } else {
85 | cm.replaceSelection(cm.getOption("indentWithTabs")? "\t":
86 | Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
87 | }
88 | }
89 |
90 | var keyboardShortcutsHelper = `
91 |
Keyboard shortcuts
92 |
93 | - F11: Fullscreen
94 | - Esc: Close Fullscreen
95 | - Ctrl-F / Cmd-F: Start searching
96 | - Ctrl-Alt-F / Cmd-Alt-F: Persistent search (dialog doesn't autoclose)
97 | - Ctrl-G / Cmd-G: Find next
98 | - Shift-Ctrl-G / Shift-Cmd-G: Find previous
99 | - Shift-Ctrl-F / Cmd-Option-F: Replace
100 | - Shift-Ctrl-R / Shift-Cmd-Option-F: Replace all
101 | - Alt-G: Jump to line
102 | - Ctrl-Space / Cmd-Space: autocomplete
103 |
104 |
`;
105 | // Editor for css.
106 | if (location.pathname.match(/\/about\/stylesheet\/?/)) {
107 | var stylesheetEditor;
108 |
109 | // Class added to apply some specific css.
110 | $body.addClass('mod-syntax');
111 | // Theme selector, doesn't really belong here but gives people the opportunity to see how it looks with the css they want to edit.
112 | $('.sheets .col').before(this.themeSelect);
113 |
114 | $('#theme_selector').val(selectedTheme);
115 |
116 | // Here apply codeMirror to the text area, the each itteration allows us to use the javascript object as codemirror works with those.
117 | $('#stylesheet_contents').each(function(index, elem){
118 |
119 | // Editor setup.
120 | stylesheetEditor = CodeMirror.fromTextArea(elem, {
121 | mode: 'text/css',
122 | autoCloseBrackets: true,
123 | lineNumbers: true,
124 | theme: selectedTheme,
125 | indentUnit: 4,
126 | extraKeys: {
127 | "Ctrl-Space": 'autocomplete',
128 | "Ctrl-Alt-F": "findPersistent",
129 | "F11": function(cm) {
130 | cm.setOption("fullScreen", !cm.getOption("fullScreen"));
131 | },
132 | "Esc": function(cm) {
133 | if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
134 | },
135 | "Tab": betterTab,
136 | "Shift-Tab": function (cm) {
137 | cm.indentSelection("subtract");
138 | }
139 | },
140 | lineWrapping: enableWordWrap
141 | });
142 |
143 | $body.find('.CodeMirror.CodeMirror-wrap').prepend(keyboardShortcutsHelper);
144 | });
145 |
146 | // In order to make save buttons work we need to hijack and replace them.
147 | var tbSyntaxButtonsHTML = '
{{save}} - {{preview}}
';
148 |
149 | var tbSyntaxButtons = TB.utils.template(tbSyntaxButtonsHTML, {
150 | 'save': TB.ui.actionButton('save', 'tb-syntax-button-save'),
151 | 'preview': TB.ui.actionButton('preview', 'tb-syntax-button-preview')
152 | });
153 |
154 | $body.find('.sheets .buttons').before(tbSyntaxButtons);
155 |
156 | // When the toolbox buttons are clicked we put back the content in the text area and click the now hidden original buttons.
157 | $body.delegate('.tb-syntax-button-save', 'click', function() {
158 | stylesheetEditor.save();
159 | $('.sheets .buttons .btn[name="save"]').click();
160 | });
161 |
162 | $body.delegate('.tb-syntax-button-preview', 'click', function() {
163 | stylesheetEditor.save();
164 | $('.sheets .buttons .btn[name="preview"]').click();
165 | });
166 |
167 | // Actually dealing with the theme dropdown is done here.
168 | $body.on('change keydown', '#theme_selector', function () {
169 | var thingy = $(this);
170 | setTimeout(function () {
171 | stylesheetEditor.setOption("theme", thingy.val());
172 | }, 0);
173 | });
174 | }
175 |
176 | // Here we deal with automod and toolbox pages containing json.
177 | if (location.pathname.match(/\/wiki\/(edit|create)\/(config\/)?automoderator(-schedule)?\/?$/)
178 | || location.pathname.match(/\/wiki\/edit\/toolbox\/?$/)) {
179 | var miscEditor;
180 | var $editform = $('#editform');
181 | var defaultMode = 'default';
182 |
183 | if (location.pathname.match(/\/wiki\/(edit|create)\/(config\/)?automoderator(-schedule)?\/?$/)) {
184 | defaultMode = "text/x-yaml";
185 | }
186 | if (location.pathname.match(/\/wiki\/edit\/toolbox\/?$/)) {
187 | defaultMode = "application/json";
188 | }
189 | // Class added to apply some specific css.
190 | $body.addClass('mod-syntax');
191 |
192 | // We also need to remove some stuff RES likes to add.
193 | $body.find('.markdownEditor-wrapper, .RESBigEditorPop, .help-toggle').remove();
194 |
195 | // Theme selector, doesn't really belong here but gives people the opportunity to see how it looks with the css they want to edit.
196 | $editform.prepend(this.themeSelect);
197 |
198 | $('#theme_selector').val(selectedTheme);
199 |
200 | // Here apply codeMirror to the text area, the each itteration allows us to use the javascript object as codemirror works with those.
201 | $('#wiki_page_content').each(function(index, elem){
202 |
203 | // Editor setup.
204 | miscEditor = CodeMirror.fromTextArea(elem, {
205 | mode: defaultMode,
206 | autoCloseBrackets: true,
207 | lineNumbers: true,
208 | theme: selectedTheme,
209 | indentUnit: 4,
210 | extraKeys: {
211 | "Ctrl-Alt-F": "findPersistent",
212 | "F11": function(cm) {
213 | cm.setOption("fullScreen", !cm.getOption("fullScreen"));
214 | },
215 | "Esc": function(cm) {
216 | if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
217 | },
218 | "Tab": betterTab,
219 | "Shift-Tab": function (cm) {
220 | cm.indentSelection("subtract");
221 | }
222 | },
223 | lineWrapping: enableWordWrap
224 | });
225 |
226 | $body.find('.CodeMirror.CodeMirror-wrap').prepend(keyboardShortcutsHelper);
227 | });
228 |
229 | // In order to make save button work we need to hijack and replace it.
230 | $('#wiki_save_button').after(TB.ui.actionButton('save page', 'tb-syntax-button-save-wiki'));
231 |
232 |
233 | // When the toolbox buttons is clicked we put back the content in the text area and click the now hidden original button.
234 | $body.delegate('.tb-syntax-button-save-wiki', 'click', function() {
235 | miscEditor.save();
236 | $('#wiki_save_button').click();
237 | });
238 |
239 | // Actually dealing with the theme dropdown is done here.
240 | $body.on('change keydown', '#theme_selector', function () {
241 | var thingy = $(this);
242 | setTimeout(function () {
243 | miscEditor.setOption("theme", thingy.val());
244 | }, 0);
245 | });
246 |
247 | }
248 | };
249 |
250 | TB.register_module(self);
251 | }
252 |
253 | (function() {
254 | window.addEventListener("TBModuleLoaded", function () {
255 | syntax();
256 | });
257 | })();
258 |
--------------------------------------------------------------------------------