works but totally sucks and needs a rewrite

master
Adam Veldhousen 4 years ago
parent 168df6cfbc
commit 31a991cd69
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -11,7 +11,11 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-markdown": "^4.3.1",
"react-redux": "^7.1.3",
"react-scripts": "3.3.0",
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"weedux": "^3.5.3-beta"
},
"scripts": {

@ -1,6 +1,7 @@
import React, { PureComponent } from "react";
import { connect, bindActionCreators } from "weedux";
import { store, actions } from "./state/index.js";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { actions } from "./state/index.js";
import debounce from "lodash.debounce";
import NoteList from "./components/NoteList/NoteList";
@ -8,10 +9,9 @@ import NoteContent from "./components/NoteContent/NoteContent";
import Omnibar from "./components/Omnibar/Omnibar";
import "./App.css";
import { getNotes } from "./state/api.js";
class App extends PureComponent {
defaultProps = { notes: [] };
static defaultProps = { selectedNote: null, notes: [], settings: {} };
state = { filter: "" };
@ -23,9 +23,8 @@ class App extends PureComponent {
handleSearch = text => this.setState({ filter: text.toLocaleLowerCase() });
handleCreate = title => {
const { createNote } = this.props;
createNote({ title, content: "", tags: [] });
getNotes();
const { upsertNote } = this.props;
upsertNote({ title, content: "", tags: [] });
};
handleSelectNote = title => {
@ -34,13 +33,13 @@ class App extends PureComponent {
};
handleSaveNoteContent = content => {
const { updateNote, selectedNote } = this.props;
updateNote({ ...selectedNote, content });
const { upsertNote, selectedNote } = this.props;
upsertNote({ ...selectedNote, content });
};
handleTagUpdate = ({ title, newTags }) => {
const { updateTags } = this.props;
updateTags({ title, tags: newTags });
handleTagUpdate = ({ title, content, newTags }) => {
const { upsertNote } = this.props;
upsertNote({ title, content, tags: newTags });
};
applyFilter = (notes = [], filter = "") =>
@ -52,9 +51,15 @@ class App extends PureComponent {
);
render() {
const { notes, selectedNote } = this.props;
const { notes, selectedNote, settings } = this.props;
const { autoSaveDelay } = settings;
// const handleSave = debounce(
// this.handleSaveNoteContent,
// autoSaveDelay || 1000
// );
const handleSave = this.handleSaveNoteContent;
const { filter } = this.state;
const handleSave = debounce(this.handleSaveNoteContent, 250);
console.log("note", selectedNote);
return (
<div className="App">
@ -72,15 +77,18 @@ class App extends PureComponent {
onTagUpdate={this.handleTagUpdate}
selectedNote={selectedNote}
/>
<NoteContent note={selectedNote} onSave={handleSave} />
<NoteContent {...selectedNote} onSave={handleSave} />
</section>
</div>
);
}
}
const ms2p = s => s;
const ms2p = s => {
const { notes, settings } = s;
return { ...notes, settings: { ...settings.settings } };
};
const md2p = dispatch => bindActionCreators({ ...actions }, dispatch);
export default connect(ms2p, md2p, store)(App);
export default connect(ms2p, md2p)(App);

@ -18,6 +18,7 @@
border: none;
background-color: black;
color: white;
resize: none;
}
.EditableNoteArea .markdownPreview {

@ -1,21 +1,26 @@
import React from "react";
import React, { useState } from "react";
import ReactMarkdown from "react-markdown";
import { noop } from "../../utils";
import "./EditableNoteArea.css";
export default function EditableNoteArea({
content = null,
content = "",
onSave = noop,
preview = false
}) {
const updateContent = ({ target: { value } }) => onSave(value);
// const [newContent, setNewContent] = useState(content);
const updateContent = ({ target: { value } }) => {
// setNewContent(value);
onSave(value);
};
return (
<section className="EditableNoteArea">
{!preview ? (
<textarea
value={content || ""}
resizeable={false}
value={content}
placeholder="This note feels cold and empty inside"
onChange={updateContent}
/>

@ -4,9 +4,8 @@ import EditableNoteArea from "../EditableNoteArea/EditableNoteArea";
import "./NoteContent.css";
export default function NoteContent({ note = null, onSave = noop }) {
export default function NoteContent({ content = "", onSave = noop }) {
const [renderMarkdown, setRenderMarkdown] = useState(false);
const { content } = note || {};
return (
<div className="NoteContent">

@ -17,7 +17,7 @@ export default function NoteList({
<ul className="NoteList" onKeyDown={handleShortcuts}>
{notes.map(n => (
<li
className={n.title === selectedNoteTitle && "selected"}
className={n.title === selectedNoteTitle ? "selected" : ""}
key={n.title}
>
<NoteListItem

@ -1,10 +1,17 @@
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { store } from "./state";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(<App />, document.getElementById("root"));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

@ -1,4 +1,5 @@
const { loadNotesInDirectory, saveNoteInDirectory } = require("./notes");
const { loadTags, updateTagsForNote } = require("./tags");
const { defaultSettings } = require("./settings");
function newNoteBackend(initSettings) {
@ -10,10 +11,14 @@ function newNoteBackend(initSettings) {
switch (type) {
case "getNotes":
const files = await loadNotesInDirectory(directory);
const loadedTags = await loadTags(directory);
return {
type: "getNotes",
payload: {
notes: files.map(f => ({ ...f, tags: [] }))
notes: files.map(f => ({
...f,
tags: loadedTags[f.title]
}))
}
};
case "saveNote":
@ -23,8 +28,10 @@ function newNoteBackend(initSettings) {
content,
tags
});
return { type: "saveNote", payload: result };
await updateTagsForNote(directory, { title, tags });
return { type: "saveNote", payload: { ...result, tags } };
case "getSettings":
return { type: "getSettings", payload: { settings } };
case "saveSettings":
default:
return { type: "noop" };

@ -0,0 +1,95 @@
const path = require("path");
const fs = require("fs");
function listNotesInDirectory(directoryPath = "") {
return new Promise(function(resolve, reject) {
fs.readdir(directoryPath, function(err, files) {
if (err) return reject(err);
resolve(
files
.map(fPath => ({
ext: path.extname(fPath),
name: fPath,
title: path.basename(fPath, path.extname(fPath)),
path: path.join(directoryPath, fPath)
}))
.filter(
({ ext }) =>
ext === ".md" || ext === ".txt" || ext === ".utf8"
)
);
});
});
}
function getNoteTime(fullPath) {
return new Promise((a, r) => {
fs.stat(fullPath, (err, stats) => {
if (err) return r(err);
a({
lastModified: stats.mtime,
created: stats.ctime,
size: stats.size
});
});
});
}
function getNoteContent(fullPath) {
return new Promise((a, r) =>
fs.readFile(fullPath, "utf8", (err, data) => {
if (err) return r(err);
a({ content: data });
})
);
}
async function loadNotesInDirectory(directoryPath = "") {
const files = await listNotesInDirectory(directoryPath);
return await Promise.all(
files.map(async ({ path, title, ext }) => {
const [noteTime, noteContent] = await Promise.all([
getNoteTime(path),
getNoteContent(path)
]);
return {
path,
title,
...noteTime,
...noteContent
};
})
);
}
async function saveNoteInDirectory(
{ directory, defaultExtension },
{ title, content }
) {
let fullPath = path.join(directory, `${title}.${defaultExtension}`);
const match = (await listNotesInDirectory(directory)).filter(
f => f.title === title
);
if (match && match.length > 0) fullPath = match[0].path;
console.log("tryint to write note: ", fullPath, " - ", match);
return Promise.all([
new Promise((a, r) => {
fs.writeFile(fullPath, content, "utf8", err => {
if (err) return r(err);
a();
});
})
]);
}
module.exports = {
loadNotesInDirectory,
saveNoteInDirectory
};

@ -0,0 +1,9 @@
module.exports = {
defaultSettings: {
directory: "./",
defaultExtension: "txt",
autoSave: true, // if notes should save automatically
autoSaveDelay: 1000, // ms to wait after last key press to save
themes: [{ name: "default" }]
}
};

@ -0,0 +1,59 @@
const path = require("path");
const fs = require("fs");
function loadTags(directoryPath) {
return new Promise((a, r) =>
fs.readFile(
path.join(directoryPath, "meta.json"),
"utf8",
async (err, data) => {
if (err) {
if (!err.message.includes("ENOENT")) return r(err);
await saveTags(directoryPath, {});
}
const tagCache = JSON.parse(data);
a(tagCache);
}
)
);
}
function saveTags(directoryPath, tags = {}) {
return new Promise((a, r) =>
fs.writeFile(
path.join(directoryPath, "meta.json"),
JSON.stringify(tags),
"utf8",
err => {
if (err) return r(err);
a({});
}
)
);
}
async function updateTagsForNote(directoryPath, { title, tags = [] }) {
if (!title || title === "") {
return Promise.reject();
}
const allTags = await loadTags(directoryPath);
allTags[title] = [...tags];
await saveTags(directoryPath, allTags);
}
async function getTagsForNote(directoryPath, { title }) {
if (!title || title === "") {
return Promise.reject();
}
const allTags = await loadTags(directoryPath);
return allTags[title];
}
module.exports = {
loadTags,
getTagsForNote,
updateTagsForNote
};

@ -1,6 +1,6 @@
const path = require("path");
const { app, shell, BrowserWindow, ipcMain } = require("electron");
const { createBackend } = require("./notes");
const { createBackend } = require("./backend");
let win;
function createWindow() {
@ -21,7 +21,8 @@ function createWindow() {
});
const backend = createBackend({
directory: "C:/Users/AdamM/Documents/notes"
directory: "./.bin"
// directory: "C:/Users/AdamM/Documents/notes"
});
ipcMain.on("note_command", async (event, args) => {

@ -1,175 +0,0 @@
const path = require("path");
const fs = require("fs");
const defaultSettings = {
directory: "./",
defaultExtension: "txt"
};
function listNotesInDirectory(directoryPath = "") {
return new Promise(function(resolve, reject) {
fs.readdir(directoryPath, function(err, files) {
if (err) return reject(err);
resolve(
files
.map(fPath => ({
ext: path.extname(fPath),
name: fPath,
friendlyName: path.basename(fPath, path.extname(fPath)),
fullPath: path.join(directoryPath, fPath)
}))
.filter(
({ ext }) =>
ext === ".md" || ext === ".txt" || ext === ".utf8"
)
);
});
});
}
function getNoteTime(fullPath) {
return new Promise((a, r) => {
fs.stat(fullPath, (err, stats) => {
if (err) return r(err);
a({
lastModified: stats.mtime,
created: stats.ctime,
size: stats.size
});
});
});
}
function getNoteContent(fullPath) {
return new Promise((a, r) =>
fs.readFile(fullPath, "utf8", (err, data) => {
if (err) return r(err);
a({ content: data });
})
);
}
function loadTags(directoryPath) {
return new Promise((a, r) =>
fs.readFile(
path.join(directoryPath, "meta.json"),
"utf8",
(err, data) => {
if (err) {
if (!err.message.includes("ENOENT")) return r(err);
return a(saveTags(directoryPath, {}));
}
a(JSON.parse(data));
}
)
);
}
function saveTags(directoryPath, tags = {}) {
return new Promise((a, r) =>
fs.writeFile(
path.join(directoryPath, "meta.json"),
JSON.stringify(tags),
"utf8",
err => {
if (err) return r(err);
a({});
}
)
);
}
let tagsCache = {};
function getNoteTags(directoryPath, friendlyName) {
return Promise.resolve(tagsCache[friendlyName] || []);
}
async function loadNotesInDirectory(directoryPath = "") {
const tagCache = await loadTags(directoryPath);
const files = await listNotesInDirectory(directoryPath);
return await Promise.all(
files.map(async ({ fullPath, friendlyName, ext }) => {
const [noteTime, noteContent] = await Promise.all([
getNoteTime(fullPath),
getNoteContent(fullPath),
getNoteTags(directoryPath, friendlyName)
]);
return {
path: fullPath,
title: friendlyName,
tags: tagsCache[friendlyName] || [],
...noteTime,
...noteContent
};
})
);
}
async function saveNoteInDirectory(
{ directory, defaultExtension },
{ title, content, tags }
) {
let fullPath = path.join(directory, `${title}.${defaultExtension}`);
const match = (await listNotesInDirectory(directory)).filter(
f => f.friendlyName === title
);
if (match && match.length > 0) fullPath = match[0].path;
tagsCache[title] = tags;
return Promise.all([
new Promise((a, r) => {
fs.writeFile(fullPath, content, "utf8", err => {
if (err) return r(err);
a();
});
}),
saveTags(directory, tagsCache)
]);
}
function newNoteBackend(initSettings) {
let settings = { ...defaultSettings, ...initSettings };
async function onNoteCommand({ type, payload }) {
console.log(`got command: ${type} -n ${JSON.stringify(payload)}`);
const { directory } = settings;
switch (type) {
case "getNotes":
const files = await loadNotesInDirectory(directory);
return {
type: "getNotes",
payload: {
notes: files.map(f => ({ ...f, tags: [] }))
}
};
case "saveNote":
const { title, content, tags } = payload;
const result = await saveNoteInDirectory(settings, {
title,
content,
tags
});
return { type: "saveNote", payload: result };
default:
return { type: "noop" };
}
}
function updateSettings({ directory }) {
settings.directory = directory;
}
return {
onNoteCommand,
updateSettings
};
}
module.exports = { createBackend: newNoteBackend };

@ -1,4 +0,0 @@
export const defaultSettings = {
directory: "./",
defaultExtension: "txt"
};

@ -34,8 +34,13 @@ contextBridge.exposeInMainWorld("api", {
receive,
receiveOnce,
sendLinkNav: target => ipcRenderer.send("element-clicked", target),
getSettings: async () => {
send("note_command", { type: "getSettings" });
const response = await receiveOnce("note_response");
return JSON.parse(response);
},
getNotes: async () => {
send("note_command", { type: "getNotes", payload: {} });
send("note_command", { type: "getNotes" });
const response = await receiveOnce("note_response");
return JSON.parse(response);
},

@ -1,8 +1,18 @@
import weedux, { middleware } from "weedux";
import { reducer as noteReducer, actions as acts } from "./actions";
import { applyMiddleware, createStore, combineReducers } from "redux";
import thunkMiddleware from "redux-thunk";
import { createLogger } from "redux-logger";
import { reducer as noteReducer, actions as acts } from "./notes";
import { reducer as settingsReducer } from "./settings";
const { thunk } = middleware;
const loggerMiddleware = createLogger();
const reducer = combineReducers({
settings: settingsReducer,
notes: noteReducer
});
// note CRUD
export const store = new weedux({}, noteReducer, [thunk]);
export const store = createStore(
reducer,
applyMiddleware(thunkMiddleware, loggerMiddleware)
);
export const actions = acts;

@ -12,9 +12,17 @@ const initialState = {
export const reducer = (cs = initialState, { type, payload }) => {
switch (type) {
case "SELECT_NOTE":
return { ...cs, selectedNote: payload.selectedNote };
return { ...cs, selectedNote: { ...payload.selectedNote } };
case "SAVE_NOTE_START":
return {
...cs,
selectedNote: { ...cs.selectedNote, content: payload.content }
};
case "SAVE_NOTE_SUCCESS":
return {
...cs,
selectedNote: { ...payload.note }
};
case "GET_NOTES_START":
return { ...cs, notesLoading: true };
case "GET_NOTES_SUCCESS":
@ -34,7 +42,7 @@ export const reducer = (cs = initialState, { type, payload }) => {
const selectNote = ({ title }) => (dispatch, getState) => {
const normalizedTitle = title.toLocaleLowerCase();
const { selectedNote, notes } = getState();
const { selectedNote, notes } = getState().notes;
let foundNote = selectedNote;
const notesFilter = notes.filter(
@ -48,13 +56,14 @@ const selectNote = ({ title }) => (dispatch, getState) => {
const deleteNote = ({ title }) => (dispatch, getState) => {};
const createNote = ({ title, content, tags = [] }) => async (
const upsertNote = ({ title, content, tags = [] }) => async (
dispatch,
getState
) => {
dispatch({ type: "SAVE_NOTE_START" });
dispatch({ type: "SAVE_NOTE_START", payload: { title, content, tags } });
try {
const result = await upsertNoteBackend({ title, content, tags });
dispatch(getNotes());
dispatch({ type: "SAVE_NOTE_SUCCESS", payload: { ...result } });
} catch (error) {
dispatch({ type: "SAVE_NOTE_FAIL", payload: { error } });
@ -62,7 +71,7 @@ const createNote = ({ title, content, tags = [] }) => async (
};
const getNotes = () => async (dispatch, getState) => {
const { notesLoading } = getState();
const { notesLoading } = getState().notes;
if (notesLoading) {
console.warn(
"trying to load notes when the operation has already begun."
@ -73,7 +82,6 @@ const getNotes = () => async (dispatch, getState) => {
dispatch({ type: "GET_NOTES_START" });
try {
const result = await getNotesBackend();
console.log(result);
dispatch({ type: "GET_NOTES_SUCCESS", payload: result });
} catch (error) {
dispatch({ type: "GET_NOTES_FAIL", payload: { error } });
@ -87,7 +95,7 @@ const updateTags = ({ title, tags }) => (dispatch, getState) => {
export const actions = {
getNotes,
deleteNote,
createNote,
upsertNote,
selectNote,
updateTags
};

@ -0,0 +1,22 @@
import { getSettings as getSettingsbackend } from "./api";
import { defaultSettings } from "../server/backend/settings";
const initialState = {
settings: { ...defaultSettings },
loading: false,
error: null
};
export const reducer = (cs = initialState, { type, payload }) => {
switch (type) {
case "GET_SETTINGS_START":
return { ...cs, loading: true };
case "GET_SETTINGS_SUCCESS":
return { ...cs, settings: { ...payload.settings }, loading: false };
case "GET_SETTINGS_FAIL":
return { ...cs, error: payload.error, loading: false };
default:
return { ...cs };
}
};

@ -930,6 +930,13 @@
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.5.5":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1"
integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/template@^7.4.0", "@babel/template@^7.7.4":
version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b"
@ -3488,6 +3495,11 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=
deep-equal@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
@ -5072,6 +5084,13 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#101685d3aff3b23ea213163f6e8e12f4f111e19f"
integrity sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==
dependencies:
react-is "^16.7.0"
hosted-git-info@^2.1.4:
version "2.8.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
@ -8896,7 +8915,7 @@ react-error-overlay@^6.0.4:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a"
integrity sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA==
react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
@ -8915,6 +8934,18 @@ react-markdown@^4.3.1:
unist-util-visit "^1.3.0"
xtend "^4.0.1"
react-redux@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79"
integrity sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==
dependencies:
"@babel/runtime" "^7.5.5"
hoist-non-react-statics "^3.3.0"
invariant "^2.2.4"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.9.0"
react-scripts@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.3.0.tgz#f26a21f208f20bd04770f43e50b5bbc151920c2a"
@ -9071,6 +9102,26 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
redux-logger@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8=
dependencies:
deep-diff "^0.3.5"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
regenerate-unicode-properties@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
@ -10229,6 +10280,11 @@ svgo@^1.0.0, svgo@^1.2.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
symbol-tree@^3.2.2:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"

Loading…
Cancel
Save