Skip to content

Instantly share code, notes, and snippets.

@sheodox
Last active May 25, 2017 02:55
Show Gist options
  • Save sheodox/2fba30197f47795b2bf5009c3634d5d9 to your computer and use it in GitHub Desktop.
Save sheodox/2fba30197f47795b2bf5009c3634d5d9 to your computer and use it in GitHub Desktop.
Memrise Add Wizard
// ==UserScript==
// @name Memrise Add Wizard
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Wizard for adding words to a course
// @author sheodox
// @match https://www.memrise.com/course/*/edit/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
const $styles = $(`
<style>
.control-panel {
position: fixed;
z-index: 10000000;
padding: 3rem;
background: #434c59;
color: white;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.control-panel label {
display: inline-block;
margin: 0 2px;
}
.control-panel input {
background-color: #23282f;
border: none;
}
.control-panel .columns .one-half {
width: 400px;
}
.control-panel .columns > :first-child {
float: left;
}
.control-panel .columns > :last-child {
margin-left: 2rem;
float: right;
}
.control-panel textarea#working-definition {
resize: both;
width: 400px;
height: 150px;
}
.control-panel #goo-definition > ol {
list-style: none;
}
.control-panel .supplemental_info {
display: block;
font-size: 75%;
}
</style>`).appendTo('head');
function createDialog() {
return $('<div class="control-panel"></div>').appendTo('body');
}
let $row,
wordFields;
function showWizard() {
wordFields = {};
getContext()
.then(getWord)
.then(lookupWord)
.then(commit);
}
//grab the necessary piece of the document before parsing with jQuery to avoid CSRF issues
function parseAndSelect(html, selector) {
let tmp = document.implementation.createHTMLDocument();
tmp.body.innerHTML = html;
return $($.parseHTML(tmp.querySelector(selector).outerHTML));
}
function getContext() {
return new Promise(res => {
const $dialog = createDialog();
$dialog.append(`
<label for="context-sentence">Context Sentence</label>
<input id="context-sentence">
`);
$dialog.find('#context-sentence')
.focus()
.on('paste', function(e) {
wordFields.context = e.originalEvent.clipboardData.getData('text');
$dialog.remove();
res();
});
});
}
function searchGoo(encoded) {
return new Promise((resolve, reject) => {
get(`https://dictionary.goo.ne.jp/srch/all/${encoded}/m1u/`)
.then(result => {
const $goo = parseAndSelect(result, '#NR-wrapper a');
get(`https://dictionary.goo.ne.jp${$goo.attr('href')}`)
.then(resolve, reject);
})
});
}
function getWord() {
return new Promise(res => {
const $dialog = createDialog();
$dialog.append(`
<p>Please select the word you want to study.</p>
<p id="context-sentence"></p>
`);
$dialog.find('#context-sentence')
.text(wordFields.context)
.on('mouseup', function() {
wordFields.common = window.getSelection().toString();
res();
$dialog.remove();
});
});
}
function get(url) {
return new Promise((res, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(response) {
res(response.responseText);
}
});
});
}
function lookupWord() {
return new Promise(res => {
const $dialog = createDialog(),
encoded = encodeURIComponent(wordFields.common);
get(`http://jisho.org/search/${encoded}`)
.then(html => {
const $jishoInfo = parseAndSelect(html, '.concept_light');
//try to parse accurate kana
const $furigana = $jishoInfo.find('.furigana').find('span'), //each character is represented by a span, empty span means it's not above a kanji
common = $jishoInfo.find('.concept_light-representation .text').text().trim();
wordFields.kana = [].reduce.call(common, function(done, next, index) {
const correspondingFurigana = $furigana.eq(index).text().trim();
return done + (correspondingFurigana ? correspondingFurigana : next);
}, '');
wordFields.common = common;
$jishoInfo.find('.sentences').remove();
$dialog.find('#jisho-definition').html($jishoInfo.find('.meanings-wrapper').html());
searchGoo(encodeURIComponent(common))
.then(html => {
const $gooInfo = parseAndSelect(html, '.explanation');
$dialog.find('#goo-definition').html($gooInfo.html());
});
});
$dialog.append(`
<label for="working-definition">Definition:</label>
<textarea id="working-definition"></textarea>
<br>
<button id="submit-definition">Definition Complete!</button>
<br>
<div class="columns" id="definition-selection">
<div class="column one-half">
<p>Jisho</p>
<div id="jisho-definition"></div>
</div>
<div class="column one-half goo">
<p>Goo</p>
<div id="goo-definition"></div>
</div>
</div>
`);
const $definition = $('#working-definition');
$dialog.find('#definition-selection')
.on('mouseup', function() {
const selection = window.getSelection().toString(),
def = $definition.val().trim() + ' ' + selection.trim();
$definition.val(def);
wordFields.definition = def;
});
$dialog.find('#submit-definition').on('click', function() {
res();
$dialog.remove();
});
});
}
function commit() {
function setInput(columnNumber, val) {
$row.find(`td[data-key="${columnNumber}"] input`).val(val);
}
setInput('1', wordFields.common);
setInput('2', wordFields.definition);
setInput('3', wordFields.kana);
setInput('4', wordFields.context);
$row.find('input:first').focus();
}
//listen for Alt + W on an input to show the wizard
$('body')
.on('keydown', 'tbody.adding td input', function(e) {
if (e.which === 87 && e.altKey) {
$row = $(this).parents('tr');
showWizard();
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment