Commit f9fb3ea1 by Рамис

add highlight and text color

parent 0d6b4cc0
......@@ -7,7 +7,7 @@ import 'react-ag-qeditor/dist/index.css'
const App = () => {
return <div style={{padding:40}}>
<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)=>{
console.log('sads', value);
}}
......
{
"name": "react-ag-qeditor",
"version": "1.0.5",
"version": "1.0.6",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
......@@ -55,6 +55,8 @@
],
"dependencies": {
"@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-link": "^2.0.0-beta.38",
"@tiptap/extension-table": "^2.0.0-beta.49",
......@@ -62,12 +64,13 @@
"@tiptap/extension-table-header": "^2.0.0-beta.22",
"@tiptap/extension-table-row": "^2.0.0-beta.19",
"@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/react": "^2.0.0-beta.109",
"@tiptap/starter-kit": "^2.0.0-beta.185",
"katex": "^0.15.3",
"rc-upload": "^4.3.3",
"react": "^17.0.2",
"react": "^16.0.0",
"sass": "^1.49.9"
}
}
......@@ -14,6 +14,9 @@ import TableHeader from '@tiptap/extension-table-header'
import Link from '@tiptap/extension-link'
import Image from '@tiptap/extension-image'
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 EditorModal from "./components/EditorModal"
......@@ -21,7 +24,7 @@ import Uploader from "./components/Uploader"
import Video from './extensions/Video'
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 [innerModalType, setInnerModalType] = useState(null);
......@@ -31,12 +34,20 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [modalTitle, setModalTitle] = useState('');
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) => {
setModalTitle(title);
setInnerModalType(type);
setModalIsOpen(true);
}
const colors = ['#000000', '#666668', '#9B9B9B', '#CCCCCC', '#5CAB4D', '#07A386', '#0BA6FE', '#003CA6', '#8B4AA8', '#F22884', '#B93E2F', '#CC5602', '#F59C12', '#FDDA02'];
const toolsLib = {
link: {
......@@ -194,7 +205,17 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
toggleHeaderCell: {
title: 'Добавить/удалить заголовок',
onClick: () => editor.chain().focus().toggleHeaderCell().run()
}
},
colorText: {
title: 'Цвет текста',
onClick: () => setColorsSelected('color')
},
highlight: {
title: 'Цвет фона',
onClick: () => setColorsSelected('highlight')
},
}
const editor = useEditor({
......@@ -222,6 +243,13 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
defaultAlignment: 'left',
types: ['heading', 'paragraph'],
alignments: ['left', 'center', 'right', 'justify'],
}),
TextStyle,
Color.configure({
types: ['textStyle'],
}),
Highlight.configure({
multicolor: true
})
],
content: value,
......@@ -297,6 +325,19 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
return (
<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:
return <div>Пусто</div>
}
......@@ -356,7 +397,7 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
<BubbleMenu editor={editor} shouldShow={({...o}) => {
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;
}
......@@ -400,22 +441,29 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
return true;
}
}} tippyOptions={{ duration: 100 }}>
<div className="atma-editor-bubble">
<div className={"atma-editor-bubble"}>
{
bubbleItems.map((type, i) => {
if(type === '|'){
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 }
/>
)
}
})
colorsSelected !== null ?
colors.map((itemColor, i) => {
return ( <div className={'qcolors'} style={{ background: itemColor}} onMouseDown={()=>{
colorsSelected === 'color' ?
editor.chain().focus().setColor(itemColor).run() : editor.chain().focus().toggleHighlight({ color: itemColor }).run();
setColorsSelected(null);
}} /> )
}) : bubbleItems.map((type, i) => {
if(type === '|'){
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>
</BubbleMenu>
......@@ -429,97 +477,112 @@ const QEditor = ({ value, onChange = ()=>{}, style, uploadOptions }) => {
isOpen={modalIsOpen}
title={modalTitle}
>
{ getInnerModal() }
{buildActionsModal([
{
title: 'Отмена',
className: ' atma-editor-cancel',
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;
}
{
getInnerModal()
}
{
buildActionsModal(innerModalType === 'color' || 'highlight' ? [] : [
{
title: 'Отмена',
className: ' atma-editor-cancel',
onClick: () => {
setUploaderUid(`uid${new Date()}`);
setUploadedPaths([]);
setModalIsOpen(false)
}
try {
switch (innerModalType) {
case 'image':
uploadedPaths.map((file, i) => {
editor.chain().focus().setImage({ src: file.path }).run();
});
break
case 'video':
uploadedPaths.map((file, i) => {
editor.chain().focus().setVideo({ src: file.path, poster: file.path + '.jpg' }).run();
});
break
case 'iframe':
let _url = embedContent;
let reg = /(http|https):\/\/([\w.]+\/?)\S*/;
const url = new URL(reg.test(_url) ? _url : 'https:' + _url);
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');
},
{
title: 'Вставить',
className: ' atma-editor-complete',
onClick: () => {
if(document.querySelectorAll('.atma-editor-uploader-progress').length > 0){
if(!confirm('Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?')){
return false;
}
}
try {
switch (innerModalType) {
// case 'color':
// if(colorsTabsActive === 'highlight'){
// editor.chain().focus().toggleHighlight({ color: colorsSelected }).run();
// }else{
// editor.chain().focus().unsetHighlight({ color: colorsSelected }).run();
// editor.commands.setColor(colorsSelected);
// }
// setColorsTabsActive('color');
// setColorsSelected('');
// setModalTitle('');
// setModalIsOpen(false);
// return;
case 'image':
uploadedPaths.map((file, i) => {
editor.chain().focus().setImage({ src: file.path }).run();
});
break
case 'video':
uploadedPaths.map((file, i) => {
editor.chain().focus().setVideo({ src: file.path, poster: file.path + '.jpg' }).run();
});
break
case 'iframe':
let _url = embedContent;
let reg = /(http|https):\/\/([\w.]+\/?)\S*/;
const url = new URL(reg.test(_url) ? _url : 'https:' + _url);
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);
break
}
console.log(_url);
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()}`);
setEmbedContent('');
setUploadedPaths([]);
setModalTitle('');
setModalIsOpen(false);
}catch (err){
console.log(err)
}
},
disabled: isDisabledAction()
}
])
setUploaderUid(`uid${new Date()}`);
setEmbedContent('');
setUploadedPaths([]);
setModalTitle('');
setModalIsOpen(false);
}catch (err){
console.log(err)
}
},
disabled: isDisabledAction()
}
])
}
</EditorModal>
</div>
......
......@@ -76,7 +76,12 @@ const ToolBar = ({ editor, toolsLib = [] }) => {
return (
<div
key={ idx }
onClick={ item.onClick }
onClick={ (e) => {
item.onClick();
e.preventDefault();
e.stopPropagation()
return false;
} }
className={ `qicon q${type}` + (editor.isActive(type) ? ' active' : '') }
title={item.title}
/>
......
......@@ -167,11 +167,13 @@ body{
opacity: 0.8;
}
}
}
.qcolors{
display: inline-block;
width: 20px;
height: 20px;
}
}
&-modal{
position: fixed;
......@@ -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{
&.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');
}
&.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{
......
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