Merge branch 'master' into wallet-encryption

This commit is contained in:
Shawn Khameneh 2018-07-25 14:30:24 -05:00 committed by GitHub
commit 2ccf40b18e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 263 additions and 152 deletions

3
.gitignore vendored
View file

@ -4,4 +4,5 @@
/static/locales
yarn-error.log
package-lock.json
.idea/
.idea/
/build/daemon.ver

View file

@ -33,7 +33,7 @@ script:
- |
if [ "$TARGET" == "windows" ]; then
docker run --rm \
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
--env-file <(env | grep -iE 'DEBUG|TARGET|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
-v ${PWD}:/project \
electronuserland/builder:wine \
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --win --publish onTag";

View file

@ -5,23 +5,36 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
## [Unreleased]
### Fixed
* Edit option missing from certain published claims ([#175](https://github.com/lbryio/lbry-desktop/issues/1756))
### Added
* Added 3D file viewer for OBJ & STL file types ([#1558](https://github.com/lbryio/lbry-desktop/pull/1558))
* Added thumbnail preview on publish page ([#1755](https://github.com/lbryio/lbry-desktop/pull/1755))
* Wallet Encryption/Decryption user flows ([#1785](https://github.com/lbryio/lbry-desktop/pull/1785))
* Wallet Encryption/Decryption user flows ([#1785](https://github.com/lbryio/lbry-desktop/pull/1785))
### Changed
* Rename the Github repo to lbry-desktop ([#1765](https://github.com/lbryio/lbry-desktop/pull/1765))
### Fixed
## [0.23.0] - 2018-07-25
### Added
* 3D file viewer for OBJ & STL file types ([#1558](https://github.com/lbryio/lbry-desktop/pull/1558))
* Thumbnail preview on publish page ([#1755](https://github.com/lbryio/lbry-desktop/pull/1755))
* Abandoned claim transactions now show in wallet history ([#1769](https://github.com/lbryio/lbry-desktop/pull/1769))
* Emoji support in the claim description ([#1800](https://github.com/lbryio/lbry-desktop/pull/1800))
* PDF preview ([#1576](https://github.com/lbryio/lbry-desktop/pull/1576))
### Changed
* Upgraded LBRY Protocol to [version 0.20.4](https://github.com/lbryio/lbry/releases/tag/v0.20.4) to assist with download availability and lower CPU usage on idle.
* Upgraded Electron-Builder and Updater to support signing the daemon and improving the auto update process ([#1784](https://github.com/lbryio/lbry-desktop/pull/1784))
* Channel page now uses caching, faster switching between channels/claims ([#1750](https://github.com/lbryio/lbry-desktop/pull/1750))
* Only show video error modal if you are on the video page & don't retry to play failed videos ([#1768](https://github.com/lbryio/lbry-desktop/pull/1768))
* Actually hide NSFW files if a user chooses to hide NSFW content via the settings page ([#1748](https://github.com/lbryio/lbry-desktop/pull/1748))
* Hide the "Community top bids" section if user chooses to hide NSFW content ([#1760](https://github.com/lbryio/lbry-desktop/pull/1760))
* Add a more descriptive error message when Shapeshift is unavailable ([#1771](https://github.com/lbryio/lbry-desktop/pull/1771))
* More descriptive error message when Shapeshift is unavailable ([#1771](https://github.com/lbryio/lbry-desktop/pull/1771))
* Rename the Github repo to lbry-desktop ([#1765](https://github.com/lbryio/lbry-desktop/pull/1765))
### Fixed
* Edit option missing from certain published claims ([#1756](https://github.com/lbryio/lbry-desktop/issues/1756))
* Fix navigation issue with channels that have more than one page ([#1797](https://github.com/lbryio/lbry-desktop/pull/1797))
## [0.22.2] - 2018-07-09

View file

@ -1,29 +0,0 @@
/* eslint-disable no-console,import/no-extraneous-dependencies,import/no-commonjs */
/**
* This script is necessary for checking that the daemon that has been downloaded during the
* yarn installing process is the one for the building target. For example, on Travis the
* Windows package is built on Linux, thus yarn will download the daemon for Linux instead of
* Windows. The script will test that and then download the right daemon for the targeted platform.
*/
const os = require('os');
const downloadDaemon = require('./downloadDaemon');
module.exports = context => {
let currentPlatform = os.platform();
if (currentPlatform === 'darwin') currentPlatform = 'macoss';
if (currentPlatform === 'win32') currentPlatform = 'windows';
let buildingPlatformTarget = context.platform.toString();
if (buildingPlatformTarget === 'mac') buildingPlatformTarget = 'macos';
if (buildingPlatformTarget !== currentPlatform) {
console.log(
"\x1b[34minfo\x1b[0m Daemon platform doesn't match target platform. Redownloading the daemon."
);
return downloadDaemon(buildingPlatformTarget);
}
return Promise.resolve();
};

View file

@ -1,6 +1,6 @@
/* eslint-disable no-console,import/no-extraneous-dependencies,import/no-commonjs */
const path = require('path');
const fs = require('fs-path');
const fs = require('fs');
const packageJSON = require('../package.json');
const axios = require('axios');
const decompress = require('decompress');
@ -11,56 +11,84 @@ const downloadDaemon = targetPlatform =>
new Promise((resolve, reject) => {
const daemonURLTemplate = packageJSON.lbrySettings.lbrynetDaemonUrlTemplate;
const daemonVersion = packageJSON.lbrySettings.lbrynetDaemonVersion;
const daemonDir = packageJSON.lbrySettings.lbrynetDaemonDir;
const daemonFileName = packageJSON.lbrySettings.lbrynetDaemonFileName;
const daemonDir = path.join(__dirname,'..',packageJSON.lbrySettings.lbrynetDaemonDir);
let daemonFileName = packageJSON.lbrySettings.lbrynetDaemonFileName;
let currentPlatform = os.platform();
if (currentPlatform === 'darwin') currentPlatform = 'macos';
if (currentPlatform === 'win32') currentPlatform = 'windows';
const daemonPlatform = targetPlatform || currentPlatform;
var daemonPlatform = process.env.TARGET || targetPlatform || currentPlatform;
if (daemonPlatform === 'mac' || daemonPlatform === 'darwin') daemonPlatform = 'macos';
if (daemonPlatform === 'win32' || daemonPlatform === 'windows') {
daemonPlatform = 'windows';
daemonFileName = daemonFileName + '.exe';
}
const daemonFilePath = path.join(daemonDir, daemonFileName);
const daemonVersionPath = path.join(__dirname, 'daemon.ver');
const tmpZipPath = path.join(__dirname, '..', 'dist', 'daemon.zip');
const daemonURL = daemonURLTemplate
.replace(/DAEMONVER/g, daemonVersion)
.replace(/OSNAME/g, daemonPlatform);
const tmpZipPath = 'dist/daemon.zip';
console.log('\x1b[34minfo\x1b[0m Downloading daemon...');
axios
.request({
responseType: 'arraybuffer',
url: daemonURL,
method: 'get',
headers: {
'Content-Type': 'application/zip',
},
})
.then(
result =>
new Promise((newResolve, newReject) => {
fs.writeFile(tmpZipPath, result.data, error => {
if (error) return newReject(error);
return newResolve();
});
})
)
.then(() => del(`${daemonDir}/${daemonFileName}*`))
.then(() =>
decompress(tmpZipPath, daemonDir, {
filter: file =>
path.basename(file.path).replace(path.extname(file.path), '') === daemonFileName,
// If a daemon and daemon.ver exists, check to see if it matches the current daemon version
const hasDaemonDownloaded = fs.existsSync(daemonFilePath);
const hasDaemonVersion = fs.existsSync(daemonVersionPath);
let downloadedDaemonVersion;
if (hasDaemonVersion) {
downloadedDaemonVersion = fs.readFileSync(daemonVersionPath, "utf8");
}
if (hasDaemonDownloaded && hasDaemonVersion && downloadedDaemonVersion === daemonVersion) {
console.log('\x1b[34minfo\x1b[0m Daemon already downloaded');
resolve('Done');
return;
} else {
console.log('\x1b[34minfo\x1b[0m Downloading daemon...');
axios
.request({
responseType: 'arraybuffer',
url: daemonURL,
method: 'get',
headers: {
'Content-Type': 'application/zip',
},
})
)
.then(() => {
console.log('\x1b[32msuccess\x1b[0m Daemon downloaded!');
resolve(true);
})
.catch(error => {
console.error(
`\x1b[31merror\x1b[0m Daemon download failed due to: \x1b[35m${error}\x1b[0m`
);
reject(error);
});
.then(
result =>
new Promise((newResolve, newReject) => {
const distPath = path.join(__dirname, '..', 'dist');
const hasDistFolder = fs.existsSync(distPath);
if (!hasDistFolder) {
fs.mkdirSync(distPath);
}
fs.writeFile(tmpZipPath, result.data, error => {
if (error) return newReject(error);
return newResolve();
});
})
)
.then(() => del(`${daemonFilePath}*`))
.then(() => decompress(tmpZipPath, daemonDir, {
filter: file =>
path.basename(file.path) === daemonFileName,
}))
.then(() => {
console.log('\x1b[32msuccess\x1b[0m Daemon downloaded!');
if (hasDaemonVersion) {
del(daemonVersionPath);
}
fs.writeFileSync(daemonVersionPath, daemonVersion, "utf8")
resolve('Done');
})
.catch(error => {
console.error(
`\x1b[31merror\x1b[0m Daemon download failed due to: \x1b[35m${error}\x1b[0m`
);
reject(error);
})
};
});
module.exports = downloadDaemon;

View file

@ -73,6 +73,5 @@
}
]
},
"artifactName": "${productName}_${version}.${ext}",
"beforeBuild": "./build/checkDaemonPlatform.js"
"artifactName": "${productName}_${version}.${ext}"
}

View file

@ -1,6 +1,6 @@
{
"name": "LBRY",
"version": "0.22.2",
"version": "0.23.0",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [
"lbry"
@ -31,7 +31,7 @@
"release": "yarn compile && electron-builder build",
"precommit": "lint-staged",
"preinstall": "yarn cache clean lbry-redux",
"postinstall": "electron-builder install-app-deps & node build/downloadDaemon.js"
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
},
"dependencies": {
"bluebird": "^3.5.1",
@ -71,6 +71,7 @@
"redux-persist-transform-filter": "0.0.16",
"redux-thunk": "^2.2.0",
"remark": "^9.0.0",
"remark-emoji": "^2.0.1",
"remark-react": "^4.0.3",
"render-media": "^3.1.0",
"reselect": "^3.0.0",

View file

@ -2,6 +2,7 @@
import * as React from 'react';
import remark from 'remark';
import reactRenderer from 'remark-react';
import remarkEmoji from 'remark-emoji';
import ExternalLink from 'component/externalLink';
import defaultSchema from 'hast-util-sanitize/lib/github.json';
@ -16,7 +17,11 @@ type MarkdownProps = {
promptLinks?: boolean,
};
const SimpleLink = ({ href, title, children }) => (<a href={href} title={title}>{children}</a>);
const SimpleLink = ({ href, title, children }) => (
<a href={href} title={title}>
{children}
</a>
);
const MarkdownPreview = (props: MarkdownProps) => {
const { content, externalLinks, promptLinks } = props;
@ -30,6 +35,7 @@ const MarkdownPreview = (props: MarkdownProps) => {
<div className="markdown-preview">
{
remark()
.use(remarkEmoji)
.use(reactRenderer, remarkOptions)
.processSync(content).contents
}

View file

@ -39,8 +39,16 @@ class Page extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
const { loading } = this.props;
const { showLoader } = this.state;
if (!this.loaderTimeout && !prevProps.loading && loading) {
this.beginLoadingTimeout();
} else if (!loading && this.loaderTimeout) {
clearTimeout(this.loaderTimeout);
if (showLoader) {
this.removeLoader();
}
}
}
@ -56,6 +64,10 @@ class Page extends React.PureComponent<Props, State> {
}, LOADER_TIMEOUT);
}
removeLoader() {
this.setState({ showLoader: false });
}
loaderTimeout: ?TimeoutID;
render() {

View file

@ -80,7 +80,7 @@ class UriIndicator extends React.PureComponent<Props> {
noPadding
className="btn--uri-indicator"
navigate="/show"
navigateParams={{ uri: channelLink }}
navigateParams={{ uri: channelLink, page: 1 }}
>
{inner}
</Button>

View file

@ -4,6 +4,7 @@ import LoadingScreen from 'component/common/loading-screen';
// ThreeJS
import * as THREE from './internal/three';
import detectWebGL from './internal/detector';
import ThreeGrid from './internal/grid';
import ThreeScene from './internal/scene';
import ThreeLoader from './internal/loader';
import ThreeRenderer from './internal/renderer';
@ -77,6 +78,12 @@ class ThreeViewer extends React.PureComponent<Props> {
window.removeEventListener('resize', this.handleResize, false);
}
transformGroup(group) {
this.fitMeshToCamera(group);
this.createWireFrame(group);
this.updateControlsTarget(group.position);
}
createOrbitControls(camera, canvas) {
const { autoRotate } = this.props;
const controls = new THREE.OrbitControls(camera, canvas);
@ -87,6 +94,7 @@ class ThreeViewer extends React.PureComponent<Props> {
controls.minDistance = 1;
controls.maxDistance = 50;
controls.autoRotate = autoRotate;
controls.enablePan = false;
return controls;
}
@ -114,32 +122,6 @@ class ThreeViewer extends React.PureComponent<Props> {
group.add(this.wireframe);
}
createMesh(geometry) {
const material = new THREE.MeshPhongMaterial({
opacity: 1,
transparent: true,
depthWrite: true,
vertexColors: THREE.FaceColors,
// Positive value pushes polygon further away
polygonOffsetFactor: 1,
polygonOffsetUnits: 1,
});
// Set material color
material.color.set(this.materialColors.green);
const mesh = new THREE.Mesh(geometry, material);
// Assign name
mesh.name = 'objectGroup';
this.scene.add(mesh);
this.fitMeshToCamera(mesh);
this.createWireFrame(mesh);
this.updateControlsTarget(mesh.position);
return mesh;
}
toggleWireFrame(show = false) {
this.wireframe.opacity = show ? 1 : 0;
this.mesh.material.opacity = show ? 0 : 1;
@ -151,7 +133,7 @@ class ThreeViewer extends React.PureComponent<Props> {
group.traverse(child => {
if (child instanceof THREE.Mesh) {
const box = new THREE.Box3().setFromObject(group);
const box = new THREE.Box3().setFromObject(child);
// Max
max.x = box.max.x > max.x ? box.max.x : max.x;
max.y = box.max.y > max.y ? box.max.y : max.y;
@ -165,12 +147,18 @@ class ThreeViewer extends React.PureComponent<Props> {
const meshY = Math.abs(max.y - min.y);
const meshX = Math.abs(max.x - min.x);
const scaleFactor = 15 / Math.max(meshX, meshY);
const scaleFactor = 10 / Math.max(meshX, meshY);
group.scale.set(scaleFactor, scaleFactor, scaleFactor);
group.position.setY((meshY / 2) * scaleFactor);
// Reset object position
const box = new THREE.Box3().setFromObject(group);
box.getCenter(group.position);
group.position.multiplyScalar(-1);
group.position.setY((meshY * scaleFactor) / 2);
group.position.setY(group.position.y + meshY * scaleFactor);
}
startLoader() {
@ -217,12 +205,52 @@ class ThreeViewer extends React.PureComponent<Props> {
this.controls.update();
}
renderModel(fileType, data) {
renderStl(data) {
const geometry = this.createGeometry(data);
this.mesh = this.createMesh(geometry);
const group = new THREE.Mesh(geometry, this.material);
// Assign name
group.name = 'objectGroup';
this.scene.add(group);
this.transformGroup(group);
this.mesh = group;
}
renderObj(event) {
const mesh = event.detail.loaderRootNode;
const group = new THREE.Group();
group.name = 'objGroup';
// Assign new material
mesh.traverse(child => {
if (child instanceof THREE.Mesh) {
// Get geometry from child
const geometry = new THREE.Geometry();
geometry.fromBufferGeometry(child.geometry);
// Create and regroup inner objects
const innerObj = new THREE.Mesh(geometry, this.material);
group.add(innerObj);
}
});
this.scene.add(group);
this.transformGroup(group);
this.mesh = group;
}
renderModel(fileType, parsedData) {
const renderTypes = {
stl: data => this.renderStl(data),
obj: data => this.renderObj(data),
};
if (renderTypes[fileType]) {
renderTypes[fileType](parsedData);
}
}
renderScene() {
const { gridColor, centerLineColor } = this.theme;
this.renderer = ThreeRenderer({
antialias: true,
shadowMap: true,
@ -230,20 +258,40 @@ class ThreeViewer extends React.PureComponent<Props> {
this.scene = ThreeScene({
showFog: true,
showGrid: true,
...this.theme,
});
const viewer = this.viewer.current;
const canvas = this.renderer.domElement;
const { offsetWidth: width, offsetHeight: height } = viewer;
// Grid
this.grid = ThreeGrid({ size: 100, gridColor, centerLineColor });
this.scene.add(this.grid);
// Camera
this.camera = new THREE.PerspectiveCamera(80, width / height, 0.1, 1000);
this.camera.position.set(-9.5, 14, 11);
// Controls
this.controls = this.createOrbitControls(this.camera, canvas);
// Set viewer size
this.renderer.setSize(width, height);
// Create model material
this.material = new THREE.MeshPhongMaterial({
opacity: 1,
transparent: true,
// depthWrite: true,
vertexColors: THREE.FaceColors,
// Positive value pushes polygon further away
// polygonOffsetFactor: 1,
// polygonOffsetUnits: 1,
});
// Set material color
this.material.color.set(this.materialColors.green);
// Load file and render mesh
this.startLoader();

View file

@ -0,0 +1,13 @@
import { GridHelper, Color } from './three';
const ThreeGrid = ({ size, gridColor, centerLineColor }) => {
const divisions = size / 2;
const grid = new GridHelper(size, divisions, new Color(centerLineColor), new Color(gridColor));
grid.material.opacity = 0.4;
grid.material.transparent = true;
return grid;
};
export default ThreeGrid;

View file

@ -1,4 +1,4 @@
import { LoadingManager, STLLoader, OBJLoader } from './three';
import { LoadingManager, STLLoader, OBJLoader2 } from './three';
const Manager = ({ onLoad, onStart, onError }) => {
const manager = new LoadingManager();
@ -12,7 +12,7 @@ const Manager = ({ onLoad, onStart, onError }) => {
const Loader = (fileType, manager) => {
const fileTypes = {
stl: () => new STLLoader(manager),
obj: () => new OBJLoader(manager),
obj: () => new OBJLoader2(manager),
};
return fileTypes[fileType] ? fileTypes[fileType]() : null;
};

View file

@ -1,18 +1,5 @@
import * as THREE from './three';
const addGrid = (scene, { gridColor, centerLineColor, size }) => {
const divisions = size / 2;
const grid = new THREE.GridHelper(
size,
divisions,
new THREE.Color(centerLineColor),
new THREE.Color(gridColor)
);
grid.material.opacity = 0.4;
grid.material.transparent = true;
scene.add(grid);
};
const addLights = (scene, color, groundColor) => {
// Light color
const lightColor = new THREE.Color(color);
@ -30,7 +17,7 @@ const addLights = (scene, color, groundColor) => {
scene.add(shadowLight);
};
const Scene = ({ backgroundColor, groundColor, showFog, showGrid, gridColor, centerLineColor }) => {
const Scene = ({ backgroundColor, groundColor, showFog }) => {
// Convert color
const bgColor = new THREE.Color(backgroundColor);
// New scene
@ -39,17 +26,8 @@ const Scene = ({ backgroundColor, groundColor, showFog, showGrid, gridColor, cen
scene.background = bgColor;
// Fog effect
scene.fog = showFog === true ? new THREE.Fog(bgColor, 1, 95) : null;
// Add grid
if (showGrid) {
addGrid(scene, {
size: 100,
gridColor,
centerLineColor,
});
}
// Add basic lights
addLights(scene, '#FFFFFF', groundColor);
// Return new three scene
return scene;
};

View file

@ -4,7 +4,8 @@ import * as THREE from 'three';
// Fix: https://github.com/mrdoob/three.js/issues/9562#issuecomment-383390251
global.THREE = THREE;
require('three/examples/js/controls/OrbitControls');
require('three/examples/js/loaders/OBJLoader');
require('three/examples/js/loaders/LoaderSupport');
require('three/examples/js/loaders/OBJLoader2');
require('three/examples/js/loaders/STLLoader');
module.exports = global.THREE;

View file

@ -23,7 +23,7 @@ class ModalIncompatibleDaemon extends React.PureComponent<Props> {
onAborted={quit}
>
{__(
'This browser is running with an incompatible version of the LBRY protocol, please close the LBRY app and rerun the installation package to repair it'
'This browser is running with an incompatible version of the LBRY protocol, please close the LBRY app and rerun the installation package to repair it. '
)}
<Button
button="link"

View file

@ -13,7 +13,7 @@ import SubscriptionsPage from './view';
const select = state => ({
loading:
selectIsFetchingSubscriptions(state) ||
Object.keys(selectSubscriptionsBeingFetched(state)).length,
Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
subscriptionsBeingFetched: selectSubscriptionsBeingFetched(state),
subscriptions: selectSubscriptions(state),
subscriptionClaims: selectSubscriptionClaims(state),

View file

@ -184,6 +184,7 @@ p {
.main {
padding: $spacing-width $spacing-width;
margin: auto;
overflow: hidden;
}
.main--contained {

View file

@ -80,9 +80,31 @@
}
a {
font-size: 1em;
color: var(--btn-external-color);
font-size: 1em;
color: var(--btn-external-color);
display: inline-block;
}
/* Lists */
ul,
ol {
margin-bottom: 2em;
}
ul {
list-style: initial;
}
li {
margin-left: 2em;
p {
display: inline-block;
}
}
ol > li,
ul > li {
list-style-position: outside;
}
}

View file

View file

@ -5446,6 +5446,10 @@ lodash.tail@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
lodash.toarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@ -5952,6 +5956,12 @@ node-abi@^2.2.0:
dependencies:
semver "^5.4.1"
node-emoji@^1.4.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826"
dependencies:
lodash.toarray "^4.4.0"
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@ -7454,6 +7464,13 @@ relateurl@0.2.x:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
remark-emoji@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.0.1.tgz#6de4be7acb05b8534b6bad679d56eab24fba5e06"
dependencies:
node-emoji "^1.4.1"
unist-util-visit "^1.1.0"
remark-parse@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"