', { class: ID + '-backdrop' })
65 | .append(this.$highlights);
66 |
67 | this.$container = $('
', { class: ID + '-container' })
68 | .insertAfter(this.$el)
69 | .append(this.$backdrop, this.$el) // moves $el into $container
70 | .on('scroll', this.blockContainerScroll.bind(this));
71 |
72 | this.browser = this.detectBrowser();
73 | switch (this.browser) {
74 | case 'firefox':
75 | this.fixFirefox();
76 | break;
77 | case 'ios':
78 | this.fixIOS();
79 | break;
80 | }
81 |
82 | // plugin function checks this for success
83 | this.isGenerated = true;
84 |
85 | // trigger input event to highlight any existing input
86 | this.handleInput();
87 | },
88 |
89 | // browser sniffing sucks, but there are browser-specific quirks to handle
90 | // that are not a matter of feature detection
91 | detectBrowser: function() {
92 | let ua = window.navigator.userAgent.toLowerCase();
93 | if (ua.indexOf('firefox') !== -1) {
94 | return 'firefox';
95 | } else if (!!ua.match(/msie|trident\/7|edge/)) {
96 | return 'ie';
97 | } else if (!!ua.match(/ipad|iphone|ipod/) && ua.indexOf('windows phone') === -1) {
98 | // Windows Phone flags itself as "like iPhone", thus the extra check
99 | return 'ios';
100 | } else {
101 | return 'other';
102 | }
103 | },
104 |
105 | // Firefox doesn't show text that scrolls into the padding of a textarea, so
106 | // rearrange a couple box models to make highlights behave the same way
107 | fixFirefox: function() {
108 | // take padding and border pixels from highlights div
109 | let padding = this.$highlights.css([
110 | 'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
111 | ]);
112 | let border = this.$highlights.css([
113 | 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
114 | ]);
115 | this.$highlights.css({
116 | 'padding': '0',
117 | 'border-width': '0'
118 | });
119 |
120 | this.$backdrop
121 | .css({
122 | // give padding pixels to backdrop div
123 | 'margin-top': '+=' + padding['padding-top'],
124 | 'margin-right': '+=' + padding['padding-right'],
125 | 'margin-bottom': '+=' + padding['padding-bottom'],
126 | 'margin-left': '+=' + padding['padding-left'],
127 | })
128 | .css({
129 | // give border pixels to backdrop div
130 | 'margin-top': '+=' + border['border-top-width'],
131 | 'margin-right': '+=' + border['border-right-width'],
132 | 'margin-bottom': '+=' + border['border-bottom-width'],
133 | 'margin-left': '+=' + border['border-left-width'],
134 | });
135 | },
136 |
137 | // iOS adds 3px of (unremovable) padding to the left and right of a textarea,
138 | // so adjust highlights div to match
139 | fixIOS: function() {
140 | this.$highlights.css({
141 | 'padding-left': '+=3px',
142 | 'padding-right': '+=3px'
143 | });
144 | },
145 |
146 | handleInput: function() {
147 | let input = this.$el.val();
148 | let ranges = this.getRanges(input, this.highlight);
149 | let unstaggeredRanges = this.removeStaggeredRanges(ranges);
150 | let boundaries = this.getBoundaries(unstaggeredRanges);
151 | this.renderMarks(boundaries);
152 | },
153 |
154 | getRanges: function(input, highlight) {
155 | let type = this.getType(highlight);
156 | switch (type) {
157 | case 'array':
158 | return this.getArrayRanges(input, highlight);
159 | case 'function':
160 | return this.getFunctionRanges(input, highlight);
161 | case 'regexp':
162 | return this.getRegExpRanges(input, highlight);
163 | case 'string':
164 | return this.getStringRanges(input, highlight);
165 | case 'range':
166 | return this.getRangeRanges(input, highlight);
167 | case 'custom':
168 | return this.getCustomRanges(input, highlight);
169 | default:
170 | if (!highlight) {
171 | // do nothing for falsey values
172 | return [];
173 | } else {
174 | console.error('unrecognized highlight type');
175 | }
176 | }
177 | },
178 |
179 | getArrayRanges: function(input, arr) {
180 | let ranges = arr.map(this.getRanges.bind(this, input));
181 | return Array.prototype.concat.apply([], ranges);
182 | },
183 |
184 | getFunctionRanges: function(input, func) {
185 | return this.getRanges(input, func(input));
186 | },
187 |
188 | getRegExpRanges: function(input, regex) {
189 | let ranges = [];
190 | let match;
191 | while (match = regex.exec(input), match !== null) {
192 | ranges.push([match.index, match.index + match[0].length]);
193 | if (!regex.global) {
194 | // non-global regexes do not increase lastIndex, causing an infinite loop,
195 | // but we can just break manually after the first match
196 | break;
197 | }
198 | }
199 | return ranges;
200 | },
201 |
202 | getStringRanges: function(input, str) {
203 | let ranges = [];
204 | let inputLower = input.toLowerCase();
205 | let strLower = str.toLowerCase();
206 | let index = 0;
207 | while (index = inputLower.indexOf(strLower, index), index !== -1) {
208 | ranges.push([index, index + strLower.length]);
209 | index += strLower.length;
210 | }
211 | return ranges;
212 | },
213 |
214 | getRangeRanges: function(input, range) {
215 | return [range];
216 | },
217 |
218 | getCustomRanges: function(input, custom) {
219 | let ranges = this.getRanges(input, custom.highlight);
220 | if (custom.className) {
221 | ranges.forEach(function(range) {
222 | // persist class name as a property of the array
223 | if (range.className) {
224 | range.className = custom.className + ' ' + range.className;
225 | } else {
226 | range.className = custom.className;
227 | }
228 | });
229 | }
230 | return ranges;
231 | },
232 |
233 | // prevent staggered overlaps (clean nesting is fine)
234 | removeStaggeredRanges: function(ranges) {
235 | let unstaggeredRanges = [];
236 | ranges.forEach(function(range) {
237 | let isStaggered = unstaggeredRanges.some(function(unstaggeredRange) {
238 | let isStartInside = range[0] > unstaggeredRange[0] && range[0] < unstaggeredRange[1];
239 | let isStopInside = range[1] > unstaggeredRange[0] && range[1] < unstaggeredRange[1];
240 | return isStartInside !== isStopInside; // xor
241 | });
242 | if (!isStaggered) {
243 | unstaggeredRanges.push(range);
244 | }
245 | });
246 | return unstaggeredRanges;
247 | },
248 |
249 | getBoundaries: function(ranges) {
250 | let boundaries = [];
251 | ranges.forEach(function(range) {
252 | boundaries.push({
253 | type: 'start',
254 | index: range[0],
255 | className: range.className
256 | });
257 | boundaries.push({
258 | type: 'stop',
259 | index: range[1]
260 | });
261 | });
262 |
263 | this.sortBoundaries(boundaries);
264 | return boundaries;
265 | },
266 |
267 | sortBoundaries: function(boundaries) {
268 | // backwards sort (since marks are inserted right to left)
269 | boundaries.sort(function(a, b) {
270 | if (a.index !== b.index) {
271 | return b.index - a.index;
272 | } else if (a.type === 'stop' && b.type === 'start') {
273 | return 1;
274 | } else if (a.type === 'start' && b.type === 'stop') {
275 | return -1;
276 | } else {
277 | return 0;
278 | }
279 | });
280 | },
281 |
282 | renderMarks: function(boundaries) {
283 | let input = this.$el.val();
284 | boundaries.forEach(function(boundary, index) {
285 | let markup;
286 | if (boundary.type === 'start') {
287 | markup = '{{hwt-mark-start|' + index + '}}';
288 | } else {
289 | markup = '{{hwt-mark-stop}}';
290 | }
291 | input = input.slice(0, boundary.index) + markup + input.slice(boundary.index);
292 | });
293 |
294 | // this keeps scrolling aligned when input ends with a newline
295 | input = input.replace(/\n(\{\{hwt-mark-stop\}\})?$/, '\n\n$1');
296 |
297 | // encode HTML entities
298 | input = input.replace(//g, '>');
299 |
300 | if (this.browser === 'ie') {
301 | // IE/Edge wraps whitespace differently in a div vs textarea, this fixes it
302 | input = input.replace(/ /g, '
');
303 | }
304 |
305 | // replace start tokens with opening tags with class name
306 | input = input.replace(/\{\{hwt-mark-start\|(\d+)\}\}/g, function(match, submatch) {
307 | var className = boundaries[+submatch].className;
308 | if (className) {
309 | return '';
310 | } else {
311 | return '';
312 | }
313 | });
314 |
315 | // replace stop tokens with closing tags
316 | input = input.replace(/\{\{hwt-mark-stop\}\}/g, '');
317 |
318 | this.$highlights.html(input);
319 | },
320 |
321 | handleScroll: function() {
322 | let scrollTop = this.$el.scrollTop();
323 | this.$backdrop.scrollTop(scrollTop);
324 |
325 | // Chrome and Safari won't break long strings of spaces, which can cause
326 | // horizontal scrolling, this compensates by shifting highlights by the
327 | // horizontally scrolled amount to keep things aligned
328 | let scrollLeft = this.$el.scrollLeft();
329 | this.$backdrop.css('transform', (scrollLeft > 0) ? 'translateX(' + -scrollLeft + 'px)' : '');
330 | },
331 |
332 | // in Chrome, page up/down in the textarea will shift stuff within the
333 | // container (despite the CSS), this immediately reverts the shift
334 | blockContainerScroll: function() {
335 | this.$container.scrollLeft(0);
336 | },
337 |
338 | destroy: function() {
339 | this.$backdrop.remove();
340 | this.$el
341 | .unwrap()
342 | .removeClass(ID + '-text ' + ID + '-input')
343 | .off(ID)
344 | .removeData(ID);
345 | },
346 | };
347 |
348 | // register the jQuery plugin
349 | $.fn.highlightWithinTextarea = function(options) {
350 | return this.each(function() {
351 | let $this = $(this);
352 | let plugin = $this.data(ID);
353 |
354 | if (typeof options === 'string') {
355 | if (plugin) {
356 | switch (options) {
357 | case 'update':
358 | plugin.handleInput();
359 | break;
360 | case 'destroy':
361 | plugin.destroy();
362 | break;
363 | default:
364 | console.error('unrecognized method string');
365 | }
366 | } else {
367 | console.error('plugin must be instantiated first');
368 | }
369 | } else {
370 | if (plugin) {
371 | plugin.destroy();
372 | }
373 | plugin = new HighlightWithinTextarea($this, options);
374 | if (plugin.isGenerated) {
375 | $this.data(ID, plugin);
376 | }
377 | }
378 | });
379 | };
380 | })(jQuery);
381 |
--------------------------------------------------------------------------------
/ext/tts/jquery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
132 |
133 |
--------------------------------------------------------------------------------
/ext/tts/sound.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 | 請選擇所要上傳的檔案
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
69 |
70 |
--------------------------------------------------------------------------------
/ext/web_api/fetch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | id |
29 | city |
30 | name |
31 | address |
32 | url |
33 | socket |
34 | latitude |
35 | longitude |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
95 |
96 |
--------------------------------------------------------------------------------
/ext/web_api/web_api_coffee.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | _id |
20 | 餐廳名稱 |
21 | 餐館電話 |
22 | 餐館地址 |
23 |
24 |
25 |
26 |
27 |
28 |
52 |
53 |
--------------------------------------------------------------------------------
/javascript_basics.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/telunyang/javascript_basics/92a1e89c7fabdfaf1e67dff981bbb14996f05c53/javascript_basics.docx
--------------------------------------------------------------------------------
/javascript_basics.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/telunyang/javascript_basics/92a1e89c7fabdfaf1e67dff981bbb14996f05c53/javascript_basics.pdf
--------------------------------------------------------------------------------