This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,3 +24,4 @@ export { Slider } from './Slider';
|
||||
export { Table } from './Table';
|
||||
export { Tabs } from './Tabs';
|
||||
export { Tooltip } from './Tooltip';
|
||||
export { TextArea } from './TextArea';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user