├── correct.mp3 ├── favicon.ico ├── README.md ├── index.html ├── irverbs.json ├── index.css └── index.js /correct.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-brother/english-phrasal-verbs/HEAD/correct.mp3 -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-brother/english-phrasal-verbs/HEAD/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an [application](https://little-brother.github.io/english-phrasal-verbs/) to learn phrasal verbs.
2 | The app selects a random phrasal verb, display definition and expects you to construct it correctly from suggested parts. 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 | 9 | If you have any questions, comments or suggestions, just let me know lb.im@yandex.ru. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 |
21 |
22 |
Powered by
23 | 24 |
Loading...
25 |
Start
26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
Help
34 |
Option
35 |
36 | 37 |
38 | 39 | 40 | 43 | 44 | 45 | 51 | 52 | 53 | 61 | 62 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
Skip
58 |
Got it!
59 |
60 |
63 |
64 |
65 | 66 |
67 |
68 |
Current verbs
69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 |
Option
80 |
81 |
82 | 83 |
84 |
85 |
10
86 |
20
87 |
30
88 |
50
89 |
90 | 91 |
92 |
No
93 |
Yes
94 |
95 | 96 |
97 |
No
98 |
Yes
99 |
100 | 101 |
102 |
No
103 |
104 |
105 |
106 | 107 |
108 |
109 |
Help
110 |
111 |
112 | 113 |
114 |

115 | This is an application to learn phrasal verbs. The app selects a random phrasal verb, displays definition and expects you to pass checks.
116 | If you fail the check, you'll be returned to previous check next time. If all tests are completed, the verb will be removed from the list and a new verb will be suggested.
117 |

118 | 119 |

120 |

Check list for phrasal verb
121 | 132 |

133 | 134 |

135 | List length-option defines how much verbs you're learning at once.
136 | Check learned verbs-option controls random checks of learned verbs. 137 |

138 | 139 |

140 | If you have any questions, comments or suggestions, just let me know lb.im@yandex.ru or visit project page.
141 |

142 | 143 |

144 |

See also
145 | 150 |

151 |
152 |
153 | 154 | -------------------------------------------------------------------------------- /irverbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "arise": ["arose","arisen"], 3 | "awake": ["awoke","awoken"], 4 | "be": ["is", "are", "were", "was","been"], 5 | "bear": ["bore","borne"], 6 | "beat": ["beat","beaten"], 7 | "become": ["became","become"], 8 | "beget": ["begot","begotten"], 9 | "begin": ["began","begun"], 10 | "bend": ["bent","bent"], 11 | "beseech": ["besought","besought"], 12 | "bet": ["bet","bet"], 13 | "bid": ["bid","bid"], 14 | "bide": ["bode","bidden"], 15 | "bind": ["bound","bound"], 16 | "bite": ["bit","bitten"], 17 | "bleed": ["bled","bled"], 18 | "blow": ["blew","blown"], 19 | "break": ["broke","broken"], 20 | "breed": ["bred","bred"], 21 | "bring": ["brought","brought"], 22 | "build": ["built","built"], 23 | "burn": ["burnt","burnt"], 24 | "buy": ["bought","bought"], 25 | "can": ["could","(none)"], 26 | "cast": ["cast","cast"], 27 | "catch": ["caught","caught"], 28 | "choose": ["chose","chosen"], 29 | "cleave": ["clove","cloven"], 30 | "cling": ["clung","clung"], 31 | "clothe": ["clad","clad"], 32 | "come": ["came","come"], 33 | "cost": ["cost","cost"], 34 | "creep": ["crept","crept"], 35 | "crow": ["crew","crowed"], 36 | "cut": ["cut","cut"], 37 | "deal": ["dealt","dealt"], 38 | "dig": ["dug","dug"], 39 | "dive": ["dove","dived"], 40 | "do": ["did","done"], 41 | "draw": ["drew","drawn"], 42 | "dream": ["dreamt","dreamt"], 43 | "drink": ["drank","drunk"], 44 | "drive": ["drove","driven"], 45 | "eat": ["ate","eaten"], 46 | "fall": ["fell","fallen"], 47 | "feed": ["fed","fed"], 48 | "feel": ["felt","felt"], 49 | "fight": ["fought","fought"], 50 | "find": ["found","found"], 51 | "fit": ["fit","fit"], 52 | "flee": ["fled","fled"], 53 | "fling": ["flung","flung"], 54 | "fly": ["flew","flown"], 55 | "forbid": ["forbade","forbidden"], 56 | "forget": ["forgot","forgotten"], 57 | "forgive": ["forgave","forgiven"], 58 | "forsake": ["forsook","forsaken"], 59 | "freeze": ["froze","frozen"], 60 | "get": ["got","got"], 61 | "give": ["gave","given"], 62 | "go": ["went","gone"], 63 | "grow": ["grew","grown"], 64 | "hang": ["hung","hung"], 65 | "have": ["has","had"], 66 | "hear": ["heard","heard"], 67 | "heave": ["hove","hoven"], 68 | "hew": ["hew","hewn"], 69 | "hide": ["hid","hidden"], 70 | "hit": ["hit","hit"], 71 | "hold": ["held","held"], 72 | "hurt": ["hurt","hurt"], 73 | "keep": ["kept","kept"], 74 | "ken": ["kent","kent"], 75 | "kneel": ["knelt","knelt"], 76 | "knit": ["knit","knit"], 77 | "know": ["knew","known"], 78 | "lade": ["laded","laden"], 79 | "lead": ["led","led"], 80 | "lean": ["leant","leant"], 81 | "leap": ["leapt","leapt"], 82 | "learn": ["learnt","learnt"], 83 | "leave": ["left","left"], 84 | "lend": ["lent","lent"], 85 | "let": ["let","let"], 86 | "lie": ["lay","lain"], 87 | "light": ["lit","lit"], 88 | "lose": ["lost","lost"], 89 | "make": ["made","made"], 90 | "may": ["might","(none)"], 91 | "mean": ["meant","meant"], 92 | "meet": ["met","met"], 93 | "mow": ["mowed","mown"], 94 | "plead": ["pled","pled"], 95 | "proofread": ["proofread","proofread"], 96 | "prove": ["proved","proven"], 97 | "put": ["put","put"], 98 | "queath": ["quoth","quoth"], 99 | "read": ["read","read"], 100 | "reave": ["reft","reft"], 101 | "rend": ["rent","rent"], 102 | "rid": ["rid","rid"], 103 | "ride": ["rode","ridden"], 104 | "ring": ["rang","rung"], 105 | "rise": ["rose","risen"], 106 | "run": ["ran","run"], 107 | "see": ["saw","seen"], 108 | "seek": ["sought","sought"], 109 | "sell": ["sold","sold"], 110 | "send": ["sent","sent"], 111 | "set": ["set","set"], 112 | "sew": ["sewed","sewn"], 113 | "shake": ["shook","shaken"], 114 | "shall": ["should","(none)"], 115 | "shave": ["shove","shaven"], 116 | "shear": ["shore","shorn"], 117 | "shed": ["shed","shed"], 118 | "shine": ["shone","shone"], 119 | "shoot": ["shot","shot"], 120 | "show": ["showed","shown"], 121 | "shrink": ["shrank","shrunk"], 122 | "shrive": ["shrove","shriven"], 123 | "shut": ["shut","shut"], 124 | "sing": ["sang","sung"], 125 | "sink": ["sank","sunk"], 126 | "sit": ["sat","sat"], 127 | "slay": ["slew","slain"], 128 | "sleep": ["slept","slept"], 129 | "slide": ["slid","slid"], 130 | "sling": ["slang","slung"], 131 | "slink": ["slunk","slunk"], 132 | "slit": ["slit","slit"], 133 | "smite": ["smote","smitten"], 134 | "sneak": ["snuck","snuck"], 135 | "sow": ["sew","sown"], 136 | "speak": ["spoke","spoken"], 137 | "speed": ["sped","sped"], 138 | "spend": ["spent","spent"], 139 | "spill": ["spilt","spilt"], 140 | "spin": ["span","spun"], 141 | "spit": ["spat","spit"], 142 | "spoil": ["spoilt","spoilt"], 143 | "spread": ["spread","spread"], 144 | "spring": ["sprang","sprung"], 145 | "stand": ["stood","stood"], 146 | "steal": ["stole","stolen"], 147 | "stick": ["stuck","stuck"], 148 | "sting": ["stang","stung"], 149 | "stink": ["stank","stunk"], 150 | "strew": ["strew","strewn"], 151 | "stride": ["strode","stridden"], 152 | "strike": ["struck","stricken"], 153 | "string": ["strang","strung"], 154 | "strive": ["strove","striven"], 155 | "swear": ["swore","sworn"], 156 | "sweep": ["swept","swept"], 157 | "swell": ["swelled","swollen"], 158 | "swim": ["swam","swum"], 159 | "swing": ["swung","swung"], 160 | "take": ["took","taken"], 161 | "teach": ["taught","taught"], 162 | "tear": ["tore","torn"], 163 | "tell": ["told","told"], 164 | "think": ["thought","thought"], 165 | "thrive": ["throve","thriven"], 166 | "throw": ["threw","thrown"], 167 | "tread": ["trod","trod"], 168 | "understand": ["understood","understood"], 169 | "wake": ["woke","woken"], 170 | "wear": ["wore","worn"], 171 | "weave": ["wove","woven"], 172 | "wed": ["wed","wed"], 173 | "weep": ["wept","wept"], 174 | "wet": ["wet","wet"], 175 | "will": ["would","(none)"], 176 | "win": ["won","won"], 177 | "wind": ["wound","wound"], 178 | "wring": ["wrung","wrung"], 179 | "write": ["wrote","written"] 180 | } -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | * {margin: 0; padding: 0;} 2 | html, body { padding: 0; margin: 0; height: 100%;} 3 | 4 | .page {font-size: 24px;} 5 | .page:not([current]) {display: none !important;} 6 | .page.center {height: 100%; display: grid; } 7 | .page.center .content {margin: auto; position: relative;} 8 | .page.absolute {position: absolute; top: 0; bottom: 0; left: 0; right: 0;} 9 | .page.absolute .content {margin: auto; position: absolute; top: 48px; right: 0; left: 0; bottom: 0; overflow: auto;} 10 | 11 | .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;} 12 | .page .caption > * {display: inline-block;} 13 | .page .caption .left {float: left; cursor: pointer;} 14 | 15 | .page .content-padding {padding: 20px;} 16 | 17 | #examples > div::first-letter {text-transform: uppercase;} 18 | 19 | #page-start {text-align: center;} 20 | #page-start a {text-decoration: none; color: #000;} 21 | #page-start #logo {display: block; margin: 0; font-size: 20px;} 22 | #page-start #logo::before {content: ' '; padding: 10px; display: block; background-size: contain; background-position: center; background-repeat: no-repeat; background-origin: content-box; background-image: url(https://www.macmillandictionary.com/external/images/logoMacmillan.png); width: 240px; height: 80px; margin: 40px 0;} 23 | #page-start #loading {color: #bbb; padding: 10px 20px; margin-top: 10px; position: relative; left: 10px;} 24 | #page-start #loading ~ #button-start {display: none !important;} 25 | 26 | #page-option .selector {display: block; margin-bottom: 20px;} 27 | #page-option .selector::before{content: attr(title); display: block; text-align: left; margin-bottom: 5px;} 28 | #page-option .selector > div {display: inline-block; width: 100px; cursor: pointer; text-align: center; line-height: 28px; font-size: 16px;} 29 | #page-option .selector > div[current]::before {content: '\2714'; margin-right: 5px;} 30 | #page-option .selector > div[value="yes"] {background: #afa;} 31 | #page-option .selector > div[value="no"] {background: #faa;} 32 | #page-option .selector > div[value="3"] {background: #afa;} 33 | #page-option .selector > div[value="5"] {background: gold;} 34 | #page-option .selector > div[value="8"] {background: #faa;} 35 | #page-option .selector > div[value="10"] {background: #afa;} 36 | #page-option .selector > div[value="20"] {background: gold;} 37 | #page-option .selector > div[value="30"] {background: #faa;} 38 | #page-option .selector > div[value="50"] {background: #aaf;} 39 | #page-option .selector.col1 > div {width: 310px;} 40 | #page-option .selector.col2 > div {width: 153px;} 41 | #page-option .selector.col3 > div {width: 100px;} 42 | #page-option .selector.col4 > div {width: 73px;} 43 | #page-option .selector.col5 > div {width: 57px;} 44 | #page-option .selector.col6 > div {width: 47px;} 45 | 46 | #page-option #verb-voice.selector {display: none;} 47 | #page-option #verb-voice.selector > div[value = "no"] {background: #faa;} 48 | #page-option #verb-voice.selector > div {background: #eee;} 49 | 50 | #page-main {display: flex; flex-direction: column;} 51 | #page-main #button-verbs {display: inline-block; float: left; cursor: pointer; margin-left: 5px; position: relative; height: 28px; width: 30px; top: 1px;} 52 | #page-main #button-verbs::before {content: ""; position: absolute; left: 0; width: 30px; height: 5px; background: #fff; box-shadow: 0 10px 0 0 #fff, 0 20px 0 0 #fff;} 53 | #page-main #button-verbs[animation] {animation-duration: 1s; animation-name: animation-verbs; animation-iteration-count: 1;} 54 | @keyframes animation-verbs {from {background: gold;} to {background: white;}} 55 | 56 | #page-main #wrapper {text-align: center; height: 100%; width: 100%;} 57 | #page-main tr.top td {height: 100px; vertical-align: bottom;} 58 | #page-main tr.center td {height: 100%; padding: 0 10px;} 59 | #page-main tr.bottom td {padding-bottom: 10px;} 60 | #page-main #definition {display: inline-block; position: relative;} 61 | #page-main #definition span {display: inline-block; margin: 0 3px 0 7px; background: #ccc; color: #ccc;} 62 | #page-main #definition #verb {min-width: 70px;} 63 | #page-main #definition #prep {min-width: 40px;} 64 | #page-main #definition span[correct = "true"] {color: #090; background: #fff;} 65 | #page-main #definition span[correct = "false"] {color: #f00; background: #fff;} 66 | #page-main #synonyms {font-size: 16px; padding: 0 0 10px 0;} 67 | #page-main #synonyms:empty {display: none;} 68 | #page-main #synonyms::before {content: '~'; margin-right: 5px;} 69 | #page-main #synonyms::after {content: '~'; margin-left: 5px;} 70 | #page-main #suggestions div {margin: 10px 0; border: 1px dashed #bbb; font-size: 20px; padding: 10px; border-radius: 5px; background: #fafafa; cursor: pointer;} 71 | #page-main #suggestions div:not([correct]):hover {background: #eef;} 72 | #page-main #suggestions div[correct = "true"] {background: #afa;} 73 | #page-main #suggestions div[correct = "false"] {background: #faa;} 74 | #page-main #examples {margin: 10px; font-style: italic; font-size: 20px;} 75 | #page-main #examples > div {margin-bottom: 10px;} 76 | #page-main #examples span[verb] {color: blue;} 77 | #page-main #examples span[prep] {color: blue;} 78 | 79 | #page-main #answer {margin-top: 10px; text-transform: uppercase; font-weight: bold;} 80 | #page-main #answer > span:not([correct]) {visibility: hidden;} 81 | #page-main #answer > span[correct = "true"] {color: #090;} 82 | #page-main #answer > span[correct = "false"] {color: #f00;} 83 | 84 | #page-main .list > div {display: inline-block; padding: 5px 15px; margin: 3px; background: #eee; border-radius: 5px; cursor: pointer;} 85 | #page-main .list > div[current] {background: #aaf !important; color: #fff;} 86 | #page-main #verbs > div {background: #ccc;} 87 | #page-main #preps > div {background: #eee; padding: 5px 10px;} 88 | #page-main #preps {font-size: 20px;} 89 | #page-main #button-block {display: none;} 90 | #page-main #button-block .button {width: 70px;} 91 | #page-main #button-block #button-verb-skip {margin-right: 5px; background: #aaf; color: #fff;} 92 | #page-main[stage="0"] #button-block {display: inline-block;} 93 | #page-main[stage="7"] #preps {display: none;} 94 | #page-main[stage="7"] #definition::first-letter {text-transform: uppercase;} 95 | #page-main[stage="9"] #verbs {display: none;} 96 | #page-main[stage="9"] #definition::first-letter {text-transform: uppercase;} 97 | 98 | #page-verbs #verbs:empty::after {content: 'The list is empty'; padding: 20px; display: inline-block;} 99 | #page-verbs #verbs > div {padding: 10px 20px; border-bottom: 1px dashed #bbb; position: relative;} 100 | #page-verbs #verbs > div #verb-prep {font-weight: bold; display: inline-block; margin-right: 10px; cursor: pointer; border-bottom: 2px dotted #000;} 101 | #page-verbs #verbs > div #synonyms {display: inline;font-size: 16px;} 102 | #page-verbs #verbs > div #synonyms:empty {display: none;} 103 | #page-verbs #verbs > div #synonyms::before {content: '(';} 104 | #page-verbs #verbs > div #synonyms::after {content: ')';} 105 | #page-verbs #verbs > div #examples > div {display: block; font-size: 16px; font-style: italic;} 106 | #page-verbs #verbs > div #examples #verb {color: blue;} 107 | #page-verbs #verbs > div #examples #prep {color: blue;} 108 | #page-verbs #verbs > div #remove {position: absolute; right: 10px; top: 7px; z-index: 1; color: #bbb; cursor: pointer;} 109 | 110 | #page-help .content p {margin-bottom: 20px;} 111 | #page-help .content a {text-decoration: none;} 112 | #page-help .content ul {margin-left: 30px;} 113 | 114 | .button {padding: 10px 30px; text-align: center; cursor: pointer; border-radius: 5px; background: #0f0; display: inline-block;} 115 | .caption-button {cursor: pointer; float: right; margin: 0 10px;} 116 | .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;} 117 | .close::before, .close::after {content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #fff;} 118 | .close::before {width: 6px; margin: 6px auto;} 119 | .close::after {margin: auto 6px; height: 6px;} -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function() { 2 | localStorage.setItem('verb-version', 2); 3 | 4 | var VERBS = []; 5 | var IRVERBS = []; 6 | 7 | var verbs = { 8 | current: loadVerbList('current'), 9 | learned: loadVerbList('learned'), 10 | session: [] 11 | } 12 | 13 | var $success = new Audio('correct.mp3'); 14 | 15 | Promise.all(['verbs.json', 'irverbs.json'].map(f => fetch(f).then(res => res.json()))) 16 | .then(function (res) { 17 | VERBS = res[0].filter(e => e.examples.length > 0 && e.synonyms.length > 0); 18 | console.log('Load: ' + VERBS.length + ' verbs'); 19 | 20 | var current_verbs = []; 21 | var learned_verbs = []; 22 | VERBS.forEach(function (v) { 23 | v.id = parseInt(v.id); 24 | v.verb_prep = v.verb + ' ' + v.prep; 25 | v.synonyms = v.synonyms.join(', '); 26 | v.ref = ('verb ' + v.verb + ' ' + v.prep).replace(/ /g, '-'); 27 | 28 | if (verbs.current.indexOf(v.id) != -1) 29 | current_verbs.push(v.id); 30 | if (verbs.learned.indexOf(v.id) != -1) 31 | learned_verbs.push(v.id); 32 | }); 33 | 34 | verbs.current = current_verbs; 35 | verbs.learned = learned_verbs; 36 | 37 | IRVERBS = res[1]; 38 | 39 | $('#page-start #loading').remove(); 40 | initOptions(); 41 | }); 42 | 43 | var $answer = $('#page-main #answer'); 44 | var $definition = $('#page-main #definition'); 45 | var $synonyms = $('#page-main #synonyms'); 46 | var $suggestions = $('#page-main #suggestions'); 47 | var $examples = $('#page-main #examples'); 48 | 49 | var $verbs = $('#page-main #verbs'); 50 | var $preps = $('#page-main #preps'); 51 | 52 | $('#page-start #button-start').addEventListener('click', () => setPage('main') || setVerb()); 53 | $('#page-main #button-help').addEventListener('click', () => setPage('help')); 54 | $('#page-main #button-option').addEventListener('click', () => setPage('option')); 55 | $('#page-option #verb-sound-enable [value="yes"]').addEventListener('click', () => $success.play()); 56 | $('.close', $e => $e.addEventListener('click', () => setPage($e.getAttribute('back')))); 57 | 58 | $('#page-main #button-verbs').addEventListener('click', function () { 59 | var $verbs = $('#page-verbs #verbs'); 60 | $verbs.innerHTML = ''; 61 | 62 | verbs.current.forEach(function (id) { 63 | var verb = VERBS.find(v => v.id == id); 64 | if (!verb) 65 | return; 66 | 67 | var $e = document.createElement('div'); 68 | $e.innerHTML = '
{verb-prep}
{synonyms}
{definition}
{examples}
' 69 | .replace('{verb-prep}', verb.verb_prep) 70 | .replace('{definition}', verb.definition) 71 | .replace('{synonyms}', verb.synonyms) 72 | .replace('{examples}', verb.examples.map(e => '
' + parseExample(verb, e) + '
').join('')); 73 | 74 | $e.querySelector('#verb-prep').onclick = () => window.open('https://www.macmillandictionary.com/dictionary/british/' + verb.verb_prep.replace(/ /g, '-')); 75 | $e.querySelector('#remove').onclick = () => removeVerb('current', id) || $e.remove(); 76 | 77 | $verbs.appendChild($e); 78 | }) 79 | setPage('verbs'); 80 | }); 81 | 82 | $('#page-main #button-verb-skip').addEventListener('click', setVerb); 83 | $('#page-main #button-verb-add').addEventListener('click', function () { 84 | var id = $answer.getAttribute('verb-id'); 85 | var verb = VERBS.find(v => v.id == id); 86 | localStorage.setItem(verb.ref, 1); 87 | addVerb('current', verb.id); 88 | 89 | setVerb(); 90 | }); 91 | 92 | $answer.addEventListener('click', function () { 93 | if ($answer.children.length == 0) 94 | speakText($answer.textContent); 95 | }); 96 | 97 | function loadVerbList (type) { 98 | return (localStorage.getItem('verb-' + type + '-list') || '') 99 | .split(',').map(e => parseInt(e)).filter(e => !isNaN(e)) 100 | .filter((e, i, arr) => arr.indexOf(e) == i) 101 | } 102 | 103 | function addVerb (type, id) { 104 | id = parseInt(id); 105 | if (verbs[type].indexOf(id) != -1) 106 | return; 107 | 108 | verbs[type].push(id); 109 | localStorage.setItem('verb-' + type + '-list', verbs[type].join(',')); 110 | } 111 | 112 | function removeVerb (type, id) { 113 | verbs[type] = verbs[type].filter(v => v != id); 114 | localStorage.setItem('verb-' + type + '-list', verbs[type].join(',')); 115 | } 116 | 117 | function initOptions() { 118 | $('#page-option .content > div', function ($opt) { 119 | var opt = $opt.id; 120 | var $e = $('#' + opt); 121 | for(var i = 0; i < $e.children.length; i++) 122 | $e.children[i].addEventListener('click', (event) => setOption(opt, event.target.getAttribute('value'))); 123 | setOption(opt, localStorage.getItem(opt)); 124 | }); 125 | } 126 | 127 | function setOption(opt, value) { 128 | var $e = $('#' + opt); 129 | var def = $e.getAttribute('default'); 130 | localStorage.setItem(opt, value || def); 131 | for(var i = 0; i < $e.children.length; i++) 132 | $e.children[i].removeAttribute('current'); 133 | 134 | var $curr = $e.querySelector('[value="' + value + '"]') || $e.querySelector('[value="' + def + '"]') 135 | $curr.setAttribute('current', true); 136 | } 137 | 138 | function getOption(opt) { 139 | var $e = $('#' + opt + ' [current]'); 140 | return $e ? $e.getAttribute('value') : $('#' + opt).getAttribute('default'); 141 | } 142 | 143 | function setVerb(verb) { 144 | var verb, verb_no, exclude_verbs, stage; 145 | var list_length = getOption('verb-list-length') || 10; 146 | 147 | if (verbs.current.length < list_length) { 148 | exclude_verbs = VERBS.filter(v => verbs.current.indexOf(v.id) != -1).map(v => v.verb_prep); 149 | do { 150 | verb_no = Math.floor(Math.random() * VERBS.length); 151 | verb = VERBS[verb_no]; 152 | } while (verbs.session.indexOf(verb.id) != -1 || verbs.current.indexOf(verb.id) != -1 || exclude_verbs.indexOf(verb.verb_prep) != -1) 153 | localStorage.removeItem(verb && verb.ref); 154 | stage = 0; 155 | } else if (getOption('verb-check-learned') == 'yes' && verbs.current.length < list_length * 1.5 && Math.random() > 0.9 && verbs.learned.length) { 156 | verb_no = Math.floor(Math.random() * verbs.learned.length); 157 | verb = VERBS.find(v => v.id = verb_no); 158 | stage = 7 + Math.floor(Math.random() * 3); 159 | } else { 160 | exclude_verbs = verbs.session.slice(-Math.floor(list_length * 0.7)); 161 | var possible_verbs = verbs.current.filter(id => exclude_verbs.indexOf(id) == -1); 162 | verb_no = Math.floor(Math.random() * possible_verbs.length); 163 | verb = VERBS.find(v => v.id == possible_verbs[verb_no]); 164 | stage = Math.max(parseInt(localStorage.getItem(verb.ref)) || 0, 0); 165 | } 166 | 167 | if (!verb) 168 | return alert('Smth wrong!'); 169 | 170 | $('#page-main').setAttribute('stage', stage); 171 | 172 | if (stage > 9) 173 | return removeVerb(verb.id, 'current') || setVerb(); 174 | 175 | verbs.session.push(verb.id); 176 | 177 | /* 178 | Stages: 179 | 0 - Show verb card 180 | 1 - Combine answer from 3 verbs and 3 preps 181 | 2 - Select one of 3 definitions by verb 182 | 3 - Combine answer from 5 verbs and 5 preps 183 | 4 - Select one of 3 synonyms by verb 184 | 5 - Select one of 5 definitions by verb 185 | 6 - Combine answer from 8 verbs and 8 preps 186 | 7 - Reconstruct verb in example 187 | 8 - Select one of 5 suggestions by synonyms 188 | 9 - Reconstruct prep in example 189 | */ 190 | 191 | function is_stage () { 192 | return Array.prototype.some.call(arguments, e => e == stage); 193 | } 194 | 195 | function getRandomList(prop, length) { 196 | if (!length) 197 | return []; 198 | 199 | var addon = { 200 | 'verb': ['go', 'get', 'put', 'turn', 'push', 'take', 'call'], 201 | 'prep': ['off', 'out', 'on', 'up', 'down', 'in'], 202 | 'definition': [], 203 | 'synonyms': [] 204 | } 205 | 206 | return verbs.current.map(id => VERBS.find(v => v.id == id)[prop]).concat(addon[prop]) 207 | .filter((e, i, arr) => arr.indexOf(e) === i && e != verb[prop]).shuffle().slice(0, length - 1) 208 | .concat(verb[prop]).shuffle().map(v => '
' + v + '
').join(''); 209 | } 210 | 211 | $answer.setAttribute('verb-id', verb.id); 212 | $answer.setAttribute('verb', verb.verb); 213 | $answer.setAttribute('prep', verb.prep); 214 | $answer.innerHTML = is_stage (0, 2, 4, 5, 8) ? verb.verb_prep : '' + verb.verb + ' ' + verb.prep + ''; 215 | $definition.removeAttribute('checked'); 216 | $definition.innerHTML = is_stage(0, 1, 3, 4, 6) ? verb.definition : ''; 217 | if (is_stage(7)) 218 | $definition.innerHTML = parseExample(verb, verb.examples[0], 'verb'); 219 | if (is_stage(9)) 220 | $definition.innerHTML = parseExample(verb, verb.examples[1] || verb.examples[0], 'prep'); 221 | 222 | $synonyms.innerHTML = is_stage(0, 1, 3, 6, 7) ? verb.synonyms : ''; 223 | $suggestions.innerHTML = is_stage(2, 5) ? getRandomList('definition', stage == 2 ? 3 : 5) : is_stage(4, 8) ? getRandomList('synonyms', stage == 4 ? 3 : 5) : ''; 224 | $suggestions.querySelectorAll('div').forEach($e => $e.addEventListener('click', onSuggestionClick)); 225 | $examples.innerHTML = is_stage(0) ? verb.examples.map(e => '
' + parseExample(verb, e) + '
').join('') : ''; 226 | 227 | var len = [0, 3, 0, 5, 0, 0, 8, 8, 0, 8][stage] || 0; 228 | $verbs.innerHTML = getRandomList('verb', len); 229 | $preps.innerHTML = getRandomList('prep', len); 230 | $('.list div', $e => $e.addEventListener('click', onVerbPrepClick)); 231 | 232 | if (is_stage(0, 2, 4, 5, 8)) 233 | speakText(verb.verb_prep); 234 | 235 | function onSuggestionClick () { 236 | if (this.hasAttribute('correct')) 237 | return; 238 | 239 | var is_correct = is_stage(2, 5) ? this.textContent.trim() == verb.definition.trim() : this.textContent.trim() == verb.synonyms; 240 | this.setAttribute('correct', is_correct); 241 | next(is_correct); 242 | } 243 | 244 | function onVerbPrepClick () { 245 | if (!!$answer.querySelector('div[correct]')) 246 | return; 247 | 248 | var $parent = this.parentNode; 249 | for(var i = 0; i < $parent.children.length; i++) 250 | $parent.children[i].removeAttribute('current'); 251 | 252 | this.setAttribute('current', true); 253 | 254 | if (is_stage(1, 3, 6) && document.querySelectorAll('.list div[current]').length == 2) { 255 | var is_verb = $verbs.querySelector('div[current]').textContent == verb.verb; 256 | var is_prep = $preps.querySelector('div[current]').textContent == verb.prep; 257 | var is_correct = is_verb && is_prep; 258 | 259 | $answer.children[0].setAttribute('correct', is_verb); 260 | $answer.children[1].setAttribute('correct', is_prep); 261 | 262 | next(is_correct); 263 | } 264 | 265 | if (is_stage(7, 9)) { 266 | $definition.setAttribute('checked', true); 267 | var prop = is_stage(7) ? 'verb' : 'prep'; 268 | var $e = $definition.querySelector('#' + prop); 269 | var is_correct = $e && ($e.getAttribute(prop) == this.textContent.trim()); 270 | $e.setAttribute('correct', is_correct); 271 | 272 | next(is_correct); 273 | }; 274 | } 275 | 276 | function next(is_correct) { 277 | localStorage.setItem(verb.ref, Math.max(stage + (is_correct ? 1 : -1), 1)); 278 | 279 | if (!is_correct) { 280 | addVerb('current', verb.id); 281 | removeVerb('learned', verb.id); 282 | } 283 | 284 | if (is_correct && stage == 9) { 285 | removeVerb('current', verb.id); 286 | addVerb('learned', verb.id); 287 | } 288 | 289 | if (is_correct && getOption('verb-sound-enable') == 'yes') 290 | $success.play(); 291 | 292 | setTimeout(() => document.dispatchEvent(new CustomEvent('speak-text', {detail: verb.verb_prep})), 500); 293 | setTimeout(setVerb, 2000); 294 | } 295 | } 296 | 297 | function parseExample(_verb, text, prop) { 298 | var verb = _verb.verb; 299 | var variants = [ 300 | verb + 'ing', verb + verb.slice(-1) + 'ing', verb.slice(0, verb.length - 1) + 'ing', verb.slice(0, verb.length - 2) + 'ying', 301 | verb + 's', verb + 'es', verb.slice(0, verb.length - 1) + 'ies', 302 | verb + 'ed', verb.slice(0, verb.length - 1) + 'ed', verb.slice(0, verb.length - 1) + 'id', verb.slice(0, verb.length - 1) + 'ied', verb + verb.slice(-1) + 'ed' 303 | ]; 304 | if (IRVERBS[verb]) 305 | variants.push.apply(variants, IRVERBS[verb]); 306 | variants.push(verb); 307 | 308 | var res = text; 309 | 310 | for(var i = 0; i < variants.length && res == text; i++) 311 | res = text.replace(new RegExp('\\b' + variants[i] + '\\b', 'i'), '' + variants[i] + ''); 312 | 313 | if (prop == 'verb') 314 | return res; 315 | 316 | var prep = _verb.prep; 317 | var verb_pos = res.length - res.indexOf('') - 7; 318 | res = prop == 'prep' ? text : res; 319 | 320 | return res.substr(0, res.length - verb_pos) + res.slice(-verb_pos).replace(new RegExp('\\b' + prep + '\\b', 'i'), '' + prep + ''); 321 | } 322 | 323 | function speakText(text) { 324 | var event = new CustomEvent('speak-text', {detail: text}); 325 | document.dispatchEvent(event); 326 | } 327 | 328 | if (typeof speechSynthesis !== 'undefined') { 329 | var voices = []; 330 | 331 | document.addEventListener('speak-text', function (event) { 332 | var text = event.detail; 333 | 334 | var current_voice = getOption('verb-voice'); 335 | if (current_voice == 'no' || !voices[current_voice]) 336 | return; 337 | var utterance = new SpeechSynthesisUtterance(text); 338 | utterance.voice = voices[current_voice]; 339 | utterance.rate = 1; 340 | speechSynthesis.speak(utterance); 341 | }); 342 | 343 | function loadVoices () { 344 | var $voices = $('#verb-voice'); 345 | voices = speechSynthesis.getVoices(); 346 | 347 | if ($voices.children.length > 1 || voices.length == 0) 348 | return; 349 | 350 | $voices.style.display = 'block'; 351 | voices.forEach(function(e, i) { 352 | if (e.lang.indexOf('en') == 0 && (e.lang.indexOf('US') != -1 || e.lang.indexOf('UK') != -1 || e.lang.indexOf('GB') != -1)) { 353 | $voices.innerHTML += '
{html}
' 354 | .replace('{value}', i) 355 | .replace('{title}', e.name) 356 | .replace('{html}', $voices.children.length); 357 | } 358 | }); 359 | 360 | if ($voices.children.length == 2) 361 | $voices.children[1].innerHTML = 'Yes'; 362 | 363 | var width = parseInt(315 / $voices.children.length) - 5; 364 | for(var i = 0; i < $voices.children.length; i++) { 365 | $voices.children[i].style.width = width + 'px'; 366 | $voices.children[i].addEventListener('click', function (event) { 367 | setOption('verb-voice', this.getAttribute('value')); 368 | speakText('Hi there!'); 369 | }); 370 | } 371 | setOption('verb-voice', localStorage.getItem('verb-voice')); 372 | } 373 | 374 | loadVoices(); 375 | if (speechSynthesis.onvoiceschanged !== undefined) 376 | speechSynthesis.onvoiceschanged = loadVoices; 377 | } 378 | 379 | function setPage(page) { 380 | $('.page', $e => $e.removeAttribute('current')); 381 | $('#page-' + page).setAttribute('current', true); 382 | } 383 | 384 | history.pushState({}, '', window.location.pathname); 385 | window.addEventListener('popstate', function(event) { 386 | var page = $('.page[current]'); 387 | if (page.id == 'page-start') 388 | return history.back(); 389 | 390 | history.pushState(null, null, window.location.pathname); 391 | if (page.id == 'page-main') 392 | return setPage('start'); 393 | 394 | page.querySelector('.close').click(); 395 | }, false); 396 | 397 | function $ (selector, apply) { 398 | return apply ? Array.prototype.slice.call(document.querySelectorAll(selector) || []).forEach(apply) : document.querySelector(selector); 399 | } 400 | 401 | Array.prototype.shuffle = function () { 402 | var array = this.slice(); 403 | var count = array.length, randomnumber, temp; 404 | while (count) { 405 | randomnumber = Math.random() * count-- | 0; 406 | temp = array[count]; 407 | array[count] = array[randomnumber]; 408 | array[randomnumber] = temp; 409 | } 410 | return array; 411 | } 412 | }); --------------------------------------------------------------------------------