first commit
commit
7918628baa
@ -0,0 +1,3 @@
|
||||
/dist
|
||||
/.cache
|
||||
/node_modules
|
@ -0,0 +1,82 @@
|
||||
@import "vars";
|
||||
|
||||
.lore-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title > input {
|
||||
background-color: var(--color-background-light);
|
||||
color: var(--color-foreground);
|
||||
border: none;
|
||||
font-size: 1.4em;
|
||||
padding: 0.3em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bar {
|
||||
border-bottom: 1px solid var(--color-accent);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-left: -1px;
|
||||
padding-bottom: 0.3em;
|
||||
padding-top: 0.3em;
|
||||
|
||||
.group {
|
||||
padding: 0.2em;
|
||||
grid-row-gap: 0.3em;
|
||||
display: inline-flex;
|
||||
border-collapse: collapse;
|
||||
border-left: 1px solid var(--color-accent);
|
||||
|
||||
span {
|
||||
border-radius: 0.2em;
|
||||
padding: 0.3em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.active {
|
||||
color: var(--color-accent-alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1em;
|
||||
background: var(--color-background-light);
|
||||
overflow: auto;
|
||||
flex-grow: 2;
|
||||
|
||||
.DraftEditor-root {
|
||||
height: 100%;
|
||||
|
||||
div[data-contents=true] {
|
||||
& > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
& > div:before {
|
||||
content: "» ";
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: lighten($background-light, 30%);
|
||||
font-size: 1.1em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
pre > pre {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid lighten($background-light, 30%);
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
$background-light: #333533;
|
||||
|
||||
:root {
|
||||
// https://coolors.co/434371-e8eddf-79aea3-242423-333533
|
||||
--color-background: #242423;
|
||||
--color-foreground: #E8EDDF;
|
||||
--color-accent: #434371;
|
||||
--color-accent-alt: #79AEA3;
|
||||
--color-background-light: #333533;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
@import "vars";
|
||||
@import "lore-editor";
|
||||
|
||||
#app, body, html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
background: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app > * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dock {
|
||||
display: grid;
|
||||
grid-template-columns: max(300px, 10%) auto max(250px, 10%);
|
||||
grid-template-rows: 40px auto 20px;
|
||||
grid-template-areas: "menu menu menu" "tree main properties" "status status status";
|
||||
}
|
||||
|
||||
.dock > * > * {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dock--main, .dock--tree, .dock--properties {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dock--status, .dock--menu {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dock--menu {
|
||||
grid-area: menu;
|
||||
}
|
||||
|
||||
.dock--tree {
|
||||
grid-area: tree;
|
||||
}
|
||||
|
||||
.dock--main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
.dock--properties {
|
||||
grid-area: properties;
|
||||
}
|
||||
|
||||
.dock--status {
|
||||
grid-area: status;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "schepper",
|
||||
"version": "1.0.0",
|
||||
"description": "Lore-management",
|
||||
"author": "eater <=@eater.me>",
|
||||
"license": "GPL3-or-later",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"sass": "^1.26.5"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "parcel public/index.html",
|
||||
"build": "parcel build public/index.html"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-react"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"draft-js": "^0.11.5",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"@eater/react-treebeard": "^3.2.4"
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Schepper</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="../src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,19 @@
|
||||
import React, {Component} from 'react'
|
||||
import LoreEditor from "./component/LoreEditor";
|
||||
import LoreTree from "./component/LoreTree";
|
||||
|
||||
export default class App extends Component {
|
||||
render(props) {
|
||||
return <div className="dock">
|
||||
<div className="dock--menu"/>
|
||||
<div className="dock--tree">
|
||||
<LoreTree />
|
||||
</div>
|
||||
<div className="dock--main">
|
||||
<LoreEditor/>
|
||||
</div>
|
||||
<div className="dock--properties"/>
|
||||
<div className="dock--status"/>
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
import React, {Component} from "react";
|
||||
import {Editor, EditorState, RichUtils} from "draft-js";
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||
import * as fa from "@fortawesome/free-solid-svg-icons"
|
||||
import "draft-js/dist/Draft.css"
|
||||
|
||||
export default class LoreEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {editorState: EditorState.createEmpty()};
|
||||
this.onChange = editorState => {
|
||||
this.setState({editorState});
|
||||
}
|
||||
this.handleKeyCommand = this.handleKeyCommand.bind(this);
|
||||
}
|
||||
|
||||
handleKeyCommand(command, editorState) {
|
||||
// Allows the use of key binds like Ctrl + B
|
||||
const newState = RichUtils.handleKeyCommand(editorState, command);
|
||||
if (newState) {
|
||||
this.onChange(newState);
|
||||
return 'handled';
|
||||
}
|
||||
|
||||
return 'not-handled';
|
||||
}
|
||||
|
||||
blockTypeButton(name, blockType) {
|
||||
const selection = this.state.editorState.getSelection();
|
||||
const currentBlockType = this.state.editorState
|
||||
.getCurrentContent()
|
||||
.getBlockForKey(selection.getStartKey())
|
||||
.getType();
|
||||
|
||||
return <span className={currentBlockType === blockType ? "active" : ""}
|
||||
onMouseDown={(e) => {
|
||||
// Don't do system mouse down event,
|
||||
// because we'll be unfocused from the text editor
|
||||
e.preventDefault()
|
||||
|
||||
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType))
|
||||
}}>{name}</span>
|
||||
}
|
||||
|
||||
styleButton(name, style) {
|
||||
const currentStyle = this.state.editorState.getCurrentInlineStyle();
|
||||
|
||||
return <span className={currentStyle.contains(style) ? "active" : ""}
|
||||
onMouseDown={(e) => {
|
||||
// Don't do system mouse down event,
|
||||
// because we'll be unfocused from the text editor
|
||||
e.preventDefault()
|
||||
|
||||
|
||||
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, style))
|
||||
}}>{name}</span>
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="lore-editor">
|
||||
<div className="title">
|
||||
<input type="text" value={this.state.title} onChange={() => this.setState({title: this.titleInput.value})}
|
||||
ref={el => this.titleInput = el}/>
|
||||
</div>
|
||||
<div className="bar">
|
||||
<span className="group">
|
||||
{this.blockTypeButton("H1", "header-one")}
|
||||
{this.blockTypeButton("H2", "header-two")}
|
||||
{this.blockTypeButton("H3", "header-three")}
|
||||
{this.blockTypeButton("H4", "header-four")}
|
||||
{this.blockTypeButton("H5", "header-five")}
|
||||
{this.blockTypeButton(<FontAwesomeIcon title="Block quote" icon={fa.faIndent}/>, "blockquote")}
|
||||
{this.blockTypeButton(<FontAwesomeIcon title="Unordered list" icon={fa.faListUl}/>, "unordered-list-item")}
|
||||
{this.blockTypeButton(<FontAwesomeIcon title="Ordered list" icon={fa.faListOl}/>, "ordered-list-item")}
|
||||
{this.blockTypeButton(<FontAwesomeIcon title="Code block" icon={fa.faCode}/>, "code-block")}
|
||||
</span>
|
||||
<span className="group">
|
||||
{this.styleButton(<FontAwesomeIcon title="Bold" icon={fa.faBold}/>, "BOLD")}
|
||||
{this.styleButton(<FontAwesomeIcon title="Italic" icon={fa.faItalic}/>, "ITALIC")}
|
||||
{this.styleButton(<FontAwesomeIcon title="Underline" icon={fa.faUnderline}/>, "UNDERLINE")}
|
||||
{this.styleButton(<FontAwesomeIcon title="Strike-through" icon={fa.faStrikethrough}/>, "STRIKETHROUGH")}
|
||||
{this.styleButton(<FontAwesomeIcon title="Monospace" icon={fa.faTextWidth}/>, "CODE")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="container">
|
||||
<Editor
|
||||
editorState={this.state.editorState}
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import React, {PureComponent} from "react";
|
||||
import {Treebeard} from "@eater/react-treebeard";
|
||||
import style from "../style";
|
||||
import Toggle from "./LoreTree/Toggle";
|
||||
import Header from "./LoreTree/Header";
|
||||
|
||||
const decorators = {
|
||||
Loading: (props) => {
|
||||
return (
|
||||
<div style={props.style}>
|
||||
loading...
|
||||
</div>
|
||||
);
|
||||
},
|
||||
Toggle,
|
||||
Header,
|
||||
Container: (props) => {
|
||||
return (
|
||||
<div style={{
|
||||
paddingLeft: props.depth * (props.style.toggle.width + 6),
|
||||
backgroundColor: (props.node.active ? style.colorBackgroundLight : null)
|
||||
}}
|
||||
onClick={props.onSelect} onDoubleClick={props.onClick}>
|
||||
<props.decorators.Toggle onClick={props.onClick} style={props.style} terminal={props.terminal}
|
||||
node={props.node}/>
|
||||
<props.decorators.Header style={props.style} terminal={props.terminal} node={props.node}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default class LoreTree extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
// Random data to test for now
|
||||
data: [{
|
||||
name: 'Lore',
|
||||
children: [
|
||||
{
|
||||
name: 'Events',
|
||||
children: [
|
||||
{name: 'Battle of Heck'},
|
||||
{name: 'Treaty Of Fuck'}
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
onToggle(node, toggled) {
|
||||
const {data} = this.state;
|
||||
if (node.children) {
|
||||
node.toggled = toggled;
|
||||
}
|
||||
|
||||
this.setState(() => ({data: [].concat(data)}));
|
||||
}
|
||||
|
||||
onSelect(node) {
|
||||
const {cursor, data} = this.state;
|
||||
// `node` is already our cursor, quick return
|
||||
if (cursor === node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
cursor.active = false;
|
||||
}
|
||||
|
||||
node.active = true;
|
||||
this.setState(() => ({cursor: node, data: [].concat(data)}));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<Treebeard data={this.state.data} style={style.theme.treebeard} decorators={decorators}
|
||||
onToggle={this.onToggle.bind(this)} onSelect={this.onSelect.bind(this)}/>)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import React, {PureComponent} from "react";
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
|
||||
import * as fa from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
export default class Header extends PureComponent {
|
||||
render() {
|
||||
const style = this.props.style.header;
|
||||
|
||||
return <span style={style.base}>
|
||||
<span style={style.title}>
|
||||
<span style={style.icon}>{this.props.node.icon ||
|
||||
<FontAwesomeIcon icon={this.props.terminal ? fa.faFile : fa.faFolder}/>}</span>
|
||||
{this.props.node.name}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import React, {PureComponent} from "react";
|
||||
import style from "../../style";
|
||||
|
||||
export default class Toggle extends PureComponent {
|
||||
render() {
|
||||
return <span onClick={this.props.onClick} style={this.props.style.toggle.base}>
|
||||
<span style={this.props.style.toggle.wrapper}>
|
||||
<svg width={this.props.style.toggle.width} height={this.props.style.toggle.height} viewBox="-1 -1 12 12">
|
||||
{this.props.terminal ? null :
|
||||
<polygon points={this.props.node.toggled ? "0,0 10,0 5,10" : "0,0 10,5 0,10"} fill={style.colorAccent}/>
|
||||
}
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import React from "react"
|
||||
import {render} from "react-dom";
|
||||
import App from "./App";
|
||||
|
||||
import "../css/main.scss";
|
||||
|
||||
render(<App/>, document.getElementById("app"));
|
@ -0,0 +1,81 @@
|
||||
// https://coolors.co/434371-e8eddf-79aea3-242423-333533
|
||||
const c = {
|
||||
colorBackground: '#242423',
|
||||
colorForeground: '#E8EDDF',
|
||||
colorAccent: '#434371',
|
||||
colorAccentAlt: '#79AEA3',
|
||||
colorBackgroundLight: '#333533',
|
||||
}
|
||||
|
||||
export default {
|
||||
...c,
|
||||
theme: {
|
||||
treebeard: {
|
||||
tree: {
|
||||
base: {
|
||||
backgroundColor: c.colorBackground
|
||||
},
|
||||
node: {
|
||||
base: {
|
||||
color: c.colorForeground
|
||||
},
|
||||
activeLink: {
|
||||
color: c.colorAccentAlt
|
||||
},
|
||||
toggle: {
|
||||
base: {
|
||||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
height: '24px',
|
||||
width: '24px'
|
||||
},
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
margin: '-7px 0 0 -7px',
|
||||
height: '14px'
|
||||
},
|
||||
height: 14,
|
||||
width: 14,
|
||||
arrow: {
|
||||
fill: '#9DA5AB',
|
||||
strokeWidth: 0
|
||||
}
|
||||
},
|
||||
header: {
|
||||
base: {
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
userSelect: 'none',
|
||||
},
|
||||
icon: {
|
||||
width: '20px',
|
||||
fontSize: '15px',
|
||||
display: 'inline-block',
|
||||
color: c.colorAccentAlt,
|
||||
},
|
||||
connector: {
|
||||
width: '2px',
|
||||
height: '12px',
|
||||
borderLeft: 'solid 2px black',
|
||||
borderBottom: 'solid 2px black',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '-21px'
|
||||
},
|
||||
title: {
|
||||
lineHeight: '24px',
|
||||
verticalAlign: 'middle'
|
||||
}
|
||||
},
|
||||
subtree: {
|
||||
listStyle: 'none',
|
||||
paddingLeft: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue