-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathse_like_I_like_it.user.js
289 lines (239 loc) · 9.52 KB
/
se_like_I_like_it.user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// ==UserScript==
// @name Stack Exchange like I like it
// @namespace http://stackapps.org/
// @description Double-click a code block to select all + edit box auto indent / tab key behavior enhancements
// @match *://stackexchange.com/*
// @match *://*.stackexchange.com/*/*
// @match *://stackoverflow.com/*/*
// @match *://*.stackoverflow.com/*/*
// @match *://stackapps.com/*/*
// @match *://serverfault.com/*/*
// @match *://superuser.com/*/*
// @match *://askubuntu.com/*/*
// @match *://mathoverflow.net/*/*
// @exclude /^https?:\/\/(chat|blog|careers)\..*/
// @version 1.5.1.1
// @downloadURL https://github.com/calraith/gm_scripts/raw/master/se_like_I_like_it.user.js
// @grant GM_info
// ==/UserScript==
// \/.+/
var key = {
tab: 9,
enter: 13,
end: 35,
home: 36,
code: 0,
history: [0,0],
contains: function(what) {
// fastest loop method for this application
// see http://jsperf.com/object-iteration-like-a-boss
for (var i in this) {
if (i === 'code') return false;
if (this[i] === what) return true;
}
}
};
function fixTabs(e) {
e = e || event;
key.code = e.keyCode || e.charCode;
// capture this + previous key code to detect double home or end
key.history.push(key.code);
key.history.shift();
if (!key.contains(key.code)) return; // not a character this script handles
var el = document.activeElement;
if (el.nodeName.toLowerCase() !== 'textarea') return; // not in a textarea
if (el.name == 'comment') return; // not an edit entry box
// All systems go!
var start = el.selectionStart,
end = el.selectionEnd,
shiftStart = shiftEnd = 0,
// indent regexp replacement function
tabFill = function(match, $1, offset) {
for (var i=0, ret='', len=match.length, m=len / 4; i<m; i++) ret += '\t';
// compensate for cursor drift
if (offset < start) {
shiftStart -= (len - i);
if (offset + len > start) shiftStart += offset + len - start;
}
if (offset < end) {
shiftEnd -= (len - i);
if (offset + len > end) shiftEnd += offset + len - end;
}
return ret;
};
// Replace space indents with tabs.
el.value = el.value.replace(/(?:^\t*)( {4})+/mg, tabFill);
if (shiftStart) {
start += shiftStart; end += shiftEnd;
el.setSelectionRange(start, end);
}
selected = el.value.substring(start, end);
var inputArr = el.value.split('\n');
// inputArr[row] contains start, and pos contains the idx of the last char of the previous line
for (var row=pos=0;pos+inputArr[row].length<start;row++){pos+=inputArr[row].length+1}
switch (key.code) {
case key.tab:
e.preventDefault();
// if selection spans more than one line
if (selected.indexOf('\n') > -1) {
// count number of rows included in selection
var rowsSelected = selected.split('\n').length;
// expand selection to cover beginning of start row until end of end row
// replace all 4-space sequences with a tab and update position indexes
// if shift key, remove one level of indent per selected row; otherwise, add a level of indent.
for (start = pos, stop = row + rowsSelected; row < stop; row++) {
inputArr[row] = (e.shiftKey) ? inputArr[row].replace(/^( {4}|\t)/,'') : '\t' + inputArr[row];
pos+=inputArr[row].length + 1;
}
end = --pos;
el.value = inputArr.join('\n');
el.setSelectionRange(start, end);
// else selection does not span multiple lines.
// If not shift key, simply insert a tab (overwriting any leading spaces) and advance the cursor.
} else if (!e.shiftKey) {
el.value = el.value.substring(0, start).replace(/( +)$/, function($1) { start -= $1.length; return ''; })
+ '\t'
+ el.value.substring(end).replace(/^ +/, '');
el.setSelectionRange(++start, start);
// otherwise, handle shift+tab
} else {
/* =======================================================
The following code block mimics the behavior of Notepad++.
If a user does shift-tab, NPP does not change the
indentation of the line unless the cursor is at the
beginning of all non-whitespace on the line. Instead,
NPP moves the cursor back one tab stop.
======================================================= */
var cursorInPos = start - pos, cursorMoved, tabStop;
// While the cursor is not at the beginning of the line and there's
// non-whitespace between the beginning of the line and the cursor,
// and start is not at the previous tab stop
while (cursorInPos && /\S/.test(inputArr[row].substring(0,cursorInPos)) && !tabStop) {
cursorMoved = start--;
cursorInPos--;
var indentLevel = (inputArr[row].match(/^\s+/) || [''])[0].length,
tempCursor = cursorInPos - indentLevel,
tabStop = !(tempCursor % 4);
}
// If cursor was not moved, outdent.
if (!cursorMoved) {
if (/\s/.test(inputArr[row].charAt(cursorInPos))) start++;
inputArr[row] = inputArr[row].replace(/^( +|\t)/,
function($1) { if (start > pos) start -= $1.length; return ''; }
);
el.value = inputArr.join('\n');
}
el.setSelectionRange(start, start);
}
break;
case key.enter:
e.preventDefault();
// ctrl+Enter to submit
if (e.ctrlKey) {
do { el = el.parentNode || null; } while (el && el.nodeName.toLowerCase() !== 'form');
return el ? el.submit() : false;
}
// unify indentation of previous line as tabs
var indent = (inputArr[row].match(/^\s+/) || [''])[0];
// insert newline + indent, discarding contents of user selection if any
el.value = el.value.substring(0, pos)
+ inputArr[row].substring(0, start - pos)
+ '\n' + indent
+ el.value.substring(end);
start+=indent.length+1;
el.setSelectionRange(start, start);
break;
case key.home:
var cursorInPos = start - pos, newStart = start, cursorMoved;
// If not double-pressed and line is wrapped, allow default behavior
if (cursorInPos > el.cols && key.history[0] !== key.home) return;
if (key.history[0] == key.history[1]) key.history[1] = 0;
e.preventDefault();
// If cursor is in the whitespace at the beginning of the line, advance to text
while (cursorInPos < inputArr[row].length && /\s/.test(inputArr[row].charAt(cursorInPos))) {
cursorMoved = newStart++;
cursorInPos++;
}
// If cursor is after non-whitespace, regress to beginning of text
while (cursorInPos && /\S/.test(inputArr[row].substring(0,cursorInPos))) {
cursorMoved = newStart--;
cursorInPos--;
}
// If the cursor wasn't moved, move it to the beginning of the line.
if (!cursorMoved) newStart -= cursorInPos;
if (e.shiftKey) {
if (start > newStart) el.setSelectionRange(newStart, end, 'backward');
else el.setSelectionRange(start, newStart);
}
else el.setSelectionRange(newStart, newStart);
break;
case key.end:
// if not double-pressed, allow default behavior.
if (inputArr[row].length <= el.cols || key.history[0] !== key.end) return;
if (key.history[0] == key.history[1]) key.history[1] = 0;
e.preventDefault();
var newEnd = pos + inputArr[row].length;
// select to next \n
if (e.shiftKey) el.setSelectionRange(start, newEnd);
else el.setSelectionRange(newEnd, newEnd);
break;
} // end switch(key.code)
} // end fixTabs()
addEventListener('keydown', fixTabs, true);
/* end keyboard behavior mods */
function selectAll() {
var range = document.createRange();
// select parent if parent === <pre>; else select self
range.selectNodeContents(this.nodeName.toLowerCase() === 'pre' ? this.childNodes[0] : this);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function addDblClick() {
var code = document.getElementsByTagName('code');
for (var i=0; i<code.length; i++) {
/* ====================================================================
If parent node is <pre>, add listener to parent. This allows double-
clicking anywhere within a code block to select all, as well as double-
clicking inline code snippets the poster enclosed in backticks.
==================================================================== */
var node = (code[i].parentNode.nodeName.toLowerCase() === 'pre' ? code[i].parentNode : code[i]);
node.removeEventListener('dblclick', selectAll, true);
node.addEventListener('dblclick', selectAll, true);
node.title = 'double-click to select all';
}
}
addDblClick();
// listen for stuff like "show 1 more comment"
var creep = new MutationObserver(addDblClick);
creep.observe(document, {subtree: true, childList: true});
/* end double-click mods */
// If on http://stackexchange.com/, check for sites not included in metadata @match directives
if (/https?:\/\/stackexchange\.com/i.test(location.href) && GM_info) {
var XHR = new XMLHttpRequest();
with (XHR) {
open("GET", "/sites?view=list", true);
onerror = function() { return false; };
onload = function() { return true; };
onreadystatechange = function() {
if (XHR.readyState == 4) {
var dom = document.implementation.createHTMLDocument();
dom.documentElement.innerHTML = XHR.responseText;
var list = dom.getElementsByClassName('list-view-container')[0].getElementsByTagName('a');
var matches = GM_info.script.matches;
for (var i=found=0; i<list.length; i++) {
if (/(twitter|blog)/i.test(list[i].href)) continue;
for (var j=0; j<matches.length; j++) {
var rxp = new RegExp('^' + matches[j].replace(/\/\*$/,'').replace(/[\.\/\*]/g, function(m) {
return (m == '*') ? '.*' : '\\' + m;
}) + '$', 'i');
if (rxp.test(list[i].href)) { found=1; break; }
}
if (!found) console.log('New stackexchange domain: ' + list[i].href);
else found=0;
}
}
};
send('');
}
}