added reduxy stuff
parent
5f8da6179f
commit
1972f9eebf
25
main.js
25
main.js
|
|
@ -1,25 +0,0 @@
|
|||
const electron = require("electron");
|
||||
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
const app = electron.app;
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
let win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "prod") {
|
||||
win.loadURL("http://localhost:3000");
|
||||
} else {
|
||||
// and load the index.html of the app.
|
||||
win.loadFile("./build/index.html");
|
||||
}
|
||||
}
|
||||
console.log(Object.keys(electron));
|
||||
|
||||
app.on("ready", createWindow);
|
||||
File diff suppressed because it is too large
Load Diff
84
package.json
84
package.json
|
|
@ -1,41 +1,51 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"date-fns": "^2.9.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-scripts": "3.3.0"
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"date-fns": "^2.9.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-scripts": "3.3.0",
|
||||
"weedux": "^3.5.3-beta"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./src/server",
|
||||
"dev": "BROWSER=none react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.electron.electron-with-create-react-app",
|
||||
"win": {
|
||||
"iconUrl": "https://cdn2.iconfinder.com/data/icons/designer-skills/128/react-256.png"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./main.js",
|
||||
"dev": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^7.1.9"
|
||||
"directories": {
|
||||
"buildResources": "public"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^7.1.9"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
background-color: #282c34;
|
||||
color: white;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.SearchArea {
|
||||
|
|
|
|||
93
src/App.js
93
src/App.js
|
|
@ -1,74 +1,60 @@
|
|||
import React, { PureComponent } from "react";
|
||||
import { connect } from "weedux";
|
||||
|
||||
import { store, actions } from "./state/index.js";
|
||||
import NoteList from "./components/NoteList/NoteList";
|
||||
import NoteContent from "./components/NoteContent";
|
||||
import Omnibar from "./components/Omnibar";
|
||||
|
||||
import { createNote, getNotes, upsertNote } from "./state";
|
||||
import NoteContent from "./components/NoteContent/NoteContent";
|
||||
import Omnibar from "./components/Omnibar/Omnibar";
|
||||
|
||||
import "./App.css";
|
||||
import { bindActionCreators } from "weedux/lib";
|
||||
|
||||
class App extends PureComponent {
|
||||
state = { ...getNotes(), selectedNote: null };
|
||||
async componentDidMount() {
|
||||
const { getNotes } = this.props;
|
||||
getNotes({ filter: "" });
|
||||
}
|
||||
|
||||
handleSearch = text => {
|
||||
this.setState({
|
||||
notes: getNotes().notes.filter(
|
||||
n =>
|
||||
n.title.includes(text) ||
|
||||
n.tags.find(x => x === text) ||
|
||||
n.content.includes(text)
|
||||
)
|
||||
});
|
||||
const normalizedText = text.toLocaleLowerCase();
|
||||
const { getNotes } = this.props;
|
||||
|
||||
getNotes({ filter: normalizedText });
|
||||
// this.setState({
|
||||
// notes: getNotes().notes.filter(
|
||||
// n =>
|
||||
// n.title.toLocaleLowerCase().includes(normalizedText) ||
|
||||
// n.tags.find(
|
||||
// x => x.toLocaleLowerCase() === normalizedText
|
||||
// ) ||
|
||||
// n.content.toLocaleLowerCase().includes(normalizedText)
|
||||
// )
|
||||
// });
|
||||
};
|
||||
|
||||
handleCreate = title => {
|
||||
const newNote = upsertNote(title);
|
||||
this.setState({ ...getNotes(), selectedNote: newNote });
|
||||
const { createNote } = this.props;
|
||||
createNote({ title, content: "", tags: [] });
|
||||
};
|
||||
|
||||
handleSelectNote = noteTitle => {
|
||||
const { notes } = this.state;
|
||||
const sn = notes.filter(({ title }) => title === noteTitle);
|
||||
if (sn.length > 0) {
|
||||
this.setState({ selectedNote: sn[0] });
|
||||
}
|
||||
handleSelectNote = title => {
|
||||
const { selectNote } = this.props;
|
||||
selectNote({ title });
|
||||
};
|
||||
|
||||
handleSaveNoteContent = content => {
|
||||
const { notes, selectedNote } = this.state;
|
||||
const idx = notes.findIndex(
|
||||
({ title }) => title === selectedNote.title
|
||||
);
|
||||
|
||||
if (idx >= 0) {
|
||||
notes[idx].content = content;
|
||||
this.setState({
|
||||
notes: [...notes],
|
||||
selectedNote: { ...notes[idx] }
|
||||
});
|
||||
console.log("updated note", selectedNote.title, "content");
|
||||
}
|
||||
const { updateNote, selectedNote } = this.props;
|
||||
updateNote({ ...selectedNote, content });
|
||||
};
|
||||
|
||||
handleTagUpdate = newTags => {
|
||||
const { notes, selectedNote } = this.state;
|
||||
const idx = notes.findIndex(
|
||||
({ title }) => title === selectedNote.title
|
||||
);
|
||||
|
||||
if (idx >= 0) {
|
||||
notes[idx].tags = newTags;
|
||||
this.setState({
|
||||
notes: [...notes],
|
||||
selectedNote: { ...notes[idx] }
|
||||
});
|
||||
console.log("updated note tags", selectedNote.tags);
|
||||
}
|
||||
handleTagUpdate = ({ title, newTags }) => {
|
||||
// const { updateTags } = this.props;
|
||||
// updateTags({ title, tags: newTags });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { notes, selectedNote } = this.state;
|
||||
const { notes, selectedNote } = this.props;
|
||||
console.log(this.props);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
|
|
@ -96,4 +82,11 @@ class App extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
const ms2p = s => {
|
||||
console.log(s);
|
||||
return s;
|
||||
};
|
||||
|
||||
const md2p = dispatch => bindActionCreators({ ...actions }, dispatch);
|
||||
|
||||
export default connect(ms2p, md2p, store)(App);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.EditableNoteArea {
|
||||
height: 90%;
|
||||
height: 100%;
|
||||
border: gray solid 1px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
|
@ -21,8 +21,9 @@
|
|||
}
|
||||
|
||||
.EditableNoteArea .markdownPreview {
|
||||
flex-basis: 1 1 min-content;
|
||||
overflow: scroll;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.EditableNoteArea .markdownPreview h1,
|
||||
|
|
@ -33,3 +34,11 @@
|
|||
.EditableNoteArea .markdownPreview p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.EditableNoteArea .markdownPreview a:visited {
|
||||
color: darkgoldenrod;
|
||||
}
|
||||
|
||||
.EditableNoteArea .markdownPreview a:link {
|
||||
color: orange;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { noop } from "../../state";
|
||||
import { noop } from "../../utils";
|
||||
|
||||
import "./EditableNoteArea.css";
|
||||
|
||||
|
|
@ -9,14 +9,16 @@ export default function EditableNoteArea({
|
|||
onSave = noop,
|
||||
preview = false
|
||||
}) {
|
||||
const updateContent = ({ target: { value } }) => {
|
||||
onSave(value);
|
||||
};
|
||||
const updateContent = ({ target: { value } }) => onSave(value);
|
||||
|
||||
return (
|
||||
<section className="EditableNoteArea">
|
||||
{!preview ? (
|
||||
<textarea value={content} onChange={updateContent} />
|
||||
<textarea
|
||||
value={content || ""}
|
||||
placeholder="This note feels cold and empty inside"
|
||||
onChange={updateContent}
|
||||
/>
|
||||
) : (
|
||||
<div className="markdownPreview">
|
||||
<ReactMarkdown source={content} />
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
.NoteContent {
|
||||
min-height: 70%;
|
||||
max-height: 70vh;
|
||||
background-color: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
flex-grow: 2;
|
||||
text-align: left;
|
||||
padding: 0 10px 15px 10px;
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState } from "react";
|
||||
import { noop } from "../state";
|
||||
import EditableNoteArea from "./EditableNoteArea/EditableNoteArea";
|
||||
import { noop } from "../../utils";
|
||||
import EditableNoteArea from "../EditableNoteArea/EditableNoteArea";
|
||||
|
||||
import "./NoteContent.css";
|
||||
|
||||
export default function NoteContent({ note = null, onSave = noop }) {
|
||||
const [renderMarkdown, setRenderMarkdown] = useState(false);
|
||||
const { content, title } = note || {};
|
||||
const { content } = note || {};
|
||||
|
||||
return (
|
||||
<div className="NoteContent">
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
.NoteList {
|
||||
min-height: 80px;
|
||||
height: 25%;
|
||||
/* flex-basis: 1 1 content; */
|
||||
flex-grow: 1;
|
||||
height: 20%;
|
||||
min-height: 20%;
|
||||
flex-shrink: 1;
|
||||
list-style-type: none;
|
||||
border-color: black;
|
||||
|
|
@ -13,7 +11,7 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.NoteList li:nth-child(even) {
|
||||
|
|
@ -29,10 +27,16 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.NoteList li.empty {
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.NoteList .NoteListItem {
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import { formatRelative } from "date-fns";
|
||||
import { noop } from "../../state";
|
||||
import { noop } from "../../utils";
|
||||
import TagList from "../TagList/TagList";
|
||||
|
||||
import "./NoteList.css";
|
||||
|
|
@ -29,7 +29,9 @@ export default function NoteList({
|
|||
))}
|
||||
|
||||
{notes.length === 0 && (
|
||||
<li>Click the text box above to create a note.</li>
|
||||
<li className="empty">
|
||||
Click the text box above to create a note.
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { noop, enterKeyPressed } from "../state";
|
||||
import { noop, enterKeyPressed } from "../../utils";
|
||||
|
||||
import "./Omnibar.css";
|
||||
|
||||
|
|
@ -7,6 +7,10 @@ export default function Omnibar({ onSearch = noop, onEnter = noop }) {
|
|||
const [text, setText] = useState("");
|
||||
|
||||
const onTextChange = text => setText(text) || onSearch(text);
|
||||
const onEnterKey = () => {
|
||||
onEnter(text);
|
||||
onTextChange("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Omnibar">
|
||||
|
|
@ -14,7 +18,8 @@ export default function Omnibar({ onSearch = noop, onEnter = noop }) {
|
|||
type="text"
|
||||
placeholder='Type here to search. Type here and press "enter" to create a note.'
|
||||
onChange={({ target }) => onTextChange(target.value)}
|
||||
onKeyPress={enterKeyPressed(() => onEnter(text))}
|
||||
value={text}
|
||||
onKeyPress={enterKeyPressed(onEnterKey)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
.TagList {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
overflow-x: scroll;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.TagList input {
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
.TagList span {
|
||||
color: #aaa;
|
||||
white-space: nowrap;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.TagList ul {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Fragment, useState } from "react";
|
||||
import { noop, enterKeyPressed } from "../../state";
|
||||
import { noop, enterKeyPressed } from "../../utils";
|
||||
import "./TagList.css";
|
||||
|
||||
export default function TagList({ tags = [], onTagUpdate = noop }) {
|
||||
|
|
@ -29,7 +29,7 @@ export default function TagList({ tags = [], onTagUpdate = noop }) {
|
|||
/>
|
||||
) : (
|
||||
<Fragment>
|
||||
{tags.length <= 0 && <span>tags</span>}
|
||||
{tags.length <= 0 && <span>Add a Tag</span>}
|
||||
<ul>
|
||||
{tags.map(t => (
|
||||
<li key={t}>{t}</li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
|
|
|
|||
29
src/index.js
29
src/index.js
|
|
@ -1,12 +1,29 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
ReactDOM.render(<App />, 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.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
|
||||
// handles opening anchor tags in the browser outside of the app
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.addEventListener(
|
||||
"click",
|
||||
function(e) {
|
||||
// only handle for a tags
|
||||
if (e.target.href) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.api.sendLinkNav(e.target.href);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
const path = require("path");
|
||||
const { app, shell, BrowserWindow, ipcMain } = require("electron");
|
||||
const { createBackend } = require("./notes");
|
||||
|
||||
let win;
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 600,
|
||||
height: 800,
|
||||
minHeight: 800,
|
||||
minWidth: 600,
|
||||
title: "XNV",
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: false,
|
||||
preload: path.join(__dirname, "preload.js")
|
||||
}
|
||||
});
|
||||
|
||||
const backend = createBackend({
|
||||
directory: "C:/Users/AdamM/Documents/notes"
|
||||
});
|
||||
|
||||
ipcMain.on("note_command", async (event, args) => {
|
||||
const response = await backend.onNoteCommand(JSON.parse(args));
|
||||
win.webContents.send("note_response", JSON.stringify(response));
|
||||
});
|
||||
|
||||
ipcMain.on("element-clicked", async (event, args) => {
|
||||
console.log("opening browser window to ", args);
|
||||
shell.openExternal(args);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "prod") {
|
||||
win.loadURL("http://localhost:3000");
|
||||
} else {
|
||||
// and load the index.html of the app.
|
||||
win.loadFile("./build/index.html");
|
||||
}
|
||||
}
|
||||
|
||||
app.on("ready", createWindow);
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
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].fullPath;
|
||||
|
||||
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}`);
|
||||
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(directory, {
|
||||
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 };
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
const validSendChannels = ["note_command", "element-clicked"];
|
||||
const validReceiveChannels = ["note_response"];
|
||||
|
||||
const send = (channel, data) => {
|
||||
if (validSendChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, JSON.stringify(data));
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
};
|
||||
|
||||
const receive = channel => {
|
||||
if (validReceiveChannels.includes(channel)) {
|
||||
return new Promise(resolve => {
|
||||
ipcRenderer.on(channel, (_, ...args) => resolve(...args));
|
||||
});
|
||||
}
|
||||
return Promise.reject();
|
||||
};
|
||||
|
||||
const receiveOnce = channel => {
|
||||
if (validReceiveChannels.includes(channel)) {
|
||||
return new Promise(resolve => {
|
||||
ipcRenderer.once(channel, (_, ...args) => resolve(...args));
|
||||
});
|
||||
}
|
||||
return Promise.reject();
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld("api", {
|
||||
send,
|
||||
receive,
|
||||
receiveOnce,
|
||||
sendLinkNav: target => ipcRenderer.send("element-clicked", target),
|
||||
getNotes: async ({ filter }) => {
|
||||
send("note_command", { type: "getNotes", payload: { filter } });
|
||||
const response = await receiveOnce("note_response");
|
||||
return JSON.parse(response);
|
||||
},
|
||||
saveNote: async ({ title, content, tags }) => {
|
||||
send("note_command", {
|
||||
type: "saveNote",
|
||||
payload: { title, content, tags }
|
||||
});
|
||||
const response = await receiveOnce("note_response");
|
||||
return JSON.parse(response);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
getNotes as getNotesBackend,
|
||||
upsertNote as upsertNoteBackend
|
||||
} from "./api";
|
||||
|
||||
export const reducer = (cs, { type, payload }) => {
|
||||
switch (type) {
|
||||
case "SELECT_NOTE":
|
||||
return { ...cs, selectedNote: payload.selectedNote };
|
||||
case "SAVE_NOTE_START":
|
||||
case "SAVE_NOTE_SUCCESS":
|
||||
case "GET_NOTES_START":
|
||||
return { ...cs, notesLoading: true };
|
||||
case "GET_NOTES_SUCCESS":
|
||||
return {
|
||||
...cs,
|
||||
notesLoading: false,
|
||||
error: null,
|
||||
notes: [...payload.notes]
|
||||
};
|
||||
case "SAVE_NOTE_FAIL":
|
||||
case "GET_NOTES_FAIL":
|
||||
return { ...cs, notesLoading: false, error: payload.error };
|
||||
default:
|
||||
return { ...cs };
|
||||
}
|
||||
};
|
||||
|
||||
const selectNote = ({ title }) => (dispatch, getState) => {
|
||||
const { selectedNote, notes } = getState();
|
||||
|
||||
let foundNote = selectedNote;
|
||||
const notesFilter = notes.filter(n => n.title === title);
|
||||
if (notesFilter.length > 0) {
|
||||
foundNote = notesFilter[0];
|
||||
}
|
||||
dispatch({ type: "SELECT_NOTE", payload: { ...foundNote } });
|
||||
};
|
||||
|
||||
const deleteNote = ({ title }) => (dispatch, getState) => {};
|
||||
|
||||
const createNote = ({ title, content, tags = [] }) => async (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
dispatch({ type: "SAVE_NOTE_START" });
|
||||
try {
|
||||
const result = await upsertNoteBackend({ title, content, tags });
|
||||
dispatch({ type: "SAVE_NOTE_SUCCESS", payload: { ...result } });
|
||||
} catch (error) {
|
||||
dispatch({ type: "SAVE_NOTE_FAIL", payload: { error } });
|
||||
}
|
||||
};
|
||||
|
||||
const getNotes = ({ filter }) => async (dispatch, getState) => {
|
||||
const { notesLoading } = getState();
|
||||
if (notesLoading) {
|
||||
console.warn(
|
||||
"trying to load notes when the operation has already begun."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: "GET_NOTES_START" });
|
||||
try {
|
||||
const result = await getNotesBackend({ filter });
|
||||
console.log(result);
|
||||
dispatch({ type: "GET_NOTES_SUCCESS", payload: result });
|
||||
} catch (error) {
|
||||
dispatch({ type: "GET_NOTES_FAIL", payload: { error } });
|
||||
}
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
getNotes,
|
||||
deleteNote,
|
||||
createNote,
|
||||
selectNote
|
||||
};
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
const createNote = ({ title = null, content = "", tags = [] }) => {
|
||||
if (title === null) {
|
||||
throw new Error("cannot create a note with a null title");
|
||||
}
|
||||
|
||||
const now = new Date().toUTCString();
|
||||
return {
|
||||
id: now,
|
||||
created: now,
|
||||
lastModified: now,
|
||||
title,
|
||||
content,
|
||||
tags
|
||||
};
|
||||
};
|
||||
|
||||
export const getNotes = async filter => {
|
||||
const { api } = window;
|
||||
|
||||
if (!api) return { ...notesStore };
|
||||
|
||||
const { getNotes } = api;
|
||||
const { payload } = await getNotes({ filter });
|
||||
console.log(payload);
|
||||
return {
|
||||
error: null,
|
||||
notes: payload.notes
|
||||
};
|
||||
};
|
||||
|
||||
export const upsertNote = async ({ title, content, tags }) => {
|
||||
const newNote = createNote({ title });
|
||||
const { api } = window;
|
||||
|
||||
if (!api) {
|
||||
notesStore.notes = notesStore.notes.concat(newNote);
|
||||
}
|
||||
|
||||
const { saveNote } = api;
|
||||
await saveNote({ ...newNote });
|
||||
|
||||
return { note: newNote };
|
||||
};
|
||||
|
||||
const notesStore = {
|
||||
error: null,
|
||||
notes: [
|
||||
createNote({
|
||||
title: "butt 1",
|
||||
content: "this is an butt note",
|
||||
tags: [
|
||||
"first",
|
||||
"second",
|
||||
"third",
|
||||
"fourth",
|
||||
"fifth",
|
||||
"sixth",
|
||||
"seventh"
|
||||
]
|
||||
}),
|
||||
createNote({
|
||||
title: "butt 2",
|
||||
content: "# butt2\nthis is a dope butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "too many buttes",
|
||||
content: "this is an butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "this note doesn't have the word butt in the content",
|
||||
content: "this is an butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "this is not an butt 5",
|
||||
content: `
|
||||
# Secret info about database
|
||||
|
||||
## auctioneers_auct_catalogs
|
||||
- ncatalog_status
|
||||
- null when the catalog isn’t published/proofed
|
||||
- ‘online’ when the catalog is publically visible
|
||||
- ‘live’ during live sales
|
||||
- ncatalog_type
|
||||
- 1 Timed Sales
|
||||
- 10 White Label
|
||||
- 1/11 Gold
|
||||
- 2/12 Platinum
|
||||
- 3/13 Platinum Plus
|
||||
- 1 Timed
|
||||
- 10+ White label
|
||||
For ncatalog_type 1 and 11 were "Gold," 2 and 12 were "Platinum," 3 and 13 were "Platinum Plus," but now 1 will typically be timed sales and 3/13 will be traditional. 10 is white label _only_ which means that the auction is not displayed on our site, except by direct URL. 10 + the level (1,2,3) means the catalog can be accessed as white label _and_ is displayed as a normal catalog on our site. ncatalog_type = 10 is really the only important functionality of that variable now, but there are still some gotchas lingering.
|
||||
|
||||
- auction_type
|
||||
- ‘j’ = jewelry only
|
||||
- ‘c’ = ‘Business and Industrial’ or ‘Real Estate’ or Automotive categories
|
||||
- ’t’ = ‘Dolls and Toys’ category
|
||||
- ‘a’ = timed (?)
|
||||
## auctioneers_auct2_lots_sold
|
||||
- Lots sold; 1 = sold, 2 = not sold
|
||||
|
||||
## auctioneers_auct2_np_bid_history
|
||||
- The bidder_id in np_bid_history is 0 for competing/floor bids sourceid = 1 is also for floor bids.
|
||||
- sourceid: 1=floor bids, 2=live, 3=absentee bid executed, 4=ios, 5=android, 6=automated bids to clear reserve price
|
||||
- Competing/floor bids are entered by the clerk and are supposed to represent anyone bidding live outside of our system (at the auction house, over the phone, on another internet platform, etc), and those will always have a bidder_id of 0.
|
||||
|
||||
Sub catalogs; they don't exist in the system formally, they're just lots grouped by listing_agent_id
|
||||
|
||||
## auctioneers_auct2_np_bid_history
|
||||
|
||||
absforcebidderid set to 20; 20 would be the SFS (smart fox server) internal userId for the live bidder that placed a bid, thus forcing the existing absentee bid (which preempts the live bid) to be sent to the clerk.
|
||||
All you really need to know is that there was a live bid that forced the absentee bid to be placed.
|
||||
Only one bid is sent
|
||||
|
||||
sourceid 1= floor, 2=internet, 3=absentee, 4=iPhone, 5=Android
|
||||
|
||||
## auctioneers_auct2_npb
|
||||
|
||||
npb_type 1 = active dispute, 0 = resolved dispute
|
||||
|
||||
auctioneers_users
|
||||
role known possible values: admin, auctioneer, bidder
|
||||
admin_group possible values: exec, support, development, admin_group, <NULL>
|
||||
|
||||
|
||||
|
||||
## auctioneers_auct2_approval
|
||||
|
||||
\`SELECT distinct approval_approved FROM liveauct_liveauctioneers.auctioneers_auct2_approval;\`
|
||||
|
||||
approval_approved = 0 - not reviewed, 1 - approved, 2 - declined, 4 - blocked by auctioneer, 5 - suspended
|
||||
|
||||
|
||||
## auctioneers_auct_bids
|
||||
|
||||
## auctioneers_auct_houses
|
||||
|
||||
## auctioneers_auct2_lots_sold
|
||||
|
||||
has ~29 million lots, we only index 21 million since some houses do high volume listings and we ignore any
|
||||
|
||||
plaintext pbuttwords for many of our clients just hanging out in here
|
||||
usernames and pbuttwords are generally the same
|
||||
|
||||
FAQ
|
||||
—————————————————————————————————————————
|
||||
Q. Can someone tell me where we are keeping the stats that we are reporting on for auction houses? I think they have stats like number of views of their items and such.
|
||||
|
||||
A. They are stored in the statistics database, which is a separate machine from our main database; however, they are aggregated and stored in auctioneers_auct_catalogs_admin_data and auctioneers_auct2_lots_admin_data for catalog and lot-specific data respectively.
|
||||
|
||||
|
||||
|
||||
Q. What’s with all the ncatalog stuff?
|
||||
|
||||
A. In the mainhost codebase the letter "n" is often used to mean new (unfortunately) to distinguish from similarly named things from the eBay era.
|
||||
|
||||
ncatalog_status is the status of the catalog once it is proofed. It is null until the catalog is proofed (i.e. published/made public on our site), at which point it is "online". "Live" indicates that the auction is taking place right now (or soon), and "done" means done.
|
||||
|
||||
ncatalog_type is not as important as it used to be. We used to have tiered service levels that had access to different features. For ncatalog_type 1 and 11 were "Gold," 2 and 12 were "Platinum," 3 and 13 were "Platinum Plus," but now 1 will typically be timed sales and 3/13 will be traditional. 10 is white label _only_ which means that the auction is not displayed on our site, except by direct URL. 10 + the level (1,2,3) means the catalog can be accessed as white label _and_ is displayed as a normal catalog on our site. ncatalog_type = 10 is really the only important functionality of that variable now, but there are still some gotchas lingering.
|
||||
|
||||
|
||||
`,
|
||||
tags: ["not butt"]
|
||||
})
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import weedux, { middleware } from "weedux";
|
||||
import { reducer as noteReducer, actions as acts } from "./actions";
|
||||
|
||||
const { thunk } = middleware;
|
||||
|
||||
// note CRUD
|
||||
export const store = new weedux({}, noteReducer, [thunk]);
|
||||
export const actions = acts;
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import debounce from "lodash.debounce";
|
||||
//import debounce from "lodash.debounce";
|
||||
|
||||
export const noop = () => {};
|
||||
export const enterKeyPressed = (cb = noop) => ({ charCode }) =>
|
||||
charCode === 13 && cb();
|
||||
|
||||
export const createNote = ({ title = null, content = "", tags = [] }) => {
|
||||
if (title === null) {
|
||||
throw new Error("cannot create a note with a null title");
|
||||
|
|
@ -19,12 +20,30 @@ export const createNote = ({ title = null, content = "", tags = [] }) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const getNotes = window.LoadNotes || (() => ({ ...notesStore }));
|
||||
export const upsertNote = newTitle => {
|
||||
const newNote = createNote({
|
||||
title: newTitle
|
||||
});
|
||||
notesStore.notes = notesStore.notes.concat(newNote);
|
||||
export const getNotes = async filter => {
|
||||
const { api } = window;
|
||||
|
||||
if (!api) return { ...notesStore };
|
||||
|
||||
const { getNotes } = api;
|
||||
const { payload } = await getNotes({ filter });
|
||||
console.log(payload);
|
||||
return {
|
||||
error: null,
|
||||
notes: payload.notes
|
||||
};
|
||||
};
|
||||
|
||||
export const upsertNote = async ({ title, content, tags }) => {
|
||||
const newNote = createNote({ title });
|
||||
const { api } = window;
|
||||
|
||||
if (!api) {
|
||||
notesStore.notes = notesStore.notes.concat(newNote);
|
||||
}
|
||||
|
||||
const { saveNote } = api;
|
||||
await saveNote({ ...newNote });
|
||||
|
||||
return newNote;
|
||||
};
|
||||
|
|
@ -33,8 +52,8 @@ const notesStore = {
|
|||
error: null,
|
||||
notes: [
|
||||
createNote({
|
||||
title: "ass 1",
|
||||
content: "this is an ass note",
|
||||
title: "butt 1",
|
||||
content: "this is an butt note",
|
||||
tags: [
|
||||
"first",
|
||||
"second",
|
||||
|
|
@ -46,22 +65,22 @@ const notesStore = {
|
|||
]
|
||||
}),
|
||||
createNote({
|
||||
title: "ass 2",
|
||||
content: "# ass2\nthis is a dope ass note",
|
||||
tags: ["ass"]
|
||||
title: "butt 2",
|
||||
content: "# butt2\nthis is a dope butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "too many asses",
|
||||
content: "this is an ass note",
|
||||
tags: ["ass"]
|
||||
title: "too many buttes",
|
||||
content: "this is an butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "this note doesn't have the word ass in the content",
|
||||
content: "this is an ass note",
|
||||
tags: ["ass"]
|
||||
title: "this note doesn't have the word butt in the content",
|
||||
content: "this is an butt note",
|
||||
tags: ["butt"]
|
||||
}),
|
||||
createNote({
|
||||
title: "this is not an ass 5",
|
||||
title: "this is not an butt 5",
|
||||
content: `
|
||||
# Secret info about database
|
||||
|
||||
|
|
@ -128,8 +147,8 @@ approval_approved = 0 - not reviewed, 1 - approved, 2 - declined, 4 - blocked by
|
|||
|
||||
has ~29 million lots, we only index 21 million since some houses do high volume listings and we ignore any
|
||||
|
||||
plaintext passwords for many of our clients just hanging out in here
|
||||
usernames and passwords are generally the same
|
||||
plaintext pbuttwords for many of our clients just hanging out in here
|
||||
usernames and pbuttwords are generally the same
|
||||
|
||||
FAQ
|
||||
—————————————————————————————————————————
|
||||
|
|
@ -149,7 +168,7 @@ ncatalog_type is not as important as it used to be. We used to have tiered servi
|
|||
|
||||
|
||||
`,
|
||||
tags: ["not ass"]
|
||||
tags: ["not butt"]
|
||||
})
|
||||
]
|
||||
};
|
||||
21
yarn.lock
21
yarn.lock
|
|
@ -4203,6 +4203,11 @@ eventemitter3@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
|
||||
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
|
||||
|
||||
events@3.x:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
|
||||
integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==
|
||||
|
||||
events@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
|
||||
|
|
@ -5299,6 +5304,11 @@ immer@1.10.0:
|
|||
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
|
||||
integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==
|
||||
|
||||
immutable@^3.8.1:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
|
||||
|
||||
import-cwd@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||
|
|
@ -8655,7 +8665,7 @@ prompts@^2.0.1:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.3"
|
||||
|
||||
prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
"prop-types@>= 15.5.10", prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
|
|
@ -11001,6 +11011,15 @@ websocket-extensions@>=0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
|
||||
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
|
||||
|
||||
weedux@^3.5.3-beta:
|
||||
version "3.5.3-beta"
|
||||
resolved "https://registry.yarnpkg.com/weedux/-/weedux-3.5.3-beta.tgz#8e68df8f43fd1fdaeb10f6a002d1b722d757ae80"
|
||||
integrity sha512-1BN0ZTxSAzhLHh9ujxYKA4afR30O5BeurueqUuZg/MA5MkdozmsGYE7hD7FvIrvNhFCzYb9DkNlZjGWhyShuTg==
|
||||
dependencies:
|
||||
events "3.x"
|
||||
immutable "^3.8.1"
|
||||
prop-types ">= 15.5.10"
|
||||
|
||||
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue