Commit f9fb3ea1 by Рамис

add highlight and text color

parent 0d6b4cc0
...@@ -7,7 +7,7 @@ import 'react-ag-qeditor/dist/index.css' ...@@ -7,7 +7,7 @@ import 'react-ag-qeditor/dist/index.css'
const App = () => { const App = () => {
return <div style={{padding:40}}> return <div style={{padding:40}}>
<QEditor <QEditor
value={'<table width="100%"><tr><td width="50%"><img src="https://picsum.photos/200" width="100" /></td><td width="50%"><img src="https://picsum.photos/200" width="100" /></td></tr></table><p><span style="font-size: 40px;"><strong>Наш принцип №4 - Непрерывно улучшать процессы</strong></span></p><p>Как мы понимаем, что мы выполняем наш принцип №4 Непрерывно улучшать процессы!</p><ul><li>Мы следим за современными тенденциями рынка и идём в ногу со временем</li><li>Мы изобретаем новые эффективные способы решения задач</li><li>Мы приветствуем и поощряем инициативу каждого сотрудника компании</li><li>Мы устраняем проблемы и ошибки в процессах</li><li>Мы используем непрерывный цикл Планируй-Делай-Контролируй-Корректируй для улучшения наших процессов</li><li>Мы измеряем результаты нашей работы. Что мы не измеряем, тем мы не управляем</li><li>Мы замечаем проблемы и ошибки в процессах и говорим о них</li></ul>'} value={'<table><tbody><tr><td colspan="1" rowspan="1"><p><img src="https://picsum.photos/200"></p></td><td colspan="1" rowspan="1"><p><img src="https://picsum.photos/200"></p></td></tr></tbody></table><p><strong><span>Наш принцип №4 - Непрерывно улучшать процессы</span></strong></p><p>Как мы понимаем, что мы выполняем наш принцип №4 Непрерывно улучшать процессы!</p><ul><li><p><span style="color: rgb(255, 78, 68)">Мы следим за современными</span> тенденциями рынка и идём в ногу со временем</p></li><li><p>Мы изобретаем новые эффективные способы решения задач</p></li><li><p>Мы приветствуем и поощряем инициативу каждого сотрудника компании</p></li><li><p>Мы устраняем проблемы и ошибки в процессах</p></li><li><p>Мы используем непрерывный цикл <mark style="background-color: #B93E2F;">Планируй-Делай-Контролируй-Корректируй</mark> для <span>улучшения</span> наших процессов</p></li><li><p>Мы измеряем результаты нашей работы. Что мы не измеряем, тем мы не управляем</p></li><li><p><mark style="background-color: tomato;">Мы замечаем проблемы и ошибки</mark> в процессах и говорим о нихd</p></li></ul>'}
onChange={(value)=>{ onChange={(value)=>{
console.log('sads', value); console.log('sads', value);
}} }}
......
{ {
"name": "react-ag-qeditor", "name": "react-ag-qeditor",
"version": "1.0.5", "version": "1.0.6",
"description": "WYSIWYG html editor", "description": "WYSIWYG html editor",
"author": "atma", "author": "atma",
"license": "MIT", "license": "MIT",
...@@ -55,6 +55,8 @@ ...@@ -55,6 +55,8 @@
], ],
"dependencies": { "dependencies": {
"@tiptap/core": "^2.0.0-beta.176", "@tiptap/core": "^2.0.0-beta.176",
"@tiptap/extension-color": "^2.0.0-beta.9",
"@tiptap/extension-highlight": "^2.0.0-beta.33",
"@tiptap/extension-image": "^2.0.0-beta.27", "@tiptap/extension-image": "^2.0.0-beta.27",
"@tiptap/extension-link": "^2.0.0-beta.38", "@tiptap/extension-link": "^2.0.0-beta.38",
"@tiptap/extension-table": "^2.0.0-beta.49", "@tiptap/extension-table": "^2.0.0-beta.49",
...@@ -62,12 +64,13 @@ ...@@ -62,12 +64,13 @@
"@tiptap/extension-table-header": "^2.0.0-beta.22", "@tiptap/extension-table-header": "^2.0.0-beta.22",
"@tiptap/extension-table-row": "^2.0.0-beta.19", "@tiptap/extension-table-row": "^2.0.0-beta.19",
"@tiptap/extension-text-align": "^2.0.0-beta.29", "@tiptap/extension-text-align": "^2.0.0-beta.29",
"@tiptap/extension-text-style": "^2.0.0-beta.23",
"@tiptap/extension-underline": "^2.0.0-beta.23", "@tiptap/extension-underline": "^2.0.0-beta.23",
"@tiptap/react": "^2.0.0-beta.109", "@tiptap/react": "^2.0.0-beta.109",
"@tiptap/starter-kit": "^2.0.0-beta.185", "@tiptap/starter-kit": "^2.0.0-beta.185",
"katex": "^0.15.3", "katex": "^0.15.3",
"rc-upload": "^4.3.3", "rc-upload": "^4.3.3",
"react": "^17.0.2", "react": "^16.0.0",
"sass": "^1.49.9" "sass": "^1.49.9"
} }
} }
...@@ -14,6 +14,9 @@ import TableHeader from '@tiptap/extension-table-header' ...@@ -14,6 +14,9 @@ import TableHeader from '@tiptap/extension-table-header'
import Link from '@tiptap/extension-link' import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image' import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align'; import TextAlign from '@tiptap/extension-text-align';
import { Color } from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import TextStyle from '@tiptap/extension-text-style';
import ToolBar from "./components/ToolBar" import ToolBar from "./components/ToolBar"
import EditorModal from "./components/EditorModal" import EditorModal from "./components/EditorModal"
...@@ -21,7 +24,7 @@ import Uploader from "./components/Uploader" ...@@ -21,7 +24,7 @@ import Uploader from "./components/Uploader"
import Video from './extensions/Video' import Video from './extensions/Video'
import Iframe from './extensions/Iframe' import Iframe from './extensions/Iframe'
const initialBubbleItems = ['bold', 'italic', 'underline', 'strike']; const initialBubbleItems = ['bold', 'italic', 'underline', 'strike', '|', 'colorText', 'highlight'];
const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
const [innerModalType, setInnerModalType] = useState(null); const [innerModalType, setInnerModalType] = useState(null);
...@@ -31,12 +34,20 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -31,12 +34,20 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
const [modalIsOpen, setModalIsOpen] = useState(false); const [modalIsOpen, setModalIsOpen] = useState(false);
const [modalTitle, setModalTitle] = useState(''); const [modalTitle, setModalTitle] = useState('');
const [bubbleItems, setBubbleItems] = useState(initialBubbleItems); const [bubbleItems, setBubbleItems] = useState(initialBubbleItems);
const [colorsTabsActive, setColorsTabsActive] = useState('color'); //highlight || color
const [colorsSelected, setColorsSelected] = useState(null);
const getRgb = (hex) => {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)})` : null;
}
const modalOpener = (type, title) => { const modalOpener = (type, title) => {
setModalTitle(title); setModalTitle(title);
setInnerModalType(type); setInnerModalType(type);
setModalIsOpen(true); setModalIsOpen(true);
} }
const colors = ['#000000', '#666668', '#9B9B9B', '#CCCCCC', '#5CAB4D', '#07A386', '#0BA6FE', '#003CA6', '#8B4AA8', '#F22884', '#B93E2F', '#CC5602', '#F59C12', '#FDDA02'];
const toolsLib = { const toolsLib = {
link: { link: {
...@@ -194,7 +205,17 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -194,7 +205,17 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
toggleHeaderCell: { toggleHeaderCell: {
title: 'Добавить/удалить заголовок', title: 'Добавить/удалить заголовок',
onClick: () => editor.chain().focus().toggleHeaderCell().run() onClick: () => editor.chain().focus().toggleHeaderCell().run()
} },
colorText: {
title: 'Цвет текста',
onClick: () => setColorsSelected('color')
},
highlight: {
title: 'Цвет фона',
onClick: () => setColorsSelected('highlight')
},
} }
const editor = useEditor({ const editor = useEditor({
...@@ -222,6 +243,13 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -222,6 +243,13 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
defaultAlignment: 'left', defaultAlignment: 'left',
types: ['heading', 'paragraph'], types: ['heading', 'paragraph'],
alignments: ['left', 'center', 'right', 'justify'], alignments: ['left', 'center', 'right', 'justify'],
}),
TextStyle,
Color.configure({
types: ['textStyle'],
}),
Highlight.configure({
multicolor: true
}) })
], ],
content: value, content: value,
...@@ -297,6 +325,19 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -297,6 +325,19 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
return ( return (
<Fragment>{ getUploader({ accept: 'image/*' }) }</Fragment> <Fragment>{ getUploader({ accept: 'image/*' }) }</Fragment>
) )
case 'color':
case 'highlight':
return (
<div className={"atma-editor-select-colors"}>
<ul className={"colors-list"}>
{
colors.map((itemColor, i)=>(
<li onClick={() => {}} style={{ backgroundColor: itemColor }} />
))
}
</ul>
</div>
)
default: default:
return <div>Пусто</div> return <div>Пусто</div>
} }
...@@ -356,7 +397,7 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -356,7 +397,7 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
<BubbleMenu editor={editor} shouldShow={({...o}) => { <BubbleMenu editor={editor} shouldShow={({...o}) => {
let items = []; let items = [];
if(o.from !== o.to && editor.isActive('paragraph') && editor.isActive('image') === false){ if(o.from !== o.to && editor.isActive('paragraph') && editor.isActive('image') === false && document.querySelectorAll('.selectedCell').length === 0){
items = initialBubbleItems; items = initialBubbleItems;
} }
...@@ -400,22 +441,29 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -400,22 +441,29 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
return true; return true;
} }
}} tippyOptions={{ duration: 100 }}> }} tippyOptions={{ duration: 100 }}>
<div className="atma-editor-bubble"> <div className={"atma-editor-bubble"}>
{ {
bubbleItems.map((type, i) => { colorsSelected !== null ?
if(type === '|'){ colors.map((itemColor, i) => {
return ( <div className={'qseparator'} /> ) return ( <div className={'qcolors'} style={{ background: itemColor}} onMouseDown={()=>{
}else{ colorsSelected === 'color' ?
return ( editor.chain().focus().setColor(itemColor).run() : editor.chain().focus().toggleHighlight({ color: itemColor }).run();
<div setColorsSelected(null);
key={ 'bubbleItems' + i } }} /> )
className={'qicon q' + type + (editor.isActive(type) ? ' active' : '')} }) : bubbleItems.map((type, i) => {
title={ toolsLib[type] ? toolsLib[type].title : '' } if(type === '|'){
onClick={ toolsLib[type].onClick } return ( <div className={'qseparator'} /> )
/> }else{
) return (
} <div
}) key={ 'bubbleItems' + i }
className={'qicon q' + type + (editor.isActive(type) ? ' active' : '')}
title={ toolsLib[type] ? toolsLib[type].title : '' }
onClick={ toolsLib[type].onClick }
/>
)
}
})
} }
</div> </div>
</BubbleMenu> </BubbleMenu>
...@@ -429,97 +477,112 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => { ...@@ -429,97 +477,112 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
isOpen={modalIsOpen} isOpen={modalIsOpen}
title={modalTitle} title={modalTitle}
> >
{ getInnerModal() } {
{buildActionsModal([ getInnerModal()
{ }
title: 'Отмена', {
className: ' atma-editor-cancel', buildActionsModal(innerModalType === 'color' || 'highlight' ? [] : [
onClick: () => { {
setUploaderUid(`uid${new Date()}`); title: 'Отмена',
setUploadedPaths([]); className: ' atma-editor-cancel',
setModalIsOpen(false) onClick: () => {
} setUploaderUid(`uid${new Date()}`);
}, setUploadedPaths([]);
{ setModalIsOpen(false)
title: 'Вставить',
className: ' atma-editor-complete',
onClick: () => {
if(document.querySelectorAll('.atma-editor-uploader-progress').length > 0){
if(!confirm('Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?')){
return false;
}
} }
try { },
switch (innerModalType) { {
case 'image': title: 'Вставить',
uploadedPaths.map((file, i) => { className: ' atma-editor-complete',
editor.chain().focus().setImage({ src: file.path }).run(); onClick: () => {
}); if(document.querySelectorAll('.atma-editor-uploader-progress').length > 0){
break if(!confirm('Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?')){
case 'video': return false;
uploadedPaths.map((file, i) => { }
editor.chain().focus().setVideo({ src: file.path, poster: file.path + '.jpg' }).run(); }
}); try {
break switch (innerModalType) {
case 'iframe': // case 'color':
// if(colorsTabsActive === 'highlight'){
let _url = embedContent; // editor.chain().focus().toggleHighlight({ color: colorsSelected }).run();
let reg = /(http|https):\/\/([\w.]+\/?)\S*/; // }else{
// editor.chain().focus().unsetHighlight({ color: colorsSelected }).run();
// editor.commands.setColor(colorsSelected);
// }
const url = new URL(reg.test(_url) ? _url : 'https:' + _url); // setColorsTabsActive('color');
let urlId = url.pathname.replace(/\/$/ig, '').split('/').pop(); // setColorsSelected('');
// setModalTitle('');
console.log(url); // setModalIsOpen(false);
// return;
switch (url.hostname) { case 'image':
case 'rutube.ru': uploadedPaths.map((file, i) => {
case 'www.rutube.ru': editor.chain().focus().setImage({ src: file.path }).run();
_url = `https://rutube.ru/pl/?pl_id&pl_type&pl_video=${urlId}`; });
break break
case 'vimeo.com': case 'video':
_url = `https://player.vimeo.com/video/${urlId}`; uploadedPaths.map((file, i) => {
editor.chain().focus().setVideo({ src: file.path, poster: file.path + '.jpg' }).run();
break });
case 'ok.ru': break
case 'www.ok.ru': case 'iframe':
_url = `//ok.ru/videoembed/${urlId}`;
break let _url = embedContent;
case 'youtu.be': let reg = /(http|https):\/\/([\w.]+\/?)\S*/;
case 'youtube.com':
case 'www.youtube.com':
if (url.hostname.indexOf('youtu.be') === -1 && url.search !== '') {
if (url.searchParams.get('v')) { const url = new URL(reg.test(_url) ? _url : 'https:' + _url);
urlId = url.searchParams.get('v'); let urlId = url.pathname.replace(/\/$/ig, '').split('/').pop();
console.log(url);
switch (url.hostname) {
case 'rutube.ru':
case 'www.rutube.ru':
_url = `https://rutube.ru/pl/?pl_id&pl_type&pl_video=${urlId}`;
break
case 'vimeo.com':
_url = `https://player.vimeo.com/video/${urlId}`;
break
case 'ok.ru':
case 'www.ok.ru':
_url = `//ok.ru/videoembed/${urlId}`;
break
case 'youtu.be':
case 'youtube.com':
case 'www.youtube.com':
if (url.hostname.indexOf('youtu.be') === -1 && url.search !== '') {
if (url.searchParams.get('v')) {
urlId = url.searchParams.get('v');
}
} }
} _url = `https://www.youtube.com/embed/${urlId}`;
_url = `https://www.youtube.com/embed/${urlId}`;
console.log(_url); console.log(_url);
break break
} }
console.log(_url); console.log(_url);
editor.chain().focus().setIframe({ src: _url }).run(); editor.chain().focus().setIframe({ src: _url }).run();
break break
} }
setUploaderUid(`uid${new Date()}`); setUploaderUid(`uid${new Date()}`);
setEmbedContent(''); setEmbedContent('');
setUploadedPaths([]); setUploadedPaths([]);
setModalTitle(''); setModalTitle('');
setModalIsOpen(false); setModalIsOpen(false);
}catch (err){ }catch (err){
console.log(err) console.log(err)
} }
}, },
disabled: isDisabledAction() disabled: isDisabledAction()
} }
]) ])
} }
</EditorModal> </EditorModal>
</div> </div>
......
...@@ -76,7 +76,12 @@ const ToolBar = ({ editor, toolsLib = [] }) => { ...@@ -76,7 +76,12 @@ const ToolBar = ({ editor, toolsLib = [] }) => {
return ( return (
<div <div
key={ idx } key={ idx }
onClick={ item.onClick } onClick={ (e) => {
item.onClick();
e.preventDefault();
e.stopPropagation()
return false;
} }
className={ `qicon q${type}` + (editor.isActive(type) ? ' active' : '') } className={ `qicon q${type}` + (editor.isActive(type) ? ' active' : '') }
title={item.title} title={item.title}
/> />
......
...@@ -167,11 +167,13 @@ body{ ...@@ -167,11 +167,13 @@ body{
opacity: 0.8; opacity: 0.8;
} }
} }
}
.qcolors{
display: inline-block;
width: 20px;
height: 20px;
}
}
&-modal{ &-modal{
position: fixed; position: fixed;
...@@ -400,6 +402,74 @@ body{ ...@@ -400,6 +402,74 @@ body{
} }
} }
//&-select-colors{
// ul.colors-list{
// display: block;
// margin: 0;
// padding: 0;
// max-width: 200px;
// letter-spacing: 0;
// font-size: 0;
// position: relative;
// left: -5px;
//
// li{
// display: inline-block;
// vertical-align: top;
// height: 40px;
// width: 40px;
// margin: 10px;
// margin-left: 0;
// margin-bottom: 0;
// border-radius: 20px;
// list-style: none;
// letter-spacing: 0;
// font-size: 0;
//
// &.active, &:hover{
// transform: scale(1.2);
// cursor: pointer;
// }
// }
// }
//
// .colors-tabs{
// font-size: 21px;
// font-weight: bold;
// margin-bottom: 35px;
// text-align: left;
//
// & > div{
// display: inline-block;
// margin-right: 20px;
// opacity: 0.3;
//
// &:hover{
// opacity: 0.6;
// cursor: pointer;
// }
//
// &.active{
// position: relative;
// opacity: 1;
// padding-bottom: 10px;
//
// &:after{
// content: "";
// display: inline-block;
// position: absolute;
// bottom: 0;
// height: 3px;
// width: 100%;
// left: 0;
// background-color: #1790FF;
// }
// }
// }
//
// }
//}
...@@ -507,6 +577,12 @@ body{ ...@@ -507,6 +577,12 @@ body{
&.qalignRight{ &.qalignRight{
background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2214%22%20height%3D%2211%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20stroke%3D%22%231D1D1F%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20d%3D%22M.7%201.559h12M7.4%205.559h5.3M.7%209.559h12%22%2F%3E%3C%2Fsvg%3E'); background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2214%22%20height%3D%2211%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20stroke%3D%22%231D1D1F%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20d%3D%22M.7%201.559h12M7.4%205.559h5.3M.7%209.559h12%22%2F%3E%3C%2Fsvg%3E');
} }
&.qcolorText{
background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2211%22%20height%3D%2214%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M1.274%2013.123c.396%200%20.624-.185.774-.606L3.164%209.37h5.019l1.125%203.147c.149.421.378.606.773.606.457%200%20.791-.29.791-.694%200-.115-.026-.246-.097-.44L6.688.95c-.193-.518-.51-.756-.993-.756-.518%200-.843.246-1.037.765L.58%2011.989c-.07.194-.097.325-.097.44%200%20.404.334.694.791.694zm2.347-5.098%202.03-5.783h.053l2.03%205.783H3.621z%22%20fill%3D%22%231F1E1D%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E');
}
&.qhighlight{
background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2217%22%20height%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20transform%3D%22translate%28.136%20.136%29%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20stroke%3D%22%231D1D1F%22%20stroke-width%3D%221.2%22%20transform%3D%22rotate%2845%206.364%206.364%29%22%20x%3D%222.464%22%20y%3D%222.464%22%20width%3D%227.8%22%20height%3D%227.8%22%20rx%3D%222%22%2F%3E%3Cpath%20stroke%3D%22%231D1D1F%22%20stroke-width%3D%221.2%22%20d%3D%22M1.52%206.364h9.714%22%2F%3E%3Cpath%20d%3D%22M13.34%2015.063c1.665%200%202.774-1.086%202.774-2.71%200-.794-.316-1.61-.598-2.216-.414-.91-1.09-1.898-1.68-2.8-.136-.211-.308-.317-.495-.317-.192%200-.364.106-.5.317-.594.91-1.278%201.91-1.692%202.832-.273.59-.586%201.398-.586%202.183%200%201.625%201.11%202.711%202.778%202.711z%22%20fill%3D%22%231F1E1D%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E');
}
} }
.qseparator{ .qseparator{
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment