Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save WFreelandEcon/d795e797632b4f363279fed5f04f4ee8 to your computer and use it in GitHub Desktop.
Save WFreelandEcon/d795e797632b4f363279fed5f04f4ee8 to your computer and use it in GitHub Desktop.
<!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