Artur
2020-05-27 12:48:25 +03:00
parent cc427f18ec
commit 3c973816d1
31 changed files with 853 additions and 351 deletions
+188
View File
@@ -0,0 +1,188 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @author Original Aleksej Komarov
* @author Changes Warlockd (https://github.com/warlockd)
* @license MIT
*/
import { classes, isFalsy } from 'common/react';
import { Component, createRef } from 'inferno';
import { Box } from './Box';
const toInputValue = value => {
if (isFalsy(value)) {
return '';
}
return value;
};
export class TextArea extends Component {
constructor(props, context) {
super(props, context);
this.textareaRef = createRef();
this.fillerRef = createRef();
this.state = {
editing: false,
};
const {
dontUseTabForIndent = false,
} = props;
// found this hack that expands the text area without
// having to hard set rows all the time
// there has GOT to be a better way though
this.autoresize = () => {
if (this.fillerRef && this.textareaRef) {
// this.fillerRef.current.innerHTML =
// this.textareaRef.current.value.replace(/\n/g, '<br/>');
}
};
this.handleOnInput = e => {
const { editing } = this.state;
const { onInput } = this.props;
if (!editing) {
this.setEditing(true);
}
if (onInput) {
onInput(e, e.target.value);
}
this.autoresize();
};
this.handleOnChange = e => {
const { editing } = this.state;
const { onChange } = this.props;
if (editing) {
this.setEditing(false);
}
if (onChange) {
onChange(e, e.target.value);
}
this.autoresize();
};
this.handleKeyPress = e => {
const { editing } = this.state;
const { onKeyPress } = this.props;
if (!editing) {
this.setEditing(true);
}
if (onKeyPress) {
onKeyPress(e, e.target.value);
}
this.autoresize();
};
this.handleKeyDown = e => {
const { editing } = this.state;
const { onKeyDown } = this.props;
if (!editing) {
this.setEditing(true);
}
if (!dontUseTabForIndent) {
const keyCode = e.keyCode || e.which;
if (keyCode === 9) {
e.preventDefault();
const s = e.target.selectionStart;
e.target.value
= e.target.value.substring(0, e.target.selectionStart)
+ "\t"
+ e.target.value.substring(e.target.selectionEnd);
e.target.selectionEnd = s +1;
}
}
if (onKeyDown) {
onKeyDown(e, e.target.value);
}
this.autoresize();
};
this.handleFocus = e => {
const { editing } = this.state;
if (!editing) {
this.setEditing(true);
}
};
this.handleBlur = e => {
const { editing } = this.state;
const { onChange } = this.props;
if (editing) {
this.setEditing(false);
if (onChange) {
onChange(e, e.target.value);
}
}
};
}
componentDidMount() {
const nextValue = this.props.value;
const input = this.textareaRef.current;
if (input) {
input.value = toInputValue(nextValue);
this.autoresize();
}
}
componentDidUpdate(prevProps, prevState) {
const { editing } = this.state;
const prevValue = prevProps.value;
const nextValue = this.props.value;
const input = this.textareaRef.current;
if (input && !editing && prevValue !== nextValue) {
input.value = toInputValue(nextValue);
this.autoresize();
}
}
setEditing(editing) {
this.setState({ editing });
}
getValue() {
return this.textareaRef.current && this.textareaRef.current.value;
}
render() {
const { props } = this;
// Input only props
const {
onChange,
onKeyDown,
onKeyPress,
onInput,
onFocus,
onBlur,
onEnter,
value,
placeholder,
...boxProps
} = this.props;
// Box props
const {
className,
fluid,
...rest
} = boxProps;
return (
<Box
className={classes([
'TextArea',
fluid && 'TextArea--fluid',
className,
])}
{...rest}>
<textarea
value={value}
ref={this.textareaRef}
className="TextArea__textarea"
placeholder={placeholder}
onChange={this.handleOnChange}
onKeyDown={this.handleKeyDown}
onKeyPress={this.handleKeyPress}
onInput={this.handleOnInput}
onFocus={this.handleFocus}
onBlur={this.handleBlur} />
</Box>
);
}
}
+1
View File
@@ -24,3 +24,4 @@ export { Slider } from './Slider';
export { Table } from './Table';
export { Tabs } from './Tabs';
export { Tooltip } from './Tooltip';
export { TextArea } from './TextArea';
+1
View File
@@ -18,6 +18,7 @@ import './styles/themes/hackerman.scss';
import './styles/themes/retro.scss';
import './styles/themes/syndicate.scss';
import './styles/themes/clockcult.scss';
import './styles/themes/paper.scss';
import { loadCSS } from 'fg-loadcss';
import { render } from 'inferno';
+153
View File
@@ -0,0 +1,153 @@
/**
* @file
* @copyright 2020 WarlockD (https://github.com/warlockd)
* @author Original WarlockD (https://github.com/warlockd)
* @author Changes stylemistake
* @license MIT
*/
import { Tabs, Box, Flex, Button, TextArea } from '../components';
import { useBackend, useSharedState, useLocalState } from '../backend';
import { Window } from '../layouts';
// import marked from 'marked';
import marked from 'marked';
import DOMPurify from 'dompurify';
// There is a sanatize option in marked but they say its deprecated.
// Might as well use a proper one then
import { createLogger } from '../logging';
import { Fragment } from 'inferno';
const logger = createLogger('PaperSheet');
const run_marked_default = value => {
const sanitizer = DOMPurify.sanitize;
// too much?
// return sanitizer(marked(sanitizer(value),
// { breaks: true, smartypants: true });
return sanitizer(marked(value,
{ breaks: true, smartypants: true }));
};
const PaperSheetView = (props, context) => {
const { data } = useBackend(context);
const {
paper_color = "white",
pen_color = "black",
text = '',
} = data;
const {
value = text || '',
...rest
} = props;
// We use this for caching so we don't keep refreshing it each time
const [marked_text, setMarkedText] = useLocalState(context, 'marked_text',
{ __html: run_marked_default(value) });
return (
<Box
backgroundColor={paper_color}
color={pen_color}
{...rest}
dangerouslySetInnerHTML={marked_text} />
);
};
const PaperSheetEdit = (props, context) => {
const { act, data } = useBackend(context);
const [text, setText] = useLocalState(context, 'text', data.text || '');
const [marked_text, setMarkedText] = useLocalState(context, 'marked_text',
{ __html: run_marked_default(text) });
const [
previewSelected,
setPreviewSelected,
] = useLocalState(context, 'preview', "Preview");
const {
paper_color = "white",
pen_color = "black",
} = data;
const onInputHandler = (e, value) => {
if (value.length < 1000) {
setText(value);
setMarkedText({ __html: run_marked_default(value) });
} else {
setText(value.substr(1000));
}
};
return (
<Flex direction="column">
<Flex.Item>
<Tabs>
<Tabs.Tab
key="marked_edit"
textColor={'black'}
backgroundColor={previewSelected === "Edit" ? "grey" : "white"}
selected={previewSelected === "Edit"}
onClick={() => setPreviewSelected("Edit")}>
Edit
</Tabs.Tab>
<Tabs.Tab
key="marked_preview"
textColor={'black'}
backgroundColor={previewSelected === "Preview" ? "grey" : "white"}
selected={previewSelected === "Preview"}
onClick={() => setPreviewSelected("Preview")}>
Preview
</Tabs.Tab>
<Tabs.Tab
key="marked_done"
textColor={'black'}
backgroundColor={previewSelected === "confirm"
? "red"
: previewSelected === "save"
? "grey"
: "white"}
selected={previewSelected === "confirm"
|| previewSelected === "save"}
onClick={() => {
if (previewSelected === "confirm") {
act('save', { text });
} else {
setPreviewSelected("confirm");
}
}}>
{ previewSelected === "confirm" ? "confirm" : "save" }
</Tabs.Tab>
</Tabs>
</Flex.Item>
<Flex.Item
grow={1}
basis={1}>
{previewSelected === "Edit" && (
<TextArea
value={text}
backgroundColor="white"
textColor="black"
height={(window.innerHeight - 80)+ "px"}
onInput={onInputHandler} />
) || (
<PaperSheetView />
)}
</Flex.Item>
</Flex>
);
};
export const PaperSheet = (props, context) => {
const { data } = useBackend(context);
const {
edit_sheet,
} = data;
return (
<Window resizable theme="paper">
<Window.Content scrollable>
{edit_sheet && (
<PaperSheetEdit />
) || (
<PaperSheetView fillPositionedParent />
)}
</Window.Content>
</Window>
);
};
+3 -1
View File
@@ -26,6 +26,8 @@
"webpack": "^4.40.2",
"webpack-build-notifier": "^2.0.0",
"webpack-bundle-analyzer": "^3.5.1",
"webpack-cli": "^3.3.9"
"webpack-cli": "^3.3.9",
"marked": "^1.0.0",
"dompurify": "^2.0.11"
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@use '../base.scss';
@use '../functions.scss' as *;
$border-color: #88bfff !default;
$border-radius: base.$border-radius !default;
.TextArea {
overflow: hidden;
position: relative;
display: inline-block;
height: "100%";
border: 1px solid $border-color;
border: 1px solid rgba($border-color, 0.75);
border-radius: $border-radius;
color: #fff;
background-color: #000;
background-color: rgba(0, 0, 0, 0.75);
margin-right: 2px;
line-height: 17px;
overflow: visible;
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box; /* Firefox, other Gecko */
box-sizing: border-box; /* Opera/IE 8+ */
width:100%;
}
.TextArea_filler {
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
box-sizing: border-box;
padding: 2px;
width: 100%;
padding-bottom: 1.5em; /* A bit more than one additional line of text. */
visibility: hidden;
}
.TextArea--fluid {
display: block;
width: auto;
height: auto;
}
.TextArea__baseline {
display: inline-block;
color: transparent;
}
.TextArea__textarea {
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 0;
outline: 0;
width: 100%;
height: 100%;
font-size: 12px;
line-height: 17px;
height: 17px;
margin: 0;
padding: 0 6px;
font-family: Verdana, sans-serif;
background-color: transparent;
color: #fff;
color: inherit;
box-sizing: border-box;
width: 100%;
/* Cut and paste to auto resize the textarea */
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
box-sizing: border-box;
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box; /* Firefox, other Gecko */
box-sizing: border-box; /* Opera/IE 8+ */
padding: 2px;
width: 100%;
overflow: hidden;
position: absolute;
height: 100%;
&:-ms-input-placeholder {
font-style: italic;
color: #777;
color: rgba(255, 255, 255, 0.45);
}
}
+1
View File
@@ -34,6 +34,7 @@
@include meta.load-css('./components/Slider.scss');
@include meta.load-css('./components/Table.scss');
@include meta.load-css('./components/Tabs.scss');
@include meta.load-css('./components/TextArea.scss');
@include meta.load-css('./components/Tooltip.scss');
// Interfaces
@@ -0,0 +1,60 @@
/**
* @file
* @copyright 2020 Paul Bruner
* @license MIT
*/
@use 'sass:color';
@use 'sass:meta';
@use '../colors.scss' with (
$primary: #000000,
$fg-map-keys: (),
$bg-map-keys: (),
);
@use '../base.scss' with (
$color-fg: rgb(0,0,0),
$color-bg: rgb(255, 255, 255),
$color-bg-grad-spread: 0%,
$border-radius: 0px,
);
// A fat warning to anyone who wants to use this: this only half works.
// It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended.
.theme-paper {
// Atomic classes
@include meta.load-css('../atomic/color.scss');
// Components
@include meta.load-css('../components/Tabs.scss');
@include meta.load-css('../components/Button.scss', $with: (
'color-default': #E8E4C9,
'color-disabled': #363636,
'color-selected': #9d0808,
'color-caution': #be6209,
'color-danger': #9a9d00,
));
// Layouts
@include meta.load-css('../layouts/Layout.scss');
@include meta.load-css('../layouts/Window.scss');
@include meta.load-css('../layouts/TitleBar.scss', $with: (
'color-background': #ffffff,
));
.Layout__content {
background-image: none;
}
.code {
font-family: Consolas,"courier new";
color: black;
background-color: #f1f1f1;
padding: 2px;
font-size: 85%;
}
}
+10
View File
@@ -2147,6 +2147,11 @@ domelementtype@1:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
dompurify@^2.0.11:
version "2.0.11"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.11.tgz#cd47935774230c5e478b183a572e726300b3891d"
integrity sha512-qVoGPjIW9IqxRij7klDQQ2j6nSe4UNWANBhZNLnsS7ScTtLb+3YdxkRY8brNTpkUiTtcXsCJO+jS0UCDfenLuA==
domelementtype@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
@@ -3782,6 +3787,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
marked@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.0.tgz#62504ad4d11550c942935ccc5e39d64e5a4c4e50"
integrity sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"