Forked from takahashihideki-git/textwell-action-wordCards.html
Created
January 31, 2018 21:04
-
-
Save WFreelandEcon/d795e797632b4f363279fed5f04f4ee8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"> | |
<title>Word Cards</title> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"> | |
<style> | |
html { | |
height: 100%; | |
} | |
body { | |
height: 100%; | |
margin: 0; | |
background-color: #000; | |
font-size: 200%; | |
color: #eee; | |
-webkit-text-size-adjust: 100%; | |
} | |
section { | |
padding: 1em; | |
} | |
h2 { | |
margin-top: 40px; | |
margin-bottom: 20px; | |
line-height: 1.2; | |
} | |
.number { | |
display: block; | |
font-size: 200%; | |
font-weight: bold; | |
color: #fff; | |
} | |
h3 { | |
color: #fff; | |
} | |
h3 .glyphicon { | |
margin-right: 0.5em; | |
font-size: 80%; | |
color: #666; | |
font-weight: bold; | |
} | |
.sentence { | |
margin: 0; | |
} | |
.sentence b { | |
color: #00A8E8; | |
} | |
.sentence span { | |
cursor: pointer; | |
} | |
.note { | |
visibility: hidden; | |
padding: 10px 0; | |
color: #ccc; | |
font-size: 80%; | |
} | |
input { | |
margin: 0.5em 0; | |
padding-left: 0.2em; | |
color: #fff; | |
background-color: #00A8E8; | |
border: 1px solid #00A8E8; | |
} | |
input:disabled { | |
color: #fff; | |
opacity: 1; | |
} | |
input.correct { | |
background-color: transparent; | |
border: 1px solid #666; | |
} | |
.message { | |
margin: 0; | |
color: #999; | |
} | |
.shadow { | |
display: inline-block; | |
width: 1px; | |
height: 1px; | |
overflow: hidden; | |
} | |
button { | |
position: fixed; | |
display: block; | |
width: 4em; | |
margin: 1em auto; | |
padding: 0.2em 1em; | |
background-color: #000; | |
border-radius: 1em; | |
} | |
.button-next { | |
right: 1em; | |
bottom: 0.5em; | |
border: 1px solid #00A8E8; | |
color: #00A8E8; | |
} | |
.button-exit { | |
left: 1em; | |
bottom: 0.5em; | |
border: 1px solid #ccc; | |
color: #ccc; | |
} | |
.indicator { | |
position: rerative; | |
margin: 0; | |
padding: 0; | |
height: 16px; | |
background: repeating-linear-gradient( -45deg, #222, #222 5px, #333 5px, #333 10px ); | |
} | |
.indicator-bar { | |
position: absolute; | |
top: 0; | |
left: 0; | |
box-sizing: border-box; | |
width: 0; | |
height: 16px; | |
margin: 0; | |
padding: 0; | |
} | |
.indicator-bar-clear { | |
background: repeating-linear-gradient( -45deg, #00A8E8, #00ABEB 5px, #08BEF4 5px, #08BEF4 10px ); | |
} | |
.indicator-number { | |
position: absolute; | |
right: 0.5em; | |
height: 16px; | |
line-height: 16px; | |
padding: 0 0.5em; | |
text-align: right; | |
font-size: 50%; | |
background-color: rgba( 0,0,0,0.2 ); | |
border-radius: 1em; | |
z-index: 1; | |
} | |
.speaker-container { | |
position: fixed; | |
width: 100%; | |
bottom: 0; | |
} | |
.speaker { | |
width: 3em; | |
height: 3em; | |
line-height: 3em; | |
margin: 1em auto; | |
border: 1px solid #00A8E8; | |
border-radius: 2em; | |
text-align: center; | |
color: #00A8E8; | |
background-color: #000; | |
cursor: pointer; | |
} | |
.speaker.active { | |
border: 1px solid #fff; | |
color: #fff; | |
} | |
.stacks { | |
display: table; | |
box-sizing: border-box; | |
width: 100%; | |
margin: 0.5em auto; | |
padding: 0; | |
list-style-type: none; | |
border: 1px solid #00A8E8; | |
border-radius: 5px; | |
font-size: 80%; | |
} | |
.stacks li { | |
position: relative; | |
display: table-cell; | |
width: 20%; | |
text-align: center; | |
border-right: 1px solid #00A8E8; | |
color:#00A8E8; | |
cursor: pointer; | |
} | |
.stacks li:last-child { | |
border: 0; | |
} | |
.stacks li.active { | |
background-color: #00A8E8; | |
color: #fff; | |
} | |
.stacks li .counter { | |
display: block; | |
position: absolute; | |
right: -5px; | |
top: -5px; | |
padding: 0.2em 0.4em; | |
font-size: 14px; | |
font-weight: bold; | |
background-color: #f00; | |
border-radius: 20px; | |
color: #fff; | |
line-height: 1; | |
z-index: 1; | |
} | |
.stackedWords { | |
padding: 0; | |
} | |
.stackedWord { | |
padding: 0.5em; | |
} | |
.landscape { | |
padding: 0 1em; | |
} | |
/* spinner */ | |
.spinner { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba( 0, 0, 0, 0.8 ); | |
} | |
.spinner-message { | |
position: absolute; | |
top: 150px; | |
left: 0; | |
width: 100%; | |
color: #fff; | |
text-align: center; | |
} | |
.loader:before, | |
.loader:after, | |
.loader { | |
border-radius: 50%; | |
width: 2.5em; | |
height: 2.5em; | |
-webkit-animation-fill-mode: both; | |
animation-fill-mode: both; | |
-webkit-animation: load7 1.8s infinite ease-in-out; | |
animation: load7 1.8s infinite ease-in-out; | |
} | |
.loader { | |
font-size: 10px; | |
margin: 80px auto; | |
position: relative; | |
text-indent: -9999em; | |
-webkit-transform: translateZ(0); | |
-ms-transform: translateZ(0); | |
transform: translateZ(0); | |
-webkit-animation-delay: -0.16s; | |
animation-delay: -0.16s; | |
} | |
.loader:before { | |
left: -3.5em; | |
-webkit-animation-delay: -0.32s; | |
animation-delay: -0.32s; | |
} | |
.loader:after { | |
left: 3.5em; | |
} | |
.loader:before, | |
.loader:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
} | |
@-webkit-keyframes load7 { | |
0%, | |
80%, | |
100% { | |
box-shadow: 0 2.5em 0 -1.3em #ffffff; | |
} | |
40% { | |
box-shadow: 0 2.5em 0 0 #ffffff; | |
} | |
} | |
@keyframes load7 { | |
0%, | |
80%, | |
100% { | |
box-shadow: 0 2.5em 0 -1.3em #ffffff; | |
} | |
40% { | |
box-shadow: 0 2.5em 0 0 #ffffff; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="portrait"> | |
<div class="indicator"> | |
<div class="indicator-number indicator-number-total"></div> | |
<div class="indicator-bar indicator-bar-clear"><div class="indicator-number indicator-number-clear"></div></div> | |
</div> | |
<section class="card"></section> | |
<div class="speaker-container"><div class="speaker"><span class="glyphicon glyphicon-headphones"></span></div></div> | |
<button class="button button-next app">next</button> | |
<button class="button button-exit app">exit</button> | |
</div> | |
<div class="landscape" style="display:none"> | |
<ul class="stacks"> | |
<li class="definitelyKnow">Definitely Know <span class="counter">0</span></li> | |
<li class="know">Know <span class="counter">0</span></li> | |
<li class="learned">Learned <span class="counter">0</span></li> | |
<li class="unknown">Unknown <span class="counter">0</span></li> | |
</ul> | |
<section class="stackedWords"></section> | |
</div> | |
<script class="template template-card" type="text/x-template"> | |
<p class="sentence" contentEditable="true">{sentence}</p> | |
<p class="note">{memo}</p> | |
</script> | |
<script class="template template-word-input" type="text/x-template"> | |
<input type="text" data-word="{word}"><span class="shadow">{word}</span> | |
</script> | |
<script class="template template-stackedWord" type="text/x-template"> | |
<span class="stackedWord" style="font-size:{size}px" contentEditable="true">{word}</span> | |
</script> | |
<textarea style="display:none"> | |
Hello **World**! | |
こんにちは、世界。 | |
</textarea> | |
<div class="spinner" style="display:none"> | |
<div class="loader">Loading...</div> | |
<div class="spinner-message"></div> | |
</div> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.js"></script> | |
<script> | |
/** | |
Sugar | |
*/ | |
document.querySelectorAsArray = function ( selector, context ) { | |
var root = document; | |
if ( context ) { | |
root = context; | |
} | |
return Array.prototype.slice.call( root.querySelectorAll( selector ) ); | |
} | |
window.camelize = function ( str ) { | |
return str ? str.replace( / ([a-z])/g, function ( match, placeholder ) { return placeholder.toUpperCase() } ) : str; | |
} | |
/** | |
Pointing Events | |
*/ | |
const TOUCHABLE = "ontouchstart" in document; | |
const POINTING = { | |
down: TOUCHABLE ? "touchstart" : "mousedown", | |
move: TOUCHABLE ? "touchmove" : "mousemove", | |
up: TOUCHABLE ? "touchend" : "mouseup", | |
}; | |
/** | |
TagLevelComputer Class | |
*/ | |
var TagLevelComputer = function ( min, max, level ) { | |
this.min = Math.sqrt( min ); | |
this.max = Math.sqrt( max ); | |
this.level = level; | |
this.factor = ( this.level - 1 ) / ( this.max - this.min ); | |
} | |
TagLevelComputer.prototype = { | |
min: null, | |
max: null, | |
level: null, | |
factor: null, | |
getLevel: function ( value ) { | |
return Math.round( ( Math.sqrt( value ) - this.min ) * this.factor ); | |
} | |
} | |
/** | |
Pile Class | |
*/ | |
var Pile = function () { | |
this.cards = new Array(); | |
this.stackedCardIndex = new Array(); | |
var self = this; | |
this.nextButton.addEventListener( "click", function () { | |
self.next(); | |
} ); | |
this.exitButton.addEventListener( "click", function () { | |
self.exit(); | |
} ); | |
this.speaker = new Speaker( { | |
oninitialized: function () { | |
self.speakerButton.addEventListener( "click", function () { | |
self.speaker.speak( self.currentCard.getSentence() ); | |
self.speakerButton.setAttribute( "class", "speaker active" ); | |
} ); | |
self.speakerButton.style.display = "block"; | |
}, | |
onend: function () { | |
self.speakerButton.setAttribute( "class", "speaker" ); | |
} | |
} ); | |
this.indicator = new Indicator( { | |
cards: this.cards, | |
speaker: this.speaker | |
} ); | |
} | |
Pile.prototype = { | |
cards: null, | |
stackedCardIndex: null, | |
currentCard: null, | |
indicator: null, | |
nextButton: document.querySelector( ".button-next" ), | |
exitButton: document.querySelector( ".button-exit" ), | |
speakerButton: document.querySelector( ".speaker" ), | |
speaker: null, | |
dictionary: document.querySelector( ".dictionary" ), | |
append: function ( card ) { | |
this.cards.push( card ); | |
if ( card.data.keyValues.status != "definitely know" ) { | |
this.stackedCardIndex.push( this.cards.length - 1 ); | |
} | |
}, | |
next: function () { | |
if ( ! this.stackedCardIndex.length ) { | |
this.refresh(); | |
if ( ! this.stackedCardIndex.length ) { | |
alert( "You know all words." ); | |
return; | |
} | |
} | |
var randomIndex = Math.floor( Math.random() * this.stackedCardIndex.length ) | |
var card = this.cards[ this.stackedCardIndex[ randomIndex ] ]; | |
this.stackedCardIndex.splice( randomIndex, 1 ); | |
if ( card ) { | |
this.currentCard = card; | |
this.currentCard.show(); | |
} | |
this.indicator.show(); | |
}, | |
refresh: function () { | |
var self = this; | |
this.stackedCardIndex = new Array(); | |
this.cards.forEach( function ( card, index ) { | |
if ( card.data.keyValues.status != "definitely know" ) { | |
self.stackedCardIndex.push( index ); | |
} | |
} ); | |
}, | |
exit: function () { | |
window.spinner.start(); | |
window.spinner.message( "Update Data ..." ); | |
var data = new Array(); | |
this.cards.forEach( function ( card ) { | |
data.push( card.data ); | |
} ); | |
window.spinner.message( "Convert data to markdown ..." ); | |
setTimeout( function () { | |
if ( window.webkit ) { | |
T( 'replaceWhole', { | |
text: window.objectsToMarkdown( data ) | |
} ); | |
} | |
else { | |
console.log( window.objectsToMarkdown( data ) ); | |
} | |
window.spinner.end(); | |
}, 500 ); | |
}, | |
update: function () { | |
this.indicator.show(); | |
}, | |
portrait: function () { | |
document.querySelector( ".portrait" ).style.display = "block"; | |
document.querySelector( ".landscape" ).style.display = "none"; | |
}, | |
landscape: function () { | |
document.querySelector( ".portrait" ).style.display = "none"; | |
document.querySelector( ".landscape" ).style.display = "block"; | |
} | |
} | |
/** | |
Card Class | |
*/ | |
var Card = function ( args ) { | |
this.data = args.data; | |
this.listener = args.listener; | |
this.words = new Array(); | |
var tempDom = document.createElement( "div" ); | |
tempDom.innerHTML = this.data.lines[ 0 ].split( /\s+/ ).map( function ( item ) { | |
if ( ! ( /<strong/ ).test( item ) ) { | |
var matches = item.match( /^(.*?)([\.\,\?\!]+)$/ ) | |
if ( matches ) { | |
item = "<span>" + matches[ 1 ] + "</span>" + matches[ 2 ]; | |
} | |
else { | |
item = "<span>" + item + "</span>"; | |
} | |
} | |
return item; | |
} ).join( " " ); | |
var self = this; | |
document.querySelectorAsArray( "strong", tempDom ).forEach( function ( element ) { | |
var word = element.innerHTML; | |
self.words.push( word ); | |
element.innerHTML = self.template.word.replace( /{word}/g, word ); | |
} ); | |
this.html = this.template.card.replace( /{sentence}/, tempDom.innerHTML ) | |
.replace( /{memo}/, this.data.lines.slice( 1 ).join( "<br><br>" ) ); | |
this.sentence = tempDom.innerText; | |
} | |
Card.prototype = { | |
data: null, | |
html: "", | |
sentence: "", | |
words: null, | |
listener: null, | |
container: { | |
card: document.querySelector( ".card" ) | |
}, | |
template: { | |
card: document.querySelector( ".template-card" ).innerHTML, | |
word: document.querySelector( ".template-word-input" ).innerHTML | |
}, | |
show: function () { | |
window.getSelection().removeAllRanges(); | |
this.container.card.innerHTML = this.html; | |
var self = this; | |
document.querySelectorAsArray( "span", this.container.card ).forEach( function ( span ) { | |
span.addEventListener( POINTING.up, function ( e ) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
self.listener.speaker.speak( span.innerText ); | |
var range = document.createRange(); | |
range.selectNodeContents( span ); | |
var select = window.getSelection(); | |
select.removeAllRanges(); | |
select.addRange( range ); | |
} ); | |
} ); | |
new Dictator( { | |
inputs: document.querySelectorAsArray( "input", this.container.card ), | |
listener: this | |
} ); | |
}, | |
getSentence: function () { | |
return this.sentence; | |
}, | |
update: function ( status ) { | |
if ( status ) { | |
if ( ! this.data.keyValues.status ) { | |
this.data.keyValues.status = "learned"; | |
} | |
else if ( this.data.keyValues.status == "unknown" ) { | |
this.data.keyValues.status = "learned"; | |
} | |
else if ( this.data.keyValues.status == "learned" ) { | |
this.data.keyValues.status = "know"; | |
} | |
else if ( this.data.keyValues.status == "know" ) { | |
this.data.keyValues.status = "definitely know"; | |
} | |
} | |
else { | |
this.data.keyValues.status = "unknown" | |
} | |
if ( ! this.data.keyValues.times ) { | |
this.data.keyValues.times = 1; | |
} | |
else { | |
this.data.keyValues.times++; | |
} | |
this.data.keyValues.update = String( new Date() ); | |
this.container.card.querySelector( ".note" ).style.visibility = "visible"; | |
this.listener.update(); | |
} | |
} | |
/** | |
Indicator Class | |
*/ | |
var Indicator = function ( args ) { | |
this.cards = args.cards; | |
this.speaker = args.speaker; | |
var self = this; | |
document.querySelectorAsArray( "li", this.container.stacks ).forEach( function ( tab ) { | |
tab.addEventListener( POINTING.up, function () { | |
if ( ( /active/ ).test( tab.getAttribute( "class" ) ) ) { | |
self.showWords( "all" ) | |
} | |
else { | |
self.showWords( tab.getAttribute( "class" ) ); | |
} | |
} ); | |
} ); | |
} | |
Indicator.prototype = { | |
container: { | |
indicator: document.querySelector( ".indicator" ), | |
stacks: document.querySelector( ".stacks" ), | |
stackedWords: document.querySelector( ".stackedWords" ) | |
}, | |
template: { | |
stackedWord: document.querySelector( ".template-stackedWord" ) | |
}, | |
cards: null, | |
speaker: null, | |
show: function () { | |
var counter = { | |
definitelyKnow: 0, | |
know: 0, | |
learned: 0, | |
unknown: 0, | |
answered: 0 | |
}; | |
this.cards.forEach( function ( card ) { | |
if ( card.data.keyValues.status ) { | |
counter[ window.camelize( card.data.keyValues.status ) ]++; | |
counter.answered++; | |
} | |
} ); | |
this.container.indicator.querySelector( ".indicator-number-total" ).innerHTML = this.cards.length; | |
this.container.indicator.querySelector( ".indicator-number-clear" ).innerHTML = counter.definitelyKnow; | |
this.container.indicator.querySelector( ".indicator-bar-clear" ).style.width = Math.floor( counter.definitelyKnow / this.cards.length * 100 ) + "%"; | |
this.container.stacks.querySelector( ".definitelyKnow .counter" ).innerHTML = counter.definitelyKnow; | |
this.container.stacks.querySelector( ".know .counter" ).innerHTML = counter.know; | |
this.container.stacks.querySelector( ".learned .counter" ).innerHTML = counter.learned; | |
this.container.stacks.querySelector( ".unknown .counter" ).innerHTML = counter.unknown; | |
this.showWords( "all" ); | |
}, | |
showWords: function ( status ) { | |
document.querySelectorAsArray( "li", this.container.stacks ).forEach( function ( tab ) { | |
tab.setAttribute( "class", tab.getAttribute( "class" ).replace( / active/, "" ) ); | |
} ); | |
if ( status != "all" ) { | |
currentTab = this.container.stacks.querySelector( "." + status ); | |
currentTab.setAttribute( "class", currentTab.getAttribute( "class" ) + " active" ); | |
} | |
var cards = new Array(); | |
var min = null; | |
var max = null; | |
// select cards where status | |
this.cards.forEach( function ( card ) { | |
if ( status == window.camelize( card.data.keyValues.status ) || status == "all" ) { | |
if ( card.data.keyValues.times ) { | |
var times = Number( card.data.keyValues.times ); | |
if ( min === null || times < min ) { | |
min = times; | |
} | |
if ( max === null || times > max ) { | |
max = times; | |
} | |
cards.push( card ); | |
} | |
} | |
} ); | |
var tagLevelComputer = new TagLevelComputer( | |
min, | |
max, | |
25 | |
); | |
var output = ""; | |
var template = ""; | |
var self = this; | |
cards.forEach( function ( card ) { | |
card.words.forEach( function ( word ) { | |
template = self.template.stackedWord.innerHTML; | |
template = template.replace( /{size}/, Number( 24 + tagLevelComputer.getLevel( Number( card.data.keyValues.times ) ) ) ); | |
template = template.replace( /{word}/, word ); | |
output += template; | |
} ); | |
} ); | |
this.container.stackedWords.innerHTML = output; | |
var self = this; | |
document.querySelectorAsArray( "span", this.container.stackedWords ).forEach( function ( span ) { | |
var touch = false; | |
span.addEventListener( POINTING.down, function ( e ) { | |
touch = true; | |
} ); | |
span.addEventListener( POINTING.move, function ( e ) { | |
touch = false; | |
} ); | |
span.addEventListener( POINTING.up, function ( e ) { | |
if ( touch ) { | |
touch = false; | |
} | |
else { | |
return; | |
} | |
e.preventDefault(); | |
e.stopPropagation(); | |
self.speaker.speak( span.innerText ); | |
var range = document.createRange(); | |
range.selectNodeContents( span ); | |
var select = window.getSelection(); | |
select.removeAllRanges(); | |
select.addRange( range ); | |
} ); | |
} ); | |
} | |
} | |
/** | |
Dictator Class | |
*/ | |
var Dictator = function ( args ) { | |
this.inputs = args.inputs; | |
this.listener = args.listener; | |
var self = this; | |
this.inputs.forEach( function ( input ) { | |
input.addEventListener( "keyup", function () { | |
self.check( input ); | |
} ); | |
var waitDoublePointing = false; | |
var doSinglePointing; | |
input.addEventListener( POINTING.up, function ( e ) { | |
if ( waitDoublePointing ) { | |
clearTimeout( doSinglePointing ); | |
waitDoublePointing = false; | |
if ( ! ( /correct/ ).test( input.getAttribute( "class" ) ) ) { | |
// give up | |
input.value = input.getAttribute( "data-word" ); | |
input.setAttribute( "class", "correct" ); | |
self.answered = true; | |
self.listener.update( false ); | |
} | |
} | |
else { | |
doSinglePointing = setTimeout( function () { | |
if ( ( /correct/ ).test( input.getAttribute( "class" ) ) ) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
self.speak( input ); | |
} | |
clearTimeout( doSinglePointing ); | |
waitDoublePointing = false; | |
}, 500 ); | |
waitDoublePointing = true; | |
} | |
} ); | |
} ); | |
this.answered = false; | |
this.answeredInputs = new Array(); | |
this.corrects = 0; | |
} | |
Dictator.prototype = { | |
inputs: null, | |
listener: null, | |
answered: false, | |
answeredInputs: null, | |
corrects: 0, | |
check: function ( input ) { | |
if ( input.value == input.getAttribute( "data-word" ) ) { | |
input.setAttribute( "class", "correct" ); | |
if ( ! this.answered && this.corrects < this.inputs.length ) { | |
var isAnswered = false; | |
this.answeredInputs.forEach( function ( answeredInput ) { | |
if ( input == answeredInput ) { | |
isAnswered = true; | |
} | |
} ); | |
if ( ! isAnswered ) { | |
this.corrects++; | |
this.answeredInputs.push( input ); | |
} | |
if ( this.corrects == this.inputs.length ) { | |
this.answered = true; | |
this.listener.update( true ); | |
} | |
} | |
} | |
else { | |
input.setAttribute( "class", "" ) | |
} | |
}, | |
speak: function ( input ) { | |
this.listener.listener.speaker.speak( input.value ); | |
input.setSelectionRange( 0, 9999 ); | |
} | |
} | |
/** | |
Speaker Class | |
*/ | |
var Speaker = function ( args ) { | |
this.oninitialized = args.oninitialized; | |
this.onend = args.onend; | |
this.initialize(); | |
} | |
Speaker.prototype = { | |
voice: null, | |
voices: null, | |
_iOSvoices: [ | |
{ name: "Maged", voiceURI: "com.apple.ttsbundle.Maged-compact", lang: "ar-SA", localService: true, "default": true }, | |
{ name: "Zuzana", voiceURI: "com.apple.ttsbundle.Zuzana-compact", lang: "cs-CZ", localService: true, "default": true }, | |
{ name: "Sara", voiceURI: "com.apple.ttsbundle.Sara-compact", lang: "da-DK", localService: true, "default": true }, | |
{ name: "Anna", voiceURI: "com.apple.ttsbundle.Anna-compact", lang: "de-DE", localService: true, "default": true }, | |
{ name: "Melina", voiceURI: "com.apple.ttsbundle.Melina-compact", lang: "el-GR", localService: true, "default": true }, | |
{ name: "Karen", voiceURI: "com.apple.ttsbundle.Karen-compact", lang: "en-AU", localService: true, "default": true }, | |
{ name: "Daniel", voiceURI: "com.apple.ttsbundle.Daniel-compact", lang: "en-GB", localService: true, "default": true }, | |
{ name: "Moira", voiceURI: "com.apple.ttsbundle.Moira-compact", lang: "en-IE", localService: true, "default": true }, | |
{ name: "Samantha (Enhanced)", voiceURI: "com.apple.ttsbundle.Samantha-premium", lang: "en-US", localService: true, "default": true }, | |
{ name: "Samantha", voiceURI: "com.apple.ttsbundle.Samantha-compact", lang: "en-US", localService: true, "default": true }, | |
{ name: "Tessa", voiceURI: "com.apple.ttsbundle.Tessa-compact", lang: "en-ZA", localService: true, "default": true }, | |
{ name: "Monica", voiceURI: "com.apple.ttsbundle.Monica-compact", lang: "es-ES", localService: true, "default": true }, | |
{ name: "Paulina", voiceURI: "com.apple.ttsbundle.Paulina-compact", lang: "es-MX", localService: true, "default": true }, | |
{ name: "Satu", voiceURI: "com.apple.ttsbundle.Satu-compact", lang: "fi-FI", localService: true, "default": true }, | |
{ name: "Amelie", voiceURI: "com.apple.ttsbundle.Amelie-compact", lang: "fr-CA", localService: true, "default": true }, | |
{ name: "Thomas", voiceURI: "com.apple.ttsbundle.Thomas-compact", lang: "fr-FR", localService: true, "default": true }, | |
{ name: "Carmit", voiceURI: "com.apple.ttsbundle.Carmit-compact", lang: "he-IL", localService: true, "default": true }, | |
{ name: "Lekha", voiceURI: "com.apple.ttsbundle.Lekha-compact", lang: "hi-IN", localService: true, "default": true }, | |
{ name: "Mariska", voiceURI: "com.apple.ttsbundle.Mariska-compact", lang: "hu-HU", localService: true, "default": true }, | |
{ name: "Damayanti", voiceURI: "com.apple.ttsbundle.Damayanti-compact", lang: "id-ID", localService: true, "default": true }, | |
{ name: "Alice", voiceURI: "com.apple.ttsbundle.Alice-compact", lang: "it-IT", localService: true, "default": true }, | |
{ name: "Kyoko", voiceURI: "com.apple.ttsbundle.Kyoko-compact", lang: "ja-JP", localService: true, "default": true }, | |
{ name: "Yuna", voiceURI: "com.apple.ttsbundle.Yuna-compact", lang: "ko-KR", localService: true, "default": true }, | |
{ name: "Ellen", voiceURI: "com.apple.ttsbundle.Ellen-compact", lang: "nl-BE", localService: true, "default": true }, | |
{ name: "Xander", voiceURI: "com.apple.ttsbundle.Xander-compact", lang: "nl-NL", localService: true, "default": true }, | |
{ name: "Nora", voiceURI: "com.apple.ttsbundle.Nora-compact", lang: "no-NO", localService: true, "default": true }, | |
{ name: "Zosia", voiceURI: "com.apple.ttsbundle.Zosia-compact", lang: "pl-PL", localService: true, "default": true }, | |
{ name: "Luciana", voiceURI: "com.apple.ttsbundle.Luciana-compact", lang: "pt-BR", localService: true, "default": true }, | |
{ name: "Joana", voiceURI: "com.apple.ttsbundle.Joana-compact", lang: "pt-PT", localService: true, "default": true }, | |
{ name: "Ioana", voiceURI: "com.apple.ttsbundle.Ioana-compact", lang: "ro-RO", localService: true, "default": true }, | |
{ name: "Milena", voiceURI: "com.apple.ttsbundle.Milena-compact", lang: "ru-RU", localService: true, "default": true }, | |
{ name: "Laura", voiceURI: "com.apple.ttsbundle.Laura-compact", lang: "sk-SK", localService: true, "default": true }, | |
{ name: "Alva", voiceURI: "com.apple.ttsbundle.Alva-compact", lang: "sv-SE", localService: true, "default": true }, | |
{ name: "Kanya", voiceURI: "com.apple.ttsbundle.Kanya-compact", lang: "th-TH", localService: true, "default": true }, | |
{ name: "Yelda", voiceURI: "com.apple.ttsbundle.Yelda-compact", lang: "tr-TR", localService: true, "default": true }, | |
{ name: "Ting-Ting", voiceURI: "com.apple.ttsbundle.Ting-Ting-compact", lang: "zh-CN", localService: true, "default": true }, | |
{ name: "Sin-Ji", voiceURI: "com.apple.ttsbundle.Sin-Ji-compact", lang: "zh-HK", localService: true, "default": true }, | |
{ name: "Mei-Jia", voiceURI: "com.apple.ttsbundle.Mei-Jia-compact", lang: "zh-TW", localService: true, "default": true } | |
], | |
oninitialized: null, | |
onspoken: null, | |
initialize: function () { | |
var voices = this.voices; | |
var speaker = this; | |
var setVoice = function () { | |
speaker.voices = window.speechSynthesis.getVoices(); | |
if ( ( /Mac/ ).test( navigator.platform ) || ( /iPhone/ ).test( navigator.platform ) || ( /iPad/ ).test( navigator.platform ) || ( /iPod/ ).test( navigator.platform ) ) { | |
speaker.voices = speaker._iOSvoices; | |
} | |
if ( speaker.voices && speaker.voices.length ) { | |
for ( var i = 0; i < speaker.voices.length; i++ ) { | |
if ( speaker.voices[ i ].lang == window.SETTINGS.lang ) { | |
speaker.voice = speaker.voices[ i ]; | |
break; | |
} | |
} | |
} | |
} | |
var voiceFetcher = setInterval( function () { | |
setVoice(); | |
if ( speaker.voices && speaker.voices.length ) { | |
clearInterval( voiceFetcher ); | |
if ( speaker.oninitialized ) { | |
speaker.oninitialized(); | |
} | |
} | |
}, 100 ); | |
}, | |
speak: function ( str ) { | |
var utterance = new SpeechSynthesisUtterance( str ); | |
utterance.lang = window.SETTINGS.lang; | |
utterance.rate = window.SETTINGS.rate; | |
utterance.pitch = window.SETTINGS.pitch; | |
if ( this.voice ) { | |
utterance.voice = this.voice; | |
} | |
if ( this.onend ) { | |
var speaker = this; | |
utterance.onend = function () { | |
speaker.onend(); | |
} | |
} | |
window.speechSynthesis.speak( utterance ); | |
} | |
} | |
/** | |
Markdown Parser and Serializer | |
*/ | |
window.markdownToObjects = function ( source ) { | |
// based on marked.js | |
// | |
// get markdown source as below ... | |
// | |
// line1-1 | |
// line1-2 | |
// ... | |
// line1-N | |
// | |
// key1-1: value1-2 | |
// key1-2: value1-2 | |
// ... | |
// key1-N: value1-N | |
// | |
// ---- | |
// line2-1 | |
// line2-2 | |
// ... | |
// | |
// and return object as below ... | |
// object.lines ( Array ) | |
// object.keyValues ( Object ) | |
var parser = new DOMParser(); | |
dom = parser.parseFromString( | |
source.split( /^----/m ).map( | |
function ( str ) { | |
return "<div>" + marked( str ) + "</div>" | |
} | |
).join( "" ), | |
"text/html" | |
); | |
var objs = new Array(); | |
var divs = Array.prototype.slice.call( dom.querySelectorAll( "div" ) ); | |
divs.forEach( function ( div ) { | |
var obj = new Object; | |
obj.lines = new Array(); | |
obj.keyValues = new Object(); | |
var ps = Array.prototype.slice.call( div.querySelectorAll( "p" ) ); | |
if ( ps.length ) { | |
ps.forEach( function ( p ) { | |
obj.lines.push( p.innerHTML ); | |
} ); | |
} | |
var code = div.querySelector( "code" ); | |
if ( code ) { | |
var codelines = div.querySelector( "code" ).innerHTML.split( /\n+/ ); | |
codelines.forEach( function ( line ) { | |
if ( ( /:/ ).test( line ) ) { | |
var keyValues = line.split( /:/ ); | |
obj.keyValues[ keyValues.shift().trim() ] = keyValues.join( ":" ).trim(); | |
} | |
} ); | |
} | |
objs.push( obj ); | |
} ); | |
return objs; | |
} | |
window.objectsToMarkdown = function ( objects ) { | |
// reverse of markdownToObjects | |
var mds = new Array(); | |
objects.forEach( function ( obj ) { | |
var lines = new Array(); | |
obj.lines.forEach( function ( line ) { | |
lines.push( line.replace( /<\/*strong>/g, "**" ) + "\n" ); | |
} ); | |
var keys = Object.keys( obj.keyValues ); | |
keys.forEach( function ( key ) { | |
lines.push( " " + key + ": " + obj.keyValues[ key ] ); | |
} ) | |
if( keys.length ) { | |
lines[ lines.length - 1 ] += "\n"; | |
} | |
mds.push( lines.join( "\n" ) ); | |
} ); | |
return mds.join( "\n----\n\n" ); | |
} | |
window.spinner = { | |
start: function () { | |
document.querySelector( ".spinner" ).style.display = "block"; | |
}, | |
end: function () { | |
document.querySelector( ".spinner" ).style.display = "none"; | |
}, | |
message: function ( str ) { | |
document.querySelector( ".spinner-message" ).innerHTML = str; | |
} | |
} | |
/** | |
Main | |
*/ | |
var pile; | |
var SETTINGS = { | |
lang: "en-US", | |
rate: "1.0", | |
pitch: "1.0" | |
}; | |
var initialize = function () { try { | |
pile = new Pile(); | |
var source; | |
if ( window.webkit ) { | |
source = T.whole; | |
window.SETTINGS = T.data.settings; | |
} | |
else { | |
source = document.querySelector( "textarea" ).value; | |
} | |
var objs = window.markdownToObjects( source ); | |
objs.forEach( function ( obj ) { | |
pile.append( new Card( { | |
data: obj, | |
listener: pile | |
} ) ) | |
} ); | |
window.onorientationchange = function() { | |
if( window.orientation == 0 ) { | |
pile.portrait(); | |
} | |
else { | |
pile.landscape(); | |
} | |
} | |
pile.update(); | |
pile.next(); | |
} catch ( e ) { console.log( e ); alert( e ); } } | |
// boot up | |
window.addEventListener( "load", function () { | |
if ( window.webkit ) { | |
// on Textwell | |
window.webkit.messageHandlers.initT.postMessage( "initialize" ); | |
} | |
else { | |
window.initialize(); | |
} | |
} ); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment