Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
d7b18e8f86 | |||
6a5b1589ef | |||
8d265c400c | |||
3b62968dd7 | |||
eeebc91723 | |||
9660dfda3f | |||
7d7a6ea24d | |||
776ed212f2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
dist/
|
||||||
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
deploy: clean build
|
||||||
|
aws s3 sync dist s3://flashcards.jdbernard.com
|
||||||
|
rm -r dist
|
||||||
|
|
||||||
|
serve-local:
|
||||||
|
(cd dist && python -m SimpleHTTPServer &)
|
||||||
|
./makewatch build
|
||||||
|
|
||||||
|
build: flashcards.*
|
||||||
|
-mkdir dist
|
||||||
|
cp flashcards.* dist
|
||||||
|
git describe --always --dirty --tags | xargs --replace=INSERTED -- sed -i -e 's/%VERSION%/INSERTED/' dist/*
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -r dist
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Simple Flashcards takes a set of text values, one value per line, and shows
|
||||||
|
them to the user one at a time. It has the following features:
|
||||||
|
|
||||||
|
* Support for prompts only or prompts and answers.
|
||||||
|
* Image URLs can be used as values for pictoral flashcards.
|
||||||
|
* Configurable timing between cards and answers.
|
||||||
|
* Configurable size of the text (small, medium, and large).
|
||||||
|
* In-order, reverse-order, and random-order traversal of the cards.
|
||||||
|
* Support for saving/loading settings and inputs (locally to the browser).
|
||||||
|
* Support for importing/exporting saved card sets as a file.
|
||||||
|
* Support for importing saved card sets from a URL.
|
||||||
|
|
||||||
|
Simple Flashcards is available at http://flashcards.jdbernard.com
|
@ -57,10 +57,12 @@ button {
|
|||||||
|
|
||||||
#settings .visibility-indicator {
|
#settings .visibility-indicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 80%;
|
||||||
transition: all linear 0.2s;
|
transition: all linear 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings #adv-settings {
|
#settings #adv-settings {
|
||||||
|
font-size: 80%;
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: max-height linear 0.3s;
|
transition: max-height linear 0.3s;
|
||||||
@ -68,7 +70,7 @@ button {
|
|||||||
|
|
||||||
#settings.adv-settings-visible #adv-settings {
|
#settings.adv-settings-visible #adv-settings {
|
||||||
max-height: 40%;
|
max-height: 40%;
|
||||||
overflow-y: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings.adv-settings-visible .visibility-indicator {
|
#settings.adv-settings-visible .visibility-indicator {
|
||||||
@ -95,6 +97,13 @@ button {
|
|||||||
min-width: 12em;
|
min-width: 12em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#adv-settings label input[type="radio"] {
|
||||||
|
min-width: unset;
|
||||||
|
margin: 0 0.25em 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#adv-settings label input[type="radio"]:first-of-type { margin-left: 0; }
|
||||||
|
|
||||||
#adv-settings button {
|
#adv-settings button {
|
||||||
margin: 0 0.5em;
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
@ -109,12 +118,58 @@ input[name=importFileName] {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#card-content { margin: auto; }
|
#card-input {
|
||||||
#card-content.small-text{ font-size: 100%; }
|
flex-grow: 2;
|
||||||
#card-content.medium-text{ font-size: 200%; }
|
display: flex;
|
||||||
#card-content.large-text{ font-size: 800%; }
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card-input label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.current { display: flex; }
|
||||||
|
|
||||||
|
.card div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card img {
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer { opacity: 0; }
|
||||||
|
.card.show-answer .answer { opacity: 1; }
|
||||||
|
|
||||||
|
.card.small img { width: 50%; }
|
||||||
|
.card.medium img { width: 75%; }
|
||||||
|
.card.large img { width: 100%; }
|
||||||
|
|
||||||
|
.card.small .prompt { font-size: 100%; }
|
||||||
|
.card.medium .prompt { font-size: 200%; }
|
||||||
|
.card.large .prompt { font-size: 800%; }
|
||||||
|
|
||||||
|
.card.small .answer { font-size: 75%; }
|
||||||
|
.card.medium .answer { font-size: 150%; }
|
||||||
|
.card.large .answer { font-size: 400%; }
|
||||||
|
|
||||||
/* Card View Visible */
|
/* Card View Visible */
|
||||||
#settings {
|
#settings {
|
||||||
@ -130,6 +185,14 @@ input[name=importFileName] {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#card-input label.only-with-answers,
|
||||||
|
#adv-settings label.only-with-answers
|
||||||
|
{ display: none; }
|
||||||
|
|
||||||
|
.cards-have-answers #card-input label.only-with-answers,
|
||||||
|
.cards-have-answers #adv-settings label.only-with-answers
|
||||||
|
{ display: flex; }
|
||||||
|
|
||||||
/* Mobile View */
|
/* Mobile View */
|
||||||
@media ( max-width: 600px ) {
|
@media ( max-width: 600px ) {
|
||||||
body {
|
body {
|
||||||
@ -160,7 +223,7 @@ input[name=importFileName] {
|
|||||||
height: calc(100vh - 2em);
|
height: calc(100vh - 2em);
|
||||||
width: calc(100vw - 2em);
|
width: calc(100vw - 2em);
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings > button, #cards > button {
|
#settings > button, #cards > button {
|
||||||
font-size: 125%;
|
font-size: 125%;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
@ -187,10 +250,8 @@ input[name=importFileName] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#debug {
|
#debug {
|
||||||
/* display: none; */
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Simple Flashcards</title>
|
<title>Simple Flashcards</title>
|
||||||
<meta charset=utf-8>
|
<meta charset=utf-8>
|
||||||
|
<meta name=viewport content='width=device-width,initial-scale=1'>
|
||||||
|
<meta name=application-name content='Simple Flashcards'>
|
||||||
|
|
||||||
<link rel=stylesheet href=flashcards.css type="text/css">
|
<link rel=stylesheet href=flashcards.css type="text/css">
|
||||||
<script src='flashcards.js'></script>
|
<script src='flashcards.js'></script>
|
||||||
@ -11,15 +13,30 @@
|
|||||||
<body class=settings-visible>
|
<body class=settings-visible>
|
||||||
<h1 class=title>Simple Flashcards</h1>
|
<h1 class=title>Simple Flashcards</h1>
|
||||||
<form id=settings action=#>
|
<form id=settings action=#>
|
||||||
<label>Items</label>
|
<div id=card-input>
|
||||||
<textarea id=items-input></textarea>
|
<label>Cards
|
||||||
|
<textarea id=prompts-input></textarea>
|
||||||
|
</label>
|
||||||
|
<label class="only-with-answers">Answers
|
||||||
|
<textarea id=answers-input></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<label class=toggle-adv-settings>Advanced Settings
|
<label class=toggle-adv-settings>Advanced Settings
|
||||||
<span class=visibility-indicator>➤</span>
|
<span class=visibility-indicator>➤</span>
|
||||||
</label>
|
</label>
|
||||||
<div id=adv-settings>
|
<div id=adv-settings>
|
||||||
<label>
|
<label>
|
||||||
<span>Seconds per slide:</span>
|
<span>Cards have answers:</span>
|
||||||
<input type=number name=slidePeriod value=3>
|
<label><input type=radio name=hasAnswers value="yes">yes</label>
|
||||||
|
<label><input type=radio name=hasAnswers value="no" checked>no</label>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Seconds per prompt:</span>
|
||||||
|
<input type=number name=promptPeriod value=3>
|
||||||
|
</label>
|
||||||
|
<label class="only-with-answers">
|
||||||
|
<span>Seconds per answer:</span>
|
||||||
|
<input type=number name=answerPeriod value=3>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Show cards </span>
|
<span>Show cards </span>
|
||||||
@ -55,14 +72,19 @@
|
|||||||
<button id=import>Import Cards</button>
|
<button id=import>Import Cards</button>
|
||||||
<input name=importFileName type=file accept='.json'>
|
<input name=importFileName type=file accept='.json'>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Import from URL: </span>
|
||||||
|
<input name=importURL type=text>
|
||||||
|
<button id=importURL>Import</button>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button class=btn-primary id=start-button>Go!</button>
|
<button class=btn-primary id=start-button>Go!</button>
|
||||||
</form>
|
</form>
|
||||||
<div id=cards>
|
<div id=cards>
|
||||||
<div id=card-content></div>
|
|
||||||
<button id=stop-button>Stop</button>
|
<button id=stop-button>Stop</button>
|
||||||
</div>
|
</div>
|
||||||
<div id=debug>
|
<div id=debug>
|
||||||
|
Version %VERSION%
|
||||||
<span class=small-only>small</span>
|
<span class=small-only>small</span>
|
||||||
<span class=medium-only>medium</span>
|
<span class=medium-only>medium</span>
|
||||||
<span class=large-only>large</span>
|
<span class=large-only>large</span>
|
||||||
|
135
flashcards.js
135
flashcards.js
@ -1,13 +1,18 @@
|
|||||||
(function() {
|
(function() {
|
||||||
const FC = {
|
const FC = {
|
||||||
currentSet: [],
|
currentSet: [],
|
||||||
items: [],
|
cards: [],
|
||||||
|
curCardEls: [], // array of card HTMLElements
|
||||||
nextCardIdx: 0,
|
nextCardIdx: 0,
|
||||||
cardOrder: [], // elements are objects: { name: 'abc', cards: 'xyz' }
|
cardOrder: [],
|
||||||
savedSets: [],
|
savedSets: [], // elements are objects: { name: 'abc', cards: 'xyz' }
|
||||||
$: document.querySelector.bind(document)
|
$: document.querySelector.bind(document),
|
||||||
|
version: "%VERSION%"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const IMG_REGEX = /(http|https|file):\/\/(.*)\.(jpg|jpeg|gif|png|svg)/;
|
||||||
|
const URL_REGEX = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
|
||||||
|
|
||||||
FC.shuffle = function(inArray) {
|
FC.shuffle = function(inArray) {
|
||||||
/* http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
/* http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
||||||
* modified to be a pure function, not mutating it's input */
|
* modified to be a pure function, not mutating it's input */
|
||||||
@ -34,6 +39,10 @@
|
|||||||
return outArray;
|
return outArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FC.zipItems = function(prompts, answers) {
|
||||||
|
return prompts.map((prompt, idx) => ({ prompt, answer: answers[idx] || null }));
|
||||||
|
}
|
||||||
|
|
||||||
FC.isRunning = function() {
|
FC.isRunning = function() {
|
||||||
return !FC.bodyEl.classList.contains('settings-visible');
|
return !FC.bodyEl.classList.contains('settings-visible');
|
||||||
};
|
};
|
||||||
@ -46,11 +55,12 @@
|
|||||||
if (ev && ev.preventDefault) ev.preventDefault();
|
if (ev && ev.preventDefault) ev.preventDefault();
|
||||||
|
|
||||||
const newSet = {
|
const newSet = {
|
||||||
name: (FC.currentSet || {}).name ||
|
name: FC.$('input[name=saveSetName]').value || 'new set',
|
||||||
FC.$('input[name=saveSetName]').value ||
|
prompts: FC.promptsInputEl.value,
|
||||||
'new set',
|
answers: FC.answersInputEl.value,
|
||||||
cards: FC.itemsEl.value,
|
cardsHaveAnswers: FC.$('input[name=hasAnswers]:checked')?.value === 'yes',
|
||||||
slidePeriod: parseInt(FC.$('input[name=slidePeriod]').value) || 3,
|
promptPeriod: parseInt(FC.$('input[name=promptPeriod]').value) || 3,
|
||||||
|
answerPeriod: parseInt(FC.$('input[name=answerPeriod]').value) || 3,
|
||||||
textSize: FC.$('select[name=textSize]').value || 'small',
|
textSize: FC.$('select[name=textSize]').value || 'small',
|
||||||
sortOrder: FC.$('select[name=cardOrder]').value || 'in-order'
|
sortOrder: FC.$('select[name=cardOrder]').value || 'in-order'
|
||||||
};
|
};
|
||||||
@ -60,10 +70,15 @@
|
|||||||
|
|
||||||
FC.populateSettingsFromSet = function(set) {
|
FC.populateSettingsFromSet = function(set) {
|
||||||
FC.$('input[name=saveSetName]').value = set.name;
|
FC.$('input[name=saveSetName]').value = set.name;
|
||||||
FC.itemsEl.value = set.cards;
|
FC.promptsInputEl.value = set.prompts;
|
||||||
FC.$('input[name=slidePeriod]').value = set.slidePeriod;
|
FC.answersInputEl.value = set.answers;
|
||||||
|
FC.$('input[name=promptPeriod]').value = set.promptPeriod;
|
||||||
|
FC.$('input[name=answerPeriod]').value = set.answerPeriod;
|
||||||
FC.$('select[name=cardOrder]').value = set.sortOrder;
|
FC.$('select[name=cardOrder]').value = set.sortOrder;
|
||||||
FC.$('select[name=textSize]').value = set.textSize;
|
FC.$('select[name=textSize]').value = set.textSize;
|
||||||
|
FC.$('input[name=hasAnswers][value="' +
|
||||||
|
(set.cardsHaveAnswers?'yes':'no') + '"]').checked = true;
|
||||||
|
FC.setCardsHaveAnswers();
|
||||||
};
|
};
|
||||||
|
|
||||||
FC.startCards = function(ev) {
|
FC.startCards = function(ev) {
|
||||||
@ -72,15 +87,18 @@
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
FC.currentSet = FC.makeSetFromSettings();
|
FC.currentSet = FC.makeSetFromSettings();
|
||||||
FC.items = FC.currentSet.cards.split('\n');
|
FC.cards = FC.zipItems(
|
||||||
|
FC.currentSet.prompts.split('\n'),
|
||||||
|
FC.currentSet.answers?.split('\n')
|
||||||
|
);
|
||||||
|
|
||||||
FC.cardContentEl.classList.remove(
|
FC.curCardEls = FC.cards.map(FC.makeCard);
|
||||||
'small-text', 'medium-text', 'large-text');
|
|
||||||
FC.cardContentEl.classList.add(FC.currentSet.textSize + '-text');
|
FC.curCardEls.forEach(cardEl => FC.cardsEl.prepend(cardEl));
|
||||||
|
|
||||||
FC.nextCardIdx = 0;
|
FC.nextCardIdx = 0;
|
||||||
|
|
||||||
const orderedIndices = [...Array(FC.items.length).keys()];
|
const orderedIndices = [...Array(FC.cards.length).keys()];
|
||||||
switch (FC.currentSet.sortOrder) {
|
switch (FC.currentSet.sortOrder) {
|
||||||
default:
|
default:
|
||||||
case 'in-order':
|
case 'in-order':
|
||||||
@ -93,20 +111,64 @@
|
|||||||
FC.cardOrder = FC.shuffle(orderedIndices);
|
FC.cardOrder = FC.shuffle(orderedIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FC.$('html').requestFullscreen();
|
||||||
FC.showNextCard();
|
FC.showNextCard();
|
||||||
FC.runningInterval = setInterval(FC.showNextCard, FC.currentSet.slidePeriod * 1000);
|
const fullInterval = (FC.currentSet.promptPeriod * 1000) +
|
||||||
|
(FC.currentSet.cardsHaveAnswers ? FC.currentSet.answerPeriod * 1000 : 0);
|
||||||
|
FC.runningInterval = setInterval(FC.showNextCard, fullInterval);
|
||||||
FC.bodyEl.classList.remove('settings-visible');
|
FC.bodyEl.classList.remove('settings-visible');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FC.makeCard = function(item, idx) {
|
||||||
|
const newCardDiv = document.createElement("div");
|
||||||
|
newCardDiv.classList.add('card', FC.currentSet.textSize);
|
||||||
|
newCardDiv.dataset.index = idx;
|
||||||
|
|
||||||
|
const promptDiv = document.createElement("div");
|
||||||
|
promptDiv.classList.add('prompt');
|
||||||
|
promptDiv.innerHTML = FC.transformContent(item.prompt);
|
||||||
|
newCardDiv.appendChild(promptDiv);
|
||||||
|
|
||||||
|
if (item.answer) {
|
||||||
|
const answerDiv = document.createElement("div");
|
||||||
|
answerDiv.classList.add('answer');
|
||||||
|
answerDiv.innerHTML = FC.transformContent(item.answer);
|
||||||
|
newCardDiv.appendChild(answerDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCardDiv;
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.transformContent = function(content) {
|
||||||
|
const matchesImg = content.toLowerCase().trim().match(IMG_REGEX);
|
||||||
|
if (matchesImg) {
|
||||||
|
return '<img src="' + content.trim() + '"/>';
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FC.stopCards = function(ev) {
|
FC.stopCards = function(ev) {
|
||||||
clearInterval(FC.runningInterval);
|
clearInterval(FC.runningInterval);
|
||||||
|
document.exitFullscreen();
|
||||||
FC.bodyEl.classList.add('settings-visible');
|
FC.bodyEl.classList.add('settings-visible');
|
||||||
|
document.querySelectorAll('.card').forEach(el => el.remove());
|
||||||
};
|
};
|
||||||
|
|
||||||
FC.showNextCard = function() {
|
FC.showNextCard = function() {
|
||||||
const curItem = FC.items[FC.cardOrder[FC.nextCardIdx]];
|
FC.$('.card.current')?.classList?.remove('current', 'show-answer');
|
||||||
FC.cardContentEl.innerHTML = curItem;
|
|
||||||
FC.nextCardIdx = (FC.nextCardIdx + 1) % FC.items.length;
|
const curCardEl = FC.$('.card[data-index="' + FC.cardOrder[FC.nextCardIdx] + '"]');
|
||||||
|
curCardEl.classList.add('current');
|
||||||
|
|
||||||
|
FC.nextCardIdx = (FC.nextCardIdx + 1) % FC.cards.length;
|
||||||
|
|
||||||
|
if (FC.currentSet.cardsHaveAnswers) {
|
||||||
|
setTimeout(
|
||||||
|
() => { curCardEl.classList.add('show-answer') },
|
||||||
|
FC.currentSet.promptPeriod * 1000
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FC.handleLoadSet = function(ev) {
|
FC.handleLoadSet = function(ev) {
|
||||||
@ -211,13 +273,39 @@
|
|||||||
reader.readAsText(fileToImport);
|
reader.readAsText(fileToImport);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FC.importCardSetFromURL = function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
fetch(FC.$('input[name=importURL]').value)
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then(set => {
|
||||||
|
if (!set.name) throw "Invalid set definition file (missing name)";
|
||||||
|
FC.saveSet(set);
|
||||||
|
FC.storeSets(FC.savedSets);
|
||||||
|
FC.updateSetList(FC.savedSets);
|
||||||
|
FC.populateSettingsFromSet(set);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert("Unable to load set from URL: " + err);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
FC.setCardsHaveAnswers = function(ev) {
|
||||||
|
if (FC.$('input[name=hasAnswers]:checked')?.value === 'yes') {
|
||||||
|
FC.bodyEl.classList.add('cards-have-answers');
|
||||||
|
} else {
|
||||||
|
FC.bodyEl.classList.remove('cards-have-answers');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Cached element references
|
// Cached element references
|
||||||
FC.settingsEl = FC.$('#settings');
|
FC.settingsEl = FC.$('#settings');
|
||||||
FC.itemsEl = FC.$('#items-input');
|
FC.promptsInputEl = FC.$('#prompts-input');
|
||||||
|
FC.answersInputEl = FC.$('#answers-input');
|
||||||
FC.bodyEl = FC.$('body');
|
FC.bodyEl = FC.$('body');
|
||||||
FC.cardsEl = FC.$('#cards');
|
FC.cardsEl = FC.$('#cards');
|
||||||
FC.cardContentEl = FC.$('#card-content');
|
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
FC.$('.toggle-adv-settings').addEventListener('click', FC.toggleAdvSettings);
|
FC.$('.toggle-adv-settings').addEventListener('click', FC.toggleAdvSettings);
|
||||||
@ -225,8 +313,11 @@
|
|||||||
FC.$('#stop-button').addEventListener('click', FC.stopCards);
|
FC.$('#stop-button').addEventListener('click', FC.stopCards);
|
||||||
FC.$('#export').addEventListener('click', FC.exportCardSet);
|
FC.$('#export').addEventListener('click', FC.exportCardSet);
|
||||||
FC.$('#import').addEventListener('click', FC.importCardSet);
|
FC.$('#import').addEventListener('click', FC.importCardSet);
|
||||||
|
FC.$('#importURL').addEventListener('click', FC.importCardSetFromURL);
|
||||||
FC.$('input[name=importFileName]')
|
FC.$('input[name=importFileName]')
|
||||||
.addEventListener('change', FC.handleImport);
|
.addEventListener('change', FC.handleImport);
|
||||||
|
document.querySelectorAll('input[name=hasAnswers]').forEach(inpEl =>
|
||||||
|
inpEl.addEventListener('change', FC.setCardsHaveAnswers));
|
||||||
|
|
||||||
FC.$('#save-set').addEventListener('click', FC.handleSaveSet);
|
FC.$('#save-set').addEventListener('click', FC.handleSaveSet);
|
||||||
FC.$('#load-set').addEventListener('click', FC.handleLoadSet);
|
FC.$('#load-set').addEventListener('click', FC.handleLoadSet);
|
||||||
|
48
makewatch
Normal file
48
makewatch
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# Don Marti <dmarti@zgp.org>
|
||||||
|
# (If you need a license on this in order to use it,
|
||||||
|
# mail me and I'll put a license on it. Otherwise,
|
||||||
|
# de minimis non curat lex.)
|
||||||
|
|
||||||
|
# re-run make, with the supplied arguments, when a
|
||||||
|
# Makefile prerequisite changes. Example:
|
||||||
|
# makewatch test
|
||||||
|
# Fun to use with Auto Reload (Firefox) or Tincr (Chrome) and Pandoc
|
||||||
|
# https://addons.mozilla.org/en-US/firefox/addon/auto-reload/
|
||||||
|
# http://tin.cr/
|
||||||
|
# http://johnmacfarlane.net/pandoc/
|
||||||
|
# Requires inotifywait. Probably requires GNU Make to
|
||||||
|
# get the right "-dnr" output. Not tested with other
|
||||||
|
# "make" implementations
|
||||||
|
|
||||||
|
# If $MAKEWATCH is set to an existing file or
|
||||||
|
# space-separated list of files, also checks those.
|
||||||
|
|
||||||
|
make_prereqs() {
|
||||||
|
# Make "make" figure out what files it's interested in.
|
||||||
|
echo "Makefile"
|
||||||
|
find $MAKEWATCH
|
||||||
|
gmake -dnr $* | tr ' ' '\n' | \
|
||||||
|
grep ".*'.$" | grep -o '\w.*\b'
|
||||||
|
}
|
||||||
|
|
||||||
|
prereq_files() {
|
||||||
|
# prerequisites mentioned in a Makefile
|
||||||
|
# that are extant files
|
||||||
|
echo ' '
|
||||||
|
for f in `make_prereqs $* | sort -u`; do
|
||||||
|
[ -e $f ] && echo -n "$f ";
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
make $*
|
||||||
|
while true; do
|
||||||
|
fl=$(prereq_files $*)
|
||||||
|
ev=$(inotifywait --quiet --format %e $fl)
|
||||||
|
if [ "xOPEN" != "x$ev" ]; then
|
||||||
|
sleep 1
|
||||||
|
make $*
|
||||||
|
fi
|
||||||
|
done
|
Loading…
x
Reference in New Issue
Block a user