Commit a67a4dc7 by Рамис

Initial commit

parent f0704801
......@@ -3,7 +3,7 @@ root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JSCodeStyleSettings version="0">
<option name="REFORMAT_C_STYLE_COMMENTS" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="INDENT_CHAINED_CALLS" value="false" />
<option name="CHAINED_CALL_DOT_ON_NEW_LINE" value="false" />
<option name="SPACE_BEFORE_UNARY_NOT" value="true" />
<option name="SPACE_AFTER_UNARY_NOT" value="true" />
</JSCodeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="BLOCK_COMMENT_ADD_SPACE" value="true" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
</codeStyleSettings>
<codeStyleSettings language="SASS">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="SCSS">
<indentOptions>
<option name="INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>
\ No newline at end of file
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/react-ag-qeditor.iml" filepath="$PROJECT_DIR$/.idea/react-ag-qeditor.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
import React from 'react'
import { ExampleComponent } from 'react-ag-qeditor'
import QEditor from 'react-ag-qeditor'
import 'react-ag-qeditor/dist/index.css'
const App = () => {
return <ExampleComponent text="Create React Library Example 😄" />
return <div style={{padding:40}}>
<QEditor />
</div>
}
export default App
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "react-ag-qeditor",
"version": "1.0.0",
"description": "Made with create-react-library",
"author": "atma",
"license": "MIT",
"repository": "atma/react-ag-qeditor",
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.js",
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl --no-compress --format modern,cjs",
"start": "microbundle-crl watch --no-compress --format modern,cjs",
"prepare": "run-s build",
"test": "run-s test:unit test:lint test:build",
"test:build": "run-s build",
"test:lint": "eslint .",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && npm install && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": "^16.0.0"
},
"devDependencies": {
"microbundle-crl": "^0.13.10",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1"
},
"files": [
"dist"
]
"name": "react-ag-qeditor",
"version": "0.0.1",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
"repository": "atma/react-ag-qeditor",
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.js",
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl --no-compress --format modern,cjs --css-modules false",
"start": "microbundle-crl watch --no-compress --format modern,cjs ",
"prepare": "run-s build",
"test": "run-s test:unit test:lint test:build",
"test:build": "run-s build",
"test:lint": "eslint .",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && npm install && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": "^16.0.0",
"katex": "^0.15.3",
"rc-upload": "^4.3.3",
"react-quill": "^1.3.5",
"sass": "^1.49.9"
},
"devDependencies": {
"microbundle-crl": "^0.13.10",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1"
},
"files": [
"dist"
]
}
import React, { Fragment } from 'react'
import ReactQuill, { Quill } from 'react-quill';
import './index.scss';
import 'react-quill/dist/quill.snow.css';
import VideoBlot from "./blots/VideoBlot";
import VideoLocalBlot from "./blots/VideoLocalBlot";
import EditorModal from "./components/EditorModal";
import Uploader from "./components/Uploader";
import "katex/dist/katex.min.css";
import katex from "katex";
export default class QEditor extends React.Component {
constructor (props) {
super(props)
this.state = {
init: false,
modalIsOpen: false,
innerModalType: null,
quillRef: null,
uploadedPaths: [],
uploaderUid: 'uid' + new Date(),
embedContent: ''
}
Quill.register('formats/video', VideoBlot)
Quill.register('formats/videoLocal', VideoLocalBlot)
}
componentDidMount () {
this.setState({
init: true
})
}
modules = {
toolbar: {
container: [
[{header: [2, 3, 4, false]}],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[
{list: 'ordered'},
{list: 'bullet'},
{indent: '-1'},
{indent: '+1'}
],
[{align: []}, {color: []}, {background: []}],
['link', 'image', 'video', 'videoLocal'],
['formula'],
['clean']
],
handlers: {
video: (a) => {
this.setState(
{
innerModalType: 'video'
},
() => this.editorModal.show({title: 'Видео по ссылке'})
)
},
videoLocal: () => {
this.setState({
innerModalType: 'videoLocal'
}, () => this.editorModal.show({title: 'Загрузить видео'}));
},
image: () => {
this.setState({
innerModalType: 'image'
}, () => this.editorModal.show({title: 'Загрузить изображение'}));
}
}
},
}
formats = [
'header',
'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet', 'indent',
'align',
'link', 'image', 'video', 'color', 'background', 'videoLocal',
'formula'/*'imageHandler',*/
]
buildActionsModal (buttons = []) {
if (buttons.length === 0) {
return null;
}
return (
<div className={'atma-editor-modal-action'}>
{
buttons.map((btn, i) => (
<button key={'mAction' + i} className={'atma-editor-btn' + btn.className}
onClick={btn.onClick}>{btn.title}</button>
))
}
</div>
)
}
getInnerModal () {
switch (this.state.innerModalType) {
case 'video':
return (
<Fragment>
<input type="text" value={this.state.embedContent} placeholder={'https://'} onInput={(e) => {
this.setState({
embedContent: e.target.value
})
}}/>
<ul className={'atma-editor-soc-video'}>
<li className={'youtube'}/>
<li className={'vimeo'}/>
<li className={'vk'}/>
<li className={'ok'}/>
</ul>
</Fragment>
)
case 'videoLocal':
return (
<Fragment>
<Uploader
key={this.state.uploaderUid}
accept={'video/*'}
action={'https://cdn.atmaguru.online/upload/?sid=test&md5=UN8SUwWR8m5XUm9v8uDtew&expires=1652081782'}
onSuccess={(file) => {
let uploadedPaths = this.state.uploadedPaths;
uploadedPaths.push(file);
this.setState({uploadedPaths});
}}
multiple={true}
/>
</Fragment>
)
case 'image':
return (
<Fragment>
<Uploader
key={this.state.uploaderUid}
accept={'image/*'}
action={'https://cdn.atmaguru.online/upload/?sid=test&md5=UN8SUwWR8m5XUm9v8uDtew&expires=1652081782'}
onSuccess={(file) => {
let uploadedPaths = this.state.uploadedPaths;
uploadedPaths.push(file);
this.setState({uploadedPaths});
}}
multiple={true}
/>
</Fragment>
)
default:
return <div>Пусто</div>
}
}
quillInsert () {
const quill = this.quillRef.getEditor();
let range = quill.getSelection();
let index = range ? range.index : 0;
switch (this.state.innerModalType) {
case 'image':
case 'videoLocal':
this.state.uploadedPaths.map((file, i) => {
quill.insertEmbed(index, this.state.innerModalType, file.path, Quill.sources.USER);
quill.setSelection(index + 1);
});
break
default:
quill.insertEmbed(index, this.state.innerModalType, this.state.embedContent, Quill.sources.USER);
quill.setSelection(index + 1);
}
this.setState({
uploaderUid: `uid${new Date()}`,
embedContent: ''
}, () => this.editorModal.hide());
}
render () {
let {init, innerModalType, modalIsOpen} = this.state;
let {maxWidth, maxHeight, value} = this.props;
window.katex = katex;
if ( ! this.state.init) {
return null;
}
return (
<Fragment>
<div className="atma-editor-wrap" style={{maxWidth}}>
<div className={'atma-editor'}>
<ReactQuill
ref={ref => this.quillRef = ref}
defaultValue={value}
theme="snow"
onChange={(value) => {console.log(value)}}
modules={this.modules}
formats={this.formats}
onChangeSelection={(range, source, editor) => {
// console.log(range, source, editor.getText());
}}
/>
</div>
<EditorModal
isOpen={modalIsOpen}
ref={ref => this.editorModal = ref}
>
{this.getInnerModal()}
{this.buildActionsModal([
{
title: 'Отмена',
className: ' atma-editor-cancel',
onClick: () => {
this.setState(
{
uploaderUid: `uid${new Date()}`
},
() => this.editorModal.hide()
)
}
},
{
title: 'Вставить',
className: ' atma-editor-complete',
onClick: () => this.quillInsert()
}
])
}
</EditorModal>
</div>
</Fragment>
)
}
}
QEditor.defaultProps = {
maxWidth: 'auto',
maxHeight: 1000
}
import { Quill } from 'react-quill'
const Video = Quill.import('formats/video')
export default class VideoBlot extends Video {
static tagName = 'iframe'
static blotName = 'video';
static create (value) {
let type = '';
let node = null;
const url = new URL(value);
let id = url.pathname.replace(/\/$/ig, '').split('/').pop();
switch (url.hostname){
case 'rutube.ru':
case 'www.rutube.ru':
value = `//rutube.ru/play/embed/${id}`;
break
case 'ok.ru':
case 'www.ok.ru':
value = `//ok.ru/videoembed/${id}`;
break
case 'youtu.be':
case 'youtube.com':
case 'www.youtube.com':
if(url.hostname.indexOf('youtu.be') === -1 && url.search !== ''){
id = url.searchParams.get('v');
}
value = `https://www.youtube.com/embed/${id}`;
// console.log(id);
break
}
node = super.create(value);
node.setAttribute('style', 'width:1280px;height:auto;aspect-ratio: 16 / 9;');
return node;
}
}
import { Quill } from 'react-quill';
const BlockEmbed = Quill.import('blots/block/embed');
export default class VideoLocalBlot extends BlockEmbed {
static tagName = 'video';
static blotName = 'videoLocal';
static create (value){
console.log(value);
let node = super.create(value);
node.setAttribute('class', 'ql-video');
node.setAttribute('src', value);
node.setAttribute('controls', true);
return node;
}
static value(node){
return node.getAttribute('src');
}
}
import React from "react";
export default class EditorModal extends React.Component {
constructor (props) {
super(props);
this.state = {
isShow: false,
title: ''
}
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
}
show = ({ title }) => {
// this.resolve = resolve;
// this.reject = reject;
this.setState({
isShow: true,
title
})
}
hide(){
// this.setState({ isShow: false }, this.reject('ab'));
this.setState({ isShow: false });
}
render () {
return (
<div className="atma-editor-modal" style={{ display: this.state.isShow ? 'flex' : 'none' }}>
<div className="atma-editor-modal-box">
{
this.state.title &&
<div className="atma-editor-modal-box-header">{ this.state.title }</div>
}
{/*<div className={'atma-editor-modal-close'} onClick={ this.hide }>×</div>*/}
{ this.props.children && this.props.children }
</div>
</div>
)
}
}
EditorModal.defaultProps = {
title: null
};
import React, { Fragment } from "react";
import Upload from "rc-upload";
class Item extends React.Component {
get src () {
return this.props.src;
}
render () {
switch (this.src) {
default:
return null;
}
}
}
Item.defaultProps = {
src: null
};
export default class Uploader extends React.Component {
constructor (props) {
super(props);
this.state = {
disabledFileUpload: false,
files: {
/* 'first': {
uid: 'first',
percent: 10,
uploaded: false
},
'second': {
uid: 'second',
percent: 80
}*/
},
dropFiles: [],
progress: 0,
isUpload: false,
isSuccess: false,
}
this.files = {};
}
componentDidUpdate (prevProps, prevState, snapshot) {
console.log(this.state.files);
}
get action () {
return this.props.action;
}
get multiple () {
return this.props.multiple;
}
get accept () {
return this.props.accept;
}
get type () {
return this.props.type;
}
get onSuccess () {
return this.props.onSuccess;
}
mediaItem(){
}
render () {
let {disabledFileUpload, isUpload, progress, dropFiles} = this.state;
return (
<Fragment>
<div className={'atma-editor-uploader-uitems'}>
{
Object.keys(this.state.files).map((uid, i) => (
<div
className={ 'atma-editor-uploader-uitems-elem' }
style={{ backgroundImage: `url(${this.state.files[uid].path})` }}
>
<div className={'atma-editor-uploader-uitems-del'}></div>
{
this.state.files[uid].uploaded !== true ? (
<div className={ 'atma-editor-uploader-progress-wrap' }>
<div className={ 'atma-editor-uploader-progress' }>
<div style={{ width: this.state.files[uid].percent + '%' }} />
</div>
</div>
) : null
}
</div>
))
}
</div>
<Upload
accept={this.accept}
action={this.action}
multiple={this.multiple}
disabled={disabledFileUpload}
beforeUpload={(a, files) => {
this.files[a.uid] = {...a, percent: 0, uploaded: false};
this.setState({
isUpload: true,
files: this.files
});
}}
onStart={(a) => {
this.setState({disabledFileUpload: true, isUpload: true});
console.log('start');
}}
onSuccess={(resp, file) => {
if (resp.state === 'success' && resp.file_path) {
let curFile = this.files[file.uid];
curFile['uploaded'] = true;
curFile['path'] = resp.poster || resp.file_path;
this.setState({
uploaderSuccess: true,
isUpload: false,
disabledFileUpload: false
}, () => this.onSuccess({path: resp.file_path, uid: file.uid}))
}
}}
onProgress={(o, file) => {
if (this.files[file.uid]) {
this.files[file.uid].percent = parseInt(o.percent);
}
//
this.setState({
files: this.files
})
// console.log(o, file);
}}
component={'div'}
type="drag"
className={ 'atma-editor-uploader-drop' }
>
<div className={'atma-editor-uploader-placeholder'}>Нажмите или перетащите файлы сюда</div>
</Upload>
</Fragment>
)
}
}
Uploader.defaultProps = {
action: null,
multiple: false,
accept: '*',
type: '',
onSuccess: ()=>{}
};
import React from 'react'
import styles from './styles.module.css'
import QEditor from './QEditor';
export const ExampleComponent = ({ text }) => {
return <div className={styles.test}>Example Component: {text}</div>
}
export default QEditor;
This diff is collapsed. Click to expand it.
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