diff --git a/index.html b/index.html
index c263ae7..8e8a0b8 100644
--- a/index.html
+++ b/index.html
@@ -289,21 +289,24 @@
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"