diff --git a/package.json b/package.json index c25b812..dea67a8 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "admin-lte": "^2.3.8", "bootstrap": "^3.3.7", "bootstrap-sass": "^3.3.7", + "draft-convert": "^1.4.8", "draft-js": "^0.10.1", "draft-js-export-html": "^0.5.4", "draft-js-utils": "^0.1.6", diff --git a/src/sass/layout/_draft.scss b/src/sass/layout/_draft.scss index 94e2de4..d6f347f 100644 --- a/src/sass/layout/_draft.scss +++ b/src/sass/layout/_draft.scss @@ -83,46 +83,9 @@ margin-bottom:10px; } - .modal{ - position: fixed; - top:0;left:0;right:0;bottom:0; - background-color: rgba(0,0,0,.05); - display: flex; - justify-content:center; - align-items:center; - z-index: 1; - } .upload-dialog{ font-size:20px; } - .dialog{ - background-color: white; - width:60%; - padding:15px; - position: relative; - color:#999; - max-width: 700px; - box-shadow: 0 10px 16px 0 rgba(0,0,0,.2),0 6px 20px 0 rgba(0,0,0,.19); - } - .dialog-title{ - margin:10px 10px 20px; - } - .dialog-close{ - position: absolute; - top:15px; - right:15px; - font-size: 21px; - font-weight: bold; - cursor:pointer; - - &:hover{ - color:#444; - } - } - .dialog-footer{ - margin:10px 0; - } - .image-list{ display: flex; flex-wrap:wrap; @@ -138,95 +101,111 @@ width:100%; min-height:60px; } - .edit-root { - background: #fff; - font-family: -apple-system,"Helvetica Neue",Arial,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; - font-size: 16px; - padding: 15px; - color:#333; - .edit-editor { - padding-bottom: 15px; + + + .edit-section { + padding: 10px 0; + } + +} +#page-affix{ + padding: 10px 0; + top: 50px; + z-index: 2; + background-color: rgba(255, 255, 255, 0.9) +} +@media screen and (max-width: 992px) { + #page-affix{ + top:0; + } +} +.edit-root { + background: #fff; + font-family: -apple-system,"Helvetica Neue",Arial,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; + font-size: 16px; + padding:0 15px; + color:#333; + .DraftEditor-editorContainer { + .public-DraftStyleDefault-block{ + line-height:1.7; + margin-bottom:20px; } - .DraftEditor-editorContainer { + h1 { + padding: 0; + margin:0; .public-DraftStyleDefault-block{ - line-height:1.7; - margin-bottom:20px; - } - h1 { - padding: 0; - margin:0; - .public-DraftStyleDefault-block{ - font-size: 24px; - font-weight: 400; - } - } - h2{ - font-weight: 500; - font-size: 18px; - padding: 0 15px; - text-align: justify; + font-size: 24px; + font-weight: 400; } - ol,ul { - li { - .public-DraftStyleDefault-block{ - margin:0; - } - &:before{ - top:3px; - } - } - } - blockquote { - border-left: 5px solid #eee; - padding: 0 20px; + } + h2{ + font-weight: 500; + font-size: 18px; + padding: 0 15px; + text-align: justify; + } + ol,ul { + li { .public-DraftStyleDefault-block{ margin:0; - color: #666; - font-size: 15px; - font-weight: 400; - line-height: 28px; } - } - figure { - margin: 0; - margin-top: 10px; - text-align: center; - iframe { - width: 100%; - height: 240px; + &:before{ + top:3px; } } } - .DraftEditor-root{ - min-height:100px; - .public-DraftEditorPlaceholder-root { - line-height:1.7; + blockquote { + border-left: 5px solid #eee; + padding: 0 20px; + .public-DraftStyleDefault-block{ + margin:0; + color: #666; + font-size: 15px; + font-weight: 400; + line-height: 28px; + } + } + figure { + margin: 0; + margin-top: 10px; + text-align: center; + img{ + max-width: 100%; + } + iframe { + width: 100%; + height: 240px; } - .edit-hidePlaceholder { - .public-DraftEditorPlaceholder-root { - display: none; - } - } } } - - .edit-section { - padding: 10px 0; + .RichEditor-root{ + .public-DraftEditorPlaceholder-root { + line-height:1.7; + } + .public-DraftEditor-content{ + min-height:150px; + } } - - - .edit-controls { + .RichEditor-hidePlaceholder{ + .public-DraftEditorPlaceholder-root { + display: none; + } + } +} +.edit-controls { padding:0 15px 0 10px; border-right:1px solid #eee; display: inline-flex; align-items:center; - color:rgb(165,165,165); + color:#999; &:last-child{ border:none; } span,.fa{ margin:0 10px; cursor:pointer; + font-size: 14px; + transition: color .3s; &:hover{ color:rgb(82,83,84); } @@ -235,19 +214,6 @@ } } } -} -#page-affix{ - padding: 10px 0; - top: 50px; - z-index: 2; - background-color: rgba(255, 255, 255, 0.9) -} -@media screen and (max-width: 992px) { - #page-affix{ - top:0; - } -} - .draft-image{ position:relative; margin:5px 0; diff --git a/src/scripts/components/Common/PlayHtmlEditor.jsx b/src/scripts/components/Common/PlayHtmlEditor.jsx new file mode 100644 index 0000000..e9398bb --- /dev/null +++ b/src/scripts/components/Common/PlayHtmlEditor.jsx @@ -0,0 +1,215 @@ +import React, { Component } from 'react' +import { Editor, EditorState,RichUtils,ContentState,Entity,convertToRaw,convertFromHTML } from 'draft-js' +// import {convertFromHTML} from 'draft-convert' +import Request from 'superagent' +import Dropzone from 'react-dropzone' + +import decorator from '../PlayDraft/DecoratorServer' +import DraftToolbar from '../PlayDraft/DraftToolbar' +import { makeId, DraftImage,mediaBlockRenderer} from '../PlayDraft/draftServer' +import { createLinkEntity,createImageEntity,createVideoEntityWithHtml,createVideoEntityWithSrc,removeEntity } from '../PlayDraft/entityServer' +import CDN from '../../widgets/cdn' + + +export default class extends Component { + constructor(props){ + super(props) + this.state = { + editorState: EditorState.createEmpty( decorator ) + } + + this.focus = () => this.refs.editor.focus() + this.onChange = editorState => this.setState({editorState}) + this.handleKeyCommand = (command) => this._handleKeyCommand(command) + this.blockRendererFn = this._blockRendererFn.bind(this) + + this.dropImage = this._dropImage.bind(this) + this.uploadImage = this._uploadImage.bind(this) + this.addImage = this._addImage.bind(this) + } + _handleKeyCommand(command) { + const {editorState} = this.state; + const newState = RichUtils.handleKeyCommand(editorState, command) + if (newState) { + this.onChange(newState) + return true + } + return false + } + _blockRendererFn(block) { + const { editorState } = this.state + const contentState = editorState.getCurrentContent() + if (block.getType() === 'atomic') { + return { + component: (props) => { + const entityKey = props.block.getEntityAt(0) + const entity = contentState.getEntity(entityKey) + const { html, src } = entity.getData() + const type = entity.getType() + + let media = null + if (type === 'image') { + media = ( + this.onChange(removeEntity(editorState,props.block.getKey()))} + /> + ) + } + return media + }, + editable: false, + }; + } + return null; + } + _dropImage(files) { + Request.get(`/api/uptoken`) + .end((err, res) => { + let uploadToken = res.body.uptoken + files.forEach((file) => { + let d = new Date() + let id = makeId() + let uploadKey = 'article/photo/' + Math.round(d.getTime() / 1000) + '_' + id + '.' + file.name.split('.').pop() + this.uploadImage(file, uploadKey, uploadToken) + }) + }) + } + _uploadImage(file, uploadKey, uploadToken) { + if (!file || file.size === 0) { + return null; + } + Request + .post('http://upload.qiniu.com/') + .field('key', uploadKey) + .field('token', uploadToken) + .field('x:filename', file.name) + .field('x:size', file.size) + .attach('file', file, file.name) + .set('Accept', 'application/json') + .end((err, res) => { + this.addImage(uploadKey) + }) + } + _addImage(imageKey) { + const { editorState } = this.state + const src = CDN.show(imageKey) + '!articlestyle'; + this.setState({ + editorState:createImageEntity(editorState,src) + }) + } + componentDidMount() { + const html = ` +
+

+ + Bold text, Italic text +

+
+
+ baidukaasa
+ ` + const blocksFromHTML = convertFromHTML(html)//.contentBlocks + // const entityMaps = convertFromHTML(html).entityMap + // blocksFromHTML.map((block,i) => { + // console.log(block.toJS()) + // }) + // let i = 1 + // while(entityMaps.get(i.toString())){ + // console.log(entityMaps.get(i.toString()).toJS()) + // i++ + // } + // const blocksFromHTML = convertFromHTML(html); + // const state = ContentState.createFromBlockArray( + // blocksFromHTML.contentBlocks, + // blocksFromHTML.entityMap + // ); + // this.setState({ + // editorState:EditorState.createWithContent(state,decorator) + // }) + // const blocksFromHTML = convertFromHTML(html); + + const defaultConvertedContentState = ContentState.createFromBlockArray( + blocksFromHTML.contentBlocks, + blocksFromHTML.entityMap, + ); + const raw = convertToRaw(defaultConvertedContentState) + console.log(raw) + + // const customConvertedContentState = CustomContentStateConverter(defaultConvertedContentState); + + // const initialEditorState = EditorState.createWithContent( + // customConvertedContentState, + // decorator, + // ); + // this.setState({ + // editorState:initialEditorState + // }) + } + render() { + const {editorState} = this.state; + let className = 'RichEditor-editor'; + var contentState = editorState.getCurrentContent(); + if (!contentState.hasText()) { + if (contentState.getBlockMap().first().getType() !== 'unstyled') { + className += ' RichEditor-hidePlaceholder' + } + } + return ( +
+
+ + + + +
+ +
+
+
+ ) + } +} + +// const CustomConvertFromHTML = (html) => { +// // Correctly seperates paragraphs into their own blocks +// const blockRenderMap = DefaultDraftBlockRenderMap.set('p', { element: 'p' }); +// const blocksFromHTML = convertFromHTML(html, getSafeBodyFromHTML, blockRenderMap); +// blocksFromHTML.contentBlocks = blocksFromHTML.contentBlocks.map(block => (block.get('type') === 'p' ? block.set('type', 'unstyled') : block)); + +// return blocksFromHTML; +// }; + +// const CustomContentStateConverter = (contentState) => { +// // Correctly assign properties to images and links +// const newBlockMap = contentState.getBlockMap().map((block) => { +// const entityKey = block.getEntityAt(0); +// if (entityKey !== null) { +// const entityBlock = contentState.getEntity(entityKey); +// const entityType = entityBlock.getType(); +// switch (entityType) { +// case 'IMAGE': { +// const newBlock = block.merge({ +// type: 'atomic', +// text: 'img', +// }); +// // const newContentState = contentState.mergeEntityData(entityKey, { mutability: 'IMMUTABLE' }); +// return newBlock; +// } +// default: +// return block; +// } +// } +// return block; +// }); +// const newContentState = contentState.set('blockMap', newBlockMap); + +// return newContentState; +// }; \ No newline at end of file diff --git a/src/scripts/components/EditPage/EditPage.jsx b/src/scripts/components/EditPage/EditPage.jsx index 3f8b2e2..201cda5 100644 --- a/src/scripts/components/EditPage/EditPage.jsx +++ b/src/scripts/components/EditPage/EditPage.jsx @@ -4,7 +4,6 @@ import { stateToHTML } from 'draft-js-export-html' import Request from 'superagent' import Dropzone from 'react-dropzone' import TagsInput from 'react-tagsinput' -import Select from 'react-select' import { Editor, @@ -20,7 +19,7 @@ import { } from 'draft-js' import decorator from '../PlayDraft/DecoratorServer' -import { getBlockStyle,makeId, DraftImage } from '../PlayDraft/draftServer' +import { getBlockStyle,makeId, DraftImage,mediaBlockRenderer } from '../PlayDraft/draftServer' import { createLinkEntity,createImageEntity,createVideoEntityWithHtml,createVideoEntityWithSrc,removeEntity } from '../PlayDraft/entityServer' import DraftToolbar from '../PlayDraft/DraftToolbar' @@ -56,8 +55,8 @@ export default class EditPage extends Component { this.onChange = (editorState) => this.setState({ editorState },() => this.saveStorage()) this.focus = () => this.refs.editor.focus() this.blur = () => this.refs.editor.blur() + this.handleKeyCommand = (command) => this._handleKeyCommand(command) - this.blockRendererFn = this._blockRendererFn.bind(this) //媒体(图片,视频..) this.addImage = this._addImage.bind(this) this.addVideo = () => this._addVideo() @@ -96,6 +95,7 @@ export default class EditPage extends Component { .end((err,res) => { const { title, cover, tags, category, gallery, raw, authorId } = res.body let rawData = convertFromRaw(raw) + console.log(rawData.toJS().entityMap) this.setState({ title, cover, tags, category, gallery, authorId:authorId.$oid, editorState:EditorState.push(this.state.editorState, rawData) @@ -113,6 +113,7 @@ export default class EditPage extends Component { $('html, body').animate({scrollTop: 0}, 1000) return false } + _onDropCover(files) { Request.get('/api/uptoken') .end((err, res) => { @@ -225,40 +226,20 @@ export default class EditPage extends Component { this.setState({ dialogSubmit:false, },()=>{ - this.context.router.push('/page') + this.props.history.push('/pages') }) } }) }) } - _blockRendererFn(block) { - const { editorState } = this.state - const contentState = editorState.getCurrentContent() - if (block.getType() === 'atomic') { - return { - component: (props) => { - const entityKey = props.block.getEntityAt(0) - const entity = contentState.getEntity(entityKey) - const { html, src } = entity.getData() - const type = entity.getType() - - let media = null - if (type === 'image') { - media = ( - this.onChange(removeEntity(editorState,props.block.getKey()))} - /> - ) - }else if (type === 'video') { - media =
- } - return media - }, - editable: false, - }; - } - return null; + _handleKeyCommand(command) { + const {editorState} = this.state; + const newState = RichUtils.handleKeyCommand(editorState, command) + if (newState) { + this.onChange(newState) + return true + } + return false } _uploadImg(files) { let _this = this @@ -400,10 +381,10 @@ export default class EditPage extends Component { render() { const { cover,title,tags,category,authorId,dialogSubmit,gallery,editorState } = this.state const contentState = editorState.getCurrentContent() - let className = 'edit-editor' + let className = 'RichEditor-editor' if (!contentState.hasText()) { if (contentState.getBlockMap().first().getType() !== 'unstyled') { - className = ' edit-hidePlaceholder' + className += ' RichEditor-hidePlaceholder' } } return ( @@ -438,8 +419,7 @@ export default class EditPage extends Component {
@@ -342,10 +343,11 @@ export default class EditToy extends Component {
-
+
详细描述 + {/* */}