├── correct.mp3
├── favicon.ico
├── audio
├── G. N. Trenité - The Chaos.mp3
└── advanced-pronunciation-fun-english.mp3
├── README.md
├── index.html
├── homophones.txt
├── index.css
├── index.js
├── diff_match_patch.js
└── texts.txt
/correct.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/little-brother/english-speech/HEAD/correct.mp3
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/little-brother/english-speech/HEAD/favicon.ico
--------------------------------------------------------------------------------
/audio/G. N. Trenité - The Chaos.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/little-brother/english-speech/HEAD/audio/G. N. Trenité - The Chaos.mp3
--------------------------------------------------------------------------------
/audio/advanced-pronunciation-fun-english.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/little-brother/english-speech/HEAD/audio/advanced-pronunciation-fun-english.mp3
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is an [application](https://little-brother.github.io/english-speech/) to the practice of English speech via Google Speech Recognition API.
2 |
3 |
4 | If app don't work properly, e.g. you don't listen any sound or you see permanent "Loading..." instead "Start"-button on first screen, it's means that your browser is obsolete.
5 | Use Chrome to avoid troubles.
6 | Minimal supported Android version is 4.1 with Chrome 69.
7 |
8 | If you have any questions, comments or suggestions, just let me know lb.im@yandex.ru.
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
Powered by
24 |
Google Chrome
Speech Recognition
25 |
Loading...
26 |
Allow access to microphone...
27 |
Start
28 |
29 |
30 |
31 |
32 |
142 |
146 |
147 |
This is an application to the practice of English speech via Google Speech Recognition API:
148 |
149 | - Push "Record"-button and hold it while you read aloud a phrase on the screen.
150 | - The voice will be recognized via Web Speech API and you will see differences between required and recognized phrases.
151 | - Listen your voice by click on "Listen"-button to detect defects in pronunciation.
152 |
153 |
154 |
155 | The phrases are grouped in text: one line is one phrase.
156 | You can choose a predefined text or create your own by click on a left icon in a header on the main screen.
157 |
158 |
Keep in mind
159 | Recognition does not always work well.
160 | Some phrases can be recognized incorrectly even with perfect pronunciation.
161 |
162 |
163 |
164 | Recommendation
165 | Use a high-quality microphone to improve recognition.
166 |
167 |
168 |
If you have any questions, comments or suggestions, just let me know lb.im@yandex.ru or visit project page.
169 |
170 |
171 |
See also
172 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/homophones.txt:
--------------------------------------------------------------------------------
1 | accessary;accessory
2 | ad;add
3 | ail;ale
4 | air;heir
5 | aisle;I'll;isle
6 | all;awl
7 | allowed;aloud
8 | alms;arms
9 | altar;alter
10 | arc;ark
11 | aren't;aunt
12 | ate;eight
13 | auger;augur
14 | auk;orc
15 | aural;oral
16 | away;aweigh
17 | awe;oar;or;ore
18 | axel;axle
19 | aye;eye;I
20 | bail;bale
21 | bait;bate
22 | baize;bays
23 | bald;bawled
24 | ball;bawl
25 | band;banned
26 | bard;barred
27 | bare;bear
28 | bark;barque
29 | baron;barren
30 | base;bass
31 | bay;bey
32 | bazaar;bizarre
33 | be;bee
34 | beach;beech
35 | bean;been
36 | beat;beet
37 | beau;bow
38 | beer;bier
39 | bel;bell;belle
40 | berry;bury
41 | berth;birth
42 | bight;bite;byte
43 | billed;build
44 | bitten;bittern
45 | blew;blue
46 | bloc;block
47 | boar;bore
48 | board;bored
49 | boarder;border
50 | bold;bowled
51 | boos;booze
52 | born;borne
53 | bough;bow
54 | boy;buoy
55 | brae;bray
56 | braid;brayed
57 | braise;brays;braze
58 | brake;break
59 | bread;bred
60 | brews;bruise
61 | bridal;bridle
62 | broach;brooch
63 | bur;burr
64 | but;butt
65 | buy;by;bye
66 | buyer;byre
67 | calendar;calender
68 | call;caul
69 | canvas;canvass
70 | cast;caste
71 | caster;castor
72 | caught;court
73 | caw;core;corps
74 | cede;seed
75 | ceiling;sealing
76 | cell;sell
77 | cells;sells
78 | censer;censor;sensor
79 | cent;scent;sent
80 | cereal;serial
81 | cheap;cheep
82 | check;cheque
83 | choir;quire
84 | chord;cord
85 | chews;choose
86 | cite;sight;site
87 | clack;claque
88 | clew;clue
89 | climb;clime
90 | close;cloze
91 | coal;kohl
92 | coarse;course
93 | coign;coin
94 | colonel;kernel
95 | complacent;complaisant
96 | complement;compliment
97 | coo;coup
98 | cops;copse
99 | council;counsel
100 | cousin;cozen
101 | creak;creek
102 | crews;cruise
103 | cue;kyu;queue
104 | curb;kerb
105 | currant;current
106 | cymbol;symbol
107 | dam;damn
108 | days;daze
109 | dear;deer
110 | descent;dissent
111 | desert;dessert
112 | deviser;divisor
113 | dew;due
114 | die;dye
115 | discreet;discrete
116 | doe;doh;dough
117 | done;dun
118 | douse;dowse
119 | draft;draught
120 | dual;duel
121 | earn;urn
122 | eery;eyrie
123 | energised;energized
124 | everyday;every day
125 | ewe;yew;you
126 | faint;feint
127 | fah;far
128 | fair;fare
129 | farther;father
130 | fate;fête
131 | faun;fawn
132 | fay;fey
133 | faze;phase
134 | feat;feet
135 | ferrule;ferule
136 | few;phew
137 | fie;phi
138 | file;phial
139 | find;fined
140 | fir;fur
141 | fizz;phiz
142 | flair;flare
143 | flaw;floor
144 | flea;flee
145 | flex;flecks
146 | flew;flu;flue
147 | floe;flow
148 | flour;flower
149 | foaled;fold
150 | for;fore;four
151 | foreword;forward
152 | fort;fought
153 | forth;fourth
154 | foul;fowl
155 | franc;frank
156 | freeze;frieze
157 | friar;fryer
158 | furs;furze
159 | gait;gate
160 | galipot;gallipot
161 | gallop;galop
162 | gamble;gambol
163 | gays;gaze
164 | genes;jeans
165 | gild;guild
166 | gilt;guilt
167 | giro;gyro
168 | gnaw;nor
169 | gneiss;nice
170 | gorilla;guerilla
171 | grate;great
172 | greave;grieve
173 | greys;graze
174 | grisly;grizzly
175 | groan;grown
176 | guessed;guest
177 | hail;hale
178 | hair;hare
179 | hall;haul
180 | hangar;hanger
181 | hart;heart
182 | haw;hoar;whore
183 | hay;hey
184 | heal;heel;he'll
185 | hear;here
186 | heard;herd
187 | he'd;heed
188 | heroin;heroine
189 | hew;hue
190 | hi;high
191 | hiccough;hiccup
192 | higher;hire
193 | him;hymn
194 | ho;hoe
195 | hoard;horde
196 | hoarse;horse
197 | hole;whole
198 | holey;holy;wholly
199 | hour;our
200 | idle;idol
201 | in;inn
202 | indict;indite
203 | it's;its
204 | jewel;joule
205 | key;quay
206 | knave;nave
207 | knead;need
208 | knew;new
209 | knight;night
210 | knit;nit
211 | knob;nob
212 | knock;nock
213 | knot;not
214 | know;no
215 | knows;nose
216 | laager;lager
217 | lac;lack
218 | lade;laid
219 | lain;lane
220 | lam;lamb
221 | laps;lapse
222 | larva;lava
223 | lase;laze
224 | law;lore
225 | lay;ley
226 | lea;lee
227 | leach;leech
228 | lead;led
229 | leak;leek
230 | lean;lien
231 | lessen;lesson
232 | learned;learnt
233 | levee;levy
234 | liar;lyre
235 | licence;license
236 | licker;liquor
237 | lie;lye
238 | lieu;loo
239 | links;lynx
240 | lo;low
241 | load;lode
242 | loan;lone
243 | locks;lox
244 | loop;loupe
245 | loot;lute
246 | made;maid
247 | mail;male
248 | main;mane
249 | maize;maze
250 | mall;maul
251 | manna;manner
252 | mantel;mantle
253 | mare;mayor
254 | mark;marque
255 | marshal;martial
256 | marten;martin
257 | mask;masque
258 | maw;more
259 | me;mi
260 | mean;mien
261 | meat;meet;mete
262 | medal;meddle
263 | metal;mettle
264 | meter;metre
265 | might;mite
266 | miner;minor;mynah
267 | mind;mined
268 | missed;mist
269 | moat;mote
270 | mode;mowed
271 | moor;more
272 | moose;mousse
273 | morning;mourning
274 | muscle;mussel
275 | naval;navel
276 | nay;neigh
277 | nigh;nye
278 | none;nun
279 | od;odd
280 | ode;owed
281 | o;oh;owe
282 | one;won
283 | packed;pact
284 | packs;pax
285 | pail;pale
286 | pain;pane
287 | pair;pare;pear
288 | palate;palette;pallet
289 | pascal;paschal
290 | paten;patten;pattern
291 | pause;paws;pores;pours
292 | pawn;porn
293 | pea;pee
294 | peace;piece
295 | peak;peek;peke;pique
296 | peal;peel
297 | pearl;purl
298 | pedal;peddle
299 | peer;pier
300 | pi;pie
301 | pica;pika
302 | place;plaice
303 | plain;plane
304 | pleas;please
305 | plum;plumb
306 | pole;poll
307 | poof;pouffe
308 | practice;practise
309 | practiced;practised
310 | praise;prays;preys
311 | principal;principle
312 | profit;prophet
313 | quarts;quartz
314 | quean;queen
315 | rain;reign;rein
316 | raise;rays;raze
317 | rap;wrap
318 | raw;roar
319 | read;reed
320 | read;red
321 | real;reel
322 | reek;wreak
323 | rest;wrest
324 | retch;wretch
325 | review;revue
326 | rheum;room
327 | right;rite;wright;write
328 | ring;wring
329 | road;rode
330 | roe;row
331 | role;roll
332 | roo;roux;rue
333 | rood;rude
334 | root;route
335 | rose;rows
336 | rota;rotor
337 | rote;wrote
338 | rough;ruff
339 | rouse;rows
340 | rung;wrung
341 | rye;wry
342 | saver;savour
343 | spade;spayed
344 | sale;sail
345 | sane;seine
346 | satire;satyr
347 | sauce;source
348 | saw;soar;sore
349 | scene;seen
350 | scull;skull
351 | sea;see
352 | seam;seem
353 | sear;seer;sere
354 | seas;sees;seize
355 | sew;so;sow
356 | shake;sheikh
357 | shear;sheer
358 | shoe;shoo
359 | show;shew
360 | sic;sick
361 | side;sighed
362 | sign;sine
363 | sink;synch
364 | slay;sleigh
365 | sloe;slow
366 | sole;soul
367 | some;sum
368 | son;sun
369 | sort;sought
370 | spa;spar
371 | staid;stayed
372 | stair;stare
373 | stake;steak
374 | stalk;stork
375 | stationary;stationery
376 | steal;steel
377 | stile;style
378 | storey;story
379 | straight;strait
380 | susie;suzy
381 | sweet;suite
382 | swat;swot
383 | tacks;tax
384 | tale;tail
385 | talk;torque
386 | tare;tear
387 | taught;taut;tort
388 | te;tea;tee
389 | team;teem
390 | tear;tier
391 | teas;tease
392 | terce;terse
393 | tern;turn
394 | there;their;they're
395 | threw;through
396 | throes;throws
397 | throne;thrown
398 | thyme;time
399 | tic;tick
400 | tide;tied
401 | tire;tyre
402 | to;too;two
403 | toad;toed;towed
404 | told;tolled
405 | tole;toll
406 | ton;tun
407 | tor;tore
408 | tough;tuff
409 | troop;troupe
410 | tuba;tuber
411 | vain;vane;vein
412 | vale;veil
413 | vial;vile
414 | wail;wale;whale
415 | wain;wane
416 | waist;waste
417 | wait;weight
418 | waive;wave
419 | wall;waul
420 | war;wore
421 | ware;wear;where
422 | warn;worn
423 | wart;wort
424 | watt;what
425 | wax;whacks
426 | way;weigh;whey
427 | we;wee;whee
428 | weak;week
429 | we'd;weed
430 | weal;we'll;wheel
431 | wean;ween
432 | weather;whether
433 | weaver;weever
434 | weir;we're
435 | were;whirr
436 | wet;whet
437 | wheald;wheeled
438 | which;witch
439 | whig;wig
440 | while;wile
441 | whine;wine
442 | whirl;whorl
443 | whirled;world
444 | whit;wit
445 | white;wight
446 | who's;whose
447 | woe;whoa
448 | wood;would
449 | yaw;yore;your;you're
450 | yoke;yolk
451 | you'll;yule
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | * {margin: 0; padding: 0;}
2 | html, body { padding: 0; margin: 0; height: 100%;}
3 | input, textarea {font-size: 24px;}
4 |
5 | .page {font-size: 24px;}
6 | .page:not([current]) {display: none !important;}
7 | .page.center {height: 100%; display: grid; }
8 | .page.center .content {margin: auto; position: relative;}
9 | .page.absolute {position: absolute; top: 0; bottom: 0; left: 0; right: 0;}
10 | .page.absolute .content {margin: auto; position: absolute; top: 48px; right: 0; left: 0; bottom: 0; overflow: auto;}
11 |
12 | .page .caption {position: fixed; text-align: center; right: 0; left: 0; color: #fff; background: #aaf; font-weight: bold; padding: 10px; text-transform: uppercase; z-index: 10;}
13 | .page .caption > * {display: inline-block;}
14 | .page .caption .left {float: left; cursor: pointer;}
15 |
16 | .page .content-padding {padding: 20px;}
17 |
18 | #page-start .content {top: -20px; text-align: center;}
19 | #page-start #logo {text-decoration: none; color: #000; width: 200px; display: inline-block; margin: 0; font-size: 20px; vertical-align: top; margin-top: 15px; margin-bottom: 15px;}
20 | #page-start #logo::before {content: ''; display: inline-block; background-size: contain; background-position: center; background-repeat: no-repeat; background-image: url(https://www.google.com/chrome/static/images/chrome-logo.svg); width: 160px; height: 160px; }
21 | #page-start #loading {color: #bbb; padding: 10px 20px; margin-top: 10px; position: relative; left: 10px;}
22 | #page-start #loading ~ #button-start {display: none !important;}
23 | #page-start #loading ~ #microphone {display: none !important;}
24 | #page-start #microphone ~ #button-start {display: none;}
25 | #page-start #microphone {text-align: center;}
26 | #page-start #button-start {padding: 10px 20px; margin-top: 10px; text-align: center; cursor: pointer; border-radius: 5px; background: #0f0;}
27 |
28 | #page-main #button-text-selector {display: inline-block; float: left; cursor: pointer; margin-left: 5px; position: relative; height: 28px; width: 30px; top: 1px;}
29 | #page-main #button-text-selector::before {content: ""; position: absolute; left: 0; width: 30px; height: 5px; background: #fff; box-shadow: 0 10px 0 0 #fff, 0 20px 0 0 #fff;}
30 |
31 | #page-main .content {margin: 10px; text-align: center;}
32 | #page-main #panel-counter {display: block; z-index: 1; text-align: center;}
33 | #page-main #panel-counter #phrase-number-input {display: none; width: 100px; text-align: center; background: #fff;}
34 | #page-main #panel-counter #phrase-number-input:focus ~ #phrase-number {display: none;}
35 | #page-main #panel-counter .button-arrow {cursor: pointer;}
36 | #page-main #panel-counter > * {display: inline-block; font-size: 56px;}
37 | #page-main #panel-counter #caption {display: block; font-size: 14px;}
38 | #page-main #panel-phrase { margin-bottom: 10px; position: absolute; left: 0; right: 0; bottom: 50%;}
39 | #page-main #panel-phrase .button {padding: 0px; margin: 0 5px; font-size: 36px; color: #bbb;}
40 | #page-main #panel-phrase .button[current="true"] {color: #0b0;}
41 | #page-main #panel-phrase #phrase {border-bottom: 1px solid #bbb; padding: 10px;}
42 | #page-main #panel-phrase #phrase > * { -webkit-touch-callout:none; -webkit-user-select:none; -khtml-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none; -webkit-tap-highlight-color:rgba(0,0,0,0);}
43 | #page-main #panel-phrase #phrase > *[current] {box-shadow: 0 1px 0px #ccc;}
44 | #page-main #panel-recognition {position: absolute; left: 0; right: 0; top: 50%; bottom: 0;}
45 | #page-main #panel-recognition #recognition {color: #b00;}
46 | #page-main #panel-recognition #recognition[confidence]:not([confidence="0%"])::after {content: attr(confidence); position: relative; bottom: -10px; font-size: 10px;}
47 | #page-main #panel-recognition #recognition[correct="true"] {color: #0b0;}
48 | #page-main #panel-recognition[mode="recognition"] #compare {display: none;}
49 | #page-main #panel-recognition[mode="compare"] #recognition {display: none;}
50 |
51 | #page-main #panel-recognition #panel-record {position: absolute; bottom: 0; width: 100%;}
52 | #page-main #panel-recognition #panel-record #button-record {background: #f00; color: #fff;}
53 | #page-main #panel-recognition #panel-record #button-record[record="true"] {background: #f99;}
54 | #page-main #panel-recognition #panel-record #button-listen {background: #66f; color: #fff;}
55 | #page-main #panel-recognition #panel-record #button-listen[hidden] {display: none;}
56 |
57 | #page-text-selector .content > * {vertical-align: top; height: 100%; overflow-y: auto;}
58 | #page-text-selector #texts {display: inline-block; float: left; width: 350px; border-right: 1px solid #bbb;}
59 | #page-text-selector #texts > div {padding: 10px; border-bottom: 1px dashed #bbb; cursor: pointer; position: relative;}
60 | #page-text-selector #texts > div[current] {background: #eef;}
61 | #page-text-selector #texts #view {display: none; color: #bbb;}
62 | #page-text-selector #texts #remove {position: absolute; top: 5px; right: 15px; z-index: 2; color: #bbb;}
63 | #page-text-selector #text-wrapper { width: auto; display: block; height: auto; max-height: 100%; min-height: 100%;}
64 | #page-text-selector #text {white-space: pre; height: 100%; margin: 10px;}
65 | #page-text-selector #text audio {display: block; margin-bottom: 10px;}
66 | @media only screen and (max-width: 700px) {
67 | #page-text-selector #texts {width: 100% !important;}
68 | #page-text-selector #texts #view {display: inline-block !important; padding: 0 10px;}
69 | #page-text-selector #text-wrapper {display: none !important;}
70 | }
71 |
72 | #page-text-add #caption-block {margin-right: 80px;}
73 | #page-text-add #caption {width: 100%; border: none; text-align: left;}
74 | #page-text-add #text-block {position: absolute; top: 60px; left: 20px; right: 20px; bottom: 28px;}
75 | #page-text-add #text {width: 100%; height: 100%; resize: none; border: none; border-top: 1px solid #bbb; padding: 10px 0; font-family: inherit;}
76 | #page-text-add #button-load-file {position: absolute; top: 20px; right: 25px; cursor: pointer;}
77 |
78 | #page-text-view .content {white-space: pre; overflow: auto;}
79 | #page-text-view .content audio {display: block; margin-bottom: 10px;}
80 |
81 | #page-option .selector {display: block; margin-bottom: 20px;}
82 | #page-option .selector::before{content: attr(title); display: block; text-align: left; margin-bottom: 5px;}
83 | #page-option .selector > div {display: inline-block; width: 100px; cursor: pointer; text-align: center; line-height: 28px; font-size: 16px; background: #eee;}
84 | #page-option .selector > div[current]::before {content: '\2714'; margin-right: 5px;}
85 | #page-option .selector > div[value="yes"] {background: #afa;}
86 | #page-option .selector > div[value="no"] {background: #faa;}
87 | #page-option .selector.col1 > div {width: 310px;}
88 | #page-option .selector.col2 > div {width: 153px;}
89 | #page-option .selector.col3 > div {width: 100px;}
90 | #page-option .selector.col4 > div {width: 73px;}
91 | #page-option .selector.col5 > div {width: 57px;}
92 | #page-option .selector.col6 > div {width: 47px;}
93 |
94 |
95 | #page-help .content a {text-decoration: none;}
96 | #page-help .content p {margin-bottom: 20px;}
97 | #page-help .content ul {margin-left: 30px;}
98 |
99 | .button {padding: 10px 30px; text-align: center; cursor: pointer; border-radius: 5px; background: #0f0; display: inline-block; -webkit-touch-callout:none; -webkit-user-select:none; -khtml-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none; -webkit-tap-highlight-color:rgba(0,0,0,0);}
100 | .caption-button {cursor: pointer; float: right; margin: 0 10px;}
101 | .close {position: absolute !important; cursor: pointer; transform: rotate(45deg); z-index: 10; border-radius: 100%; top: 2px; right: 2px; margin: 2px 10px; display: inline-block; width: 40px; height: 40px;}
102 | .close::before, .close::after {content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #fff;}
103 | .close::before {width: 6px; margin: 6px auto;}
104 | .close::after {margin: auto 6px; height: 6px;}
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', function() {
2 | if (typeof speechSynthesis == 'undefined' || typeof webkitSpeechRecognition == 'undefined')
3 | return;
4 |
5 | var texts, homophones;
6 | var current_phrase_no, nextPhraseTimer;
7 | var $success = new Audio('correct.mp3');
8 |
9 | Promise.all(['texts.txt', 'homophones.txt'].map(f => fetch(f).then(res => res.text())))
10 | .then(function (res) {
11 | texts = parseTexts(res[0]);
12 | homophones = parseHomophones(res[1]);
13 |
14 | for (id in texts)
15 | addText(id, texts[id].name, texts[id].text);
16 |
17 | loadVoices(initOptions);
18 | if (speechSynthesis.onvoiceschanged !== undefined)
19 | speechSynthesis.onvoiceschanged = () => loadVoices(initOptions);
20 |
21 | $('#page-start #loading').remove();
22 | });
23 |
24 | $('#page-start #button-start').addEventListener('click', function () {
25 | setPage('main');
26 | var $e = $('#page-text-selector #texts div[id = "' + localStorage.getItem('speech-current-text') + '"]') || $('#texts').children[0];
27 | if ($e)
28 | $e.click();
29 | });
30 | $('#page-option #speech-success-ring [value="yes"]').addEventListener('click', () => $success.play());
31 | $('#page-main #button-help').addEventListener('click', () => setPage('help'));
32 | $('#page-main #button-option').addEventListener('click', () => setPage('option'));
33 | $('#page-main #button-text-selector').addEventListener('click', function () {
34 | setPage('text-selector');
35 | $('#page-text-selector #texts').scrollTop = $('#texts div[current]').offsetTop;
36 | });
37 | $('#page-main #phrase').addEventListener('click', (event) => speakText(event.target.textContent));
38 | $('.close', $e => $e.addEventListener('click', () => setPage($e.getAttribute('back'))));
39 |
40 | $('#page-text-selector #button-text-add').addEventListener('click', function () {
41 | $('#page-text-add #caption').value = '';
42 | $('#page-text-add #text').value = '';
43 | setPage('text-add');
44 | });
45 |
46 | $('#page-text-add #button-text-save').addEventListener('click', function () {
47 | setPage('text-selector');
48 |
49 | var caption = $('#page-text-add #caption').value.trim() || 'My text';
50 | var text = $('#page-text-add #text').value.trim();
51 | if (!text)
52 | return;
53 |
54 | var id = new Date().getTime();
55 | texts[id] = {id, name, text, phrases: text.split('\n').map(e => e.trim()).filter(e => !!e)};
56 | addText(id, caption, text);
57 | $('#page-text-selector #texts div[id = "'+ id + '"]').click();
58 | $('#page-text-selector #texts').scrollTop = $('#texts').scrollHeight;
59 |
60 | var user_texts = (localStorage.getItem('speech-user-texts') || '');
61 | var user_text = '\nid: ' + id + '\nname: ' + caption + '\n\n' + text;
62 | localStorage.setItem('speech-user-texts', user_texts + '\n===' + user_text);
63 | })
64 |
65 | function addText(id, name, text) {
66 | var $div = document.createElement('div');
67 | $div.setAttribute('id', id);
68 | $div.innerHTML = name;
69 | $div.addEventListener('click', function () {
70 | if ($div.hasAttribute('current'))
71 | return;
72 |
73 | for (var i = 0; i < $('#texts').children.length; i++)
74 | $('#texts').children[i].removeAttribute('current');
75 | this.setAttribute('current', true);
76 |
77 | $('#text').innerHTML = text;
78 | localStorage.setItem('speech-current-text', this.id);
79 | setPhrase($('.page[current]').id == 'page-text-selector' ? 0 : localStorage.getItem('speech-phrase'));
80 | });
81 |
82 | var $view = document.createElement('div');
83 | $view.setAttribute('id', 'view');
84 | $view.innerHTML = '➤';
85 | $view.addEventListener('click', function (event) {
86 | event.stopImmediatePropagation();
87 |
88 | if (!$div.hasAttribute('current'))
89 | $div.click();
90 |
91 | setPage('text-view');
92 | $('#page-text-view .caption').children[0].innerHTML = name;
93 | $('#page-text-view .content').innerHTML = text;
94 | })
95 | $div.appendChild($view);
96 |
97 | var $remove = document.createElement('div');
98 | $remove.setAttribute('id', 'remove');
99 | $remove.innerHTML = '✖';
100 | $remove.addEventListener('click', function (event) {
101 | event.stopImmediatePropagation();
102 |
103 | if (!confirm('Are you sure you want to remove "' + name +'"?'))
104 | return;
105 |
106 | if ($div.hasAttribute('current'))
107 | ($div.previousSibling || $div.nextSibling).click();
108 |
109 | var deleted = localStorage.getItem('speech-deleted-texts') || '';
110 | localStorage.setItem('speech-deleted-texts', deleted + ';' + id);
111 | $div.remove();
112 | })
113 | $div.appendChild($remove);
114 |
115 | $('#texts').appendChild($div);
116 | }
117 |
118 | function loadFile() {
119 | var file = $('#page-text-add #upload').files[0];
120 | if (file) {
121 | var reader = new FileReader();
122 | reader.onload = (event) => $('#page-text-add #text').value = event.target.result;
123 | reader.readAsText(file);
124 | }
125 | }
126 | $('#page-text-add #upload').addEventListener('change', loadFile, false);
127 | $('#page-text-add #button-load-file').addEventListener('click', () => $('#page-text-add #upload').click());
128 |
129 | $('#page-main #phrase-number').addEventListener('click', function () {
130 | $('#page-main #phrase-number-input').style.display = 'inline-block';
131 | $('#page-main #phrase-number-input').value = current_phrase_no + 1;
132 | $('#page-main #phrase-number-input').focus();
133 | });
134 |
135 | $('#page-main #phrase-number-input').addEventListener('blur', (event) => event.target.style.display = 'none');
136 | $('#page-main #phrase-number-input').addEventListener('keypress', function (event) {
137 | if (event.keyCode == 13 && !isNaN(this.value)) {
138 | this.blur();
139 | setPhrase(this.value - 1);
140 | }
141 | });
142 | $('#page-main #panel-counter #button-prev-phrase').addEventListener('click', () => setPhrase(current_phrase_no - 1));
143 | $('#page-main #panel-counter #button-next-phrase').addEventListener('click', () => setPhrase(current_phrase_no + 1));
144 |
145 | function setPhrase (no) {
146 | clearTimeout(nextPhraseTimer);
147 |
148 | no = parseInt(no) || 0;
149 | var id = $('#texts div[current]').id;
150 | var text = texts[id];
151 |
152 | if (no < 0)
153 | return setPhrase(0);
154 |
155 | if (no > text.phrases.length - 1)
156 | return setPhrase(text.phrases.length - 1);
157 |
158 | current_phrase_no = no;
159 | $('#page-main #panel-counter #button-prev-phrase').style.visibility = no == 0 ? 'hidden' : 'visible';
160 | $('#page-main #panel-counter #button-next-phrase').style.visibility = no == text.phrases.length - 1 ? 'hidden' : 'visible';
161 |
162 | $('#page-main #phrase-number').innerHTML = no + 1 + '/' + text.phrases.length;
163 | $('#page-main #panel-counter #caption').innerHTML = text.name;
164 | $('#page-main #phrase').innerHTML = text.phrases[no].split(' ').map((e) => e.indexOf('<') == -1 ? '