Merge pull request #305 from Mavrin/master

#299 add toolbar for markdown formatting
This commit is contained in:
Umputun
2019-04-14 19:33:03 -05:00
committed by GitHub
24 changed files with 329 additions and 36 deletions
+17
View File
@@ -0,0 +1,17 @@
declare namespace JSX {
interface IntrinsicElements {
'markdown-toolbar': {
for: string;
children?: any;
className: string;
};
'md-bold': any;
'md-header': any;
'md-italic': any;
'md-quote': any;
'md-code': any;
'md-link': any;
'md-unordered-list': any;
'md-ordered-list': any;
}
}
+15
View File
@@ -0,0 +1,15 @@
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
let el = this;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
+2
View File
@@ -1,5 +1,7 @@
import 'core-js/es/promise';
import 'focus-visible';
import '@webcomponents/custom-elements';
import './closest-polyfill';
export default async function loadPolyfills() {
const fillCoreJs = async () => {
@@ -0,0 +1,4 @@
.input__control-panel {
height: 30px;
background-color: #eee;
}
@@ -2,7 +2,7 @@
$lineHeight: 1.4;
$fontSize: 16px;
$paddingVrt: 10px;
$lines: 3;
$lines: 4;
$height: calc($fontSize * $lineHeight * $lines + $paddingVrt * 2);
box-sizing: border-box;
@@ -0,0 +1,31 @@
.input__toolbar {
display: block;
float: right;
}
.input__toolbar-item {
background: none;
border: 0;
color: #586069;
display: block;
float: left;
padding: 4px 5px;
&:hover {
color: #0aa;
}
}
.input__toolbar-icon {
display: inline-block;
fill: currentColor;
}
.input__toolbar-group {
display: inline-block;
margin-left: 20px;
}
.input__toolbar-group:first-child {
margin-left: 0;
}
@@ -35,4 +35,15 @@
.input__preview-wrapper {
background: #eee;
}
.input__toolbar-item {
color: #ddd;
&:hover {
color: #0aa;
}
}
.input__control-panel {
background-color: #333;
}
}
+2 -1
View File
@@ -2,6 +2,7 @@
position: relative;
display: block;
font-size: 0;
border: 12px solid;
border-style: solid;
border-width: 6px 12px 12px 12px;
border-radius: 2px;
}
+10 -2
View File
@@ -13,12 +13,15 @@ import { StaticStore } from '@app/common/static_store';
import { siteId, url, pageTitle } from '@app/common/settings';
import { extractErrorMessageFromResponse } from '@app/utils/errorUtils';
import MarkdownToolbar from './markdown-toolbar';
import TextareaAutosize from './textarea-autosize';
const RSS_THREAD_URL = `${BASE_URL}${API_BASE}/rss/post?site=${siteId}&url=${url}`;
const RSS_SITE_URL = `${BASE_URL}${API_BASE}/rss/site?site=${siteId}`;
const RSS_REPLIES_URL = `${BASE_URL}${API_BASE}/rss/reply?site=${siteId}&user=`;
let textareaId = 0;
interface Props {
/** user id for rss link generation */
userId?: User['id'];
@@ -52,10 +55,11 @@ const Labels = {
export class Input extends Component<Props, State> {
textAreaRef?: TextareaAutosize;
textareaId: string;
constructor(props: Props) {
super(props);
textareaId = textareaId + 1;
this.textareaId = `textarea_${textareaId}`;
this.state = {
preview: null,
isErrorShown: false,
@@ -165,8 +169,12 @@ export class Input extends Component<Props, State> {
onSubmit={this.send}
aria-label="New comment"
>
<div className="input__control-panel">
<MarkdownToolbar textareaId={this.textareaId} />
</div>
<div className="input__field-wrapper">
<TextareaAutosize
id={this.textareaId}
ref={ref => (this.textAreaRef = ref)}
className="input__field"
placeholder="Your comment here"
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function BoldIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 10 16" version="1.1" width="10" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M1 2h3.83c2.48 0 4.3.75 4.3 2.95 0 1.14-.63 2.23-1.67 2.61v.06c1.33.3 2.3 1.23 2.3 2.86 0 2.39-1.97 3.52-4.61 3.52H1V2zm3.66 4.95c1.67 0 2.38-.66 2.38-1.69 0-1.17-.78-1.61-2.34-1.61H3.13v3.3h1.53zm.27 5.39c1.77 0 2.75-.64 2.75-1.98 0-1.27-.95-1.81-2.75-1.81h-1.8v3.8h1.8v-.01z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function CodeIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function HeaderIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 18 16" version="1.1" width="18" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M13.62 9.08L12.1 3.66h-.06l-1.5 5.42h3.08zM5.7 10.13S4.68 6.52 4.53 6.02h-.08l-1.13 4.11H5.7zM17.31 14h-2.25l-.95-3.25h-4.07L9.09 14H6.84l-.69-2.33H2.87L2.17 14H0l3.3-9.59h2.5l2.17 6.34L10.86 2h2.52l3.94 12h-.01z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function ItalicIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 6 16" version="1.1" width="6" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M2.81 5h1.98L3 14H1l1.81-9zm.36-2.7c0-.7.58-1.3 1.33-1.3.56 0 1.13.38 1.13 1.03 0 .75-.59 1.3-1.33 1.3-.58 0-1.13-.38-1.13-1.03z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function LinkIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function OrderedListIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M12.01 13c0 .59 0 1-.59 1H4.6c-.59 0-.59-.41-.59-1 0-.59 0-1 .59-1h6.81c.59 0 .59.41.59 1h.01zM4.6 4h6.81C12 4 12 3.59 12 3c0-.59 0-1-.59-1H4.6c-.59 0-.59.41-.59 1 0 .59 0 1 .59 1zm6.81 3H4.6c-.59 0-.59.41-.59 1 0 .59 0 1 .59 1h6.81C12 9 12 8.59 12 8c0-.59 0-1-.59-1zm-9.4-6h-.72c-.3.19-.58.25-1.03.34V2h.75v2.14H.17V5h2.84v-.86h-1V1zm.392 8.12c-.129 0-.592.04-.802.07.53-.56 1.14-1.25 1.14-1.89C2.72 6.52 2.18 6 1.38 6c-.59 0-.97.2-1.38.64l.58.58c.19-.19.38-.38.64-.38.28 0 .48.16.48.52 0 .53-.77 1.2-1.7 2.06V10h3v-.88h-.598zm-.222 3.79v-.03c.44-.19.64-.47.64-.86 0-.7-.56-1.11-1.44-1.11-.48 0-.89.19-1.28.52l.55.64c.25-.2.44-.31.69-.31.27 0 .42.13.42.36 0 .27-.2.44-.86.44v.75c.83 0 .98.17.98.47 0 .25-.23.38-.58.38-.28 0-.56-.14-.81-.38l-.48.66c.3.36.77.56 1.41.56.83 0 1.53-.41 1.53-1.16 0-.5-.31-.81-.77-.94v.01z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function QuoteIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M6.16 3.5C3.73 5.06 2.55 6.67 2.55 9.36c.16-.05.3-.05.44-.05 1.27 0 2.5.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61-1.9 0-2.99-1.52-2.99-4.25 0-3.8 1.75-6.53 5.02-8.42L6.16 3.5zm7 0c-2.43 1.56-3.61 3.17-3.61 5.86.16-.05.3-.05.44-.05 1.27 0 2.5.86 2.5 2.41 0 1.61-1.03 2.61-2.5 2.61-1.89 0-2.98-1.52-2.98-4.25 0-3.8 1.75-6.53 5.02-8.42l1.14 1.84h-.01z"
/>
</svg>
);
}
@@ -0,0 +1,13 @@
/** @jsx h */
import { h } from 'preact';
export default function UnorderedListIcon() {
return (
<svg className="input__toolbar-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
<path
fill-rule="evenodd"
d="M2 13c0 .59 0 1-.59 1H.59C0 14 0 13.59 0 13c0-.59 0-1 .59-1h.81c.59 0 .59.41.59 1H2zm2.59-9h6.81c.59 0 .59-.41.59-1 0-.59 0-1-.59-1H4.59C4 2 4 2.41 4 3c0 .59 0 1 .59 1zM1.41 7H.59C0 7 0 7.41 0 8c0 .59 0 1 .59 1h.81c.59 0 .59-.41.59-1 0-.59 0-1-.59-1h.01zm0-5H.59C0 2 0 2.41 0 3c0 .59 0 1 .59 1h.81c.59 0 .59-.41.59-1 0-.59 0-1-.59-1h.01zm10 5H4.59C4 7 4 7.41 4 8c0 .59 0 1 .59 1h6.81c.59 0 .59-.41.59-1 0-.59 0-1-.59-1h.01zm0 5H4.59C4 12 4 12.41 4 13c0 .59 0 1 .59 1h6.81c.59 0 .59-.41.59-1 0-.59 0-1-.59-1h.01z"
/>
</svg>
);
}
@@ -0,0 +1,63 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
import '@github/markdown-toolbar-element';
import BoldIcon from './markdown-toolbar-icons/bold-icon';
import HeaderIcon from './markdown-toolbar-icons/header-icon';
import ItalicIcon from './markdown-toolbar-icons/italic-icon';
import QuoteIcon from './markdown-toolbar-icons/quote-icon';
import CodeIcon from './markdown-toolbar-icons/code-icon';
import LinkIcon from './markdown-toolbar-icons/link-icon';
import UnorderedListIcon from './markdown-toolbar-icons/unordered-list-icon';
import OrderedListIcon from './markdown-toolbar-icons/ordered-list-icon';
interface Props {
textareaId: string;
}
const boldLabel = 'Add bold text <cmd-b>';
const headerLabel = 'Add header text';
const italicLabel = 'Add italic text <cmd-i>';
const quoteLabel = 'Insert a quote';
const codeLabel = 'Insert a code';
const linkLabel = 'Add a link <cmd-k>';
const unorderedListLabel = 'Add a bulleted list';
const orderedListLabel = 'Add a numbered list';
export default class MarkdownToolbar extends Component<Props> {
render(props: RenderableProps<Props>) {
return (
<markdown-toolbar className="input__toolbar" for={props.textareaId}>
<div className="input__toolbar-group">
<md-header className="input__toolbar-item" title={headerLabel} aria-label={headerLabel}>
<HeaderIcon />
</md-header>
<md-bold className="input__toolbar-item" title={boldLabel} aria-label={boldLabel}>
<BoldIcon />
</md-bold>
<md-italic className="input__toolbar-item" title={italicLabel} aria-label={italicLabel}>
<ItalicIcon />
</md-italic>
</div>
<div className="input__toolbar-group">
<md-quote className="input__toolbar-item" title={quoteLabel} aria-label={quoteLabel}>
<QuoteIcon />
</md-quote>
<md-code className="input__toolbar-item" title={codeLabel} aria-label={codeLabel}>
<CodeIcon />
</md-code>
<md-link className="input__toolbar-item" title={linkLabel} aria-label={linkLabel}>
<LinkIcon />
</md-link>
</div>
<div className="input__toolbar-group">
<md-unordered-list className="input__toolbar-item" title={unorderedListLabel} aria-label={unorderedListLabel}>
<UnorderedListIcon />
</md-unordered-list>
<md-ordered-list className="input__toolbar-item" title={orderedListLabel} aria-label={orderedListLabel}>
<OrderedListIcon />
</md-ordered-list>
</div>
</markdown-toolbar>
);
}
}
+2
View File
@@ -8,6 +8,7 @@ require('./__button/input__button.scss');
require('./__button/_type/_preview/input__button_type_preview.scss');
require('./__button/_type/_send/input__button_type_send.scss');
require('./__control-panel/input__control-panel.scss');
require('./__counter/input__counter.scss');
require('./__error/input__error.scss');
require('./__field/input__field.scss');
@@ -18,6 +19,7 @@ require('./__rss/input__rss.scss');
require('./__rss-link/input__rss-link.scss');
require('./__markdown/input__markdown.scss');
require('./__markdown-link/input__markdown-link.scss');
require('./__markdown-toolbar/input__markdown-toolbar.scss');
require('./_theme/_dark/input_theme_dark.scss');
require('./_theme/_light/input_theme_light.scss');
+1
View File
@@ -11,4 +11,5 @@ module.exports = {
'\\.scss$': '<rootDir>/app/testUtils/mockStyles.js',
'@app/(.*)': '<rootDir>/app/$1',
},
setupFilesAfterEnv: ['<rootDir>/setup-jest-env.js'],
};
+36 -30
View File
@@ -1188,6 +1188,11 @@
"minimist": "^1.2.0"
}
},
"@github/markdown-toolbar-element": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-0.2.0.tgz",
"integrity": "sha512-PMb2OiJKmrte/JbOUnhqAh1fTUBdIi0DskpTmSo+Jg/HaBahjtKUlhxe0+sH33Dxxvg9OQLeFXuB6XL3F9Gtuw=="
},
"@jest/console": {
"version": "24.7.1",
"resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz",
@@ -1717,6 +1722,11 @@
"@xtuc/long": "4.2.2"
}
},
"@webcomponents/custom-elements": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.2.2.tgz",
"integrity": "sha512-9lckVAFpDEma94Ass/DHXY5ZoLZI5p+ambJQnra40npWIq+SQynvUHsJjLVmYartb4lZH9u+HLgfiAEnQih10Q=="
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -3741,6 +3751,15 @@
"esutils": "^2.0.2"
}
},
"document-register-element": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/document-register-element/-/document-register-element-1.13.1.tgz",
"integrity": "sha512-92ZyLDKg9j4rOll//NNXj25f+8rAzOkYsGJonhugKwXfeqH7bzs8Ucpvey0WzZ2ZzKdrvW9RnUw3UyOZ/uhBFw==",
"dev": true,
"requires": {
"lightercollective": "^0.1.0"
}
},
"dom-converter": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@@ -4835,8 +4854,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@@ -4857,14 +4875,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4879,20 +4895,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5009,8 +5022,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -5022,7 +5034,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5037,7 +5048,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5045,14 +5055,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -5071,7 +5079,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5152,8 +5159,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -5165,7 +5171,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -5251,8 +5256,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5288,7 +5292,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5308,7 +5311,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5352,14 +5354,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -7183,6 +7183,12 @@
"type-check": "~0.3.2"
}
},
"lightercollective": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lightercollective/-/lightercollective-0.1.0.tgz",
"integrity": "sha512-J9tg5uraYoQKaWbmrzDDexbG6hHnMcWS1qLYgJSWE+mpA3U5OCSeMUhb+K55otgZJ34oFdR0ECvdIb3xuO5JOQ==",
"dev": true
},
"lint-staged": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.5.tgz",
+3
View File
@@ -47,6 +47,7 @@
"clean-webpack-plugin": "^2.0.1",
"copy-webpack-plugin": "^5.0.2",
"css-loader": "^2.1.1",
"document-register-element": "^1.13.1",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
@@ -80,6 +81,8 @@
"whatwg-fetch": "^3.0.0"
},
"dependencies": {
"@github/markdown-toolbar-element": "^0.2.0",
"@webcomponents/custom-elements": "^1.2.2",
"bem-react-helper": "^1.1.2",
"core-js": "^3.0.1",
"focus-visible": "^4.1.5",
+1
View File
@@ -0,0 +1 @@
require('document-register-element/pony')(window);
+26 -2
View File
@@ -62,8 +62,32 @@ module.exports = () => ({
rules: [
{
test: /\.js(x?)$/,
exclude: /node_modules/,
use: 'babel-loader',
oneOf: [
{
include: /node_modules\/@github\/markdown-toolbar-element/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 1%', 'android >= 4.4.4', 'ios >= 9', 'IE >= 11'],
},
useBuiltIns: 'usage',
corejs: 3,
},
],
],
},
},
},
{
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
{
test: /\.ts(x?)$/,