Skip to content

Commit

Permalink
Merge pull request #44 from ftlabs/feature/43/persistence
Browse files Browse the repository at this point in the history
[Feature] Persistence
  • Loading branch information
Lily2point0 authored Jun 6, 2017
2 parents 69babd4 + 2e82b3b commit 13420e4
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 23 deletions.
22 changes: 11 additions & 11 deletions src/js/crossword_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
else if (match = /^pubdate:?\s+(\d{4}\/\d\d\/\d\d)$/i.exec(line) ) { crossword.pubdate = match[1]; }
else if (match = /^(?:size|dimensions):?\s+(15x15|17x17)$/i.exec(line) ) { crossword.dimensions = match[1]; }
else if (match = /^(across|down):?$/i .exec(line) ) { cluesGrouping = match[1]; }
else if (match = /^-\s\((\d+),(\d+)\)\s+(\d+)\.\s+(.+)\s+\(([A-Z,-]+|[0-9,-]+)\)$/.exec(line) ) {
else if (match = /^-\s\((\d+),(\d+)\)\s+(\d+)\.\s+(.+)\s+\(([A-Z,\-*]+|[0-9,-]+)\)$/.exec(line) ) {
if (! /(across|down)/.test(cluesGrouping)) {
crossword.errors.push("ERROR: clue specified but no 'across' or 'down' grouping specified");
break;
Expand Down Expand Up @@ -157,14 +157,14 @@
// and unpack the answerCSV

// convert "ANSWER,PARTS-INTO,NUMBERS" into number csv e.g. "6,5-4,6" (etc)
if ( /^[A-Z,\-]+$/.test(clue.answerCSV) ) {
clue.numericCSV = clue.answerCSV.replace(/[A-Z]+/g, match => {return match.length.toString() } );
if ( /^[A-Z,\-*]+$/.test(clue.answerCSV) ) {
clue.numericCSV = clue.answerCSV.replace(/[A-Z*]+/g, match => {return match.length.toString() } );
} else {
clue.numericCSV = clue.answerCSV;
}

// and if the answer is solely Xs, replace that with the number csv
if ( /^[X,\-]+$/.test(clue.answerCSV) ) {
// and if the answer is solely *s, replace that with the number csv
if ( /^[*,\-]+$/.test(clue.answerCSV) ) {
clue.answerCSV = clue.numericCSV;
}

Expand All @@ -175,7 +175,7 @@
if (pInt == 0) {
clueError("answer contains a word size of 0");
}
return 'X'.repeat( pInt );
return '*'.repeat( pInt );
} else {
if (p.length == 0) {
clueError("answer contains an empty word");
Expand Down Expand Up @@ -239,7 +239,7 @@
if (crossword.errors.length == 0) {
for (var i = 2; i <= maxId; i++) {
let prevClue = knownIds[i-1];
let clue = knownIds[i];
let clue = knownIds[i];

if ( (clue.coordinates[0] + clue.coordinates[1] * maxCoord) <= (prevClue.coordinates[0] + prevClue.coordinates[1] * maxCoord) ) {
if (clue.coordinates[1] < prevClue.coordinates[1]) {
Expand Down Expand Up @@ -375,11 +375,11 @@
});

{
// if the answers are just placeholders (lots of Xs)
// if the answers are just placeholders (lots of *s or Xs)
// assume they are not to be displayed,
// so delete them from the spec
let concatAllAnswerWordsStrings = spec.answers.across.join('') + spec.answers.down.join('');
if ( /^X+$/.test(concatAllAnswerWordsStrings) ) {
if ( /^(X+|\*+)$/.test(concatAllAnswerWordsStrings) ) {
delete spec['answers'];
}
}
Expand Down Expand Up @@ -422,7 +422,7 @@
"Coordinates of clue in grid are (across,down), so (1,1) = top left, (17,17) = bottom right.",
"ID is a number, followed by a full stop.",
"(WORDS,IN,ANSWER): capitalised, and separated by commas or hyphens, or (numbers) separated by commas or hyphens.",
"ANSWERS with all words of XXXXXX are converted to numbers.",
"ANSWERS with all words of ***** are converted to numbers.",
];
lines = lines.concat( footerComments.map(c => { return `# ${c}`; } ) );

Expand Down Expand Up @@ -506,4 +506,4 @@
'whateverItIs' : parseWhateverItIs,
'intoSpecJson' : parseWhateverItIsIntoSpecJson
};
}));
}));
153 changes: 141 additions & 12 deletions src/js/oCrossword.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ function buildGrid(
const cluesEl = rootEl.querySelector('ul.o-crossword-clues');
const {cols, rows} = size;
const emptyCell = rootEl.querySelector('.empty-fallback');
let answerStore, isStorage;
const cookie = 'FT-crossword_' + name.split(/[ ,]+/).join('');

expireStorage();

if(!answers) {
if(localStorage.getItem(cookie)) {
answerStore = JSON.parse(localStorage.getItem(cookie));
isStorage = true;
} else {
answerStore = {
"across": [],
"down": [],
"timestamp": Date.now()
}

isStorage = false;
}
}

for (let i=0; i<rows; i++) {
const tr = document.createElement('tr');
Expand Down Expand Up @@ -118,7 +137,20 @@ function buildGrid(
tempInput.setAttribute('data-link-identifier', 'A' + across[0] + '-' + i);
tempInput.setAttribute('tabindex', -1);
if(answers) {
tempInput.value = answers.across[index][i];
let val = (answers.across[index][i] === '*')?'':answers.across[index][i];
tempInput.value = val;
}

if(answerStore) {
if(isStorage) {
let val = (answerStore.across[index][i] === '*')?'':answerStore.across[index][i];
tempInput.value = val;
} else {
if(answerStore.across[index] === undefined) {
answerStore.across[index] = '';
}
answerStore.across[index] += '*';
}
}

let count = 0;
Expand Down Expand Up @@ -172,7 +204,20 @@ function buildGrid(
tempInput.setAttribute('tabindex', -1);

if(answers) {
tempInput.value = answers.down[index][i];
let val = (answers.down[index][i] === '*')?'':answers.down[index][i];
tempInput.value = val;
}

if(answerStore) {
if(isStorage) {
let val = (answerStore.down[index][i] === '*')?'':answerStore.down[index][i];
tempInput.value = val;
} else {
if(answerStore.down[index] === undefined) {
answerStore.down[index] = '';
}
answerStore.down[index] += '*';
}
}

let count = 0;
Expand Down Expand Up @@ -206,24 +251,48 @@ function buildGrid(
});
}

if (answers) {
if (answers || answerStore) {
let target = (answers)?answers:answerStore;
clues.across.forEach(function acrossForEach(across, i) {
const answer = answers.across[i];
const answer = target.across[i];
const answerLength = answer.length;
getGridCellsByNumber(gridEl, across[0], 'across', answerLength);
getGridCellsByNumber(gridEl, across[0], 'across', answerLength).forEach((td, i) => {
td.textContent = answer[i];
let val = (answer[i] === '*')?'':answer[i];
td.textContent = val;
});
});

clues.down.forEach(function downForEach(down, i) {
const answer = answers.down[i];
const answer = target.down[i];
const answerLength = answer.length;
getGridCellsByNumber(gridEl, down[0], 'down', answerLength).forEach((td, i) => {
td.textContent = answer[i];
let val = (answer[i] === '*')?'':answer[i];
td.textContent = val;
});
});
}

if(answerStore) {
rootEl.setAttribute('data-storage', JSON.stringify(answerStore));
rootEl.setAttribute('data-storage-id', cookie);
}
}

function expireStorage() {
const ts = Date.now();

for (let i = 0; i < localStorage.length; i++){
if (localStorage.key(i).substring(0,12) == 'FT-crossword') {
let storedItem = JSON.parse(localStorage.getItem(localStorage.key(i)));
let difference = ts - storedItem.timestamp;

let daysCreated = difference/1000/60/60/24;

if(daysCreated > 28) {
localStorage.removeItem(localStorage.key(i));
}
}
}
}

function getRelativeCenter(e, el) {
Expand Down Expand Up @@ -319,6 +388,7 @@ function getLetterIndex(gridEl, cell, number, direction) {
OCrossword.prototype.assemble = function assemble() {
const gridEl = this.rootEl.querySelector('table');
const cluesEl = this.rootEl.querySelector('ul.o-crossword-clues');
let answerStore = JSON.parse(this.rootEl.getAttribute('data-storage'));
const gridMap = new Map();
let currentlySelectedGridItem = null;
for (const el of cluesEl.querySelectorAll('[data-o-crossword-number]')) {
Expand Down Expand Up @@ -391,6 +461,16 @@ OCrossword.prototype.assemble = function assemble() {
let previousClueSelection = null;
let isTab = false;

const resetButton = document.createElement('button');
resetButton.classList.add('o-crossword-reset');
if(answersEmpty()) {
resetButton.classList.add('hidden');
}
resetButton.textContent = 'Reset grid';

this.addEventListener(resetButton, 'click', clearAnswers);
this.rootEl.insertBefore(resetButton, gridWrapper);

function constructInputIdentifier(data, direction) {
let identifier;

Expand Down Expand Up @@ -549,7 +629,7 @@ OCrossword.prototype.assemble = function assemble() {
defSync.value = e.target.value;
}

updateScreenReaderAnswer(e.target);
updateScreenReaderAnswer(e.target, gridSync);

nextInput(e.target, -1);
}, timer);
Expand Down Expand Up @@ -806,17 +886,32 @@ OCrossword.prototype.assemble = function assemble() {
++filledCount;
answerValue.push(input.value);
} else {
answerValue.push(".");
answerValue.push("*");
}
});

if(answerStore) {
const dir = targetData.getAttribute('data-o-crossword-direction');
const offset = (dir === 'down')?cluesEl.querySelector('.o-crossword-clues-across').childElementCount:0;
const targetIndex = parseInt(targetData.getAttribute('data-o-crossword-clue-id')) - offset;
answerStore[dir][targetIndex] = answerValue.join('');

saveLocal();

if(answersEmpty()) {
resetButton.classList.add('hidden');
} else {
resetButton.classList.remove('hidden');
}
}

let combineCount = 0;
let combinedValue = [];

for(let i = 0; i < answerValue.length; ++i) {
if(answerValue[i] === '.') {
if(answerValue[i] === '*') {
++combineCount;
if((i < answerValue.length - 1 && answerValue[i + 1] !== '.') || i === answerValue.length - 1) {
if((i < answerValue.length - 1 && answerValue[i + 1] !== '*') || i === answerValue.length - 1) {
if(combineCount > 1) {
combinedValue.push(" " + combineCount + " blanks ");
} else {
Expand Down Expand Up @@ -856,6 +951,40 @@ OCrossword.prototype.assemble = function assemble() {
});
}

const saveLocal = function saveLocal() {
try {
let answerStoreID = this.rootEl.getAttribute('data-storage-id');
localStorage.setItem(answerStoreID, JSON.stringify( answerStore ) );
} catch(err){
console.log('Error trying to save state', err);
}
}.bind(this);

function clearAnswers(e) {
resetButton.classList.add('hidden');
let inputs = cluesEl.querySelectorAll('input');
let cells = gridEl.querySelectorAll('td:not(.empty)');

Array.from(inputs).forEach(input => {
input.value = '';
});

Array.from(cells).forEach(cell => {
cell.textContent = '';
});

try {
let answerStoreID = this.parentElement.getAttribute('data-storage-id');
localStorage.removeItem(answerStoreID);
} catch(err){
console.log('Error trying to save state', err);
}
}

function answersEmpty() {
return (/^[*,\-]+$/).test(answerStore.across) && (/^[*,\-]+$/).test(answerStore.down);
}

const onResize = function onResize(init) {
var isMobile = false;
const cellSizeMax = 40;
Expand Down
12 changes: 12 additions & 0 deletions src/scss/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -305,5 +305,17 @@
}
}
}

.o-crossword-reset {
margin: 0 10px 20px;

@include oGridRespondTo(M) {
margin: 0;
right: 10px;
top: 10px;
position: absolute;
z-index: 2;
}
}
}

4 changes: 4 additions & 0 deletions src/scss/_print.scss
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,8 @@
}
}
}

.o-crossword-reset {
display: none;
}
}

0 comments on commit 13420e4

Please sign in to comment.