From 6cfca7b04b0df32ba5068996eda2262ba01f4c05 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 13 Aug 2019 18:10:44 +0100 Subject: [PATCH 01/26] Develop (#175) * added support for docx - needs better integration altho it works * adding timecodes and speakers to plain text export * develop: Fix 159 performance problem (#171) * moved header into a component with shouldComponentUopdate false to avoid unecessary re-renders - test * moved Header component into separate file +removed unused styling, commented out for now in case it's needed, eg in mobile view? * moved playback_rates const into separate file * optimised re-render of playback rate * optimise re-render for VideoPlayer * added some notes - draft on how to prevent uncessary re-renders in React * Added some comments * small note in docs about using console.log in render to measure performance * ammend to notes * Update 2019-05-16-prevent-unnecessary-re-renders-in-react.md * notes update * notes fix * trying out why-did-you-update * updated MediaPlayer and subcomponents * made ToolTip 'how does this work' into it's own component * updated Demo app to reduce unecessary re-renders * added react-visibility-sensor * refactor settings * removed unecessary attributes from state of components + WrapperBlock performance tweak using react-visibility-sensor * develop: Update timestamps diff (#172) * Added timestamp update via diff tool * Added missing function * Commited intermediate state * Rewrote timestamp alignment and differ to be integrated in each other instead of doing a 2-step process. * Update Timestamps now works correctly. * Fixed errors from rebase, removed debug code * Moved UpdateTimestamp into its own folder. * added updateTimestampsSSTAlign which updates the timestamps with the sst-align code * Added documentation * Merged timer for updating the timestamps and local save. * Selection state is now kept across updates to timestamps * Fixed bug where words with punctuation always are considered as new words. Timestamp update function now also uses the alignWords function directly instead of alignJSONText, removing some overhead. * Fixed small bug which raised an error if an empty block was present during timestamp update * Changed time of timestamp-update. Now re-calculates the timestamps after 5 seconds if the transcript has been edited or if the user saves the transcript manually with the save button * Code cleanup * develop: Murezzda update timestamps diff (#173) * Added timestamp update via diff tool * Added missing function * Commited intermediate state * Rewrote timestamp alignment and differ to be integrated in each other instead of doing a 2-step process. * Update Timestamps now works correctly. * Fixed errors from rebase, removed debug code * Moved UpdateTimestamp into its own folder. * added updateTimestampsSSTAlign which updates the timestamps with the sst-align code * Added timestamp update via diff tool * Added missing function * Commited intermediate state * Rewrote timestamp alignment and differ to be integrated in each other instead of doing a 2-step process. * Update Timestamps now works correctly. * Fixed errors from rebase, removed debug code * Moved UpdateTimestamp into its own folder. * added updateTimestampsSSTAlign which updates the timestamps with the sst-align code * Added documentation * Merged timer for updating the timestamps and local save. * Selection state is now kept across updates to timestamps * Fixed bug where words with punctuation always are considered as new words. Timestamp update function now also uses the alignWords function directly instead of alignJSONText, removing some overhead. * Fixed small bug which raised an error if an empty block was present during timestamp update * Changed time of timestamp-update. Now re-calculates the timestamps after 5 seconds if the transcript has been edited or if the user saves the transcript manually with the save button * Code cleanup * some changes to show sudgestions for PR * added some of changes sudgested in PR * Update package.json * develop: Murezzda update timestamps diff dpe groups words by speaker (#174) * Added timestamp update via diff tool * Added missing function * Commited intermediate state * Rewrote timestamp alignment and differ to be integrated in each other instead of doing a 2-step process. * Update Timestamps now works correctly. * Fixed errors from rebase, removed debug code * Moved UpdateTimestamp into its own folder. * added updateTimestampsSSTAlign which updates the timestamps with the sst-align code * Added timestamp update via diff tool * Added missing function * Commited intermediate state * Rewrote timestamp alignment and differ to be integrated in each other instead of doing a 2-step process. * Update Timestamps now works correctly. * Fixed errors from rebase, removed debug code * Moved UpdateTimestamp into its own folder. * added updateTimestampsSSTAlign which updates the timestamps with the sst-align code * Added documentation * Merged timer for updating the timestamps and local save. * Selection state is now kept across updates to timestamps * Fixed bug where words with punctuation always are considered as new words. Timestamp update function now also uses the alignWords function directly instead of alignJSONText, removing some overhead. * Fixed small bug which raised an error if an empty block was present during timestamp update * Changed time of timestamp-update. Now re-calculates the timestamps after 5 seconds if the transcript has been edited or if the user saves the transcript manually with the save button * Code cleanup * some changes to show sudgestions for PR * added some of changes sudgested in PR * adjusted DPE adapter so that it preserves paragraphs break within contiguos speakers * fixed one test * commented out auto align left aligning as a step before save btn and before export function, rather then as a step that happens everytime autosave is triggered, as that might be unecessary, and add performance overhead, I also noticed the cursor position jumped after realignement, thought something was been put in place to preserve/avoid that? * updated package-lock * fixed vulneranilities * Getting ready to publish alpha * 1.0.4-alpha.0 * 1.0.4@alpha * fixed notes * changing speaker and timecodes to be unselectable * 1.0.4-alpha.1 * 1.0.4-alpha.1 unselectabel speakers and timecodes * fix speaker and timecodes at paragraph level after realignement * 1.0.4-alpha.2 * fixed docx integration * Subtitles export (#168) * added option to export srt files and layout to export other type of captions with auto segmentation of lines * added support for other subtitles formats * fixed npm audit * implemented export in UI * fixed test added sample files for adding tests for subtitle composer module at later stage * added optional analytics for export download options * updated CSS * 1.0.4-alpha.3 * fixed subtiles segmentation algo was picking the wrong words from the list * moved PR template in github folder * cleaned up code for subtitles parsing * 1.0.4-alpha.4 * fixed alignement algo after interpolation words time boundares where overlapping * fixed interpolation * fixed filename of word doc export * fixed TimeBox and playback rate not working * 1.0.4-alpha.5 * removed scroll sync (#181) fix https://github.com/bbc/react-transcript-editor/issues/180 * refactor changes from James review https://github.com/bbc/react-transcript-editor/pull/160 * Develop branch - should component update refactor (#182) * Refactor should component updatre for transcript editor * Refactor should component updatre for PlayerControls * Refactor should component updatre for TimeBox * Refactor should component update for ProgressBar * Refactor should component update for TimedTextEditor * Refactor should component update for Header * fix from PR review Fixed from James comments from https://github.com/bbc/react-transcript-editor/pull/144#pullrequestreview-273740995 * Update package.json * Update package.json --- .../PULL_REQUEST_TEMPLATE.md | 0 demo/app.js | 10 +- demo/index.module.css | 1 - demo/select-export-format.js | 9 + ...prevent-unnecessary-re-renders-in-react.md | 330 + docs/notes/2019-07-31-npm-tags.md | 21 + package-lock.json | 658 +- package.json | 18 +- packages/components/media-player/index.js | 72 +- .../media-player/src/PLAYBACK_RATES.js | 16 + .../media-player/src/PlaybackRate.js | 41 +- .../src/PlayerControls/TimeBox.js | 36 + .../index.js} | 49 +- .../index.module.css} | 2 +- .../media-player/src/ProgressBar.js | 15 +- packages/components/settings/index.js | 14 +- .../UpdateTimestamps/example-usage.js | 34 + .../UpdateTimestamps/index.js | 89 + .../NathanielGleicher-aws-dpe.sample.json | 35490 ++++++++++++++++ ...niel Gleicher-F0ykdaOck_M.en.txt.sample.js | 259 + .../sample/alignement-result.sample.json | 34098 +++++++++++++++ .../UpdateTimestamps/stt-align-node.js | 186 + packages/components/timed-text-editor/Word.js | 13 +- .../timed-text-editor/WrapperBlock.js | 61 +- .../timed-text-editor/WrapperBlock.module.css | 4 +- .../components/timed-text-editor/index.js | 187 +- .../components/transcript-editor/index.js | 242 +- .../transcript-editor/index.module.css | 41 +- .../transcript-editor/src/ExportOptions.js | 63 + .../transcript-editor/src/Header.js | 63 + .../transcript-editor/src/HowDoesThisWork.js | 57 + .../transcript-editor/src/index.module.css | 95 + packages/components/video-player/index.js | 10 + packages/export-adapters/docx/index.js | 56 + packages/export-adapters/index.js | 48 +- .../compose-subtitles/csv.js | 18 + .../compose-subtitles/itt.js | 50 + .../compose-subtitles/premiere.js | 28 + .../compose-subtitles/srt.js | 11 + .../compose-subtitles/ttml.js | 18 + .../compose-subtitles/util/escape-text.js | 6 + .../compose-subtitles/util/format-seconds.js | 3 + .../compose-subtitles/util/tc-format.js | 10 + .../compose-subtitles/vtt.js | 12 + .../subtitles-generator/example-usage.js | 33 + .../subtitles-generator/index.js | 106 + .../presegment-text/README.md | 87 + .../divide-into-two-lines/README.md | 69 + .../divide-into-two-lines/index.js | 37 + .../divide-into-two-lines/index.test.js | 50 + .../presegment-text/fold/README.md | 54 + .../presegment-text/fold/index.js | 68 + .../presegment-text/fold/index.test.js | 32 + .../presegment-text/index.js | 41 + .../presegment-text/index.test.js | 18 + .../line-break-between-sentences/README.md | 38 + .../line-break-between-sentences/index.js | 7 + .../index.test.js | 17 + .../presegment-text/steps.md | 236 + .../text-segmentation/HONORIFICS.txt | 84 + .../text-segmentation/README.md | 40 + .../text-segmentation/index.js | 27 + .../text-segmentation/index.test.js | 20 + .../remove-space-after-carriage-return.js | 9 + .../util/remove-space-at-beginning-of-line.js | 9 + .../sample/test-premiere.sample.xml | 217 + .../sample/test-presegment.sample.txt | 494 + .../sample/test.sample.csv | 296 + .../sample/test.sample.json | 1002 + .../sample/test.sample.txt | 494 + .../sample/words-list-2.sample.json | 34434 +++++++++++++++ .../sample/words-list.sample.json | 8862 ++++ packages/export-adapters/txt/index.js | 26 +- packages/index.js | 8 +- .../group-words-by-speakers.js | 165 +- ...est.js => group-words-by-speakers.test.js} | 0 .../digital-paper-edit/index.test.js | 2 +- packages/stt-adapters/index.js | 1 + webpack.config.js | 4 +- 79 files changed, 119019 insertions(+), 612 deletions(-) rename PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md (100%) create mode 100644 docs/notes/2019-05-16-prevent-unnecessary-re-renders-in-react.md create mode 100644 docs/notes/2019-07-31-npm-tags.md create mode 100644 packages/components/media-player/src/PLAYBACK_RATES.js create mode 100644 packages/components/media-player/src/PlayerControls/TimeBox.js rename packages/components/media-player/src/{PlayerControls.js => PlayerControls/index.js} (81%) rename packages/components/media-player/src/{PlayerControls.module.css => PlayerControls/index.module.css} (97%) create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/example-usage.js create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/index.js create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/sample/NathanielGleicher-aws-dpe.sample.json create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/sample/The Facebook Dilemma - Nathaniel Gleicher-F0ykdaOck_M.en.txt.sample.js create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/sample/alignement-result.sample.json create mode 100644 packages/components/timed-text-editor/UpdateTimestamps/stt-align-node.js create mode 100644 packages/components/transcript-editor/src/ExportOptions.js create mode 100644 packages/components/transcript-editor/src/Header.js create mode 100644 packages/components/transcript-editor/src/HowDoesThisWork.js create mode 100644 packages/components/transcript-editor/src/index.module.css create mode 100644 packages/export-adapters/docx/index.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/csv.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/itt.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/premiere.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/srt.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/ttml.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/util/escape-text.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/util/format-seconds.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/util/tc-format.js create mode 100644 packages/export-adapters/subtitles-generator/compose-subtitles/vtt.js create mode 100644 packages/export-adapters/subtitles-generator/example-usage.js create mode 100644 packages/export-adapters/subtitles-generator/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/README.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/divide-into-two-lines/README.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/divide-into-two-lines/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/divide-into-two-lines/index.test.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/fold/README.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/fold/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/fold/index.test.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/index.test.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/line-break-between-sentences/README.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/line-break-between-sentences/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/line-break-between-sentences/index.test.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/steps.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/HONORIFICS.txt create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/README.md create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/index.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/index.test.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/util/remove-space-after-carriage-return.js create mode 100644 packages/export-adapters/subtitles-generator/presegment-text/util/remove-space-at-beginning-of-line.js create mode 100644 packages/export-adapters/subtitles-generator/sample/test-premiere.sample.xml create mode 100644 packages/export-adapters/subtitles-generator/sample/test-presegment.sample.txt create mode 100644 packages/export-adapters/subtitles-generator/sample/test.sample.csv create mode 100644 packages/export-adapters/subtitles-generator/sample/test.sample.json create mode 100644 packages/export-adapters/subtitles-generator/sample/test.sample.txt create mode 100644 packages/export-adapters/subtitles-generator/sample/words-list-2.sample.json create mode 100644 packages/export-adapters/subtitles-generator/sample/words-list.sample.json rename packages/stt-adapters/digital-paper-edit/{groups-words-by-speakers.test.js => group-words-by-speakers.test.js} (100%) diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/demo/app.js b/demo/app.js index 3bd4822a..ce05b32c 100644 --- a/demo/app.js +++ b/demo/app.js @@ -1,5 +1,9 @@ import React from 'react'; - +// NOTE: This slows down performance, even during development +// if (process.env.NODE_ENV !== 'production') { +// const { whyDidYouUpdate } = require('why-did-you-update'); +// whyDidYouUpdate(React, { exclude: [ /^HotKeysWrapper/ ] } ); +// } import TranscriptEditor from '../packages/components/transcript-editor'; import SttTypeSelect from './select-stt-json-type'; import ExportFormatSelect from './select-export-format'; @@ -116,7 +120,9 @@ class App extends React.Component { if (ext === 'json') { tmpData = JSON.stringify(data, null, 2); } - this.download(tmpData, `${ this.state.mediaUrl }.${ ext }`); + if (ext !== 'docx') { + this.download(tmpData, `${ this.state.mediaUrl }.${ ext }`); + } }; // https://stackoverflow.com/questions/2897619/using-html5-javascript-to-generate-and-save-a-file diff --git a/demo/index.module.css b/demo/index.module.css index 60aa0d0c..b5f5837c 100644 --- a/demo/index.module.css +++ b/demo/index.module.css @@ -124,7 +124,6 @@ body { @media (max-width: 767px) { body { padding: 0; - text-align: center; } .demoNavItem { diff --git a/demo/select-export-format.js b/demo/select-export-format.js index a5dd1450..3add5806 100644 --- a/demo/select-export-format.js +++ b/demo/select-export-format.js @@ -9,6 +9,15 @@ const ExportFormatSelect = props => { + + + + + + + + + ; }; diff --git a/docs/notes/2019-05-16-prevent-unnecessary-re-renders-in-react.md b/docs/notes/2019-05-16-prevent-unnecessary-re-renders-in-react.md new file mode 100644 index 00000000..dc35e142 --- /dev/null +++ b/docs/notes/2019-05-16-prevent-unnecessary-re-renders-in-react.md @@ -0,0 +1,330 @@ +# How to prevent unnecessary re-renders - Draft + +Some notes on how to How to prevent unnecessary re-renders in React + +## Start here + +[React docs - Thinking in React](https://reactjs.org/docs/thinking-in-react.html) + +[React JS cheat sheet](https://devhints.io/react) + +[SOLID: Part 1 - The Single Responsibility Principle](https://code.tutsplus.com/tutorials/solid-part-1-the-single-responsibility-principle--net-36074) + +if want to go deeper in ['the single responsibility principle' read this](https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html) + +>Another wording for the Single Responsibility Principle is: +> +> > Gather together the things that change for the same reasons. Separate those things that change for different reasons. + + +## States and Props + +>State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app, you don’t need it. +[from react docs](https://reactjs.org/docs/thinking-in-react.html#step-3-identify-the-minimal-but-complete-representation-of-ui-state) + + +>Let’s go through each one and figure out which one is state. Simply ask three questions about each piece of data: +> +>1. Is it passed in from a parent via props? If so, it probably isn’t state. +>2. Does it remain unchanged over time? If so, it probably isn’t state. +>3. Can you compute it based on any other state or props in your component? If so, it isn’t state. +[React docs - thinking in react](https://reactjs.org/docs/thinking-in-react.html#step-4-identify-where-your-state-should-live) + + +>Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand, so follow these steps to figure it out: +> +>For each piece of state in your application: + +> - Identify every component that renders something based on that state. +> -Find a common owner component (a single component above all the components that need the state in the hierarchy). +> - Either the common owner or another component higher up in the hierarchy should own the state. +> - If you can’t find a component where it makes sense to own the state, create a new component simply for holding the state and add it somewhere in the hierarchy above the common owner component. + +[React docs - thinking in react](https://reactjs.org/docs/thinking-in-react.html) + + +### updating state +updating state is async + +>There is one crucial case where it makes sense to use a function over an object: when you update the state depending on the previous state or props. If you don't use a function, the local state management can cause bugs. The React setState() method is asynchronous. React batches setState() calls and executes them eventually. Sometimes, the previous state or props changes between before we can rely on it in our setState() call. +[React book](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter6.md#revisited-setstate) + +>With the function approach, the function in setState() is a callback that operates on the state and props at the time of executing the callback function. Even though setState() is asynchronous, with a function it takes the state and props at the time when it is executed. + +[React book](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter6.md#revisited-setstate) + +## Component Lifecycle +[react lifecycle methods diagram](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) + +good to read along side this chapter [Chapter 3 boook -the-road-to-learn-react - Getting Real with APIs ](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter3.md) + +## Re-renders + + +>A React Component works hard. As the user manipulates the state of the application, it may re-render 5, 10, 100 times. Sometimes, that’s a good thing. But if you don’t understand what is causing the re-renders, and whether they are necessary, your app could suffer serious slowdown. +[How to Benchmark React Components: The Quick and Dirty Guide](https://engineering.musefind.com/how-to-benchmark-react-components-the-quick-and-dirty-guide-f595baf1014c) + +> A re-render can only be triggered if a component’s state has changed. The state can change from a props change, or from a direct setState change. The component gets the updated state and React decides if it should re-render the component. Unfortunately, by default React is incredibly simplistic and basically re-renders everything all the time. +>Component changed? Re-render. Parent changed? Re-render. Section of props that doesn't actually impact the view changed? Re-render. + +[source](https://lucybain.com/blog/2017/react-js-when-to-rerender/) + +## Re-render vs re-paint +calling a re-render doesn't mean that React is repainting the DOM. +it means that react it's calling it's diffing algorith to check changes against the virtual dom. it might do that, and still determine that no re-paint is needed. the issue is that calling unecessary re-renders (those when it ends up determine that no change is neeeded) to often, can lead to slower performance. + + +>Now, I might be painting an alarming picture of unnecessary work that has been going on right under our noses. One thing to keep in mind is that a render method being called is not the same thing as the DOM ultimately getting updated. There are a few additional steps React takes where the DOM is diffed (aka the previous version compared with the new/current version) to truly see if any changes need to be represented. All of these few additional steps is work, and for more complex apps with a lot of components, there will be many MANY instances of a few additional steps that will start to add up. Some of this is additional work done by React's internals. Some of this additional work is just important things we do in our render methods, for we often have a lot of code there to help generate the appropriate JSX. Rarely is our render method returning a static piece of JSX with no evaluation or calculation happening, so minimizing unnecessary render calls is a good thing! +[source](https://www.kirupa.com/react/avoiding_unnecessary_renders.htm) + + +## `shouldComponentUpdate` + +>By default, shouldComponentUpdate returns true. That’s what causes the “update everything all the time” we saw above. However, you can overwrite shouldComponentUpdate to give it more “smarts” if you need the performance boost. Instead of letting React re-render all the time, you can tell React when you don’t want to trigger a re-render. + +[source](https://lucybain.com/blog/2017/react-js-when-to-rerender/) + + +> Returning false does not prevent child components from re-rendering when their state changes. +... +>We do not recommend doing deep equality checks or using JSON.stringify() in shouldComponentUpdate(). It is very inefficient and will harm performance. +[source - react docs](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) + + +>This applies to the children’s state but not their props. So if a child component is internally managing some aspect of its state (with a setState of its own), that will still be updated. But if the parent component returns false from shouldComponentUpdate it will not pass the updated props along to its children, and so the children will not re-render, even if their props had updated. + +[source](https://lucybain.com/blog/2017/react-js-when-to-rerender/) + + +>This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs. Consider using the built-in PureComponent instead of writing shouldComponentUpdate() by hand. PureComponent performs a shallow comparison of props and state, and reduces the chance that you’ll skip a necessary update. +[React docs](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) + + + + + +[How to Benchmark React Components: The Quick and Dirty Guide](https://engineering.musefind.com/how-to-benchmark-react-components-the-quick-and-dirty-guide-f595baf1014c) + +## React Pure components + +`PureComponent` + +``` +import React, { PureComponent } from "react"; + +class MenuButton extends PureComponent { +... + +``` + +[React pure components](https://reactjs.org/docs/react-api.html#reactpurecomponent) + + +>Based on the concept of purity in functional programming paradigms, a function is said to be pure if: +> +> - its return value is only determined by its input values. +> - its return value is always the same for the same input values. +>A React component can be considered pure if it renders the same output for the same state and props. For class components like this, React provides the PureComponent base class. Class components that extend the React.PureComponent class are treated as pure components. +> +>Pure components have some performance improvements and render optimizations since React implements the shouldComponentUpdate() method for them with a shallow comparison for props and state. +[source](https://logrocket.com/blog/pure-functional-components/) + +--- + +in a normal component, before it updates, `shoulComponentUpdate` will fire. +You can add code there to check if you want the component to update. eg if you return false, it won’t update. + +there’s a force update, but if you are using that, you might need to check the logic. + +Pure component → expec to work similar to pure function + +- no side effects +- no notification `shouldComponentUpdate` + +They are an optimization pattern, faster then ordinary component. (coz don’t fire `shouldComponentUpdate` decide for itself if needs to update) + +pure components should include components that “act as pure components”. + +used for presentation/read only. + +Useful for refactoring, and speeding up for performance. eg if you want it to refresh less often. + +from React BBC academy course. +--- + + +>If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases. +[React - Pure Component](https://reactjs.org/docs/react-api.html#reactpurecomponent) + + +## `React.StrictMode` +use `React.StrictMode` to identify components with unsafe lifecycles + +>StrictMode is a tool for highlighting potential problems in an application. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants. + +[React docs - Strict Mode](https://reactjs.org/docs/strict-mode.html) + +[Strict mode warnings](https://fb.me/react-strict-mode-warnings) + + +## Error Handling + +error, store it in your local state, and show a message to the user. + +[React 16 - error handling](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) + +[why not use try catch ](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html#why-not-use-try--catch) + +> Lastly, componentDidCatch(error, info) was introduced in React 16 as a way to catch errors in components. For instance, displaying the sample list in your application works fine, but there could be a time when a list in the local state is set to null by accident (e.g. when fetching the list from an external API, but the request failed and you set the local state of the list to null). It becomes impossible to filter and map the list, because it is null and not an empty list. The component would be broken, and the whole application would fail. Using componentDidCatch(), you can catch the + +[source](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter3.md) + +[Error boundaries](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html#introducing-error-boundaries) + +>A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an “error boundary”. +> +>Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. +> +> A class component becomes an error boundary if it defines a new lifecycle method called componentDidCatch(error, info) +[React docs](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html#introducing-error-boundaries) + + +>Where to Place Error Boundaries +>The granularity of error boundaries is up to you. You may wrap top-level route components to display a “Something went wrong” message to the user, just like server-side frameworks often handle crashes. You may also wrap individual widgets in an error boundary to protect them from crashing the rest of the application. +[react docs](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html#where-to-place-error-boundaries) + + +[`componentDidCatch`](https://reactjs.org/docs/react-component.html#componentdidcatch) + + +--- +in js - try and catch, finally - to manage errors + +in react, do only in event handling. + +error boundaries, allows to handle errors in constructor and render. + +`componentDidCatch` is the error handler catch errors in any components, underneath this one in the component hierarchy. +error boundary for anything below it. + +does not trap errors in itself, in it’s own component. + +generally added to the container. + +react defaults was, if somethings goes wrong, render nothing. +with error boundaries, can narrow down when error occoured + + +From react BBC course + +--- + + +### Error Handling API requests +[Error Handling - with fetch API](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter3.md#error-handling) + +## Loadng + +>The initial value of that isLoading property is false. We don't load anything before the App component is mounted. When the request is made, the loading state is set to true. The request will succeed eventually, and you can set the loading state to false. + +[Book - loadding...](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter5.md#loading-) + +### React.Suspense + +>React.Suspense let you specify the loading indicator in case some components in the tree below it are not yet ready to render. Today, lazy loading components is the only use case supported by : + +[React docs](https://reactjs.org/docs/react-api.html#reactsuspense) + +## React events + +[events cheat sheet](https://frontarm.com/james-k-nelson/react-events-cheatsheet/) + +## Tests + +[Tests - book chapter](https://github.com/pietrop/the-road-to-learn-react/blob/master/manuscript/chapter4.md) + +## Containment + +[react docs](https://reactjs.org/docs/composition-vs-inheritance.html#containment) + + +## Sources + +- [How does React decide to re-render a component?](https://lucybain.com/blog/2017/react-js-when-to-rerender/) +- [react lifecycle methods diagram](http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/) +[React docs -> Avoid Reconciliation](https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation) +- [React pure components](https://reactjs.org/docs/react-api.html#reactpurecomponent) + + +## Actions you can take to improve performance + +### `shouldComponentUpdate` + +```js +shouldComponentUpdate(nextProps, nextState){ + return false; +} +``` + +### `PureComponent` +use `PureComponent` + +``` +import React, { PureComponent } from "react"; + +class MenuButton extends PureComponent { +... + +``` + +### [` why-did-you-update`](https://github.com/maicki/why-did-you-update) + +use ` why-did-you-update` lib to identify unecessary re-renders + +``` +npm install --save-dev why-did-you-update +``` + +```js +import React from 'react'; + +if (process.env.NODE_ENV !== 'production') { + const {whyDidYouUpdate} = require('why-did-you-update'); + whyDidYouUpdate(React); +} +``` + +[Common Fixing Scenarios](https://github.com/maicki/why-did-you-update#common-fixing-scenarios) + +### React DevTools tools profiler + +[React docs - Introducing the React Profiler](https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html) + +### add a `console.log` in render method +add a `console.log` in render method of component, to see count of how often is called, before and after tweaks + +### `React.StrictMode` +use `React.StrictMode` as described above to identify other issues in your code. + +### consider refactoring +consider refactoring the logic of the app and components to break them into smaller more manageable units. + +[source](https://medium.com/dailyjs/react-is-slow-react-is-fast-optimizing-react-apps-in-practice-394176a11fba) + +## `getDerivedStateFromProps` + +[You Probably Don't Need Derived State](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html) + + +### Blog +- [REACT 16: PREVENT UNNECESSARY RE-RENDERS WITH FUNCTIONAL SETSTATE() +](https://x-team.com/blog/react-render-setstate/) +- [5 Ways to Stop Wasting Renders in React/Redux](https://medium.com/voobans-tech-stories/5-ways-to-stop-wasting-renders-in-react-redux-73b3c5d86f50) +- [Avoiding Unnecessary Renders in React](https://www.kirupa.com/react/avoiding_unnecessary_renders.htm) +- [How to prevent a rerender in React](https://www.robinwieruch.de/react-prevent-rerender-component/) +- [How to Benchmark React Components: The Quick and Dirty Guide](https://engineering.musefind.com/how-to-benchmark-react-components-the-quick-and-dirty-guide-f595baf1014c) +- [Pure Functional Components in React 16.6, Memoizing functional components with the React.memo() API](https://logrocket.com/blog/pure-functional-components/) + +### books +- [book - The Road to learn React](https://github.com/the-road-to-learn-react/the-road-to-learn-react) +- [Book - React.js Essentials ](https://github.com/fedosejev/react-essentials) \ No newline at end of file diff --git a/docs/notes/2019-07-31-npm-tags.md b/docs/notes/2019-07-31-npm-tags.md new file mode 100644 index 00000000..8d72a2b4 --- /dev/null +++ b/docs/notes/2019-07-31-npm-tags.md @@ -0,0 +1,21 @@ +# Npm tags + +First make sure you have done a commit of latest changes then + +>You can run `npm version 1.0.4-alpha.1` to update `package.json` and create a git tag in one go (see https://docs.npmjs.com/cli/version). + +- [Publishing a beta or alpha version to NPM](https://medium.com/@kevinkreuzer/publishing-a-beta-or-alpha-version-to-npm-46035b630dd7) + +this changes `package.json` version to be + +```json + "version": "1.0.4-alpha.0", +``` + +then you can run `npm run publish:public` which under the hood preps the files and folder and runs `npm publish dist --access public`. + +To install in another repo + +``` +npm install @bbc/react-transcript-editor@alpha +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 10267734..96ae6bd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@bbc/react-transcript-editor", - "version": "1.0.2", + "version": "1.0.4-alpha.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2097,17 +2097,32 @@ "@babel/types": "^7.3.0" } }, + "@types/image-size": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/image-size/-/image-size-0.0.29.tgz", + "integrity": "sha512-d47SGzTnoUXSLRn3Kej43FZXLduZjHJqkb26BmxKp9fQveCvjfirtpk7a5iLCGkJ/rur9kxUf7DwD2eKlPxjMg==", + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", "dev": true }, + "@types/jszip": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.1.6.tgz", + "integrity": "sha512-m8uFcI+O2EupCfbEVQWsBM/4nhbegjOHL7cQgBpM95FeF98kdFJXzy9/8yhx4b3lCRl/gMBhcvyh30Qt3X+XPQ==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "11.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.0.tgz", - "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==", - "dev": true + "integrity": "sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng==" }, "@types/q": { "version": "1.5.2", @@ -2528,7 +2543,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -2670,8 +2684,7 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.3.2", @@ -4063,7 +4076,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4083,9 +4095,9 @@ "dev": true }, "character-entities-html4": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.2.tgz", - "integrity": "sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", + "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==", "dev": true }, "character-entities-legacy": { @@ -4436,9 +4448,9 @@ "dev": true }, "collapse-white-space": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", - "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", + "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", "dev": true }, "collection-visit": { @@ -4455,7 +4467,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -4463,8 +4474,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.3.3", @@ -4703,8 +4713,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.0", @@ -5215,6 +5224,14 @@ "randombytes": "^2.0.0" } }, + "difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha1-teMDYabbAjF21WKJLbhZQKcY9H4=", + "requires": { + "heap": ">= 0.2.0" + } + }, "dir-glob": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", @@ -5240,6 +5257,19 @@ "esutils": "^2.0.2" } }, + "docx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/docx/-/docx-4.7.1.tgz", + "integrity": "sha512-MTToHT11MV8Srnzy+JJ2gyotEhub3t5ey+96J12OCMujvLGjEoLigtTnIvMonKlA+TvDtNKbGsiU2h8WOD6wdw==", + "requires": { + "@types/image-size": "0.0.29", + "@types/jszip": "^3.1.4", + "image-size": "^0.6.2", + "jszip": "^3.1.5", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + } + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -5262,7 +5292,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, "requires": { "domelementtype": "^1.3.0", "entities": "^1.1.1" @@ -5295,8 +5324,7 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domexception": { "version": "1.0.1", @@ -5311,7 +5339,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, "requires": { "domelementtype": "1" } @@ -5320,7 +5347,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -5373,6 +5399,13 @@ "fbjs": "^0.8.15", "immutable": "~3.7.4", "object-assign": "^4.1.0" + }, + "dependencies": { + "immutable": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", + "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks=" + } } }, "duplexer": { @@ -5570,8 +5603,7 @@ "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "enzyme": { "version": "3.9.0", @@ -5666,8 +5698,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.11.1", @@ -6258,6 +6289,11 @@ "original": "^1.0.0" } }, + "everpolate": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/everpolate/-/everpolate-0.0.3.tgz", + "integrity": "sha1-OxsxhGVJRxKqHrEGFERo6kF9ASk=" + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -7013,25 +7049,29 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, "requires": { @@ -7041,13 +7081,15 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, "requires": { @@ -7057,37 +7099,43 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "requires": { @@ -7096,25 +7144,29 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -7123,13 +7175,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -7145,7 +7199,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, "requires": { @@ -7159,13 +7214,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, "requires": { @@ -7174,7 +7231,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -7183,7 +7241,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -7193,19 +7252,22 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, "requires": { @@ -7214,13 +7276,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, "requires": { @@ -7229,13 +7293,15 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, "requires": { @@ -7245,7 +7311,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, "requires": { @@ -7254,7 +7321,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, "requires": { @@ -7263,13 +7331,15 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "needle": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", "dev": true, "optional": true, "requires": { @@ -7280,7 +7350,8 @@ }, "node-pre-gyp": { "version": "0.10.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", "dev": true, "optional": true, "requires": { @@ -7298,7 +7369,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -7308,13 +7380,15 @@ }, "npm-bundled": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==", "dev": true, "optional": true, "requires": { @@ -7324,7 +7398,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -7336,19 +7411,22 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, "requires": { @@ -7357,19 +7435,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -7379,19 +7460,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, "requires": { @@ -7403,7 +7487,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -7411,7 +7496,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -7426,7 +7512,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, "requires": { @@ -7435,43 +7522,50 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, "requires": { @@ -7482,7 +7576,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -7491,7 +7586,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, "requires": { @@ -7500,13 +7596,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, "requires": { @@ -7521,13 +7619,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, "requires": { @@ -7536,22 +7636,24 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true } } }, "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -7935,9 +8037,9 @@ } }, "handlebars": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", - "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -8094,6 +8196,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, "highlight.js": { "version": "9.12.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", @@ -8228,7 +8335,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, "requires": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -8242,7 +8348,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -8416,17 +8521,22 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "image-size": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", + "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==" + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "immer": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/immer/-/immer-1.12.1.tgz", "integrity": "sha512-3fmKM6ovaqDt0CdC9daXpNi5x/YCYS3i4cwLdTVkhJdk5jrDXoPs7lCm3IqM3yhfSnz4tjjxbRG2CziQ7m8ztg==", "dev": true }, - "immutable": { - "version": "3.7.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", - "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks=" - }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -8517,8 +8627,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -9019,9 +9128,9 @@ "dev": true }, "is-whitespace-character": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz", - "integrity": "sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", + "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", "dev": true }, "is-windows": { @@ -9031,9 +9140,9 @@ "dev": true }, "is-word-character": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.2.tgz", - "integrity": "sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", + "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", "dev": true }, "is-wsl": { @@ -9045,8 +9154,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -9898,6 +10006,17 @@ "array-includes": "^3.0.3" } }, + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", @@ -9990,6 +10109,14 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -10055,9 +10182,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash-es": { "version": "4.17.11", @@ -10065,6 +10192,11 @@ "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10077,6 +10209,11 @@ "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", "dev": true }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -10098,14 +10235,17 @@ "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", - "dev": true + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" }, "lodash.pick": { "version": "4.4.0", @@ -10147,9 +10287,9 @@ } }, "longest-streak": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", - "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", + "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==", "dev": true }, "loose-envify": { @@ -10270,15 +10410,15 @@ } }, "markdown-escapes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.2.tgz", - "integrity": "sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", + "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", "dev": true }, "markdown-table": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz", - "integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", "dev": true }, "markdown-to-jsx": { @@ -10304,9 +10444,9 @@ "dev": true }, "mathml-tag-names": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz", - "integrity": "sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz", + "integrity": "sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==", "dev": true }, "md5.js": { @@ -10321,9 +10461,9 @@ } }, "mdast-util-compact": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz", - "integrity": "sha512-d2WS98JSDVbpSsBfVvD9TaDMlqPRz7ohM/11G0rp5jOBb5q96RJ6YLszQ/09AAixyzh23FeIpCGqfaamEADtWg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz", + "integrity": "sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" @@ -10681,9 +10821,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -11135,8 +11275,12 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "number-to-words": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/number-to-words/-/number-to-words-1.2.4.tgz", + "integrity": "sha512-/fYevVkXRcyBiZDg6yzZbm0RuaD6i0qRfn8yr+6D0KgBMOndFPxuW10qCHpzs50nN8qKuv78k8MuotZhcVX6Pw==" }, "nwsapi": { "version": "2.1.3", @@ -11538,8 +11682,7 @@ "pako": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", - "dev": true + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, "parallel-transform": { "version": "1.1.0", @@ -11886,7 +12029,6 @@ "version": "7.0.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", - "dev": true, "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -11896,14 +12038,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -12234,9 +12374,9 @@ "dev": true }, "prettier": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", - "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", + "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", "dev": true }, "prettier-stylelint": { @@ -12339,9 +12479,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "p-limit": { @@ -12460,8 +12600,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.3", @@ -12794,7 +12933,6 @@ "version": "16.8.6", "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13019,7 +13157,6 @@ "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13258,6 +13395,14 @@ "react-lifecycles-compat": "^3.0.4" } }, + "react-visibility-sensor": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz", + "integrity": "sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==", + "requires": { + "prop-types": "^15.7.2" + } + }, "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -13298,7 +13443,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13956,8 +14100,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-eval": { "version": "0.4.1", @@ -14041,6 +14184,23 @@ } } }, + "sanitize-html": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", + "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", + "requires": { + "chalk": "^2.4.1", + "htmlparser2": "^3.10.0", + "lodash.clonedeep": "^4.5.0", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.mergewith": "^4.6.1", + "postcss": "^7.0.5", + "srcset": "^1.0.0", + "xtend": "^4.0.1" + } + }, "sass-graph": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", @@ -14291,14 +14451,20 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "sbd": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/sbd/-/sbd-1.0.16.tgz", + "integrity": "sha512-aqmwFVt1EH1sEdyoDZhXVER/SKXevNaSs5GuONR7sSBruKp4N9kR9bJsP2JJ14OE1c7/Dg/ql5QuUi3AwFdgmg==", + "requires": { + "sanitize-html": "^1.20.1" + } }, "scheduler": { "version": "0.13.6", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -14436,10 +14602,15 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -14600,6 +14771,11 @@ } } }, + "smpte-timecode": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/smpte-timecode/-/smpte-timecode-1.2.3.tgz", + "integrity": "sha512-6U+vdStmprzmpzEWS+9pw8GfiChBcMmJOdJxBjaXlGJhB9U/jcQhvh0buxJzEfhuX8E0OfYJy4hayBgAO92dBg==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -14867,6 +15043,15 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "srcset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", + "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "requires": { + "array-uniq": "^1.0.2", + "number-is-nan": "^1.0.0" + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -14906,9 +15091,9 @@ "dev": true }, "state-toggle": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.1.tgz", - "integrity": "sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", + "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", "dev": true }, "static-extend": { @@ -15086,7 +15271,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15481,9 +15665,9 @@ "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "normalize-path": { @@ -15880,13 +16064,13 @@ "dev": true }, "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "dev": true, "requires": { "block-stream": "*", - "fstream": "^1.0.2", + "fstream": "^1.0.12", "inherits": "2" } }, @@ -16226,9 +16410,9 @@ "dev": true }, "trim-trailing-lines": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", - "integrity": "sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", + "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", "dev": true }, "trough": { @@ -16334,9 +16518,9 @@ } }, "unherit": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.1.tgz", - "integrity": "sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", + "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -16408,38 +16592,15 @@ } }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { @@ -16476,24 +16637,24 @@ } }, "unist-util-find-all-after": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.2.tgz", - "integrity": "sha512-nDl79mKpffXojLpCimVXnxhlH/jjaTnDuScznU9J4jjsaUtBdDbxmlc109XtcqxY4SDO0SwzngsxxW8DIISt1w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.4.tgz", + "integrity": "sha512-CaxvMjTd+yF93BKLJvZnEfqdM7fgEACsIpQqz8vIj9CJnUb9VpyymFS3tg6TCtgrF7vfCJBF5jbT2Ox9CBRYRQ==", "dev": true, "requires": { - "unist-util-is": "^2.0.0" + "unist-util-is": "^3.0.0" } }, "unist-util-is": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.2.tgz", - "integrity": "sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", "dev": true }, "unist-util-remove-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz", - "integrity": "sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", + "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" @@ -16506,21 +16667,21 @@ "dev": true }, "unist-util-visit": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.0.tgz", - "integrity": "sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", "dev": true, "requires": { "unist-util-visit-parents": "^2.0.0" } }, "unist-util-visit-parents": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz", - "integrity": "sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", "dev": true, "requires": { - "unist-util-is": "^2.1.2" + "unist-util-is": "^3.0.0" } }, "universalify": { @@ -16784,8 +16945,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -16903,9 +17063,9 @@ } }, "vfile-location": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.4.tgz", - "integrity": "sha512-KRL5uXQPoUKu+NGvQVL4XLORw45W62v4U4gxJ3vRlDfI9QsT4ZN1PNXn/zQpKUulqGDpYuT0XDfp5q9O87/y/w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", + "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", "dev": true }, "vfile-message": { @@ -17194,6 +17354,16 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "why-did-you-update": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/why-did-you-update/-/why-did-you-update-1.0.6.tgz", + "integrity": "sha512-XVrdHhdrPBDuSW8b/uH6DCb1/0984qv8KElpE8NZiRvWZX8nw49av577+ZyIrxSNesi6r2cQEhpxQTKFFHTj8A==", + "dev": true, + "requires": { + "lodash": "^4.17.11", + "react-fast-compare": "^2.0.3" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -17317,6 +17487,19 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" + }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -17326,8 +17509,7 @@ "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index 70fe79b7..515c7f7b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@bbc/react-transcript-editor", "description": "A React component to make transcribing audio and video easier and faster.", - "version": "1.0.4", + "version": "1.0.4-alpha.5", "keywords": [ "transcript", "transcriptions", @@ -24,7 +24,8 @@ "build:storybook": "rimraf build && build-storybook -c .storybook -o build", "build:storybook:serve": "npx serve build", "deploy:ghpages": "rimraf build && npm run build:storybook && gh-pages -d build", - "publish:public": "npm run build:component && rm ./dist/package.json || true && cp package.json ./dist/package.json && rm ./dist/README.md || true && cp README.md ./dist/README.md || true && npm publish dist --access public", + "pre:publish": "npm run build:component && rm ./dist/package.json || true && cp package.json ./dist/package.json && rm ./dist/README.md || true && cp README.md ./dist/README.md || true ", + "publish:public": "npm run pre:publish && npm publish dist --access public", "publish:dry:run": "npm publish --dry-run" }, "jest": { @@ -46,11 +47,17 @@ "@fortawesome/free-solid-svg-icons": "^5.6.3", "@fortawesome/react-fontawesome": "^0.1.3", "babel-polyfill": "^6.26.0", + "difflib": "^0.2.4", + "docx": "^4.7.1", "draft-js": "^0.10.5", + "everpolate": "0.0.3", "mousetrap": "1.5.2", + "number-to-words": "^1.2.4", "prop-types": "^15.6.2", "react-keyboard-shortcuts": "^1.1.3", - "react-simple-tooltip": "^2.3.3" + "react-simple-tooltip": "^2.3.3", + "sbd": "^1.0.15", + "smpte-timecode": "^1.2.3" }, "peerDependencies": { "react": "^16.6.0", @@ -94,7 +101,7 @@ "style-loader": "^0.23.1", "stylelint-config-standard": "^18.2.0", "webpack": "^4.29.6", - "webpack-cli": "^3.3.0" + "why-did-you-update": "^1.0.6" }, "contributors": [ { @@ -122,6 +129,5 @@ "not dead", "not ie <= 11", "not op_mini all" - ], - "prettier": {} + ] } diff --git a/packages/components/media-player/index.js b/packages/components/media-player/index.js index 20901d84..1a5cc8d9 100644 --- a/packages/components/media-player/index.js +++ b/packages/components/media-player/index.js @@ -12,20 +12,7 @@ import { timecodeToSeconds } from '../../util/timecode-converter'; -const PLAYBACK_RATES = [ - { value: 0.2, label: '0.2' }, - { value: 0.25, label: '0.25' }, - { value: 0.5, label: '0.5' }, - { value: 0.75, label: '0.75' }, - { value: 1, label: '1' }, - { value: 1.25, label: '1.25' }, - { value: 1.5, label: '1.5' }, - { value: 1.75, label: '1.75' }, - { value: 2, label: '2' }, - { value: 2.5, label: '2.5' }, - { value: 3, label: '3' }, - { value: 3.5, label: '3.5' } -]; +import PLAYBACK_RATES from './src/PLAYBACK_RATES.js'; class MediaPlayer extends React.Component { constructor(props) { @@ -67,11 +54,40 @@ class MediaPlayer extends React.Component { componentDidMount() { // TODO: Should these hook functions be optional? are they needed? what do they actually do? + // TODO: these hook functions need refactoring, they are causing performance problems this.props.hookSeek(this.setCurrentTime); this.props.hookPlayMedia(this.togglePlayMedia); this.props.hookIsPlaying(this.isPlaying); } + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.rollBackValueInSeconds !== this.state.rollBackValueInSeconds) { + return true; + } + if (nextProps.timecodeOffset !== this.state.timecodeOffset) { + return true; + } + // TODO: workaround to keep the hook functions, only call re-renders + // if current time has changed. And it seems eliminate component's unecessary re-renders. + if (nextProps.currentTime !== this.props.currentTime) { + return true; + } + + if (nextState.playbackRate !== this.state.playbackRate) { + return true; + } + + if (nextProps.mediaDuration !== this.props.mediaDuration ) { + return true; + } + + if (nextState.isMute !== this.state.isMute) { + return true; + } + + return false; + } + setCurrentTime = newCurrentTime => { if (newCurrentTime !== '' && newCurrentTime !== null) { // hh:mm:ss:ff - mm:ss - m:ss - ss - seconds number or string and hh:mm:ss @@ -392,20 +408,26 @@ class MediaPlayer extends React.Component { } }; + // performance optimization + getProgressBarMax = () => { + return this.props.videoRef.current !== null + ? parseInt(this.props.videoRef.current.duration).toString() + : '100'; + } + + // performance optimization + getProgressBarValue = () => { + return this.props.videoRef.current !== null + ? parseInt(this.props.videoRef.current.currentTime) + : 0; + } + render() { const progressBar = ( ); diff --git a/packages/components/media-player/src/PLAYBACK_RATES.js b/packages/components/media-player/src/PLAYBACK_RATES.js new file mode 100644 index 00000000..f6e884e1 --- /dev/null +++ b/packages/components/media-player/src/PLAYBACK_RATES.js @@ -0,0 +1,16 @@ +const PLAYBACK_RATES = [ + { value: 0.2, label: '0.2' }, + { value: 0.25, label: '0.25' }, + { value: 0.5, label: '0.5' }, + { value: 0.75, label: '0.75' }, + { value: 1, label: '1' }, + { value: 1.25, label: '1.25' }, + { value: 1.5, label: '1.5' }, + { value: 1.75, label: '1.75' }, + { value: 2, label: '2' }, + { value: 2.5, label: '2.5' }, + { value: 3, label: '3' }, + { value: 3.5, label: '3.5' } +]; + +export default PLAYBACK_RATES; \ No newline at end of file diff --git a/packages/components/media-player/src/PlaybackRate.js b/packages/components/media-player/src/PlaybackRate.js index 7b70f62f..23ecc54a 100644 --- a/packages/components/media-player/src/PlaybackRate.js +++ b/packages/components/media-player/src/PlaybackRate.js @@ -1,33 +1,38 @@ import React from 'react'; import PropTypes from 'prop-types'; -import styles from './PlaybackRate.module.css'; +// import styles from './PlaybackRate.module.css'; +import Select from './Select'; +import style from './PlayerControls/index.module.css'; + class PlaybackRate extends React.Component { + // to avoid unnecessary re-renders + shouldComponentUpdate(nextProps) { + if (nextProps.playbackRate !== this.props.playbackRate) { + return true; + } + + return false; + } render() { return ( -
-

Playback Rate - { ` x${ this.props.playBackRate } ` } -

- + - + - -
- - ); - return (
- {this.props.mediaUrl === null ? null : header} + {this.props.mediaUrl === null ? null :
}
diff --git a/packages/components/transcript-editor/index.module.css b/packages/components/transcript-editor/index.module.css index 2bc5021c..79de0c63 100644 --- a/packages/components/transcript-editor/index.module.css +++ b/packages/components/transcript-editor/index.module.css @@ -1,13 +1,5 @@ @value color-subt-green, color-darkest-grey, color-labs-red from '../../config/style-guide/colours.module.css'; -/* ::selection { - background: color-subt-green; -} - -::moz-selection { - background: color-subt-green; -} */ - .container { position: relative; } @@ -55,17 +47,6 @@ background-color: #f9f9f9; position: relative; } - - .header { - background-color: black; - width: 100%; - display: block; - } - - .nav { - width: 100%; - } - .grid { margin-top: 1em; margin-left: 1em; @@ -115,24 +96,6 @@ /* Mobile devices - excluding ipad*/ @media (max-width: 767px) { - /* .container { - display: flex; - flex-direction: column; - justify-content: space-evenly; - flex-wrap: nowrap; - background-color: #f9f9f9; - position: relative; - } */ - - .header { - background-color: black; - width: 100%; - } - - .nav { - width: 100%; - } - .main { width: 100%; margin: 0 auto; @@ -144,9 +107,9 @@ top: 0; right: 0; padding: 0; - } + } - .settingsButton { + .settingsButton { line-height: 1em; margin-left: 0.5em; background: color-darkest-grey; diff --git a/packages/components/transcript-editor/src/ExportOptions.js b/packages/components/transcript-editor/src/ExportOptions.js new file mode 100644 index 00000000..54b9a052 --- /dev/null +++ b/packages/components/transcript-editor/src/ExportOptions.js @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faWindowClose } from '@fortawesome/free-solid-svg-icons'; + +import style from './index.module.css'; + +class ExportOptions extends React.Component { + + render() { + const btns = this.props.exportOptionsList.map((opt) => { + return (<> +
+ ); + }); + + return ( +
+

Export Options

+
+ +
+ +
+ {btns} +
+
+ ); + } +} + +ExportOptions.propTypes = { + handleExportToggle: PropTypes.func +// showTimecodes: PropTypes.bool, +// showSpeakers: PropTypes.bool, +// timecodeOffset: PropTypes.number, +// handleShowTimecodes: PropTypes.func, +// handleShowSpeakers: PropTypes.func, +// handleSetTimecodeOffset: PropTypes.func, +// handleSettingsToggle: PropTypes.func, +// handlePauseWhileTyping: PropTypes.func, +// handleIsScrollIntoViewChange: PropTypes.func, +// handleRollBackValueInSeconds: PropTypes.func, +// defaultValueScrollSync: PropTypes.bool, +// defaultValuePauseWhileTyping: PropTypes.bool, +// defaultRollBackValueInSeconds: PropTypes.number, +// previewIsDisplayed: PropTypes.bool, +// handlePreviewIsDisplayed: PropTypes.func, +// // previewViewWidth: PropTypes.string, +// handleChangePreviewViewWidth: PropTypes.func, +// handleAnalyticsEvents: PropTypes.func +}; + +export default ExportOptions; diff --git a/packages/components/transcript-editor/src/Header.js b/packages/components/transcript-editor/src/Header.js new file mode 100644 index 00000000..f5ee321f --- /dev/null +++ b/packages/components/transcript-editor/src/Header.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { + faCog, + faKeyboard, + faShare +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import style from '../index.module.css'; + +class Header extends React.Component { + + // to avoid unnecessary re-renders + shouldComponentUpdate(nextProps) { + if (nextProps !== this.props) return true; + + return false; + } + render() { + const props = this.props; + + return (<> +
+ {props.showSettings ? props.settings : null} + {props.showShortcuts ? props.shortcuts : null} + {props.showExportOptions ? props.exportOptions : null} + {props.tooltip} +
+ +
+ + + +
+ ); + }; +} + +export default Header; \ No newline at end of file diff --git a/packages/components/transcript-editor/src/HowDoesThisWork.js b/packages/components/transcript-editor/src/HowDoesThisWork.js new file mode 100644 index 00000000..17c7b9da --- /dev/null +++ b/packages/components/transcript-editor/src/HowDoesThisWork.js @@ -0,0 +1,57 @@ +import React from 'react'; +import { + faKeyboard, + faQuestionCircle, + faMousePointer, + faICursor, + faUserEdit, + faSave +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import Tooltip from 'react-simple-tooltip'; +import style from '../index.module.css'; + +const helpMessage = ( +
+ + + Double click on a word or timestamp to jump to that point in the + video. + + + + Start typing to edit text. + + + + You can add and change names of speakers in your transcript. + + + + Use keyboard shortcuts for quick control. + + + + Save & export to get a copy to your desktop. + +
+); + +const HowDoesThisWork = ( + + + How does this work? + +); + +export default HowDoesThisWork; diff --git a/packages/components/transcript-editor/src/index.module.css b/packages/components/transcript-editor/src/index.module.css new file mode 100644 index 00000000..c9709998 --- /dev/null +++ b/packages/components/transcript-editor/src/index.module.css @@ -0,0 +1,95 @@ +@value color-darkest-grey from '../../../config/style-guide/colours.module.css'; + +.settings { + position: absolute; + left: 0; + right: 0; + margin: 0 auto; + width: 30%; + min-width: 300px; + min-height: 300px; + text-align: center; + vertical-align: middle; + color: white; + background: #4a4a4a; + padding: 1em; + font-weight: lighter; + z-index: 2; + } + + .header { + margin-top: 0; + margin-bottom: 1em; + } + + .closeButton { + position: absolute; + top: 0; + right: 0; + padding: 1em; + cursor: pointer; + } + + .controlsContainer { + display: flex; + flex-direction: column; + align-content: flex-start; + align-items: center; + margin: 0 auto; + } + + .settingElement { + text-align: left; + align-self: auto; + margin-bottom: 0.5em; + } + + .label { + display: inline-block; + min-width: 200px; + width: 200px; + } + + .rollbackValue { + height: 2em; + width: 48px; + box-sizing: border-box; + border: none; + text-align: center; + font-weight: bold; + margin-right: 16px; + vertical-align: middle; + } + + .timecodeLabel { + display: block; + text-align: center; + } + + +.playerButton { + width: 48px; + height: 48px; + padding: 0.5em; + border: 0; + color: white; + background: color-darkest-grey; + font-size: 1em; + cursor: pointer; + margin-right: 0.3rem; + margin-top: 0.3rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.playerButton { + width: 100%; + height: 48px; + margin: 0; +} + + +.playerButton:hover { + background-color: gray; +} \ No newline at end of file diff --git a/packages/components/video-player/index.js b/packages/components/video-player/index.js index 394cf6c4..aaa492e9 100644 --- a/packages/components/video-player/index.js +++ b/packages/components/video-player/index.js @@ -3,6 +3,16 @@ import PropTypes from 'prop-types'; import styles from './index.module.css'; class VideoPlayer extends React.Component { + + // to avoid unnecessary re-renders + shouldComponentUpdate(nextProps) { + if (nextProps.previewIsDisplayed !== this.props.previewIsDisplayed) { + return true; + } + + return false; + } + handlePlayMedia = () => { if (this.props.videoRef.current !== null) { return this.props.videoRef.current.paused diff --git a/packages/export-adapters/docx/index.js b/packages/export-adapters/docx/index.js new file mode 100644 index 00000000..f779a5e7 --- /dev/null +++ b/packages/export-adapters/docx/index.js @@ -0,0 +1,56 @@ +import { Document, Paragraph, TextRun, Packer } from 'docx'; +import { shortTimecode } from '../../util/timecode-converter/'; + +export default (blockData, transcriptTitle) => { + // const lines = blockData.blocks.map(x => x.text); + + return generateDocxFromDraftJs(blockData, transcriptTitle); + // return lines.join('\n\n'); +}; + +function generateDocxFromDraftJs(blockData, transcriptTitle) { + + const doc = new Document({ + creator: 'Test', + description: 'Test Description', + title: transcriptTitle, + }); + + // Transcript Title + // TODO: get title in programmatically - optional value + const textTitle = new TextRun(transcriptTitle); + const paragraphTitle = new Paragraph(); + paragraphTitle.addRun(textTitle); + paragraphTitle.heading1().center(); + doc.addParagraph(paragraphTitle); + + // add spacing + var paragraphEmpty = new Paragraph(); + doc.addParagraph(paragraphEmpty); + + blockData.blocks.forEach((draftJsParagraph) => { + // TODO: use timecode converter module to convert from seconds to timecode + const paragraphSpeakerTimecodes = new Paragraph(shortTimecode(draftJsParagraph.data.words[0].start)); + const speaker = new TextRun(draftJsParagraph.data.speaker).bold().tab(); + const textBreak = new TextRun('').break(); + paragraphSpeakerTimecodes.addRun(speaker); + doc.addParagraph(paragraphSpeakerTimecodes); + const paragraphText = new Paragraph(draftJsParagraph.text); + paragraphText.addRun(textBreak); + doc.addParagraph(paragraphText); + }); + + const packer = new Packer(); + + packer.toBlob(doc).then(blob => { + const filename = `${ transcriptTitle }.docx`; + // // const type = 'application/octet-stream'; + const a = document.createElement('a'); + a.href = window.URL.createObjectURL(blob); + a.download = filename; + a.click(); + + return blob; + }); + +} \ No newline at end of file diff --git a/packages/export-adapters/index.js b/packages/export-adapters/index.js index bdc494aa..01ff1389 100644 --- a/packages/export-adapters/index.js +++ b/packages/export-adapters/index.js @@ -1,22 +1,68 @@ import draftToTxt from './txt/index'; +import draftToDocx from './docx/index'; import draftToTxtSpeakersTimecodes from './txt-speakers-timecodes/index'; import draftToDigitalPaperEdit from './draftjs-to-digital-paper-edit/index.js'; +import subtitlesGenerator from './subtitles-generator/index.js'; /** * Adapters for Draft.js conversion * @param {json} blockData - Draft.js blocks * @param {string} exportFormat - the type of file supported by the available adapters */ -const exportAdapter = (blockData, exportFormat) => { +const exportAdapter = (blockData, exportFormat, transcriptTitle) => { switch (exportFormat) { case 'draftjs': return { data: blockData, ext: 'json' }; case 'txt': return { data: draftToTxt(blockData), ext: 'txt' }; + case 'docx': + return { data: draftToDocx(blockData, transcriptTitle), ext: 'docx' }; case 'txtspeakertimecodes': return { data: draftToTxtSpeakersTimecodes(blockData), ext: 'txt' }; case 'digitalpaperedit': return { data: draftToDigitalPaperEdit(blockData), ext: 'json' }; + case 'srt': + var { words } = draftToDigitalPaperEdit(blockData); + const srtContent = subtitlesGenerator({ words, type: 'srt', numberOfCharPerLine: 35 }); + + return { data: srtContent, ext: 'srt' }; + + case 'premiereTTML': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'premiere' }); + + return { data: content, ext: 'ttml' }; + case 'ttml': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'ttml' }); + + return { data: content, ext: 'ttml' }; + case 'itt': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'itt' }); + + return { data: content, ext: 'itt' }; + + case 'csv': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'csv' }); + + return { data: content, ext: 'csv' }; + case 'vtt': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'vtt' }); + + return { data: content, ext: 'vtt' }; + case 'json-captions': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'json' }); + + return { data: content, ext: 'json' }; + case 'pre-segment-txt': + var { words } = draftToDigitalPaperEdit(blockData); + var content = subtitlesGenerator({ words, type: 'pre-segment-txt' }); + + return { data: content, ext: 'txt' }; default: // code block console.error('Did not recognise the export format'); diff --git a/packages/export-adapters/subtitles-generator/compose-subtitles/csv.js b/packages/export-adapters/subtitles-generator/compose-subtitles/csv.js new file mode 100644 index 00000000..bdcbd406 --- /dev/null +++ b/packages/export-adapters/subtitles-generator/compose-subtitles/csv.js @@ -0,0 +1,18 @@ +function csvGenerator(srtJsonContent) { + let lines = 'N, In, Out, Text\n'; + srtJsonContent.forEach((srtLineO, index) => { + lines += `${ index + 1 },`; + //need to surround timecodes with "\"" escaped " to escape the , for the milliseconds + lines += `\"${ srtLineO.start }\",\"${ srtLineO.end }\",`; + // removing line breaks and and removing " as they break the csv. + // wrapping text in escaped " to escape any , for the csv. + // adding carriage return \n to signal end of line in csv + // Preserving line break within srt lines to allow round trip from csv back to srt file in same format. + // by replacing \n with \r\n. + lines += `\"${ srtLineO.text.replace(/\n/g, '\r\n') }\"\n`; + }); + + return lines; +} + +export default csvGenerator; \ No newline at end of file diff --git a/packages/export-adapters/subtitles-generator/compose-subtitles/itt.js b/packages/export-adapters/subtitles-generator/compose-subtitles/itt.js new file mode 100644 index 00000000..37a363b1 --- /dev/null +++ b/packages/export-adapters/subtitles-generator/compose-subtitles/itt.js @@ -0,0 +1,50 @@ +import tcFormat from './util/tc-format.js'; +import escapeText from './util/escape-text.js'; + +const ittGenerator = (vttJSON, lang = 'en-GB', FPS = 25) => { + let ittOut = + ` + + + +