From 1321a5ca9f3e506ae05a9c1354799836eb83d96e Mon Sep 17 00:00:00 2001 From: Derek Sifford Date: Sun, 11 Dec 2016 22:15:09 -0500 Subject: [PATCH] 4.9.0 (#266) * Early progress on reference editing feature * feat: Add ability to edit references This adds the ability to edit references by double clicking a reference in the reference list. This commit also improves test coverage across the board. Closes #261 * test: improve test coverage * chore: update dependencies * swap out react-addons-css-transition-group for react-motion * feat: improve animation smoothing * chore: update i18n for new views * chore: update i18n * chore: update deps * Prepare 4.9.0 release **Styles Added:** - Harvard - Universiti Teknologi Malaysia - Journal of Dairy Science - Journal of Instrumentation - Revista Latinoamericana de Recursos Naturales --- ISSUE_TEMPLATE.md | 2 +- package.json | 24 +- scripts/fixtures.js | 36 +- src/academic-bloggers-toolkit.php | 4 +- src/academic-bloggers-toolkit.pot | 58 +- src/lib/css/collections/reference-list.styl | 28 +- src/lib/js/reference-list/API.ts | 26 +- .../js/reference-list/__tests__/API-test.ts | 16 +- .../js/reference-list/__tests__/Store-test.ts | 22 +- src/lib/js/reference-list/components/Card.tsx | 2 + .../js/reference-list/components/ItemList.tsx | 34 +- src/lib/js/reference-list/components/Menu.tsx | 47 +- .../components/ReferenceList.tsx | 111 +++- .../components/__tests__/ItemList-test.tsx | 39 ++ .../components/__tests__/Menu-test.tsx | 1 + .../__tests__/ReferenceList-test.tsx | 525 +++++++++++---- .../EditReferenceWindow.tsx | 98 +++ .../__tests__/EditReferenceWindow-test.tsx | 87 +++ .../edit-reference-window/index.tsx | 12 + .../components/pubmed-window/PubmedWindow.tsx | 5 +- .../__tests__/PubmedWindow-test.tsx | 96 +++ .../components/ManualEntryContainer.tsx | 10 +- .../reference-window/components/People.tsx | 35 +- .../components/ReferenceWindow.tsx | 26 +- .../__tests__/ManualEntryContainer-test.tsx | 7 +- .../components/__tests__/People-test.tsx | 83 ++- .../__tests__/ReferenceWindow-test.tsx | 191 ++++++ .../tinymce/views/edit-reference-window.html | 9 + src/lib/js/utils/Constants.ts | 23 +- src/lib/js/utils/TinymceFunctions.ts | 26 +- .../__tests__/preventScrollPropagation.ts | 6 +- src/lib/php/i18n.php | 5 + src/readme.txt | 6 +- src/vendor/citationstyles.php | 2 +- tsconfig.json | 8 +- types/ABT.d.ts | 9 +- types/globals.d.ts | 158 ++--- types/tinymce.d.ts | 4 + webpack.config.js | 2 +- yarn.lock | 607 +++++++++--------- 40 files changed, 1738 insertions(+), 752 deletions(-) create mode 100644 src/lib/js/tinymce/components/edit-reference-window/EditReferenceWindow.tsx create mode 100644 src/lib/js/tinymce/components/edit-reference-window/__tests__/EditReferenceWindow-test.tsx create mode 100644 src/lib/js/tinymce/components/edit-reference-window/index.tsx create mode 100644 src/lib/js/tinymce/components/pubmed-window/__tests__/PubmedWindow-test.tsx create mode 100644 src/lib/js/tinymce/components/reference-window/components/__tests__/ReferenceWindow-test.tsx create mode 100644 src/lib/js/tinymce/views/edit-reference-window.html diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 12832121..87f65684 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -4,7 +4,7 @@ Please use this template for all bug reports. Before submitting an issue, please try disabling all other plugins to ensure issue is isolated within ABT --> -**ABT Version:** 4.8.1 +**ABT Version:** 4.9.0 **PHP Version:** 5.6 diff --git a/package.json b/package.json index a1ece87e..46dc9848 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "academic-bloggers-toolkit", - "version": "4.8.1", + "version": "4.9.0", "description": "A plugin extending the functionality of WordPress for Academic Blogging.", "main": "index.js", "scripts": { @@ -30,17 +30,17 @@ "homepage": "https://github.com/dsifford/academic-bloggers-toolkit#readme", "devDependencies": { "autoprefixer": "^6.5.3", - "babel-core": "^6.17.0", + "babel-core": "^6.20.0", "babel-loader": "^6.2.9", - "babel-polyfill": "^6.16.0", + "babel-polyfill": "^6.20.0", "babel-preset-es2015": "^6.16.0", "babel-preset-react": "^6.16.0", "babel-register": "^6.16.3", - "browser-sync": "^2.18.2", + "browser-sync": "^2.18.5", "css-loader": "^0.26.1", "del": "^2.2.2", "enzyme": "^2.6.0", - "eslint": "^3.11.1", + "eslint": "^3.12.0", "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-import": "^2.2.0", "gulp": "gulpjs/gulp#4.0", @@ -56,10 +56,11 @@ "react-addons-test-utils": "^15.4.1", "style-loader": "^0.13.1", "stylint": "^1.5.7", - "ts-loader": "^1.3.0", + "ts-loader": "^1.3.2", "tslint": "^4.0.2", "tslint-microsoft-contrib": "^2.0.14", "tslint-react": "^2.0.0", + "typescript": "next", "webpack": "beta", "webpack-bundle-analyzer": "^2.1.1", "webpack-stream": "^3.2.0" @@ -68,18 +69,17 @@ "@types/enzyme": "^2.5.39", "@types/jest": "^16.0.1", "@types/node": "^6.0.51", - "@types/react": "^0.14.54", - "@types/react-addons-css-transition-group": "^0.14.18", + "@types/react": "^0.14.55", "@types/react-dom": "^0.14.19", + "@types/react-motion": "^0.0.20", "bibtex-parse-js": "^0.0.23", - "mobx": "^2.6.5", + "mobx": "^2.7.0", "mobx-react": "^4.0.3", "react": "^15.4.1", - "react-addons-css-transition-group": "^15.4.1", "react-addons-shallow-compare": "^15.4.1", "react-dom": "^15.4.1", - "react-virtualized-select": "^2.1.1", - "typescript": "next" + "react-motion": "^0.4.5", + "react-virtualized-select": "^2.1.1" }, "jest": { "transform": { diff --git a/scripts/fixtures.js b/scripts/fixtures.js index cecce6f5..987d8f01 100644 --- a/scripts/fixtures.js +++ b/scripts/fixtures.js @@ -14,7 +14,7 @@ exports.reflistState = { citationID: 'htmlSpanId', citationItems: [ { - id: 'citationId', + id: 'aaaaaaaa', item: { ISSN: '3', PMID: '12345', @@ -24,7 +24,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'citationId', + id: 'aaaaaaaa', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -40,7 +40,7 @@ exports.reflistState = { }, }, { - id: 'citationId', + id: 'bbbbbbbb', item: { ISSN: '3', PMID: '12345', @@ -50,7 +50,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'citationId', + id: 'bbbbbbbb', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -79,7 +79,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'citationId', + id: 'aaaaaaaa', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -94,7 +94,7 @@ exports.reflistState = { volume: '6', }, { - id: 'citationId', + id: 'aaaaaaaa', sortkeys: ['0'], }, ]], @@ -103,7 +103,7 @@ exports.reflistState = { citationID: 'otherHtmlSpanId', citationItems: [ { - id: 'otherCitationId', + id: 'bbbbbbbb', item: { ISSN: '3', PMID: '12345', @@ -113,7 +113,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'otherCitationId', + id: 'bbbbbbbb', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -142,7 +142,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'otherCitationId', + id: 'bbbbbbbb', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -157,14 +157,14 @@ exports.reflistState = { volume: '6', }, { - id: 'otherCitationId', + id: 'bbbbbbbb', sortkeys: ['0'], }, ]], }, ], CSL: { - citationId: { + aaaaaaaa: { ISSN: '3', PMID: '12345', URL: 'http://www.test.com', @@ -173,7 +173,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'citationId', + id: 'aaaaaaaa', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -187,7 +187,7 @@ exports.reflistState = { type: 'article-journal', volume: '6', }, - otherCitationId: { + bbbbbbbb: { ISSN: '3', PMID: '12345', URL: 'http://www.test.com', @@ -196,7 +196,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'otherCitationId', + id: 'bbbbbbbb', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -210,7 +210,7 @@ exports.reflistState = { type: 'article-journal', volume: '6', }, - uncitedCitationId: { + cccccccc: { ISSN: '3', PMID: '12345', URL: 'http://www.test.com', @@ -219,7 +219,7 @@ exports.reflistState = { 'container-title-short': 'J Test', 'container-title': 'Journal of Testing', edition: '2', - id: 'uncitedCitationId', + id: 'cccccccc', issue: '4', issued: { 'date-parts': [['2016', '08', '19']] }, journalAbbreviation: 'J Test', @@ -262,6 +262,10 @@ exports.ABT_i18n = { }, }, tinymce: { + editReferenceWindow: { + title: 'Edit Reference', + confirm: 'Confirm', + }, importWindow: { title: 'Import References from File', upload: 'Choose File', diff --git a/src/academic-bloggers-toolkit.php b/src/academic-bloggers-toolkit.php index 6fcc940b..dd7934d2 100644 --- a/src/academic-bloggers-toolkit.php +++ b/src/academic-bloggers-toolkit.php @@ -4,14 +4,14 @@ * Plugin Name: Academic Blogger's Toolkit * Plugin URI: https://wordpress.org/plugins/academic-bloggers-toolkit/ * Description: A plugin extending the functionality of Wordpress for academic blogging - * Version: 4.8.1 + * Version: 4.9.0 * Author: Derek P Sifford * Author URI: https://github.com/dsifford * License: GPL3 or later * Text Domain: academic-bloggers-toolkit */ -define('ABT_VERSION', '4.8.1'); +define('ABT_VERSION', '4.9.0'); /** * Load plugin translations diff --git a/src/academic-bloggers-toolkit.pot b/src/academic-bloggers-toolkit.pot index 9db5ec26..4949fe87 100644 --- a/src/academic-bloggers-toolkit.pot +++ b/src/academic-bloggers-toolkit.pot @@ -1,8 +1,8 @@ -# Copyright (C) 2016 Academic Blogger's Toolkit 4.8.1 -# This file is distributed under the same license as the Academic Blogger's Toolkit 4.8.1 package. +# Copyright (C) 2016 Academic Blogger's Toolkit 4.9.0 +# This file is distributed under the same license as the Academic Blogger's Toolkit 4.9.0 package. msgid "" msgstr "" -"Project-Id-Version: Academic Blogger's Toolkit 4.8.1\n" +"Project-Id-Version: Academic Blogger's Toolkit 4.9.0\n" "Report-Msgid-Bugs-To: https://github.com/dsifford/academic-bloggers-toolkit/issues\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -220,7 +220,7 @@ msgstr "" msgid "Number or Range of Numbers (100-200)" msgstr "" -#: lib/php/fieldmaps.php:348, lib/php/i18n.php:98 +#: lib/php/fieldmaps.php:348, lib/php/i18n.php:103 msgid "ISBN" msgstr "" @@ -356,7 +356,7 @@ msgstr "" msgid "DOI" msgstr "" -#: lib/php/fieldmaps.php:849, lib/php/fieldmaps.php:921, lib/php/fieldmaps.php:993, lib/php/fieldmaps.php:1133, lib/php/fieldmaps.php:1296, lib/php/i18n.php:100 +#: lib/php/fieldmaps.php:849, lib/php/fieldmaps.php:921, lib/php/fieldmaps.php:993, lib/php/fieldmaps.php:1133, lib/php/fieldmaps.php:1296, lib/php/i18n.php:105 msgid "URL" msgstr "" @@ -537,86 +537,94 @@ msgid "Uncited Items" msgstr "" #: lib/php/i18n.php:69 -msgid "Import" +msgid "Edit Reference" msgstr "" #: lib/php/i18n.php:70 +msgid "Confirm" +msgstr "" + +#: lib/php/i18n.php:74 +msgid "Import" +msgstr "" + +#: lib/php/i18n.php:75 msgid "Import References from File" msgstr "" -#: lib/php/i18n.php:71 +#: lib/php/i18n.php:76 msgid "Choose File" msgstr "" -#: lib/php/i18n.php:75 +#: lib/php/i18n.php:80 msgid "Select" msgstr "" -#: lib/php/i18n.php:76 +#: lib/php/i18n.php:81 msgid "Next" msgstr "" -#: lib/php/i18n.php:77 +#: lib/php/i18n.php:82 msgid "Previous" msgstr "" -#: lib/php/i18n.php:78, lib/php/i18n.php:99 +#: lib/php/i18n.php:83, lib/php/i18n.php:104 msgid "Search" msgstr "" -#: lib/php/i18n.php:79, lib/php/i18n.php:89 +#: lib/php/i18n.php:84, lib/php/i18n.php:94 msgid "Search PubMed for Reference" msgstr "" -#: lib/php/i18n.php:80 +#: lib/php/i18n.php:85 msgid "View" msgstr "" -#: lib/php/i18n.php:85 +#: lib/php/i18n.php:90 msgid "Add Manually" msgstr "" -#: lib/php/i18n.php:86, lib/php/i18n.php:109 +#: lib/php/i18n.php:91, lib/php/i18n.php:114 msgid "Add Reference" msgstr "" -#: lib/php/i18n.php:87 +#: lib/php/i18n.php:92 msgid "Add with Identifier" msgstr "" -#: lib/php/i18n.php:88 +#: lib/php/i18n.php:93 msgid "Insert citation inline" msgstr "" -#: lib/php/i18n.php:90 +#: lib/php/i18n.php:95 msgid "Search PubMed" msgstr "" -#: lib/php/i18n.php:93 +#: lib/php/i18n.php:98 msgid "DOI/PMID/PMCID" msgstr "" -#: lib/php/i18n.php:96 +#: lib/php/i18n.php:101 msgid "Autocite" msgstr "" -#: lib/php/i18n.php:97 +#: lib/php/i18n.php:102 msgid "Citation Type" msgstr "" -#: lib/php/i18n.php:103 +#: lib/php/i18n.php:108 msgid "Add Contributor" msgstr "" -#: lib/php/i18n.php:104 +#: lib/php/i18n.php:109 msgid "Contributors" msgstr "" -#: lib/php/i18n.php:105 +#: lib/php/i18n.php:110 msgid "Given Name, M.I." msgstr "" -#: lib/php/i18n.php:106 +#: lib/php/i18n.php:111 msgid "Surname" msgstr "" diff --git a/src/lib/css/collections/reference-list.styl b/src/lib/css/collections/reference-list.styl index b414cedb..d87e85ab 100644 --- a/src/lib/css/collections/reference-list.styl +++ b/src/lib/css/collections/reference-list.styl @@ -21,6 +21,11 @@ background: $light-gray box-shadow: $depth-1 +// Menu +.abt-reflist-menu + position: relative + z-index: 555 + // Panels .abt-panel @@ -38,29 +43,6 @@ padding: 5px 0 justify-content: space-around - -// React CSS Transition Group -.menu-enter - max-height: 1px - overflow: hidden - &.menu-enter-active - transition: max-height .2s linear - max-height: 400px - -.menu-leave - max-height: 400px - &.menu-leave-active - transition: max-height .1s linear - max-height: 1px - overflow: hidden - - -// Menu -.abt-reflist-menu - border-bottom: solid $border 1px - padding: 0 5px 5px 5px - - // ItemList .abt-item-heading display: flex diff --git a/src/lib/js/reference-list/API.ts b/src/lib/js/reference-list/API.ts index e5ca2f7c..70d3642b 100644 --- a/src/lib/js/reference-list/API.ts +++ b/src/lib/js/reference-list/API.ts @@ -55,32 +55,32 @@ export function getRemoteData(identifierList: string, mce: TinyMCE.WindowManager }); } -export function parseManualData(payload: ABT.ReferenceWindowPayload): Promise { +export function parseManualData(data: ABT.ManualData): Promise { return new Promise(resolve => { - payload.people.forEach(person => { - if (payload.manualData[person.type] === undefined) { - payload.manualData[person.type] = [{ family: person.family, given: person.given }]; + data.people.forEach(person => { + if (data.manualData[person.type] === undefined) { + data.manualData[person.type] = [{ family: person.family, given: person.given }]; return; } - payload.manualData[person.type].push({ family: person.family, given: person.given }); + data.manualData[person.type].push({ family: person.family, given: person.given }); }); // Process date fields ['accessed', 'event-date', 'issued'].forEach(dateType => { - if (!payload.manualData[dateType]) return; - payload.manualData[dateType] = parseCSLDate(payload.manualData[dateType], 'RIS'); + if (!data.manualData[dateType]) return; + data.manualData[dateType] = parseCSLDate(data.manualData[dateType], 'RIS'); }); - // Create a unique ID - payload.manualData.id = generateID(); + // Create a unique ID if one doesn't exist + if (!data.manualData.id) data.manualData.id = generateID(); - Object.keys(payload.manualData).forEach(key => { - if (payload.manualData[key] === '') { - delete payload.manualData[key]; + Object.keys(data.manualData).forEach(key => { + if (data.manualData[key] === '') { + delete data.manualData[key]; return; } }); - resolve([payload.manualData]); + resolve([data.manualData]); }); } diff --git a/src/lib/js/reference-list/__tests__/API-test.ts b/src/lib/js/reference-list/__tests__/API-test.ts index ea98ef57..1dc41d40 100644 --- a/src/lib/js/reference-list/__tests__/API-test.ts +++ b/src/lib/js/reference-list/__tests__/API-test.ts @@ -1,6 +1,6 @@ import { getRemoteData, parseManualData } from '../API'; -const testCSL = require('../../../../../scripts/fixtures.js').reflistState.CSL.citationId; +const testCSL = require('../../../../../scripts/fixtures.js').reflistState.CSL.aaaaaaaa; describe('API', () => { describe('getRemoteData()', () => { @@ -20,6 +20,12 @@ describe('API', () => { expect(d[0].title).toBe('Not all prehospital time is equal'); }); }); + it('should get a single PMCID', () => { + return getRemoteData('PMC2837541', mce) + .then(d => { + expect(d[0].title).toBe('Trauma Patients without a Trauma Diagnosis: The Data Gap'); + }); + }); it('should get a combination of DOIs and PMIDs', () => { return getRemoteData('10.1097/TA.0000000000000999,12345', mce) .then(d => { @@ -82,5 +88,13 @@ describe('API', () => { expect(d[0].editor.length).toBe(1); }); }); + it('should generateId() if an ID does not already exists', () => { + mockData.manualData.id = ''; + return parseManualData(mockData) + .then(d => { + expect(d[0].id).not.toBe(''); + expect(d[0].id).not.toBeUndefined(); + }); + }); }); }); diff --git a/src/lib/js/reference-list/__tests__/Store-test.ts b/src/lib/js/reference-list/__tests__/Store-test.ts index 1135e6b2..c2f4552d 100644 --- a/src/lib/js/reference-list/__tests__/Store-test.ts +++ b/src/lib/js/reference-list/__tests__/Store-test.ts @@ -12,11 +12,11 @@ describe('Reflist Store', () => { store = new Store(testState); }); it('should return citedIDs', () => { - expect(store.citations.citedIDs).toEqual(['citationId', 'otherCitationId']); + expect(store.citations.citedIDs).toEqual(['aaaaaaaa', 'bbbbbbbb']); }); it('should return uncited CSL', () => { expect(store.citations.uncited.length).toEqual(1); - store.citations.CSL.set('newcitation', {id: 'newcitation', title: 'new citation'}); + store.citations.CSL.set('newcitation', {id: 'zzzzzzzz', title: 'new citation'}); expect(store.citations.uncited.length).toEqual(2); }); it('should return cited CSL', () => { @@ -25,7 +25,7 @@ describe('Reflist Store', () => { it('should return a "lookup" correctly', () => { expect(store.citations.lookup).toEqual( { - ids: ['citationId', 'otherCitationId', 'uncitedCitationId'], + ids: ['aaaaaaaa', 'bbbbbbbb', 'cccccccc'], titles: ['Test Title', 'Other Test Title', 'Test Title Uncited'], } ); @@ -38,12 +38,12 @@ describe('Reflist Store', () => { expect(store.citations.init(store.citations.citationByIndex)).toBeUndefined(); }); it('should handle an undefined language in cleanCSL', () => { - const cite = store.citations.CSL.get('citationId'); + const cite = store.citations.CSL.get('aaaaaaaa'); cite.language = 'gibberish'; - store.citations.CSL.set('citationId', cite); - expect(store.citations.CSL.get('citationId').language).toBe('gibberish'); + store.citations.CSL.set('aaaaaaaa', cite); + expect(store.citations.CSL.get('aaaaaaaa').language).toBe('gibberish'); store.citations.CSL = store.citations.cleanCSL(JSON.parse(JSON.stringify(store.citations.CSL))); - expect(store.citations.CSL.get('citationId').language).toBe('en-US'); + expect(store.citations.CSL.get('aaaaaaaa').language).toBe('en-US'); }); it('should intercept a citation already defined', () => { const cite = { PMID: '12345', title: 'Test Title', type: 'article-journal' }; @@ -59,7 +59,7 @@ describe('Reflist Store', () => { expect(store.citations.CSL.keys().length).toBe(3); }); it('should allow non-existing CSL to be set', () => { - const cite = JSON.parse(JSON.stringify(store.citations.CSL.get('citationId'))); + const cite = JSON.parse(JSON.stringify(store.citations.CSL.get('aaaaaaaa'))); cite.title = 'Something different'; store.citations.CSL.set('sameCitation', cite); expect(store.citations.CSL.keys().length).toBe(4); @@ -74,16 +74,16 @@ describe('Reflist Store', () => { parent.appendChild(el); document.body.appendChild(parent); - store.citations.removeItems(['citationId'], document); + store.citations.removeItems(['aaaaaaaa'], document); - expect(store.citations.citationByIndex.length).toBe(1); + expect(store.citations.citationByIndex.length).toBe(2); expect(store.citations.CSL.keys().length).toBe(3); }); it('removeItems() should completely delete all traces of uncited items', () => { expect(store.citations.citationByIndex.length).toBe(2); expect(store.citations.CSL.keys().length).toBe(3); - store.citations.removeItems(['uncitedCitationId'], document); + store.citations.removeItems(['cccccccc'], document); expect(store.citations.citationByIndex.length).toBe(2); expect(store.citations.CSL.keys().length).toBe(2); diff --git a/src/lib/js/reference-list/components/Card.tsx b/src/lib/js/reference-list/components/Card.tsx index 7873d4e3..e33004a6 100644 --- a/src/lib/js/reference-list/components/Card.tsx +++ b/src/lib/js/reference-list/components/Card.tsx @@ -73,9 +73,11 @@ export class Card extends React.PureComponent { const { CSL, isSelected } = this.props; return (
diff --git a/src/lib/js/reference-list/components/ItemList.tsx b/src/lib/js/reference-list/components/ItemList.tsx index 8e70c1e1..03a6cbe4 100644 --- a/src/lib/js/reference-list/components/ItemList.tsx +++ b/src/lib/js/reference-list/components/ItemList.tsx @@ -1,11 +1,18 @@ import * as React from 'react'; import { Card } from './Card'; +import { toJS, ObservableMap, action } from 'mobx'; import { observer } from 'mobx-react'; +import { editReferenceWindow } from '../../utils/TinymceFunctions'; +import { parseManualData } from '../API'; import { preventScrollPropagation } from '../../utils/helpers/'; +import { EVENTS } from '../../utils/Constants'; + +declare const tinyMCE: TinyMCE.MCE; interface Props extends React.HTMLProps { readonly items: CSL.Data[]; readonly selectedItems: string[]; + CSL: ObservableMap; click: (id: string, isSelected: boolean) => void; toggle: (id: string, explode?: boolean) => void; isOpen: boolean; @@ -24,7 +31,7 @@ export class ItemList extends React.PureComponent { } render() { - const { items, selectedItems, click, children, isOpen, maxHeight, id } = this.props; + const { items, selectedItems, click, children, isOpen, maxHeight, id, CSL } = this.props; if (!items) return null; return (
@@ -40,6 +47,7 @@ export class ItemList extends React.PureComponent { { isOpen && { } interface ItemsProps extends React.HTMLProps { + CSL: ObservableMap; readonly items: CSL.Data[]; readonly selectedItems: string[]; readonly withTooltip: boolean; @@ -69,6 +78,28 @@ class Items extends React.Component { this.element = c; } + editSingleReference = (e: React.MouseEvent) => { + const refId = e.currentTarget.getAttribute('data-reference-id'); + editReferenceWindow( + tinyMCE.EditorManager.get('content'), + toJS(this.props.items.find(i => i.id === refId)) + ) + .then(parseManualData) + .then(m => [refId, m[0]]) + .then(this.finalizeEdits) + .catch(err => { + if (!err) return; // User exited early + Rollbar.error('itemList.tsx -> editSingleReference', err); + }); + } + + @action + finalizeEdits = (d: [string, CSL.Data]) => { + this.props.CSL.delete(d[0]); + this.props.CSL.set(d[0], d[1]); + dispatchEvent(new CustomEvent(EVENTS.REFERENCE_EDITED)); + } + render() { return (
{ -1} diff --git a/src/lib/js/reference-list/components/Menu.tsx b/src/lib/js/reference-list/components/Menu.tsx index c2a3bb23..2a3ed705 100644 --- a/src/lib/js/reference-list/components/Menu.tsx +++ b/src/lib/js/reference-list/components/Menu.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { TransitionMotion, spring } from 'react-motion'; import VSelect from 'react-virtualized-select'; import { observable, action } from 'mobx'; import { observer } from 'mobx-react'; @@ -7,6 +8,7 @@ import { PanelButton } from './PanelButton'; declare const ABT_Custom_CSL: BackendGlobals.ABT_Custom_CSL; interface Props extends React.HTMLProps { + isOpen: boolean; cslStyle: string; itemsSelected: boolean; submitData(kind: string, data?: string): void; @@ -68,6 +70,18 @@ export class Menu extends React.PureComponent { this.props.submitData('CHANGE_STYLE', data.value); } + willEnter = () => ({ + height: 0, + opacity: 0, + scale: 0, + }) + + willLeave = () => ({ + height: spring(0), + opacity: spring(0), + scale: spring(0), + }) + dynamicOptionHeightHandler = ({option}) => { switch (true) { case option.label.length > 110: @@ -87,8 +101,30 @@ export class Menu extends React.PureComponent { render() { return ( -
- {/*
*/} + + {styles => styles.length > 0 ? +
{
-
+
{ backspaceRemoves={false} />
- {/*
*/} -
+
: null + } + ); } } diff --git a/src/lib/js/reference-list/components/ReferenceList.tsx b/src/lib/js/reference-list/components/ReferenceList.tsx index 3460037c..aaf3a5db 100644 --- a/src/lib/js/reference-list/components/ReferenceList.tsx +++ b/src/lib/js/reference-list/components/ReferenceList.tsx @@ -1,26 +1,30 @@ import * as React from 'react'; +import { observable, IObservableArray, reaction, action } from 'mobx'; +import { observer } from 'mobx-react'; +// import { TransitionMotion, spring } from 'react-motion'; import { EVENTS } from '../../utils/Constants'; import * as MCE from '../../utils/TinymceFunctions'; import { CSLProcessor } from '../../utils/CSLProcessor'; -import { observable, IObservableArray, reaction, action } from 'mobx'; -import { observer } from 'mobx-react'; import { getRemoteData, parseManualData } from '../API'; -import * as CSSTransitionGroup from 'react-addons-css-transition-group'; import DevTools, { configureDevtool } from '../../utils/DevTools'; - -const DevTool = DevTools(); -configureDevtool({ - logFilter: change => change.type === 'action', -}); - import { Store } from '../Store'; import { Menu } from './Menu'; import { PanelButton } from './PanelButton'; import { ItemList } from './ItemList'; import { Spinner } from '../../components/Spinner'; +const DevTool = DevTools(); +configureDevtool({ logFilter: change => change.type === 'action' }); + declare const tinyMCE: TinyMCE.MCE; -const { OPEN_REFERENCE_WINDOW, TINYMCE_READY, TINYMCE_HIDDEN, TINYMCE_VISIBLE, TOGGLE_PINNED_STATE } = EVENTS; +const { + OPEN_REFERENCE_WINDOW, + REFERENCE_EDITED, + TINYMCE_READY, + TINYMCE_HIDDEN, + TINYMCE_VISIBLE, + TOGGLE_PINNED_STATE, +} = EVENTS; @observer export class ReferenceList extends React.Component<{store: Store}, {}> { @@ -82,10 +86,11 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { componentDidMount() { addEventListener(OPEN_REFERENCE_WINDOW, this.openReferenceWindow); - addEventListener(TINYMCE_HIDDEN, () => this.toggleLoading(true)); + addEventListener(TINYMCE_HIDDEN, this.toggleLoading.bind(this, true)); addEventListener(TINYMCE_READY, this.initTinyMCE); - addEventListener(TINYMCE_VISIBLE, () => this.toggleLoading(false)); + addEventListener(TINYMCE_VISIBLE, this.toggleLoading.bind(this, false)); addEventListener(TOGGLE_PINNED_STATE, this.togglePinned); + addEventListener(REFERENCE_EDITED, this.initProcessor); addEventListener('scroll', this.handleScroll); } @@ -288,13 +293,13 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { this.editor.setProgressState(false); }) .catch(err => { + this.editor.setProgressState(false); Rollbar.error('ReferenceList.tsx -> insertInlineCitation', err); this.editor.windowManager.alert( `${this.errors.unexpected.message}.\n\n` + `${err.name}: ${err.message}\n\n` + `${this.errors.unexpected.reportInstructions}` ); - this.editor.setProgressState(false); }); this.clearSelection(); @@ -312,8 +317,6 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { @action openReferenceWindow = () => { MCE.referenceWindow(this.editor).then(payload => { - if (!payload) return; - const preprocess: Promise = payload.addManually ? parseManualData(payload) : getRemoteData(payload.identifierList, this.editor.windowManager); @@ -323,7 +326,6 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { if (data.length === 0) return; this.props.store.citations.addItems(data); - // FIXME: This needs to be heavily tested data = data.reduce((prev, curr) => { let matchingKey = ''; const title = curr.title.toLowerCase(); @@ -343,10 +345,9 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { } } - if (matchingKey !== '') - return [...prev, this.props.store.citations.CSL.get(matchingKey)]; - - return [...prev, curr]; + return matchingKey !== '' + ? [...prev, this.props.store.citations.CSL.get(matchingKey)] + : [...prev, curr]; }, []); if (!payload.attachInline) return; @@ -360,6 +361,10 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { `${this.errors.unexpected.reportInstructions}` ); }); + }) + .catch(err => { + if (!err) return; // User exited early + Rollbar.error('ReferenceList.tsx -> openReferenceWindow', err); }); } @@ -368,6 +373,7 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { if (!data) return; this.props.store.citations.addItems(data); }).catch(err => { + if (!err) return; // User exited early Rollbar.error('ReferenceList.tsx -> openImportWindow', err); this.editor.windowManager.alert( `${this.errors.unexpected.message}.\n\n` + @@ -603,23 +609,16 @@ export class ReferenceList extends React.Component<{store: Store}, {}> { />
- - { this.menuOpen && - 0} - cslStyle={this.props.store.citationStyle} - submitData={this.handleMenuSelection} - /> - } - + 0} + cslStyle={this.props.store.citationStyle} + submitData={this.handleMenuSelection} + /> { this.props.store.citations.cited.length > 0 && { { this.props.store.citations.uncited.length > 0 && { ); } } + +/* + { + return { + height: spring(0), + maxHeight: spring(0), + scale: spring(0), + }; + }} + willEnter={() => { + return { + height: 0, + maxHeight: 0, + scale: 0, + }; + }} + styles={this.menuOpen ? [{ + key: 'menu', + style: { + height: 85, + maxHeight: spring(85), + scale: spring(1), + }, + }] : []} +> + {styles => + styles.length > 0 ? + 0} + cslStyle={this.props.store.citationStyle} + submitData={this.handleMenuSelection} + /> : null + } + + */ diff --git a/src/lib/js/reference-list/components/__tests__/ItemList-test.tsx b/src/lib/js/reference-list/components/__tests__/ItemList-test.tsx index 9bf7f567..19d52180 100644 --- a/src/lib/js/reference-list/components/__tests__/ItemList-test.tsx +++ b/src/lib/js/reference-list/components/__tests__/ItemList-test.tsx @@ -1,12 +1,21 @@ +jest.mock('../../../utils/TinymceFunctions'); +jest.mock('../../API'); import * as React from 'react'; +import { map } from 'mobx'; import { mount } from 'enzyme'; import { ItemList } from '../ItemList'; +import { editReferenceWindow } from '../../../utils/TinymceFunctions'; +import { parseManualData } from '../../API'; + +const ERW = editReferenceWindow as jest.Mock; +const PMD = parseManualData as jest.Mock; const setup = (open = true, items = [{id: 'aaa'}, {id: 'bbb'}, {id: 'ccc'}]) => { const spy = jest.fn(); const component = mount( }; describe('', () => { + beforeAll(() => { + window.tinyMCE = { + EditorManager: { + get() { + return null; + } + } + } as any; + }); + beforeEach(() => { + ERW.mockImplementation(() => new Promise(res => res())); + PMD.mockImplementation(() => [{id: 'test-id', type: 'article-journal'}]); + }); it('should not render with no items', () => { const { component } = setup(true, null); expect(component.isEmptyRender()).toBe(true); @@ -49,4 +71,21 @@ describe('', () => { expect(spy).toHaveBeenCalledTimes(2); expect(spy.mock.calls[1]).toEqual(['test-id', true]); }); + it('should trigger editSingleReference on double click', () => { + const { component } = setup(); + const card = component.find('Card').first(); + card.simulate('doubleClick'); + }); + it('should handle errors on submit', () => { + ERW.mockImplementation(() => new Promise((_, __) => { throw new Error('Some unhandled error'); })); + const { component } = setup(); + const card = component.find('Card').first(); + card.simulate('doubleClick'); + }); + it('should handle cases where user exits early', () => { + ERW.mockImplementation(() => new Promise((_, rej) => rej())); + const { component } = setup(); + const card = component.find('Card').first(); + card.simulate('doubleClick'); + }); }); diff --git a/src/lib/js/reference-list/components/__tests__/Menu-test.tsx b/src/lib/js/reference-list/components/__tests__/Menu-test.tsx index c73d73a9..f1fffaf3 100644 --- a/src/lib/js/reference-list/components/__tests__/Menu-test.tsx +++ b/src/lib/js/reference-list/components/__tests__/Menu-test.tsx @@ -35,6 +35,7 @@ const setup = () => { const spy = jest.fn(); const component = mount( null }; +window['Rollbar'] = { error: jest.fn() }; window['tinyMCE'] = { editors: { content: { - setProgressState: () => null, + dom: { + doc: document, + getStyle: jest.fn(), + }, + insertContent: jest.fn(), + selection: { + getContent: jest.fn(), + }, + setProgressState: jest.fn(), + windowManager: { alert: jest.fn() }, }, }, } as any; +const mocks = { + alert: window['tinyMCE'].editors['content'].windowManager.alert as jest.Mock, + getContent: window['tinyMCE'].editors['content'].selection.getContent as jest.Mock, + getRemoteData: API.getRemoteData as jest.Mock, + getStyle: window['tinyMCE'].editors['content'].dom.getStyle as jest.Mock, + importWindow: MCE.importWindow as jest.Mock, + insertContent: window['tinyMCE'].editors['content'].insertContent as jest.Mock, + parseInlineCitations: MCE.parseInlineCitations as jest.Mock, + parseManualData: API.parseManualData as jest.Mock, + referenceWindow: MCE.referenceWindow as jest.Mock, + rollbar: window['Rollbar'].error as jest.Mock, + setBibliography: MCE.setBibliography as jest.Mock, + setProgressState: window['tinyMCE'].editors['content'].setProgressState as jest.Mock, +}; + const setup = () => { const store = new Store(reflistState as BackendGlobals.ABT_Reflist_State); const component = mount( ); + const instance = (component.instance() as any); return { component, - instance: (component.instance() as any), + instance, }; }; @@ -40,119 +66,120 @@ const setupDimentions = (scrollTop = 0, childHeights = [100, 100, 100]) => { uncited1.clientHeight = childHeights[2]; }; -const setupInstance = (instance, { - staticBibReturnBool = false, - getStyle = 'defined', - content = '', - staticBibError = false, -} = {}) => { - instance.selected = ['12345678']; - instance.processor.makeBibliography = () => [{ html: '
test
', id: '12345678' }]; - instance.processor.citeproc = { - opt: { - xclass: 'in-text', - }, - registry: { - citationreg: { - citationById: { - tester: {}, - }, - }, - }, - }; - instance.processor.init = () => new Promise(res => res(true)); - instance.editor = { - dom: { doc: document }, - selection: { - getContent: () => content, - }, - setProgressState: () => null, - windowManager: { alert: jest.fn() }, - }; - instance.processor.createStaticBibliography = staticBibError - ? () => new Promise((_res, rej) => { rej(new Error('test error')); }) - : () => new Promise(res => staticBibReturnBool ? res(false) : res([{ html: '
test
', id: '11111' }])); - switch (getStyle) { - case 'defined': - instance.editor.dom.getStyle = () => '0 0 50px'; - break; - case 'undefined': - instance.editor.dom.getStyle = () => undefined; - break; - default: - } - instance.editor.insertContent = () => null; -}; - describe('', () => { - before(() => { - const main = document.createElement('div'); - const cited = document.createElement('div'); - const citedChild1 = document.createElement('div'); - citedChild1.id = 'cited1'; - const citedChild2 = document.createElement('div'); - citedChild2.id = 'cited2'; - const uncited = document.createElement('div'); - const uncitedChild = document.createElement('div'); - uncitedChild.id = 'uncited1'; - main.id = 'abt-reflist'; - cited.id = 'cited'; - uncited.id = 'uncited'; - cited.appendChild(citedChild1); - cited.appendChild(citedChild2); - uncited.appendChild(uncitedChild); - main.appendChild(cited); - main.appendChild(uncited); - document.body.appendChild(main); + beforeAll(() => { + window['ABT_Custom_CSL'] = { value: null }; + }); + beforeEach(() => { + (jest as any).resetAllMocks(); + spyOn(CSLProcessor.prototype, 'makeBibliography').and.callFake(() => ''); + window['ABT_CitationStyles'] = [{ label: 'Test', value: 'american-medical-association' }]; + document.body.innerHTML = ` + +
+
+
+
+
+
+
+
+
+ `; }); it('should render with loading spinner', () => { const { component } = setup(); expect(component.find('Spinner').length).toBe(1); }); - - describe('insertStaticBibliography()', () => { - it('should work in standard form', () => { + describe('initProcessor', () => { + it('should init', () => { + spyOn(CSLProcessor.prototype, 'init').and.callFake(() => new Promise(res => res([[]]))); const { instance } = setup(); - setupInstance(instance); - instance.insertStaticBibliography(); + instance.processor.citeproc = { + opt: { xclass: 'in-text' }, + }; + mocks.parseInlineCitations.mockImplementationOnce(() => new Promise((res) => res())); + instance.initTinyMCE(); + instance.initProcessor(); + expect(instance.editor.setProgressState).toHaveBeenCalledTimes(2); }); - it('should throw/catch an error at getStyle - default margin to 0 0 28px', () => { + it('should handle errors on initialization', () => { + spyOn(CSLProcessor.prototype, 'init').and.callFake(() => new Promise((_, rej) => rej())); const { instance } = setup(); - setupInstance(instance, { - getStyle: '', + instance.processor.citeproc = { + opt: { xclass: 'in-text' }, + }; + instance.initTinyMCE(); + instance.initProcessor().catch(() => { + expect(mocks.rollbar).toHaveBeenCalledTimes(1); }); - instance.insertStaticBibliography(); }); - it('should default to margin 0 0 28px if getStyle returns falsy', () => { - const { instance } = setup(); - setupInstance(instance, { - getStyle: 'undefined', - }); - instance.insertStaticBibliography(); + }); + describe('insertStaticBibliography()', () => { + let instance; + beforeEach(() => { + spyOn(CSLProcessor.prototype, 'createStaticBibliography') + .and.callFake(() => new Promise(res => res([{id: 'test-id', html: '
Test Bib
'}]))); + spyOn(CSLProcessor.prototype, 'init').and.callFake(() => new Promise(res => res([[]]))); + (CSLProcessor.prototype.createStaticBibliography as jasmine.Spy).calls.reset(); + (CSLProcessor.prototype.init as jasmine.Spy).calls.reset(); + mocks.parseInlineCitations.mockImplementationOnce(() => new Promise((res) => res())); + ({ instance } = setup()); + instance.initTinyMCE(); }); - it('should alert and exit if boolean returned from createStaticBibliography', () => { - const { instance } = setup(); - setupInstance(instance, { - content: '
', - staticBibReturnBool: true, - }); - instance.insertStaticBibliography(); + it('should work in standard form', async () => { + instance.toggleSelect('citationId', false); + await instance.insertStaticBibliography(); + expect(instance.editor.insertContent).toHaveBeenCalledTimes(1); + expect(instance.editor.insertContent).toHaveBeenCalledWith( + `
` + + `
` + + `
Test Bib
` + + `
` + + `
` + ); }); - it('should throw error', () => { - spyOn(MCE, 'parseInlineCitations').and.returnValue( - new Promise(res => res(true)) + it('should throw/catch an error at getStyle - default margin to 0 0 28px', async () => { + mocks.getStyle.mockImplementationOnce(() => { throw 'error'; }); + await instance.insertStaticBibliography(); + expect(mocks.getStyle).toHaveBeenCalled(); + expect(mocks.insertContent).toHaveBeenCalledWith( + `
` + + `
` + + `
Test Bib
` + + `
` + + `
` ); - const { instance } = setup(); - setupInstance(instance, { - staticBibError: true, - }); - instance.insertStaticBibliography(); + }); + it('should alert and exit if boolean returned from createStaticBibliography', async () => { + (CSLProcessor.prototype.createStaticBibliography as jasmine.Spy) + .and.callFake(() => new Promise(res => res(false))); + await instance.insertStaticBibliography(); + expect(mocks.alert).toHaveBeenCalled(); + expect(mocks.insertContent).not.toHaveBeenCalled(); + }); + it('should handle errors', async () => { + spyOn(instance, 'clearSelection').and.throwError('test'); + await instance.insertStaticBibliography(); + }); + it('should overwrite an existing static bib if selected', async () => { + mocks.getContent.mockImplementationOnce( + () => '
' + ); + await instance.insertStaticBibliography(); + expect((CSLProcessor.prototype.createStaticBibliography as jasmine.Spy).calls.count()).toBe(1); + expect((CSLProcessor.prototype.createStaticBibliography as jasmine.Spy) + .calls.mostRecent().args[0][0].title).toBe('Test Title'); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.insertContent).toHaveBeenCalled(); }); }); describe('insertInlineCitation()', () => { - it('should preventDefault() and throw an error at the end', () => { + let instance; + beforeEach(() => { + spyOn(CSLProcessor.prototype, 'init').and.callFake(() => new Promise(res => res([[]]))); spyOn(MCE, 'getRelativeCitationPositions').and.returnValue({ currentIndex: 0, locations: [ @@ -160,31 +187,290 @@ describe('', () => { [['22222222', 2]], ], }); + spyOn(CSLProcessor.prototype, 'prepareInlineCitationData').and.returnValue({}); + mocks.parseInlineCitations.mockImplementationOnce(() => new Promise((res) => res())); + ({ instance } = setup()); + instance.initTinyMCE(); + instance.processor.citeproc = { + opt: { + xclass: 'in-text', + }, + registry: { + citationreg: { + citationById: { + citation: {}, + } + } + } + }; + (CSLProcessor.prototype.init as jasmine.Spy).calls.reset(); + (CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy).calls.reset(); + (MCE.getRelativeCitationPositions as jasmine.Spy).calls.reset(); + (jest as any).resetAllMocks(); + }); + it('should insert citation from selection', async () => { + spyOn(MCE, 'parseInlineCitations').and.returnValue(new Promise(res => res(true))); + instance.toggleSelect('aaaaaaaa', false); + const mockEvent = jest.fn(); + await instance.insertInlineCitation({ preventDefault: mockEvent }); + expect(mockEvent).toHaveBeenCalled(); + expect(mocks.setBibliography).toHaveBeenCalled(); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.setProgressState.mock.calls[0][0]).toBe(true); + expect(mocks.setProgressState.mock.calls[1][0]).toBe(false); + expect(mocks.rollbar).not.toHaveBeenCalled(); + }); + it('should insert citation from array passed to function', async () => { + spyOn(MCE, 'parseInlineCitations').and.returnValue(new Promise(res => res(true))); + const expectedData = [{id: 'fakeid', title: 'faketitle'}]; + await instance.insertInlineCitation(null, expectedData); + expect((CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy).calls.count()).toBe(1); + expect((CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy) + .calls.first().args[0]).toEqual(expectedData); + expect(mocks.setBibliography).toHaveBeenCalled(); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.setProgressState.mock.calls[0][0]).toBe(true); + expect(mocks.setProgressState.mock.calls[1][0]).toBe(false); + expect(mocks.rollbar).not.toHaveBeenCalled(); + }); + it('should merge selected citations if selected', async () => { spyOn(MCE, 'parseInlineCitations').and.returnValue(new Promise(res => res(true))); - const spy = jest.fn(); + instance.toggleSelect('aaaaaaaa', false); + mocks.getContent.mockImplementationOnce( + () => '' + ); + await instance.insertInlineCitation(); + expect((CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy).calls.count()).toBe(1); + expect((CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy) + .calls.mostRecent().args[0][0].title).toBe('Test Title'); + expect((CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy) + .calls.mostRecent().args[0][1].title).toBe('Other Test Title'); + expect(mocks.setBibliography).toHaveBeenCalled(); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.setProgressState.mock.calls[0][0]).toBe(true); + expect(mocks.setProgressState.mock.calls[1][0]).toBe(false); + expect(mocks.rollbar).not.toHaveBeenCalled(); + }); + it('should handle unexpected errors', async () => { + (CSLProcessor.prototype.prepareInlineCitationData as jasmine.Spy).and.throwError('Unexpected'); + await instance.insertInlineCitation(); + expect(mocks.rollbar).toHaveBeenCalled(); + expect(mocks.alert).toHaveBeenCalled(); + expect(mocks.setBibliography).not.toHaveBeenCalled(); + }); + it('should handle promise rejections', async () => { + spyOn(MCE, 'parseInlineCitations').and.returnValue(new Promise((_, rej) => rej())); + await instance.insertInlineCitation(); + }); + }); + + describe('openReferenceWindow', () => { + beforeEach(() => { + (jest as any).resetAllMocks(); + mocks.getRemoteData.mockImplementation( + () => new Promise(res => res([{id: 'yyyyyyyy', title: 'New Title', author: []}])) + ); + mocks.parseManualData.mockImplementation( + () => new Promise(res => res([{id: 'bbbbbbbb', title: 'Test Title', author: []}])) + ); + }); + afterEach(() => { + (jest as any).resetAllMocks(); + }); + it('should handle non-duplicate manual data', async () => { const { instance } = setup(); - setupInstance(instance); - instance.insertInlineCitation({ preventDefault: spy }); - expect(spy).toHaveBeenCalledTimes(1); + instance.insertInlineCitation = jest.fn(); + instance.editor = window['tinyMCE'].editors['content']; + mocks.referenceWindow.mockImplementation( + () => new Promise(res => res({addManually: true})) + ); + await instance.openReferenceWindow(); + expect(mocks.parseManualData).toHaveBeenCalled(); + expect(mocks.getRemoteData).not.toHaveBeenCalled(); }); - it('should match the regex pattern and throw an error at clusters', () => { + it('should handle duplicate remote data, and attach inline', async () => { const { instance } = setup(); - setupInstance(instance, { - content: 'test', - }); - spyOn(MCE, 'getRelativeCitationPositions').and.throwError('testing'); - instance.insertInlineCitation(); + instance.insertInlineCitation = jest.fn(); + instance.editor = window['tinyMCE'].editors['content']; + mocks.referenceWindow.mockImplementation( + () => new Promise(res => res({attachInline: true, addManually: false})) + ); + await instance.openReferenceWindow(); + expect(mocks.getRemoteData).toHaveBeenCalled(); + expect(mocks.parseManualData).not.toHaveBeenCalled(); + }); + it('should handle errors', async () => { + const { instance } = setup(); + instance.insertInlineCitation = jest.fn(); + instance.editor = window['tinyMCE'].editors['content']; + mocks.referenceWindow.mockImplementation( + () => new Promise(res => res()) + ); + await instance.openReferenceWindow(); + mocks.referenceWindow.mockImplementation( + () => new Promise(res => res({addManually: true})) + ); + mocks.parseManualData.mockImplementation( + () => new Promise(res => res()) + ); + await instance.openReferenceWindow(); + expect(mocks.rollbar).toHaveBeenCalled(); + }); + it('should exit if data has 0 length', async () => { + mocks.referenceWindow.mockImplementation( + () => new Promise(res => res({addManually: true})) + ); + mocks.parseManualData.mockImplementation( + () => new Promise(res => res([])) + ); + const { instance } = setup(); + instance.insertInlineCitation = jest.fn(); + instance.editor = window['tinyMCE'].editors['content']; + await instance.openReferenceWindow(); + }); + it('should exit if user exits early', async () => { + const { instance } = setup(); + mocks.referenceWindow.mockImplementation( + () => new Promise((_, rej) => rej()) + ); + await instance.openReferenceWindow(); + expect(mocks.alert).not.toHaveBeenCalled(); + }); + }); + + describe('openImportWindow', () => { + it('should exit if data is empty', () => { + mocks.importWindow.mockImplementation(() => new Promise(res => res())); + const { instance } = setup(); + expect(instance.props.store.citations.CSL.keys().length).toBe(3); + instance.openImportWindow(); + expect(instance.props.store.citations.CSL.keys().length).toBe(3); + }); + it('should import citations', async () => { + mocks.importWindow.mockImplementation( + () => new Promise(res => res([{id: 'zzzzzzzz', title: 'Imported Citation'}])) + ); + const { instance } = setup(); + expect(instance.props.store.citations.CSL.keys().length).toBe(3); + await instance.openImportWindow(); + expect(instance.props.store.citations.CSL.keys().length).toBe(4); + }); + it('should handle errors', async () => { + mocks.importWindow.mockImplementation( + () => new Promise((_, rej) => rej('error')) + ); + const { instance } = setup(); + expect(mocks.alert).not.toHaveBeenCalled(); + expect(mocks.rollbar).not.toHaveBeenCalled(); + await instance.openImportWindow(); + // expect(mocks.alert).toHaveBeenCalled(); + // expect(mocks.rollbar).toHaveBeenCalled(); + }); + it('should handle when user exits early', async () => { + mocks.importWindow.mockImplementation( + () => new Promise((_, rej) => rej()) + ); + const { instance } = setup(); + await instance.openImportWindow(); + // expect(mocks.alert).not.toHaveBeenCalled(); + // expect(mocks.rollbar).not.toHaveBeenCalled(); + }); + }); + + describe('handleMenuSelection', () => { + it('should return if "kind" is empty or invalid', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.handleMenuSelection(); + expect(instance.initProcessor).not.toHaveBeenCalled(); + expect(instance.openImportWindow).not.toHaveBeenCalled(); + expect(instance.reset).not.toHaveBeenCalled(); + expect(instance.insertStaticBibliography).not.toHaveBeenCalled(); + }); + it('should change style', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.handleMenuSelection('CHANGE_STYLE', 'apa-5th'); + expect(instance.props.store.citationStyle).toBe('apa-5th'); + expect(instance.initProcessor).toHaveBeenCalled(); + expect(instance.openImportWindow).not.toHaveBeenCalled(); + expect(instance.reset).not.toHaveBeenCalled(); + expect(instance.insertStaticBibliography).not.toHaveBeenCalled(); + }); + it('should trigger import window', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.handleMenuSelection('IMPORT_RIS'); + expect(instance.initProcessor).not.toHaveBeenCalled(); + expect(instance.openImportWindow).toHaveBeenCalled(); + expect(instance.reset).not.toHaveBeenCalled(); + expect(instance.insertStaticBibliography).not.toHaveBeenCalled(); + }); + it('should trigger refresh', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.editor = window['tinyMCE'].editors['content']; + expect(instance.props.store.citations.byIndex.length).toBe(2); + + instance.handleMenuSelection('REFRESH_PROCESSOR'); + expect(instance.initProcessor).toHaveBeenCalled(); + expect(instance.openImportWindow).not.toHaveBeenCalled(); + expect(instance.reset).not.toHaveBeenCalled(); + expect(instance.insertStaticBibliography).not.toHaveBeenCalled(); + expect(instance.props.store.citations.byIndex.length).toBe(1); + }); + it('should trigger reset', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.handleMenuSelection('DESTROY_PROCESSOR'); + expect(instance.initProcessor).not.toHaveBeenCalled(); + expect(instance.openImportWindow).not.toHaveBeenCalled(); + expect(instance.reset).toHaveBeenCalled(); + expect(instance.insertStaticBibliography).not.toHaveBeenCalled(); + }); + it('should trigger insertStaticBibliography', () => { + const { instance } = setup(); + instance.initProcessor = jest.fn(); + instance.openImportWindow = jest.fn(); + instance.reset = jest.fn(); + instance.insertStaticBibliography = jest.fn(); + instance.handleMenuSelection('INSERT_STATIC_BIBLIOGRAPHY'); + expect(instance.initProcessor).not.toHaveBeenCalled(); + expect(instance.openImportWindow).not.toHaveBeenCalled(); + expect(instance.reset).not.toHaveBeenCalled(); + expect(instance.insertStaticBibliography).toHaveBeenCalled(); }); }); describe('@actions', () => { + beforeEach(() => { + (jest as any).resetAllMocks(); + }); it('deleteCitations', () => { const { instance } = setup(); - setupInstance(instance); + instance.editor = window['tinyMCE'].editors['content']; + instance.initProcessor = jest.fn(); + instance.toggleSelect('aaaaaaaa', false); + expect(instance.props.store.citations.cited.length).toBe(2); instance.deleteCitations(); - instance.deleteCitations({ preventDefault: () => null }); - instance.selected = ['12345678']; instance.deleteCitations(); + expect(instance.props.store.citations.cited.length).toBe(1); + expect(instance.initProcessor).toHaveBeenCalledTimes(1); }); it('toggleLoading()', () => { const { instance } = setup(); @@ -238,14 +524,12 @@ describe('', () => { expect(instance.menuOpen).toBe(false); expect(component.find('.dashicons.dashicons-menu.hamburger-menu').length).toBe(1); expect(component.find('.dashicons.dashicons-no-alt.hamburger-menu').length).toBe(0); - expect(component.find('Menu').length).toBe(0); // Toggle open instance.toggleMenu(); expect(instance.menuOpen).toBe(true); expect(component.find('.dashicons.dashicons-menu.hamburger-menu').length).toBe(0); expect(component.find('.dashicons.dashicons-no-alt.hamburger-menu').length).toBe(1); - expect(component.find('Menu').length).toBe(1); // Toggle Closed (timeout due to CSS transition time) instance.toggleMenu(); @@ -256,7 +540,6 @@ describe('', () => { expect(instance.menuOpen).toBe(false); expect(component.find('.dashicons.dashicons-menu.hamburger-menu').length).toBe(1); expect(component.find('.dashicons.dashicons-no-alt.hamburger-menu').length).toBe(0); - expect(component.find('Menu').length).toBe(0); }); }); it('toggleList()', () => { @@ -350,5 +633,15 @@ describe('', () => { expect(instance.citedListUI.maxHeight).toBe('102px'); expect(instance.uncitedListUI.maxHeight).toBe('347px'); }); + it('reset()', () => { + (MCE.reset as jest.Mock).mockImplementationOnce(() => null); + const { instance } = setup(); + instance.editor = window['tinyMCE'].editors['content']; + instance.initProcessor = jest.fn(); + instance.reset(); + expect((MCE.reset) as jest.Mock).toHaveBeenCalled(); + expect(mocks.setProgressState).toHaveBeenCalled(); + expect(instance.initProcessor).toHaveBeenCalled(); + }); }); }); diff --git a/src/lib/js/tinymce/components/edit-reference-window/EditReferenceWindow.tsx b/src/lib/js/tinymce/components/edit-reference-window/EditReferenceWindow.tsx new file mode 100644 index 00000000..66487f2f --- /dev/null +++ b/src/lib/js/tinymce/components/edit-reference-window/EditReferenceWindow.tsx @@ -0,0 +1,98 @@ +import * as React from 'react'; +import { observable, map, action, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import { People } from '../reference-window/components/People'; +import { MetaFields } from '../reference-window/components/MetaFields'; +import { Modal } from '../../../utils/Modal'; +import { PERSON_TYPE_KEYS, DATE_TYPE_KEYS } from '../../../utils/Constants'; +import DevTools from '../../../utils/DevTools'; + +const DevTool = DevTools(); + +@observer +export class EditReferenceWindow extends React.Component<{}, {}> { + + labels = top.ABT_i18n.tinymce.editReferenceWindow; + modal: Modal = new Modal(this.labels.title); + params = top.tinyMCE.activeEditor.windowManager.windows[0].settings.params; + + @observable + people = observable([]); + + @observable + primitives = map(); + + @observable + loading = true; + + constructor(props) { + super(props); + this.initialize(); + } + + componentDidMount() { + this.modal.resize(); + reaction( + () => [this.people.length, this.loading], + () => this.modal.resize(), + false, + 100 + ); + } + + @action + initialize = () => { + for (const key of Object.keys(this.params.reference)) { + if (typeof this.params.reference[key] !== 'object') { + this.primitives.set(key, `${this.params.reference[key]}`); + } + if (DATE_TYPE_KEYS.indexOf(key) > -1) { + this.primitives.set(key, this.params.reference[key]['date-parts'][0].join('/')); + } + if (PERSON_TYPE_KEYS.indexOf(key) > -1) { + for (const person of this.params.reference[key]) { + const p: CSL.TypedPerson = {...person, type: key}; + this.people.push(p); + } + } + } + this.loading = false; + } + + handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const wm = top.tinyMCE.activeEditor.windowManager; + wm.setParams({ + data: { + manualData: this.primitives.toJS(), + people: this.people.slice(), + } + }); + wm.close(); + } + + render() { + if (this.loading) return null; + + return ( +
+ + + +
+
+ +
+
+ + ); + } +} diff --git a/src/lib/js/tinymce/components/edit-reference-window/__tests__/EditReferenceWindow-test.tsx b/src/lib/js/tinymce/components/edit-reference-window/__tests__/EditReferenceWindow-test.tsx new file mode 100644 index 00000000..40ea5c76 --- /dev/null +++ b/src/lib/js/tinymce/components/edit-reference-window/__tests__/EditReferenceWindow-test.tsx @@ -0,0 +1,87 @@ +jest.mock('../../../../utils/Modal'); +import * as React from 'react'; +import { mount } from 'enzyme'; +import { EditReferenceWindow } from '../EditReferenceWindow'; + +describe('', () => { + beforeAll(() => { + top.tinyMCE = { + activeEditor: { + windowManager: { + close: jest.fn(), + setParams: jest.fn(), + windows: [ + { + settings: { + params: { + reference: { + author: [ + { family: 'Doe', given: 'John' }, + { family: 'Smith', given: 'Jane' }, + ], + id: 'test-id', + issued: { + 'date-parts': [ + ['2016', '05', '23'], + ] + }, + title: 'Test Title', + type: 'article-journal', + }, + } + } + } + ], + } + } + } as any; + }); + + const setup = () => { + const component = mount( + + ); + return { + component, + instance: component.instance() as any, + wm: top.tinyMCE.activeEditor.windowManager, + }; + }; + + it('should initialize', () => { + const { instance } = setup(); + expect(instance.people.length).toBe(2); + expect(instance.primitives.get('issued')).toBe('2016/05/23'); + }); + it('should trigger empty render if loading', () => { + const { component, instance } = setup(); + instance.loading = true; + expect(component.isEmptyRender()).toBe(true); + }); + it('should submit on click', () => { + const expected = { + data: { + manualData: { + id: 'test-id', + issued: '2016/05/23', + title: 'Test Title', + type: 'article-journal', + }, + people: [ + { family: 'Doe', given: 'John', type: 'author' }, + { family: 'Smith', given: 'Jane', type: 'author' }, + ], + } + }; + + const { component, wm } = setup(); + const form = component.find('form'); + const setParams = wm.setParams as jest.Mock<{}>; + const close = wm.close as jest.Mock<{}>; + form.simulate('submit'); + + expect(setParams).toHaveBeenCalledTimes(1); + expect(setParams).toHaveBeenCalledWith(expected); + expect(close).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/lib/js/tinymce/components/edit-reference-window/index.tsx b/src/lib/js/tinymce/components/edit-reference-window/index.tsx new file mode 100644 index 00000000..4eac612e --- /dev/null +++ b/src/lib/js/tinymce/components/edit-reference-window/index.tsx @@ -0,0 +1,12 @@ +import 'babel-polyfill'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { useStrict } from 'mobx'; +import { EditReferenceWindow } from './EditReferenceWindow'; + +useStrict(true); + +ReactDOM.render( + , + document.getElementById('abt-root') +); diff --git a/src/lib/js/tinymce/components/pubmed-window/PubmedWindow.tsx b/src/lib/js/tinymce/components/pubmed-window/PubmedWindow.tsx index 3bc6f899..b9cfeb89 100644 --- a/src/lib/js/tinymce/components/pubmed-window/PubmedWindow.tsx +++ b/src/lib/js/tinymce/components/pubmed-window/PubmedWindow.tsx @@ -76,7 +76,10 @@ export class PubmedWindow extends React.Component<{}, {}> { e.preventDefault(); pubmedQuery(this.query) .then(this.consumeQueryData) - .catch(err => top.tinyMCE.activeEditor.windowManager.alert(err.message)); + .catch(err => { + this.toggleLoadState(); + top.tinyMCE.activeEditor.windowManager.alert(err.message); + }); } preventScrollPropagation = (e: React.WheelEvent) => { diff --git a/src/lib/js/tinymce/components/pubmed-window/__tests__/PubmedWindow-test.tsx b/src/lib/js/tinymce/components/pubmed-window/__tests__/PubmedWindow-test.tsx new file mode 100644 index 00000000..eddd9dbd --- /dev/null +++ b/src/lib/js/tinymce/components/pubmed-window/__tests__/PubmedWindow-test.tsx @@ -0,0 +1,96 @@ +jest.mock('../../../../utils/Modal'); +jest.mock('../../../../utils/resolvers/'); +jest.mock('../ResultList'); + +import * as React from 'react'; +import { mount } from 'enzyme'; +import { PubmedWindow} from '../PubmedWindow'; +import { pubmedQuery } from '../../../../utils/resolvers/'; + +window['tinyMCE'] = { + activeEditor: { + windowManager: { + alert: jest.fn(), + windows: [ + { data: {}, submit: jest.fn() }, + ], + } + } +} as any; + +const mocks = { + alert: window['tinyMCE'].activeEditor.windowManager.alert, + pmq: pubmedQuery as jest.Mock, + submit: window['tinyMCE'].activeEditor.windowManager.windows[0].submit, +}; + +const setup = () => { + const component = mount( + + ); + const instance = component.instance() as any; + return { + component, + instance, + }; +}; + +describe('', () => { + it('should render with loading spinner', () => { + const { component, instance } = setup(); + instance.toggleLoadState(); + expect(instance.isLoading).toBe(true); + expect(component.find('Spinner').length).toBe(1); + }); + it('should update query on input change', () => { + const { component, instance } = setup(); + const input = component.find('input[type="text"]'); + expect(input.props().value).toBe(''); + input.simulate('change', { currentTarget: { value: 'TESTING' }}); + instance.query = 'TESTING'; // Issue with enzyme + expect(component.find('input[type="text"]').props().value).toBe('TESTING'); + }); + it('should handle scroll', () => { + const { component } = setup(); + const root = component.first(); + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + root.simulate('wheel', { preventDefault, stopPropagation }); + expect(stopPropagation).toHaveBeenCalled(); + expect(preventDefault).toHaveBeenCalled(); + }); + it('should handle queries', async () => { + mocks.pmq.mockImplementation(() => new Promise(res => res([{title: 'testing'}]))); + const { instance, component } = setup(); + const form = component.find('form'); + await form.simulate('submit'); + expect(instance.results.slice()).toEqual([{title: 'testing'}]); + }); + it('should change page', () => { + const { instance } = setup(); + expect(instance.page).toBe(0); + instance.changePage(1); + expect(instance.page).toBe(1); + }); + it('should deliver the pmid', () => { + const { instance } = setup(); + instance.deliverPMID('1234567'); + expect(window['tinyMCE'].activeEditor.windowManager.windows[0].data).toEqual({pmid: '1234567'}); + expect(mocks.submit).toHaveBeenCalled(); + }); + it('should handle errors', async () => { + mocks.pmq.mockImplementation(() => new Promise((_, rej) => rej())); + const { component, instance } = setup(); + const form = component.find('form'); + await form.simulate('submit'); + expect(instance.results.slice()).toEqual([]); + }); + it('should handle situations where the query returns zero results', async () => { + mocks.pmq.mockImplementation(() => new Promise(res => res([]))); + const { instance, component } = setup(); + const form = component.find('form'); + await form.simulate('submit'); + expect(instance.results.slice()).toEqual([]); + expect(mocks.alert).toHaveBeenCalled(); + }); +}); diff --git a/src/lib/js/tinymce/components/reference-window/components/ManualEntryContainer.tsx b/src/lib/js/tinymce/components/reference-window/components/ManualEntryContainer.tsx index bf5f699c..77fabb71 100644 --- a/src/lib/js/tinymce/components/reference-window/components/ManualEntryContainer.tsx +++ b/src/lib/js/tinymce/components/reference-window/components/ManualEntryContainer.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { observable, ObservableMap, action } from 'mobx'; +import { observable, ObservableMap, action, IObservableArray } from 'mobx'; import { observer } from 'mobx-react'; import { preventScrollPropagation } from '../../../../utils/helpers/'; @@ -10,11 +10,8 @@ import { Spinner } from '../../../../components/Spinner'; interface ManualEntryProps { loading: boolean; manualData: ObservableMap; - people: CSL.TypedPerson[]; + people: IObservableArray; autoCite(kind: 'webpage'|'book'|'chapter', query: string): void; - addPerson(): void; - changePerson(index: string, field: string, value: string): void; - removePerson(index: string): void; typeChange(citationType: string): void; } @@ -96,9 +93,6 @@ export class ManualEntryContainer extends React.PureComponent } diff --git a/src/lib/js/tinymce/components/reference-window/components/People.tsx b/src/lib/js/tinymce/components/reference-window/components/People.tsx index e26d7331..1a3446c1 100644 --- a/src/lib/js/tinymce/components/reference-window/components/People.tsx +++ b/src/lib/js/tinymce/components/reference-window/components/People.tsx @@ -1,12 +1,10 @@ import * as React from 'react'; +import { action, IObservableArray } from 'mobx'; import { observer } from 'mobx-react'; interface PeopleProps { - people: CSL.TypedPerson[]; + people: IObservableArray; citationType: CSL.CitationType; - addPerson(): void; - changePerson(index: string, field: string, value: string): void; - removePerson(index: string): void; } @observer @@ -15,20 +13,23 @@ export class People extends React.PureComponent { fieldmaps = top.ABT_i18n.fieldmaps; labels = top.ABT_i18n.tinymce.referenceWindow.people; - handleChange = (e: React.FormEvent) => { - this.props.changePerson( - e.currentTarget.getAttribute('data-index'), - e.currentTarget.getAttribute('data-field'), - e.currentTarget.value, - ); + @action + addPerson = () => { + this.props.people.push({ family: '', given: '', type: 'author' }); } - handleAddPerson = () => { - this.props.addPerson(); + @action + removePerson = (e: React.MouseEvent) => { + const index = e.currentTarget.getAttribute('data-index'); + this.props.people.remove(this.props.people[index]); } - handleRemovePerson = (e: React.MouseEvent) => { - this.props.removePerson(e.currentTarget.getAttribute('data-index')); + @action + updatePerson = (e: React.FormEvent) => { + const index = e.currentTarget.getAttribute('data-index'); + const field = e.currentTarget.getAttribute('data-field'); + const value = e.currentTarget.value; + this.props.people[index][field] = value; } render() { @@ -41,8 +42,8 @@ export class People extends React.PureComponent {
{this.props.people.map((person: CSL.TypedPerson, i: number) => { id="add-person" className="abt-btn abt-btn_flat" value={this.labels.add} - onClick={this.handleAddPerson} + onClick={this.addPerson} />
diff --git a/src/lib/js/tinymce/components/reference-window/components/ReferenceWindow.tsx b/src/lib/js/tinymce/components/reference-window/components/ReferenceWindow.tsx index d79c65ba..8fbba0d7 100644 --- a/src/lib/js/tinymce/components/reference-window/components/ReferenceWindow.tsx +++ b/src/lib/js/tinymce/components/reference-window/components/ReferenceWindow.tsx @@ -1,14 +1,12 @@ import * as React from 'react'; import { Modal } from '../../../../utils/Modal'; -import { observable, computed, IObservableArray, reaction, map, action, toJS } from 'mobx'; +import { observable, computed, reaction, map, action, toJS } from 'mobx'; import { observer } from 'mobx-react'; import { getFromURL, getFromISBN } from '../../../../utils/resolvers/'; import DevTools, { configureDevtool } from '../../../../utils/DevTools'; const DevTool = DevTools(); -configureDevtool({ - logFilter: change => change.type === 'action', -}); +configureDevtool({ logFilter: change => change.type === 'action' }); import { IdentifierInput } from './IdentifierInput'; import { ManualEntryContainer } from './ManualEntryContainer'; @@ -36,7 +34,7 @@ export class ReferenceWindow extends React.Component<{}, {}> { manualData = map([['type', 'webpage']]); @observable - people: IObservableArray = observable([ + people = observable([ { family: '', given: '', type: 'author' } as CSL.TypedPerson, ]); @@ -51,11 +49,6 @@ export class ReferenceWindow extends React.Component<{}, {}> { }; }; - @action - addPerson = () => { - this.people.push({ family: '', given: '', type: 'author' }); - } - @action appendPMID = (pmid: string) => { this.identifierList = this.identifierList @@ -111,11 +104,6 @@ export class ReferenceWindow extends React.Component<{}, {}> { this.people.replace([{ family: '', given: '', type: 'author' }]); } - @action - removePerson = (index: string) => { - this.people.remove(this.people[index]); - } - @action toggleAttachInline = () => { this.attachInline = !this.attachInline; @@ -135,11 +123,6 @@ export class ReferenceWindow extends React.Component<{}, {}> { this.changeType('webpage'); } - @action - updatePerson = (index: string, field: string, value: string) => { - this.people[index][field] = value; - } - componentDidMount() { this.modal.resize(); reaction( @@ -199,13 +182,10 @@ export class ReferenceWindow extends React.Component<{}, {}> { } { this.addManually && } diff --git a/src/lib/js/tinymce/components/reference-window/components/__tests__/ManualEntryContainer-test.tsx b/src/lib/js/tinymce/components/reference-window/components/__tests__/ManualEntryContainer-test.tsx index 392037cc..02689d91 100644 --- a/src/lib/js/tinymce/components/reference-window/components/__tests__/ManualEntryContainer-test.tsx +++ b/src/lib/js/tinymce/components/reference-window/components/__tests__/ManualEntryContainer-test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { mount } from 'enzyme'; -import { map } from 'mobx'; +import { map, observable } from 'mobx'; import { ManualEntryContainer, AutoCite } from '../ManualEntryContainer'; const setup = ( @@ -11,12 +11,9 @@ const setup = ( const component = mount( ); diff --git a/src/lib/js/tinymce/components/reference-window/components/__tests__/People-test.tsx b/src/lib/js/tinymce/components/reference-window/components/__tests__/People-test.tsx index a69e24ea..4f431810 100644 --- a/src/lib/js/tinymce/components/reference-window/components/__tests__/People-test.tsx +++ b/src/lib/js/tinymce/components/reference-window/components/__tests__/People-test.tsx @@ -1,61 +1,84 @@ import * as React from 'react'; +import { observable, IObservableArray } from 'mobx'; import { mount } from 'enzyme'; import { People } from '../People'; +const peopleStore: IObservableArray = observable([]); + const setup = ( citationType: CSL.CitationType = 'article-journal', people: CSL.TypedPerson[] = [{family: 'Doe', given: 'John', type: 'author'}] ) => { - const addPerson = jest.fn(); - const changePerson = jest.fn(); - const removePerson = jest.fn(); + peopleStore.replace(people); const component = mount( ); return { - addPerson, - changePerson, - removePerson, - component, addButton: component.find('#add-person'), - removeButton: component.find('.abt-btn.abt-btn_flat.abt-btn_icon'), - select: component.find('select'), + component, + instance: component.instance(), }; }; describe('', () => { - it('should call addPerson on click', () => { - const { addButton, addPerson } = setup(); + it('should render with a single person', () => { + const { component } = setup(); + expect(component.find('Person').length).toBe(1); + + const person = component.find('Person').first(); + expect(person.find('#person-family-0').props().value).toBe('Doe'); + expect(person.find('#person-given-0').props().value).toBe('John'); + }); + it('should add an empty author when "Add Contributor" is clicked', () => { + const { component, addButton } = setup(); + expect(component.find('Person').length).toBe(1); addButton.simulate('click'); - addButton.simulate('click'); - addButton.simulate('click'); + expect(component.find('Person').length).toBe(2); - expect(addPerson).toHaveBeenCalledTimes(3); + const newPerson = component.find('Person').at(1); + expect(newPerson.find('#person-family-1').props().value).toBe(''); + expect(newPerson.find('#person-given-1').props().value).toBe(''); + expect(newPerson.find('select').props().value).toBe('author'); }); - it('should call removePerson on remove button click', () => { - const { removeButton, removePerson } = setup(); + it('should remove a person when remove button is clicked', () => { + const people = [ + {family: 'Doe', given: 'John', type: 'author'}, + {family: 'Smith', given: 'Jane', type: 'author'}, + ]; + const { component } = setup('article-journal', people as CSL.TypedPerson[]); + expect(component.find('Person').length).toBe(2); + + const removeButton = component.find('input[type="button"][value="⨯"]').at(1); removeButton.simulate('click'); - expect(removePerson).toHaveBeenCalledTimes(1); + + expect(component.find('Person').length).toBe(1); + const person = component.find('Person').first(); + expect(person.find('#person-family-0').props().value).toBe('Doe'); + expect(person.find('#person-given-0').props().value).toBe('John'); }); - it('should call changePerson appropriately when input fields are changed', () => { + it('should update fields when data is changed', () => { + const { component } = setup(); + const person = component.find('Person').first(); + const familyNameInput = person.find('#person-family-0'); + const givenNameInput = person.find('#person-given-0'); - const { component, changePerson } = setup(); + expect(familyNameInput.props().value).toBe('Doe'); + expect(givenNameInput.props().value).toBe('John'); - const firstNameInput = component.find('#person-given-0'); - const lastNameInput = component.find('#person-family-0'); + familyNameInput.simulate('change', { + currentTarget: { value: 'FAMILYNAMETEST' } + }); + givenNameInput.simulate('change', { + currentTarget: { value: 'GIVENNAMETEST' } + }); - firstNameInput.simulate('change'); - lastNameInput.simulate('change'); + peopleStore.replace([{family: 'FAMILYNAMETEST', given: 'GIVENNAMETEST', type: 'author'}]); - expect(changePerson).toHaveBeenCalledTimes(2); - expect(changePerson.mock.calls[0]).toEqual(['0', 'given', 'John']); - expect(changePerson.mock.calls[1]).toEqual(['0', 'family', 'Doe']); + expect(familyNameInput.props().value).toBe('FAMILYNAMETEST'); + expect(givenNameInput.props().value).toBe('GIVENNAMETEST'); }); }); diff --git a/src/lib/js/tinymce/components/reference-window/components/__tests__/ReferenceWindow-test.tsx b/src/lib/js/tinymce/components/reference-window/components/__tests__/ReferenceWindow-test.tsx new file mode 100644 index 00000000..1468d770 --- /dev/null +++ b/src/lib/js/tinymce/components/reference-window/components/__tests__/ReferenceWindow-test.tsx @@ -0,0 +1,191 @@ +jest.mock('../../../../../utils/Modal'); +jest.mock('../../../../../utils/resolvers/'); + +import * as React from 'react'; +import { mount } from 'enzyme'; +import { ReferenceWindow } from '../ReferenceWindow'; +import { getFromURL, getFromISBN } from '../../../../../utils/resolvers/'; + +const setup = () => { + const component = mount( + + ); + const instance = component.instance() as any; + return { + component, + instance, + }; +}; + +window['tinyMCE'] = { + activeEditor: { + windowManager: { + alert: jest.fn(), + close: jest.fn(), + setParams: jest.fn(), + } + } +} as any; + +const mocks = { + alert: window['tinyMCE'].activeEditor.windowManager.alert, + close: window['tinyMCE'].activeEditor.windowManager.close, + getFromISBN: getFromISBN as jest.Mock, + getFromURL: getFromURL as jest.Mock, + setParams: window['tinyMCE'].activeEditor.windowManager.setParams, +}; + +describe('', () => { + it('should render with manual reference input hidden', () => { + const { component, instance } = setup(); + expect(instance.addManually).toBe(false); + expect(component.find('ManualEntryContainer').length).toBe(0); + }); + it('should toggle manual reference input when "addManually" is true', () => { + const { component, instance } = setup(); + expect(component.find('ManualEntryContainer').length).toBe(0); + instance.toggleAddManual(); + + expect(instance.addManually).toBe(true); + expect(instance.manualData.get('type')).toBe('webpage'); + expect(component.find('ManualEntryContainer').length).toBe(1); + }); + it('should handle text field change', () => { + const { instance } = setup(); + expect(instance.identifierList).toBe(''); + instance.changeIdentifiers('12345'); + expect(instance.identifierList).toBe('12345'); + }); + it('should toggle attachInline', () => { + const { instance } = setup(); + expect(instance.attachInline).toBe(true); + instance.toggleAttachInline(); + expect(instance.attachInline).toBe(false); + }); + it('appendPMID()', () => { + const { instance } = setup(); + expect(instance.identifierList).toBe(''); + instance.appendPMID('12345'); + expect(instance.identifierList).toBe('12345'); + }); + it('should toggle loading state', () => { + const { instance } = setup(); + expect(instance.isLoading).toBe(false); + instance.toggleLoadingState(); + expect(instance.isLoading).toBe(true); + instance.toggleLoadingState(true); + expect(instance.isLoading).toBe(true); + instance.toggleLoadingState(false); + expect(instance.isLoading).toBe(false); + }); + it('should handle scroll events', () => { + const { component } = setup(); + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + component.first().simulate('wheel', { stopPropagation, preventDefault }); + expect(stopPropagation).toHaveBeenCalled(); + expect(preventDefault).toHaveBeenCalled(); + }); + it('should handle submit', () => { + const { instance } = setup(); + const preventDefault = jest.fn(); + instance.handleSubmit({ preventDefault }); + expect(preventDefault).toHaveBeenCalled(); + expect(mocks.setParams).toHaveBeenCalled(); + expect(mocks.close).toHaveBeenCalled(); + }); + describe('handleAutocite()', () => { + it('should handle a webpage with one author', async () => { + mocks.getFromURL.mockImplementation( + () => new Promise(res => res({ + accessed: '2016-12-10T', + authors: [ + { firstname: 'John', lastname: 'Doe' }, + ], + content_title: 'Google Website', + issued: '2016-12-10T', + site_title: 'Google', + url: 'https://google.com', + })) + ); + const { instance } = setup(); + expect(instance.manualData.get('title')).toBeUndefined(); + expect(instance.manualData.get('issued')).toBeUndefined(); + expect(instance.people[0].family).toBe(''); + await instance.handleAutocite('webpage', 'testing'); + expect(instance.manualData.get('title')).toBe('Google Website'); + expect(instance.manualData.get('issued')).toBe('2016/12/10'); + expect(instance.people[0].family).toBe('Doe'); + }); + it('should handle a webpage with no authors', async () => { + mocks.getFromURL.mockImplementation( + () => new Promise(res => res({ + accessed: '2016-12-10T', + authors: [ + {}, + ], + content_title: 'Google Website', + issued: '2016-12-10T', + site_title: 'Google', + url: 'https://google.com', + })) + ); + const { instance } = setup(); + expect(instance.manualData.get('title')).toBeUndefined(); + expect(instance.manualData.get('issued')).toBeUndefined(); + expect(instance.people[0].family).toBe(''); + await instance.handleAutocite('webpage', 'testing'); + expect(instance.manualData.get('title')).toBe('Google Website'); + expect(instance.manualData.get('issued')).toBe('2016/12/10'); + expect(instance.people[0].family).toBe(''); + }); + it('should handle webpage type errors', async () => { + mocks.getFromURL.mockImplementation( + () => new Promise((_, rej) => rej()) + ); + const { instance } = setup(); + instance.handleAutocite('webpage', 'testing'); + }); + it('should handle book autocites', async () => { + mocks.getFromISBN.mockImplementation( + () => new Promise(res => res({ + authors: [ + {}, + ], + issued: '2016-12-10T', + 'number-of-pages': 155, + publisher: 'Test Publisher', + title: 'Test Book', + })) + ); + const { instance } = setup(); + expect(instance.manualData.get('title')).toBeUndefined(); + await instance.handleAutocite('book', 'testing'); + expect(instance.manualData.get('title')).toBe('Test Book'); + }); + it('should handle book section autocites', async () => { + mocks.getFromISBN.mockImplementation( + () => new Promise(res => res({ + authors: [ + {}, + ], + issued: '2016-12-10T', + 'number-of-pages': 155, + publisher: 'Test Publisher', + title: 'Test Book Section', + })) + ); + const { instance } = setup(); + expect(instance.manualData.get('title')).toBeUndefined(); + await instance.handleAutocite('chapter', 'testing'); + expect(instance.manualData.get('container-title')).toBe('Test Book Section'); + }); + it('should handle book-type errors', () => { + mocks.getFromISBN.mockImplementation( + () => new Promise((_, rej) => rej()) + ); + const { instance } = setup(); + instance.handleAutocite('chapter', 'testing'); + }); + }); +}); diff --git a/src/lib/js/tinymce/views/edit-reference-window.html b/src/lib/js/tinymce/views/edit-reference-window.html new file mode 100644 index 00000000..ca0b068f --- /dev/null +++ b/src/lib/js/tinymce/views/edit-reference-window.html @@ -0,0 +1,9 @@ + + + +
+ + + diff --git a/src/lib/js/utils/Constants.ts b/src/lib/js/utils/Constants.ts index 5d306987..01261103 100644 --- a/src/lib/js/utils/Constants.ts +++ b/src/lib/js/utils/Constants.ts @@ -5,7 +5,7 @@ export const EVENTS = { INSERT_REFERENCE: 'INSERT_REFERENCE', OPEN_REFERENCE_WINDOW: 'OPEN_REFERENCE_WINDOW', - REFERENCE_ADDED: 'REFERENCE_ADDED', + REFERENCE_EDITED: 'REFERENCE_EDITED', TINYMCE_HIDDEN: 'TINYMCE_HIDDEN', TINYMCE_READY: 'TINYMCE_READY', TINYMCE_VISIBLE: 'TINYMCE_VISIBLE', @@ -30,6 +30,27 @@ export const referenceWindowEvents = { TOGGLE_MANUAL: 'TOGGLE_MANUAL', }; +export const PERSON_TYPE_KEYS = [ + 'author', + 'container-author', + 'editor', + 'director', + 'interviewer', + 'illustrator', + 'composer', + 'translator', + 'recipient', +]; + +export const DATE_TYPE_KEYS = [ + 'accessed', + 'container', + 'event-date', + 'issued', + 'original-date', + 'submitted', +]; + /** * Empty object for holding the field data for manual input * @note - The following fields were skipped: diff --git a/src/lib/js/utils/TinymceFunctions.ts b/src/lib/js/utils/TinymceFunctions.ts index 82890d5b..e2f22507 100644 --- a/src/lib/js/utils/TinymceFunctions.ts +++ b/src/lib/js/utils/TinymceFunctions.ts @@ -7,11 +7,11 @@ declare const ABT_wp: BackendGlobals.ABT_wp; * @return A Promise which resolves to ABT.ReferenceWindowPayload */ export function referenceWindow(editor: TinyMCE.Editor): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { editor.windowManager.open({ height: 10, onclose: (e) => { - if (!e.target.params.data) resolve(null); + if (!e.target.params.data) reject(null); resolve(e.target.params.data); }, params: { @@ -31,11 +31,11 @@ export function referenceWindow(editor: TinyMCE.Editor): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { editor.windowManager.open({ height: 10, onclose: (e) => { - if (!e.target.params.data) resolve(null); + if (!e.target.params.data) reject(null); resolve(e.target.params.data); }, title: top.ABT_i18n.tinymce.importWindow.title, @@ -45,6 +45,24 @@ export function importWindow(editor: TinyMCE.Editor): Promise { }); } +export function editReferenceWindow(editor: TinyMCE.Editor, ref: CSL.Data): Promise { + return new Promise((resolve, reject) => { + editor.windowManager.open({ + height: 10, + onclose: (e) => { + if (!e.target.params.data) reject(null); + resolve(e.target.params.data); + }, + params: { + reference: ref, + }, + title: top.ABT_i18n.tinymce.editReferenceWindow.title, + url: `${ABT_wp.abt_url}/lib/js/tinymce/views/edit-reference-window.html`, + width: 600, + }); + }); +} + interface CitationPositions { /** The index of the HTMLSpanElement being inserted */ currentIndex: number; diff --git a/src/lib/js/utils/helpers/__tests__/preventScrollPropagation.ts b/src/lib/js/utils/helpers/__tests__/preventScrollPropagation.ts index 5fc27fe6..7e3b6c16 100644 --- a/src/lib/js/utils/helpers/__tests__/preventScrollPropagation.ts +++ b/src/lib/js/utils/helpers/__tests__/preventScrollPropagation.ts @@ -1,7 +1,7 @@ -import { preventScrollPropagation } from '../preventScrollPropagation'; +import { preventScrollPropagation } from '../'; describe('preventScrollPropagation()', () => { - class Component { + class TestComponent { element; constructor(o, h, t) { this.element = { @@ -16,7 +16,7 @@ describe('preventScrollPropagation()', () => { const stopPropagation = jest.fn(); const preventDefault = jest.fn(); return { - component: new Component(o, h, t), + component: new TestComponent(o, h, t), e: { stopPropagation, preventDefault, diff --git a/src/lib/php/i18n.php b/src/lib/php/i18n.php index 08e105f9..37892d42 100644 --- a/src/lib/php/i18n.php +++ b/src/lib/php/i18n.php @@ -65,6 +65,11 @@ function abt_generate_translations() { ], ]; + $ABT_i18n->tinymce->editReferenceWindow = [ + 'title' => __('Edit Reference', 'academic-bloggers-toolkit'), + 'confirm' => __('Confirm', 'academic-bloggers-toolkit'), + ]; + $ABT_i18n->tinymce->importWindow = [ 'importBtn' => __('Import', 'academic-bloggers-toolkit'), 'title' => __('Import References from File', 'academic-bloggers-toolkit'), diff --git a/src/readme.txt b/src/readme.txt index 8f4c02c3..a5dbc41f 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -4,7 +4,7 @@ Donate link: https://donorbox.org/academic-bloggers-toolkit Tags: academia, academic, bibliographies, bibliography, bibtex, citation, citations, cite, citing, CSL, curriculum vitae, cv, doi, endnote, footnote, footnotes, journal, mendeley, papers, pmid, pmcid, publications, publish, pubmed, reference, reference list, reference manager, references, referencing, ris, scholar, scholarly, zotero Requires at least: 4.2.2 Tested up to: 4.7 -Stable tag: 4.8.1 +Stable tag: 4.9.0 License: GPL3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -57,6 +57,10 @@ Yikes! I'm sorry about that. Please report all issues on the Academic Blogger's == Changelog == += 4.9.0 = + +[Click here](https://headwayapp.co/academic-bloggers-toolkit-changelog) to view changes. + = 4.8.1 = [Click here](https://headwayapp.co/academic-bloggers-toolkit-changelog) to view changes. diff --git a/src/vendor/citationstyles.php b/src/vendor/citationstyles.php index b2f7d6a0..7867a01d 100644 --- a/src/vendor/citationstyles.php +++ b/src/vendor/citationstyles.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 61f9ff7a..6447d78a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -54,10 +54,14 @@ "src/lib/js/reference-list/components/ReferenceList.tsx", "src/lib/js/reference-list/index.tsx", "src/lib/js/reference-list/Store.ts", + "src/lib/js/tinymce/components/edit-reference-window/__tests__/EditReferenceWindow-test.tsx", + "src/lib/js/tinymce/components/edit-reference-window/EditReferenceWindow.tsx", + "src/lib/js/tinymce/components/edit-reference-window/index.tsx", "src/lib/js/tinymce/components/import-window/__tests__/ImportWindow-test.tsx", "src/lib/js/tinymce/components/import-window/ImportWindow.tsx", "src/lib/js/tinymce/components/import-window/index.tsx", "src/lib/js/tinymce/components/pubmed-window/__tests__/Paginate-test.tsx", + "src/lib/js/tinymce/components/pubmed-window/__tests__/PubmedWindow-test.tsx", "src/lib/js/tinymce/components/pubmed-window/__tests__/ResultList-test.tsx", "src/lib/js/tinymce/components/pubmed-window/index.tsx", "src/lib/js/tinymce/components/pubmed-window/Paginate.tsx", @@ -68,6 +72,7 @@ "src/lib/js/tinymce/components/reference-window/components/__tests__/ManualEntryContainer-test.tsx", "src/lib/js/tinymce/components/reference-window/components/__tests__/MetaFields-test.tsx", "src/lib/js/tinymce/components/reference-window/components/__tests__/People-test.tsx", + "src/lib/js/tinymce/components/reference-window/components/__tests__/ReferenceWindow-test.tsx", "src/lib/js/tinymce/components/reference-window/components/ButtonRow.tsx", "src/lib/js/tinymce/components/reference-window/components/IdentifierInput.tsx", "src/lib/js/tinymce/components/reference-window/components/ManualEntryContainer.tsx", @@ -113,9 +118,8 @@ "node_modules/@types/enzyme/index.d.ts", "node_modules/@types/jest/index.d.ts", "node_modules/@types/node/index.d.ts", - "node_modules/@types/react-addons-css-transition-group/index.d.ts", - "node_modules/@types/react-addons-transition-group/index.d.ts", "node_modules/@types/react-dom/index.d.ts", + "node_modules/@types/react-motion/index.d.ts", "node_modules/@types/react/index.d.ts", "node_modules/mobx/lib/mobx.d.ts", "node_modules/mobx-react/index.d.ts", diff --git a/types/ABT.d.ts b/types/ABT.d.ts index 2c1e6651..22b13a27 100644 --- a/types/ABT.d.ts +++ b/types/ABT.d.ts @@ -129,12 +129,15 @@ declare namespace ABT { webpage: FieldMap; } - interface ReferenceWindowPayload { + interface ManualData { + manualData: CSL.Data; + people: CSL.TypedPerson[]; + } + + interface ReferenceWindowPayload extends ManualData { addManually: boolean; attachInline: boolean; identifierList: string; - manualData: CSL.Data; - people: CSL.TypedPerson[]; } interface URLMeta { diff --git a/types/globals.d.ts b/types/globals.d.ts index 58f6e10c..3c2da45b 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -21,101 +21,105 @@ declare namespace BackendGlobals { // tslint:disable-next-line interface ABT_i18n { - readonly citationTypes: ABT.CitationTypes; - readonly errors: { - readonly badRequest: 'Request not valid'; - readonly broken: 'BROKEN!'; - readonly denied: 'Site denied request'; - readonly fileExtensionError: 'Invalid file extension. Extension must be .ris, .bib, or .bibtex'; - readonly filetypeError: 'The file could not be processed.'; - readonly identifiersNotFound: { - readonly all: 'The following identifiers could not be found'; - readonly some: 'The following identifiers could not be found'; + citationTypes: ABT.CitationTypes; + errors: { + badRequest: 'Request not valid'; + broken: 'BROKEN!'; + denied: 'Site denied request'; + fileExtensionError: 'Invalid file extension. Extension must be .ris, .bib, or .bibtex'; + filetypeError: 'The file could not be processed.'; + identifiersNotFound: { + all: 'The following identifiers could not be found'; + some: 'The following identifiers could not be found'; }; - readonly networkError: 'Network Error'; - readonly noResults: 'Your search returned 0 results'; - readonly prefix: 'Error'; - readonly risLeftovers: 'The following references were unable to be processed'; - readonly statusError: 'Request returned a non-200 status code'; - readonly warnings: { - readonly warning: 'Warning'; - readonly reason: 'Reason'; - readonly noBib: { - readonly message: 'Cannot create publication list for currently selected citation style'; - readonly reason: 'Style does not include bibliography'; + networkError: 'Network Error'; + noResults: 'Your search returned 0 results'; + prefix: 'Error'; + risLeftovers: 'The following references were unable to be processed'; + statusError: 'Request returned a non-200 status code'; + warnings: { + warning: 'Warning'; + reason: 'Reason'; + noBib: { + message: 'Cannot create publication list for currently selected citation style'; + reason: 'Style does not include bibliography'; }; }; - readonly unexpected: { - readonly message: 'An unexpected error occurred'; - readonly reportInstructions: 'Please report this error, including the steps taken to trigger it, here: \nhttps://github.com/dsifford/academic-bloggers-toolkit/issues'; // tslint:disable-line + unexpected: { + message: 'An unexpected error occurred'; + reportInstructions: 'Please report this error, including the steps taken to trigger it, here: \nhttps://github.com/dsifford/academic-bloggers-toolkit/issues'; // tslint:disable-line }; }; - readonly fieldmaps: ABT.FieldMappings; - readonly misc: { - readonly footnotes: 'Footnotes'; + fieldmaps: ABT.FieldMappings; + misc: { + footnotes: 'Footnotes'; }; - readonly referenceList: { - readonly menu: { + referenceList: { + menu: { tooltips: { - readonly destroy: 'Delete all references'; - readonly help: 'Usage instructions'; - readonly importRIS: 'Import references from file'; - readonly refresh: 'Refresh reference list'; - readonly staticPubList: 'Insert Static Publication List'; + destroy: 'Delete all references'; + help: 'Usage instructions'; + importRIS: 'Import references from file'; + refresh: 'Refresh reference list'; + staticPubList: 'Insert Static Publication List'; }; }; - readonly referenceList: { - readonly citedItems: 'Cited Items'; - readonly tooltips: { - readonly add: 'Add reference to reference list'; - readonly insert: 'Insert selected references'; - readonly pin: 'Pin reference list to visible window'; - readonly remove: 'Remove selected references from reference list'; + referenceList: { + citedItems: 'Cited Items'; + tooltips: { + add: 'Add reference to reference list'; + insert: 'Insert selected references'; + pin: 'Pin reference list to visible window'; + remove: 'Remove selected references from reference list'; }; - readonly uncitedItems: 'Uncited Items'; + uncitedItems: 'Uncited Items'; }; }; - readonly tinymce: { - readonly importWindow: { - readonly importBtn: 'Import'; - readonly title: 'Import References from File'; - readonly upload: 'Choose File'; + tinymce: { + editReferenceWindow: { + title: 'Edit Reference', + confirm: 'Confirm', + }; + importWindow: { + importBtn: 'Import'; + title: 'Import References from File'; + upload: 'Choose File'; }; - readonly pubmedWindow: { - readonly addReference: 'Select'; - readonly next: 'Next'; - readonly previous: 'Previous'; - readonly search: 'Search'; - readonly title: 'Search PubMed for Reference'; - readonly viewReference: 'View'; + pubmedWindow: { + addReference: 'Select'; + next: 'Next'; + previous: 'Previous'; + search: 'Search'; + title: 'Search PubMed for Reference'; + viewReference: 'View'; }; - readonly referenceWindow: { - readonly buttonRow: { - readonly addManually: 'Add Manually'; - readonly addReference: 'Add Reference'; - readonly addWithIdentifier: 'Add with Identifier'; - readonly insertInline: 'Insert citation inline'; - readonly pubmedWindowTitle: 'Search PubMed for Reference'; - readonly searchPubmed: 'Search PubMed'; + referenceWindow: { + buttonRow: { + addManually: 'Add Manually'; + addReference: 'Add Reference'; + addWithIdentifier: 'Add with Identifier'; + insertInline: 'Insert citation inline'; + pubmedWindowTitle: 'Search PubMed for Reference'; + searchPubmed: 'Search PubMed'; }; - readonly identifierInput: { - readonly label: 'DOI/PMID/PMCID'; + identifierInput: { + label: 'DOI/PMID/PMCID'; }; - readonly manualEntryContainer: { - readonly autocite: 'Autocite'; - readonly citationType: 'Citation Type'; - readonly ISBN: 'ISBN'; - readonly search: 'Search'; - readonly URL: 'URL'; + manualEntryContainer: { + autocite: 'Autocite'; + citationType: 'Citation Type'; + ISBN: 'ISBN'; + search: 'Search'; + URL: 'URL'; }; - readonly people: { - readonly add: 'Add Contributor'; - readonly contributors: 'Contributors'; - readonly given: 'Given Name, M.I.'; - readonly surname: 'Surname'; + people: { + add: 'Add Contributor'; + contributors: 'Contributors'; + given: 'Given Name, M.I.'; + surname: 'Surname'; }; - readonly referenceWindow: { - readonly title: 'Add Reference'; + referenceWindow: { + title: 'Add Reference'; }; }; }; diff --git a/types/tinymce.d.ts b/types/tinymce.d.ts index 9f89ccee..96fb2365 100644 --- a/types/tinymce.d.ts +++ b/types/tinymce.d.ts @@ -60,6 +60,10 @@ declare namespace TinyMCE { setProgressState(state: boolean): void; } + interface EditorManager { + get(editorId: string): TinyMCE.Editor; + } + interface WindowManager { data?: Object; editor?: Editor; diff --git a/webpack.config.js b/webpack.config.js index e4323a2a..633f644c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,12 +50,12 @@ module.exports = { 'lib/js/reference-list/index': './src/lib/js/reference-list/', 'lib/js/tinymce/components/reference-window/index': './src/lib/js/tinymce/components/reference-window/', // eslint-disable-line 'lib/js/tinymce/components/pubmed-window/index': './src/lib/js/tinymce/components/pubmed-window/', // eslint-disable-line + 'lib/js/tinymce/components/edit-reference-window/index': './src/lib/js/tinymce/components/edit-reference-window/', // eslint-disable-line 'lib/js/tinymce/components/import-window/index': './src/lib/js/tinymce/components/import-window/', // eslint-disable-line vendor: [ 'react', 'react-dom', 'react-addons-shallow-compare', - 'react-addons-css-transition-group', 'mobx', 'mobx-react', 'mobx-react-devtools', diff --git a/yarn.lock b/yarn.lock index 018bb74c..74e5f762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,29 +16,26 @@ version "6.0.51" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.51.tgz#84cbf25111516ec9304d0b61469dc0fa9d12ba32" -"@types/react-addons-css-transition-group@^0.14.18": - version "0.14.18" - resolved "https://registry.yarnpkg.com/@types/react-addons-css-transition-group/-/react-addons-css-transition-group-0.14.18.tgz#8fbe91860503f23dba77b7bdf25442707b445526" - dependencies: - "@types/react" "*" - "@types/react-addons-transition-group" "*" - -"@types/react-addons-transition-group@*": - version "0.14.17" - resolved "https://registry.yarnpkg.com/@types/react-addons-transition-group/-/react-addons-transition-group-0.14.17.tgz#78b3980b7dc80e4dc442db79b3e58f4295d8f870" - dependencies: - "@types/react" "*" - "@types/react-dom@^0.14.19": version "0.14.19" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-0.14.19.tgz#698a14335e32fb1bc61db65f5647c9668a7fc2eb" dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^0.14.54": +"@types/react-motion@^0.0.20": + version "0.0.20" + resolved "https://registry.yarnpkg.com/@types/react-motion/-/react-motion-0.0.20.tgz#c703645cc3a1f88dad59b7c7346c04732e1c4b74" + dependencies: + "@types/react" "*" + +"@types/react@*": version "0.14.54" resolved "https://registry.yarnpkg.com/@types/react/-/react-0.14.54.tgz#0fb0dadad895febeb468a885d0fa2284d5bbefae" +"@types/react@^0.14.55": + version "0.14.55" + resolved "https://registry.yarnpkg.com/@types/react/-/react-0.14.55.tgz#f7f7a6ad0c94780531e95632435b2798c5dad11b" + Base64@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" @@ -83,17 +80,23 @@ acorn-globals@^1.0.4: dependencies: acorn "^2.1.0" -acorn-jsx@^3.0.0: +acorn-jsx@^3.0.0, acorn-jsx@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" dependencies: acorn "^3.0.4" +acorn-object-spread@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/acorn-object-spread/-/acorn-object-spread-1.0.0.tgz#48ead0f4a8eb16995a17a0db9ffc6acaada4ba68" + dependencies: + acorn "^3.1.0" + acorn@^2.1.0, acorn@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" -acorn@^3.0.0, acorn@^3.0.4: +acorn@^3.0.0, acorn@^3.0.4, acorn@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -246,18 +249,10 @@ asn1.js@^4.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -asn1@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" - asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" -assert-plus@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" - assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" @@ -295,10 +290,6 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async@0.1.15: - version "0.1.15" - resolved "https://registry.yarnpkg.com/async/-/async-0.1.15.tgz#2180eaca2cf2a6ca5280d41c0585bec9b3e49bd3" - async@1.5.2, async@1.x, async@^1.3.0, async@^1.4.0, async@^1.4.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -313,7 +304,7 @@ async@^0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@^2.0.1, async@^2.1.2: +async@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/async/-/async-2.1.2.tgz#612a4ab45ef42a70cde806bad86ee6db047e8385" dependencies: @@ -346,27 +337,27 @@ aws4@^1.2.1: version "1.5.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" -babel-code-frame@^6.11.0, babel-code-frame@^6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.16.0.tgz#f90e60da0862909d3ce098733b5d3987c97cb8de" +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.20.0.tgz#b968f839090f9a8bc6d41938fb96cb84f7387b26" dependencies: chalk "^1.1.0" esutils "^2.0.2" js-tokens "^2.0.0" -babel-core@^6.0.0, babel-core@^6.17.0, babel-core@^6.18.0: - version "6.18.2" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.18.2.tgz#d8bb14dd6986fa4f3566a26ceda3964fa0e04e5b" +babel-core@^6.0.0, babel-core@^6.18.0, babel-core@^6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.20.0.tgz#ab0d7176d9dea434e66badadaf92237865eab1ec" dependencies: - babel-code-frame "^6.16.0" - babel-generator "^6.18.0" + babel-code-frame "^6.20.0" + babel-generator "^6.20.0" babel-helpers "^6.16.0" babel-messages "^6.8.0" babel-register "^6.18.0" - babel-runtime "^6.9.1" + babel-runtime "^6.20.0" babel-template "^6.16.0" - babel-traverse "^6.18.0" - babel-types "^6.18.0" + babel-traverse "^6.20.0" + babel-types "^6.20.0" babylon "^6.11.0" convert-source-map "^1.1.0" debug "^2.1.1" @@ -378,13 +369,13 @@ babel-core@^6.0.0, babel-core@^6.17.0, babel-core@^6.18.0: slash "^1.0.0" source-map "^0.5.0" -babel-generator@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.18.0.tgz#e4f104cb3063996d9850556a45aae4a022060a07" +babel-generator@^6.18.0, babel-generator@^6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.20.0.tgz#fee63614e0449390103b3097f3f6a118016c6766" dependencies: babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.18.0" + babel-runtime "^6.20.0" + babel-types "^6.20.0" detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.2.0" @@ -743,13 +734,13 @@ babel-plugin-transform-strict-mode@^6.18.0: babel-runtime "^6.0.0" babel-types "^6.18.0" -babel-polyfill@^6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.16.0.tgz#2d45021df87e26a374b6d4d1a9c65964d17f2422" +babel-polyfill@^6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.20.0.tgz#de4a371006139e20990aac0be367d398331204e7" dependencies: - babel-runtime "^6.9.1" + babel-runtime "^6.20.0" core-js "^2.4.0" - regenerator-runtime "^0.9.5" + regenerator-runtime "^0.10.0" babel-preset-es2015@^6.16.0: version "6.18.0" @@ -810,7 +801,14 @@ babel-register@^6.16.3, babel-register@^6.18.0: mkdirp "^0.5.1" source-map-support "^0.4.2" -babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.9.0, babel-runtime@^6.9.1: +babel-runtime@^6.0.0, babel-runtime@^6.20.0, babel-runtime@^6.9.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.20.0.tgz#87300bdcf4cd770f09bf0048c64204e17806d16f" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-runtime@^6.11.6: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.18.0.tgz#0f4177ffd98492ef13b9f823e9994a02584c9078" dependencies: @@ -827,25 +825,25 @@ babel-template@^6.14.0, babel-template@^6.15.0, babel-template@^6.16.0, babel-te babylon "^6.11.0" lodash "^4.2.0" -babel-traverse@^6.16.0, babel-traverse@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.18.0.tgz#5aeaa980baed2a07c8c47329cd90c3b90c80f05e" +babel-traverse@^6.16.0, babel-traverse@^6.18.0, babel-traverse@^6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.20.0.tgz#5378d1a743e3d856e6a52289994100bbdfd9872a" dependencies: - babel-code-frame "^6.16.0" + babel-code-frame "^6.20.0" babel-messages "^6.8.0" - babel-runtime "^6.9.0" - babel-types "^6.18.0" + babel-runtime "^6.20.0" + babel-types "^6.20.0" babylon "^6.11.0" debug "^2.2.0" globals "^9.0.0" invariant "^2.2.0" lodash "^4.2.0" -babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.8.0, babel-types@^6.9.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.18.0.tgz#1f7d5a73474c59eb9151b2417bbff4e4fce7c3f8" +babel-types@^6.16.0, babel-types@^6.18.0, babel-types@^6.20.0, babel-types@^6.8.0, babel-types@^6.9.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.20.0.tgz#3869ecb98459533b37df809886b3f7f3b08d2baa" dependencies: - babel-runtime "^6.9.1" + babel-runtime "^6.20.0" esutils "^2.0.2" lodash "^4.2.0" to-fast-properties "^1.0.1" @@ -906,10 +904,6 @@ beeper@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.0.tgz#9ee6fc1ce7f54feaace7ce73588b056037866a2c" -benchmark@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-1.0.0.tgz#2f1e2fa4c359f11122aa183082218e957e390c73" - better-assert@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" @@ -934,12 +928,6 @@ binaryextensions@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-1.0.1.tgz#1e637488b35b58bda5f4774bf96a5212a8c90755" -bl@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.0.3.tgz#fc5421a28fd4226036c3b3891a66a25bc64d226e" - dependencies: - readable-stream "~2.0.5" - blob@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" @@ -1003,16 +991,16 @@ browser-resolve@^1.11.2: dependencies: resolve "1.1.7" -browser-sync-client@^2.3.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/browser-sync-client/-/browser-sync-client-2.4.3.tgz#e965033e0c83e5f06caacb516755b694836cea4f" +browser-sync-client@2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/browser-sync-client/-/browser-sync-client-2.4.4.tgz#e2a6c27f770e0ad0ffed76964dfb6a971fcf55eb" dependencies: etag "^1.7.0" fresh "^0.3.0" -browser-sync-ui@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/browser-sync-ui/-/browser-sync-ui-0.6.1.tgz#d8b98ea3b755632287350a37ee2eaaacabea28d2" +browser-sync-ui@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/browser-sync-ui/-/browser-sync-ui-0.6.2.tgz#9e7994004d463e55a024bdd149583b11ad8f87f3" dependencies: async-each-series "0.1.1" connect-history-api-fallback "^1.1.0" @@ -1021,35 +1009,36 @@ browser-sync-ui@0.6.1: stream-throttle "^0.1.3" weinre "^2.0.0-pre-I0Z7U9OV" -browser-sync@^2.18.2: - version "2.18.2" - resolved "https://registry.yarnpkg.com/browser-sync/-/browser-sync-2.18.2.tgz#3c101274c507e2f734dbce4e4b19bd3e124c3ea7" +browser-sync@^2.18.5: + version "2.18.5" + resolved "https://registry.yarnpkg.com/browser-sync/-/browser-sync-2.18.5.tgz#c04b10037289df5157a363d42100069d77e744e9" dependencies: - browser-sync-client "^2.3.3" - browser-sync-ui "0.6.1" + browser-sync-client "2.4.4" + browser-sync-ui "0.6.2" bs-recipes "1.3.2" - chokidar "1.6.0" + chokidar "1.6.1" connect "3.5.0" dev-ip "^1.0.1" easy-extender "2.3.2" eazy-logger "3.0.2" emitter-steward "^1.0.0" - fs-extra "0.30.0" - http-proxy "1.15.1" + fs-extra "1.0.0" + http-proxy "1.15.2" immutable "3.8.1" - localtunnel "1.8.1" + localtunnel "1.8.2" micromatch "2.3.11" opn "4.0.2" - portscanner "^1.0.0" + portscanner "2.1.1" qs "6.2.1" resp-modifier "6.0.2" rx "4.1.0" serve-index "1.8.0" serve-static "1.11.1" server-destroy "1.0.1" - socket.io "1.5.0" - ua-parser-js "0.7.10" - yargs "6.0.0" + socket.io "1.6.0" + socket.io-client "1.6.0" + ua-parser-js "0.7.12" + yargs "6.4.0" browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.0.6" @@ -1118,6 +1107,25 @@ bser@^1.0.2: dependencies: node-int64 "^0.4.0" +buble@^0.12.0: + version "0.12.5" + resolved "https://registry.yarnpkg.com/buble/-/buble-0.12.5.tgz#c66ffe92f9f4a3c65d3256079b711e2bd0bc5013" + dependencies: + acorn "^3.1.0" + acorn-jsx "^3.0.1" + acorn-object-spread "^1.0.0" + chalk "^1.1.3" + magic-string "^0.14.0" + minimist "^1.2.0" + os-homedir "^1.0.1" + +bubleify@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/bubleify/-/bubleify-0.5.1.tgz#f65c47cee31b80cad8b9e747bbe187d7fe51e927" + dependencies: + buble "^0.12.0" + object-assign "^4.0.1" + buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -1251,9 +1259,9 @@ chokidar@1.5.2: optionalDependencies: fsevents "^1.0.0" -chokidar@1.6.0, chokidar@^1.0.0, chokidar@^1.4.3: - version "1.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.0.tgz#90c32ad4802901d7713de532dc284e96a63ad058" +chokidar@1.6.1, chokidar@^1.0.0, chokidar@^1.4.3: + version "1.6.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" dependencies: anymatch "^1.3.0" async-each "^1.0.0" @@ -1426,9 +1434,9 @@ component-emitter@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" -component-emitter@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.0.tgz#ccd113a86388d06482d03de3fc7df98526ba8efe" +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" component-inherit@0.0.3: version "0.0.3" @@ -1709,10 +1717,6 @@ cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": dependencies: cssom "0.3.x" -ctype@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1742,16 +1746,18 @@ dateformat@^1.0.11: get-stdin "^4.0.1" meow "^3.3.0" -debug@*, debug@2.2.0, debug@^2.1.1, debug@^2.2.0, debug@~2.2.0: +debug@*, debug@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" + dependencies: + ms "0.7.2" + +debug@2.2.0, debug@^2.1.1, debug@^2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: ms "0.7.1" -debug@0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1987,26 +1993,26 @@ end-of-stream@^1.1.0: dependencies: once "~1.3.0" -engine.io-client@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.7.0.tgz#0bb81d3563ab7afb668f1e1b400c9403b03006ee" +engine.io-client@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.0.tgz#7b730e4127414087596d9be3c88d2bc5fdb6cf5c" dependencies: - component-emitter "1.1.2" + component-emitter "1.2.1" component-inherit "0.0.3" - debug "2.2.0" - engine.io-parser "1.3.0" + debug "2.3.3" + engine.io-parser "1.3.1" has-cors "1.1.0" indexof "0.0.1" - parsejson "0.0.1" - parseqs "0.0.2" - parseuri "0.0.4" + parsejson "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" ws "1.1.1" - xmlhttprequest-ssl "1.5.1" + xmlhttprequest-ssl "1.5.3" yeast "0.1.2" -engine.io-parser@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.0.tgz#61a35c7f3a3ccd1b179e4f52257a7a8cfacaeb21" +engine.io-parser@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.1.tgz#9554f1ae33107d6fbd170ca5466d2f833f6a07cf" dependencies: after "0.8.1" arraybuffer.slice "0.0.6" @@ -2015,24 +2021,17 @@ engine.io-parser@1.3.0: has-binary "0.1.6" wtf-8 "1.0.0" -engine.io@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.7.0.tgz#a417857af4995d9bbdf8a0e03a87e473ebe64fbe" +engine.io@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.0.tgz#3eeb5f264cb75dbbec1baaea26d61f5a4eace2aa" dependencies: accepts "1.3.3" base64id "0.1.0" - debug "2.2.0" - engine.io-parser "1.3.0" + cookie "0.3.1" + debug "2.3.3" + engine.io-parser "1.3.1" ws "1.1.1" -enhanced-resolve@^0.9.0, enhanced-resolve@~0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.2.0" - tapable "^0.1.8" - enhanced-resolve@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-2.3.0.tgz#a115c32504b6302e85a76269d7a57ccdd962e359" @@ -2042,6 +2041,23 @@ enhanced-resolve@^2.2.0: object-assign "^4.0.1" tapable "^0.2.3" +enhanced-resolve@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.0.0.tgz#cf30a6600bc236a4fcef627bb8e5adf072511a8e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.5" + +enhanced-resolve@~0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.2.0" + tapable "^0.1.8" + entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -2204,9 +2220,9 @@ eslint-plugin-import@^2.2.0: minimatch "^3.0.3" pkg-up "^1.0.0" -eslint@^3.11.1: - version "3.11.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.11.1.tgz#408be581041385cba947cd8d1cd2227782b55dbf" +eslint@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.12.0.tgz#1dfa4ef0082e35feed90a0fb1f7996d1d426b249" dependencies: babel-code-frame "^6.16.0" chalk "^1.1.3" @@ -2219,7 +2235,7 @@ eslint@^3.11.1: esutils "^2.0.2" file-entry-cache "^2.0.0" glob "^7.0.3" - globals "^9.2.0" + globals "^9.14.0" ignore "^3.2.0" imurmurhash "^0.1.4" inquirer "^0.12.0" @@ -2561,14 +2577,6 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@~1.0.0-rc3: - version "1.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" - dependencies: - async "^2.0.1" - combined-stream "^1.0.5" - mime-types "^2.1.11" - form-data@~2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.1.tgz#4adf0342e1a79afa1e84c8c320a9ffc82392a1f3" @@ -2593,15 +2601,13 @@ fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" -fs-extra@0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" +fs-extra@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" dependencies: graceful-fs "^4.1.2" jsonfile "^2.1.0" klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" fs.realpath@^1.0.0: version "1.0.0" @@ -2775,9 +2781,9 @@ global-prefix@^0.1.4: is-windows "^0.2.0" which "^1.2.12" -globals@^9.0.0, globals@^9.2.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.12.0.tgz#992ce90828c3a55fa8f16fada177adb64664cf9d" +globals@^9.0.0, globals@^9.14.0: + version "9.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" globby@^5.0.0: version "5.0.0" @@ -2962,7 +2968,7 @@ handlebars@^4.0.1, handlebars@^4.0.3: optionalDependencies: uglify-js "^2.6" -har-validator@~2.0.2, har-validator@~2.0.6: +har-validator@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" dependencies: @@ -3023,7 +3029,7 @@ hash.js@^1.0.0: dependencies: inherits "^2.0.1" -hawk@~3.1.0, hawk@~3.1.3: +hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -3093,21 +3099,13 @@ http-errors@~1.5.0: setprototypeof "1.0.1" statuses ">= 1.3.0 < 2" -http-proxy@1.15.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.15.1.tgz#91a6088172e79bc0e821d5eb04ce702f32446393" +http-proxy@1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.15.2.tgz#642fdcaffe52d3448d2bda3b0079e9409064da31" dependencies: eventemitter3 "1.x.x" requires-port "1.x.x" -http-signature@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" - dependencies: - asn1 "0.1.11" - assert-plus "^0.1.5" - ctype "0.5.3" - http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -3337,6 +3335,13 @@ is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" +is-number-like@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.3.tgz#0255245d2d9c9a68ca2d207b0c90714367a66c19" + dependencies: + bubleify "^0.5.1" + lodash.isfinite "^3.3.2" + is-number@^2.0.2, is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -3426,8 +3431,8 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" is-unc-path@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.1.tgz#ab2533d77ad733561124c3dc0f5cd8b90054c86b" + version "0.1.2" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" dependencies: unc-path-regex "^0.1.0" @@ -3816,10 +3821,6 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json3@3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.2.6.tgz#f6efc93c06a04de9aec53053df2559bb19e2038b" - json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" @@ -3943,13 +3944,13 @@ loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.6, loader-utils@^0 json5 "^0.5.0" object-assign "^4.0.1" -localtunnel@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.8.1.tgz#d51b2bb7a7066afb05b57fc9db844015098f2e17" +localtunnel@1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/localtunnel/-/localtunnel-1.8.2.tgz#913051e8328b51f75ad8a22ad1f5c5b8c599a359" dependencies: debug "2.2.0" openurl "1.1.0" - request "2.65.0" + request "2.78.0" yargs "3.29.0" lodash._arraycopy@^3.0.0: @@ -4130,6 +4131,10 @@ lodash.isequal@^4.0.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.4.0.tgz#6295768e98e14dc15ce8d362ef6340db82852031" +lodash.isfinite@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" + lodash.isfunction@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.8.tgz#4db709fc81bc4a8fd7127a458a5346c5cdce2c6b" @@ -4260,6 +4265,12 @@ macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" +magic-string@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.14.0.tgz#57224aef1701caeed273b17a39a956e72b172462" + dependencies: + vlq "^0.2.1" + make-error-cause@^1.1.1: version "1.2.2" resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d" @@ -4328,6 +4339,13 @@ memory-fs@^0.3.0, memory-fs@~0.3.0: errno "^0.1.3" readable-stream "^2.0.1" +memory-fs@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -4394,7 +4412,7 @@ mime-db@~1.25.0: version "1.25.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392" -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.13: +mime-types@^2.1.12, mime-types@~2.1.13: version "2.1.13" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88" dependencies: @@ -4458,9 +4476,9 @@ mobx-react@^4.0.3: dependencies: hoist-non-react-statics "^1.2.0" -mobx@^2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-2.6.5.tgz#9ea2a7f049c886b943981ea491bfcfba301dec49" +mobx@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-2.7.0.tgz#cf3d82d18c0ca7f458d8f2a240817b3dc7e54a01" mout@~0.5.0: version "0.5.0" @@ -4470,6 +4488,10 @@ ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" +ms@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + multi-stage-sourcemap@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/multi-stage-sourcemap/-/multi-stage-sourcemap-0.2.1.tgz#b09fc8586eaa17f81d575c4ad02e0f7a3f6b1105" @@ -4618,7 +4640,7 @@ node-status-codes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" -node-uuid@~1.4.3, node-uuid@~1.4.7: +node-uuid@~1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f" @@ -4687,18 +4709,18 @@ number-is-nan@^1.0.0: version "1.3.9" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.3.9.tgz#8bab486ff7fa3dfd086656bbe8b17116d3692d2a" -oauth-sign@~0.8.0, oauth-sign@~0.8.1: +oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@4.1.0, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" + object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" -object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - object-component@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" @@ -4897,21 +4919,21 @@ parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" -parsejson@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.1.tgz#9b10c6c0d825ab589e685153826de0a3ba278bcc" +parsejson@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" dependencies: better-assert "~1.0.0" -parseqs@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.2.tgz#9dfe70b2cddac388bde4f35b1f240fa58adbe6c7" +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" dependencies: better-assert "~1.0.0" -parseuri@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.4.tgz#806582a39887e1ea18dd5e2fe0e01902268e9350" +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" dependencies: better-assert "~1.0.0" @@ -4933,14 +4955,10 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" -path-is-absolute@1.0.0: +path-is-absolute@1.0.0, path-is-absolute@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.0.tgz#263dada66ab3f2fb10bf7f9d24dd8f3e570ef912" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" @@ -4988,6 +5006,10 @@ pbkdf2@^3.0.3: dependencies: create-hmac "^1.1.2" +performance-now@^0.2.0, performance-now@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5027,11 +5049,12 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" -portscanner@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-1.0.0.tgz#3b5cfe393828b5160abc600e6270ebc2f1590558" +portscanner@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.1.1.tgz#eabb409e4de24950f5a2a516d35ae769343fbb96" dependencies: - async "0.1.15" + async "1.5.2" + is-number-like "^1.0.3" postcss-calc@^5.2.0: version "5.3.1" @@ -5357,15 +5380,11 @@ qs@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" -qs@6.2.1, "qs@>= 0.4.0": +qs@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" -qs@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.1.tgz#801fee030e0b9450d6385adc48a4cc55b44aedfc" - -qs@~6.3.0: +"qs@>= 0.4.0", qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" @@ -5384,6 +5403,12 @@ querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" +raf@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.0.tgz#93845eeffc773f8129039f677f80a36044eee2c3" + dependencies: + performance-now "~0.2.0" + randomatic@^1.1.3: version "1.1.5" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.5.tgz#5e9ef5f2d573c67bd2b8124ae90b5156e457840b" @@ -5408,10 +5433,6 @@ rc@^1.0.1, rc@^1.1.6, rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" -react-addons-css-transition-group@^15.4.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/react-addons-css-transition-group/-/react-addons-css-transition-group-15.4.1.tgz#60b133fac5116e4009e56ab0674dc2ddcc1a18f6" - react-addons-shallow-compare@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.4.1.tgz#b68103dd4d13144cb221065f6021de1822bd435a" @@ -5432,6 +5453,13 @@ react-input-autosize@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.1.0.tgz#3fe1ac832387d8abab85f6051ceab1c9e5570853" +react-motion@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.4.5.tgz#ecc42f692fec9b2de4c92f85e26375071f779b76" + dependencies: + performance-now "^0.2.0" + raf "^3.1.0" + react-select@^1.0.0-beta14: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.0.0-rc.2.tgz#9fc11b149a3dc1ac831289d21b40a59742f82f8d" @@ -5515,7 +5543,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@~2.0.0, readable-stream@~2.0.5: +readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -5580,6 +5608,10 @@ regenerate@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.1.tgz#0300203a5d2fdcf89116dce84275d011f5903f33" +regenerator-runtime@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz#257f41961ce44558b18f7814af48c17559f9faeb" + regenerator-runtime@^0.9.5: version "0.9.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz#403d6d40a4bdff9c330dd9392dcbb2d9a8bba1fc" @@ -5655,31 +5687,7 @@ replacestream@^4.0.0: object-assign "^4.0.1" readable-stream "^2.0.2" -request@2.65.0: - version "2.65.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.65.0.tgz#cc1a3bc72b96254734fc34296da322f9486ddeba" - dependencies: - aws-sign2 "~0.6.0" - bl "~1.0.0" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~1.0.0-rc3" - har-validator "~2.0.2" - hawk "~3.1.0" - http-signature "~0.11.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - node-uuid "~1.4.3" - oauth-sign "~0.8.0" - qs "~5.2.0" - stringstream "~0.0.4" - tough-cookie "~2.2.0" - tunnel-agent "~0.4.1" - -request@^2.55.0, request@^2.75.0: +request@2.78.0, request@^2.55.0, request@^2.75.0: version "2.78.0" resolved "https://registry.yarnpkg.com/request/-/request-2.78.0.tgz#e1c8dec346e1c81923b24acdb337f11decabe9cc" dependencies: @@ -5933,59 +5941,49 @@ sntp@1.x.x: dependencies: hoek "2.x.x" -socket.io-adapter@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.4.0.tgz#fb9f82ab1aa65290bf72c3657955b930a991a24f" +socket.io-adapter@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" dependencies: - debug "2.2.0" - socket.io-parser "2.2.2" + debug "2.3.3" + socket.io-parser "2.3.1" -socket.io-client@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.5.0.tgz#08232d0adb5a665a7c24bd9796557a33f58f38ae" +socket.io-client@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.6.0.tgz#5b668f4f771304dfeed179064708386fa6717853" dependencies: backo2 "1.0.2" component-bind "1.0.0" - component-emitter "1.2.0" - debug "2.2.0" - engine.io-client "1.7.0" + component-emitter "1.2.1" + debug "2.3.3" + engine.io-client "1.8.0" has-binary "0.1.7" indexof "0.0.1" object-component "0.0.3" - parseuri "0.0.4" - socket.io-parser "2.2.6" + parseuri "0.0.5" + socket.io-parser "2.3.1" to-array "0.1.4" -socket.io-parser@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.2.2.tgz#3d7af6b64497e956b7d9fe775f999716027f9417" - dependencies: - benchmark "1.0.0" - component-emitter "1.1.2" - debug "0.7.4" - isarray "0.0.1" - json3 "3.2.6" - -socket.io-parser@2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.2.6.tgz#38dfd61df50dcf8ab1d9e2091322bf902ba28b99" +socket.io-parser@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" dependencies: - benchmark "1.0.0" component-emitter "1.1.2" debug "2.2.0" isarray "0.0.1" json3 "3.3.2" -socket.io@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.5.0.tgz#024dd9719d9267d6a6984eebe2ab5ceb9a0b8a98" +socket.io@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.6.0.tgz#3e40d932637e6bd923981b25caf7c53e83b6e2e1" dependencies: - debug "2.2.0" - engine.io "1.7.0" + debug "2.3.3" + engine.io "1.8.0" has-binary "0.1.7" - socket.io-adapter "0.4.0" - socket.io-client "1.5.0" - socket.io-parser "2.2.6" + object-assign "4.1.0" + socket.io-adapter "0.5.0" + socket.io-client "1.6.0" + socket.io-parser "2.3.1" sort-keys@^1.0.0: version "1.1.2" @@ -6265,6 +6263,10 @@ tapable@^0.2.3, tapable@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.4.tgz#a7814605089d4ba896c33c7e3566e13dcd194aa5" +tapable@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.5.tgz#1ff6ce7ade58e734ca9bfe36ba342304b377a4d0" + tar-pack@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.3.0.tgz#30931816418f55afc4d21775afdd6720cee45dae" @@ -6388,10 +6390,6 @@ tough-cookie@^2.3.1, tough-cookie@~2.3.0: dependencies: punycode "^1.4.1" -tough-cookie@~2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.2.2.tgz#c83a1830f4e5ef0b93ef2a3488e724f8de016ac7" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -6404,13 +6402,12 @@ tryit@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" -ts-loader@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-1.3.0.tgz#a94128fcfda5528f19d7d23344265ed56111013f" +ts-loader@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-1.3.2.tgz#55b21ebb3fc41b854861df9ccab5ff52817e79a9" dependencies: - arrify "^1.0.0" colors "^1.0.3" - enhanced-resolve "^0.9.0" + enhanced-resolve "^3.0.0" loader-utils "^0.2.6" object-assign "^4.1.0" semver "^5.0.1" @@ -6466,12 +6463,12 @@ typedarray@~0.0.5: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" typescript@next: - version "2.2.0-dev.20161206" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.0-dev.20161206.tgz#12afecd7146249a84c1b06338c9cda29261fd00d" + version "2.2.0-dev.20161211" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.0-dev.20161211.tgz#aae660106b16945abf6bbb6d74a007205f92cd0a" -ua-parser-js@0.7.10, ua-parser-js@^0.7.9: - version "0.7.10" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f" +ua-parser-js@0.7.12, ua-parser-js@^0.7.9: + version "0.7.12" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" uglify-js@2.7.0: version "2.7.0" @@ -6704,6 +6701,10 @@ vinyl@^1.0.0, vinyl@^1.1.0: clone-stats "^0.0.1" replace-ext "0.0.1" +vlq@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c" + vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -6988,9 +6989,9 @@ xmlbuilder@^4.1.0: dependencies: lodash "^4.0.0" -xmlhttprequest-ssl@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.1.tgz#3b7741fea4a86675976e908d296d4445961faa67" +xmlhttprequest-ssl@1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0: version "4.0.1" @@ -7007,12 +7008,6 @@ yargs-parser@^2.4.0: camelcase "^3.0.0" lodash.assign "^4.0.6" -yargs-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.0.2.tgz#7f7173a8c7cca1d81dc7c18692fc07c2c2e2b1e0" - dependencies: - camelcase "^3.0.0" - yargs-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.1.0.tgz#313df030f20124124aeae8fbab2da53ec28c56d7" @@ -7048,25 +7043,7 @@ yargs@4.7.1: y18n "^3.2.1" yargs-parser "^2.4.0" -yargs@6.0.0, yargs@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.0.0.tgz#900479df4e8bf6ab0e87216f5ed2b2760b968345" - dependencies: - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - window-size "^0.2.0" - y18n "^3.2.1" - yargs-parser "^4.0.2" - -yargs@^6.3.0: +yargs@6.4.0, yargs@^6.0.0, yargs@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.4.0.tgz#816e1a866d5598ccf34e5596ddce22d92da490d4" dependencies: