diff --git a/index.html b/index.html index c263ae7..8e8a0b8 100644 --- a/index.html +++ b/index.html @@ -289,21 +289,24 @@

- Import JSON
- Import a previously-exported JSON file. +

- Import Words
- Import a CSV file of words. (Download an example file with the correct formatting.) + + Download an example file with the correct formatting

- Export JSON
+ Export JSON
Export your work as a JSON file to re-import later.

- Export Words
+ Export Words
Export a CSV file of your words.

diff --git a/package.json b/package.json index 38a9cb1..4da239f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "marked": "^0.6.2", - "normalize.css": "^8.0.1" + "normalize.css": "^8.0.1", + "papaparse": "^4.6.3" } } diff --git a/src/helpers.js b/src/helpers.js index c85e288..28f0e87 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -4,6 +4,24 @@ export function cloneObject(object) { return JSON.parse(JSON.stringify(object)); } +export function download(data, filename, type) { + var file = new Blob([data], { type }); + if (window.navigator.msSaveOrOpenBlob) // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + else { // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } +} + export function getIndicesOf(searchStr, findIn, caseSensitive) { // https://stackoverflow.com/a/3410557 const searchStrLen = searchStr.length; @@ -52,5 +70,5 @@ export function removeTags(html) { } export function slugify(string) { - return removeDiacritics(string).replace(/[!a-zA-Z0-9-_]/g, '-'); + return removeDiacritics(string).replace(/[^a-zA-Z0-9-_]/g, '-'); } diff --git a/src/js/dictionaryManagement.js b/src/js/dictionaryManagement.js index 92d01e6..2d586fc 100644 --- a/src/js/dictionaryManagement.js +++ b/src/js/dictionaryManagement.js @@ -1,7 +1,8 @@ -import { renderDictionaryDetails, renderPartsOfSpeech } from "./render"; -import { removeTags, cloneObject, getTimestampInSeconds } from "../helpers"; +import { renderDictionaryDetails, renderPartsOfSpeech, renderAll } from "./render"; +import { removeTags, cloneObject, getTimestampInSeconds, download, slugify } from "../helpers"; import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants"; -import { addMessage } from "./utilities"; +import { addMessage, getNextId } from "./utilities"; +import { addWord } from "./wordManagement"; export function updateDictionary () { @@ -93,6 +94,118 @@ export function clearDictionary() { window.currentDictionary = cloneObject(DEFAULT_DICTIONARY); } +export function importDictionary() { + const importDictionaryField = document.getElementById('importDictionaryFile'); + + if (importDictionaryField.files.length === 1) { + if (confirm('Importing a dicitonary file will overwrite and replace your current dictionary!\nDo you want to continue?')) { + addMessage('Importing Dictionary...'); + const fileReader = new FileReader(); + fileReader.onload = function (fileLoadedEvent) { + const textFromFileLoaded = fileLoadedEvent.target.result; + const importedDictionary = JSON.parse(textFromFileLoaded); + if (importedDictionary && importedDictionary.hasOwnProperty('words')) { + window.currentDictionary = importedDictionary; + saveDictionary(); + renderAll(); + importDictionaryField.value = ''; + document.getElementById('editModal').style.display = 'none'; + addMessage('Dictionary Imported Successfully'); + } else { + addMessage('Dictionary could not be imported', 10000); + } + }; + + fileReader.readAsText(importDictionaryField.files[0], "UTF-8"); + } + } +} + +export function importWords() { + const importWordsField = document.getElementById('importWordsCSV'); + + if (importWordsField.files.length === 1) { + if (confirm('Importing a CSV file with words will add all of the words in the file to your dictionary regardless of duplication!\nDo you want to continue?')) { + addMessage('Importing words...'); + import('papaparse').then(papa => { + let wordsImported = 0; + papa.parse(importWordsField.files[0], { + header: true, + encoding: "utf-8", + step: results => { + if (results.errors.length > 0) { + results.errors.forEach(err => { + addMessage('Error Importing Word: ' + err); + console.error('Error Importing Word: ', err) + }); + } else { + const row = results.data[0]; + addWord({ + name: removeTags(row.word).trim(), + pronunciation: removeTags(row.pronunciation).trim(), + partOfSpeech: removeTags(row['part of speech']).trim(), + definition: removeTags(row.definition).trim(), + details: removeTags(row.explanation).trim(), + wordId: getNextId(), + }, false, false); + wordsImported++; + } + }, + complete: () => { + saveDictionary(); + renderAll(); + importWordsField.value = ''; + document.getElementById('editModal').style.display = 'none'; + addMessage(`Done Importing ${wordsImported} Words`); + }, + error: err => { + addMessage('Error Importing Words: ' + err); + console.error('Error Importing Words: ', err); + }, + skipEmptyLines: true, + }); + }); + } + } +} + +export function exportDictionary() { + addMessage('Exporting JSON...'); + + setTimeout(() => { + const file = JSON.stringify(window.currentDictionary), + { name, specification } = window.currentDictionary; + + const fileName = slugify(name + '_' + specification) + '.json'; + + download(file, fileName, 'application/json;charset=utf-8'); + }, 1); +} + +export function exportWords() { + addMessage('Exporting Words...'); + + setTimeout(() => { + import('papaparse').then(papa => { + const { name, specification } = window.currentDictionary; + + const fileName = slugify(name + '_' + specification) + '_words.csv'; + + const words = window.currentDictionary.words.map(word => { + return { + word: word.name, + pronunciation: word.pronunciation, + 'part of speech': word.partOfSpeech, + definition: word.definition, + explanation: word.details, + } + }); + const csv = papa.unparse(words, { quotes: true }); + download(csv, fileName, 'text/csv;charset=utf-8'); + }); + }, 1); +} + export function migrateDictionary() { let migrated = false; if (!window.currentDictionary.hasOwnProperty('version')) { diff --git a/src/js/setupListeners.js b/src/js/setupListeners.js index b6ebe12..c993b58 100644 --- a/src/js/setupListeners.js +++ b/src/js/setupListeners.js @@ -1,7 +1,7 @@ import {showSection, hideDetailsPanel} from './displayToggles'; import { renderWords, renderEditForm, renderMaximizedTextbox, renderInfoModal, renderIPATable } from './render'; import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from './wordManagement'; -import { openEditModal, saveEditModal, saveAndCloseEditModal } from './dictionaryManagement'; +import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords } from './dictionaryManagement'; import { goToNextPage, goToPreviousPage, goToPage } from './pagination'; import { insertAtCursor, getInputSelection, setSelectionRange } from './StackOverflow/inputCursorManagement'; import { usePhondueDigraphs } from './KeyboardFire/phondue/ipaField'; @@ -82,6 +82,10 @@ function setupEditFormInteractions() { function setupEditFormButtons() { document.getElementById('editSave').addEventListener('click', () => saveEditModal()); document.getElementById('editSaveAndClose').addEventListener('click', () => saveAndCloseEditModal()); + document.getElementById('importDictionaryFile').addEventListener('change', importDictionary); + document.getElementById('importWordsCSV').addEventListener('change', importWords); + document.getElementById('exportDictionaryButton').addEventListener('click', exportDictionary); + document.getElementById('exportWordsButton').addEventListener('click', exportWords); setupMaximizeButtons(); } @@ -196,8 +200,8 @@ export function setupSettingsModal() { } export function setupWordEditFormButtons() { - const saveChangesButtons = document.getElementsByClassName('edit-save-changes'); - const cancelChangesButtons = document.getElementsByClassName('edit-cancel'); + const saveChangesButtons = document.getElementsByClassName('edit-save-changes'), + cancelChangesButtons = document.getElementsByClassName('edit-cancel'); Array.from(saveChangesButtons).forEach(button => { button.removeEventListener('click', confirmEditWord); button.addEventListener('click', confirmEditWord); diff --git a/yarn.lock b/yarn.lock index f32dc7e..d02c161 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3524,6 +3524,11 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== +papaparse@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.6.3.tgz#742e5eaaa97fa6c7e1358d2934d8f18f44aee781" + integrity sha512-LRq7BrHC2kHPBYSD50aKuw/B/dGcg29omyJbKWY3KsYUZU69RKwaBHu13jGmCYBtOc4odsLCrFyk6imfyNubJQ== + parcel-bundler@^1.12.3: version "1.12.3" resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.3.tgz#2bbf70bfa2d06097f071653285040bd125684d09"