| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- <style>
- /* assets/styles.css */
- /* --- General application styles --------------------------------------------------- */
- .app {
- display: flex;
- flex-direction: row;
- justify-content: center;
- }
- .app textarea {
- width: 100%;
- height: 300px;
- font-family: 'Courier New', Courier, monospace;
- box-sizing: border-box;
- font-size: 14px;
- }
- /* --- Product offer editor styles ----------------------------------------------------- */
- .app .app__offer-editor {
- flex: 1 1 auto;
- }
- /* --- Generic product preview styles --------------------------------------------------- */
- .app .product-preview {
- background-repeat: no-repeat;
- background-position: center;
- background-image: var(--product-image);
- background-size: cover;
- height: 120px;
- position: relative;
- overflow: hidden;
- box-shadow: 1px 1px 3px hsla(0, 0%, 0%, .3);
- min-width: 120px;
- }
- .app .product-preview .product-preview__name {
- padding: 10px;
- background: hsl(0, 0%, 100%);
- font-weight: bold;
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- line-height: 1.5em;
- }
- .app .product-preview .product-preview__price {
- position: absolute;
- top: 0;
- right: 0;
- display: block;
- background: hsl(346, 100%, 56%);
- padding: 6px 10px;
- min-width: 50px;
- text-align: center;
- color: hsl(0, 0%, 100%);
- text-transform: uppercase;
- font-size: .8em;
- line-height: 1.5em;
- }
- .app .product-preview .product-preview__add {
- display: none;
- }
- /* --- Product list styles --------------------------------------------------- */
- .app .app__product-list {
- margin-left: 20px;
- padding: 0 20px;
- border-left: 1px solid hsl(0, 0%, 87%);
- }
- .app .app__product-list ul {
- display: grid;
- grid-template-columns: 1fr;
- grid-gap: 10px;
- list-style-type: none;
- margin: 1.5em 0;
- padding: 0;
- }
- .app .app__product-list .product-preview {
- opacity: .7;
- }
- .app .app__product-list .product-preview:hover {
- opacity: 1;
- }
- .app .app__product-list .product-preview:hover .product-preview__add {
- display: block;
- }
- .app .app__product-list .product-preview .product-preview__add {
- display: none;
- position: absolute;
- width: 40px;
- height: 40px;
- top: 45%;
- left: 50%;
- border: 0;
- padding: 0;
- cursor: pointer;
- font-weight: bold;
- text-align: center;
- border-radius: 100px;
- background: hsl(0, 0%, 100%);
- transform: translate(-50%, -50%);
- box-shadow: 2px 2px 2px hsla(0, 0%, 0%, .3);
- }
- .app .app__product-list .product-preview .product-preview__add span {
- font-size: 25px;
- vertical-align: middle;
- color: hsl(0, 0%, 24%);
- line-height: 40px;
- display: inline-block;
- }
- .app .app__product-list .product-preview .product-preview__name {
- font-size: 10px;
- }
- .app .app__product-list .product-preview .product-preview__price {
- font-size: 10px;
- }
- /* --- In-editor product widget styles --------------------------------------------------- */
- .app .ck-content .product {
- margin: 1em;
- animation: slideUp 0.3s ease;
- }
- .app .ck-content .table td {
- vertical-align: middle;
- }
- @keyframes slideUp {
- 0% {
- opacity: 0;
- transform: translateY(1em);
- }
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
- }
- </style>
- <div id="snippet-react-in-widget">
- <div class="app"></div>
- </div>
- <script type="text/babel">
- // react/productpreview.js
- class ProductPreview extends React.Component {
- render() {
- const style = {
- '--product-image': `url(${ this.props.image })`
- };
- return <div
- className="product-preview"
- style={style}>
- <button
- className="product-preview__add"
- onClick={() => this.props.onClick( this.props.id )}
- title="Add to the offer"
- >
- <span>+</span>
- </button>
- <span className="product-preview__name">{this.props.name}</span>
- <span className="product-preview__price">from {this.props.price}</span>
- </div>
- }
- }
- // react/productlist.js
- class ProductList extends React.Component {
- render() {
- return <div className="app__product-list">
- <h3>Products</h3>
- <ul>
- {this.props.products.map( product => {
- return <li key={product.id}>
- <ProductPreview
- id={product.id}
- onClick={this.props.onClick}
- {...product}
- />
- </li>;
- })}
- </ul>
- <p><b>Tip</b>: Clicking the product will add it to the editor.</p>
- </div>;
- }
- }
- // app.js
- // The React application class. It renders the editor and the product list.
- class App extends React.Component {
- constructor( props ) {
- super( props );
- // A place to store the reference to the editor instance created by the <CKEditor> component.
- // The editor instance is created asynchronously and is only available when the editor is ready.
- this.editor = null;
- this.state = {
- // The initial editor data. It is bound to the editor instance and will change as
- // the user types and modifies the content of the editor.
- editorData: '<h2>Check our last minute deals!</h2><p>The capital city of <a href="https://en.wikipedia.org/wiki/Malta">Malta</a> is the top destination this summer. It’s home to a cutting-edge contemporary architecture, baroque masterpieces, delicious local cuisine and at least 8 months of sun.</p><section class="product" data-id="2"></section><p>You’ll definitely love exploring <a href="https://en.wikipedia.org/wiki/Warsaw">Warsaw</a>! Best time to visit the city is July and August, when it’s cool enough to not break a sweat and hot enough to enjoy summer. The city which has quite a combination of both old and modern textures is located by the river Vistula.</p><section class="product" data-id="1"></section><h3>Other destinations</h3><figure class="table"><table><thead><tr><th>Destination</th><th>Trip details</th></tr></thead><tbody><tr><td><section class="product" data-id="3"></section><p> </p></td><td>Getting used to an entirely different culture can be challenging. While it’s also nice to learn about cultures online or from books, nothing comes close to experiencing cultural diversity in person. You learn to appreciate each and every single one of the differences while you become more culturally fluid. <a href="http://ckeditor.com">Find out more...</a></td></tr><tr><td><section class="product" data-id="4"></section><p> </p></td><td>Tourists frequently admit that Taj Mahal "simply cannot be described with words". And that’s probably true. The more you try the more speechless you become. Words give only a semblance of truth. <a href="http://ckeditor.com">Find out more...</a></td></tr></tbody></table></figure>'
- };
- // The configuration of the <CKEditor> instance.
- this.editorConfig = {
- plugins: [
- // A set of editor features to be enabled and made available to the user.
- Essentials, Heading, Bold, Italic, Underline,
- Link, Paragraph, Table, TableToolbar,
- // Your custom plugin implementing the widget is loaded here.
- ProductPreviewEditing
- ],
- toolbar: {
- viewportTopOffset: window.getViewportTopOffsetConfig(),
- items: [
- 'heading',
- '|',
- 'bold', 'italic', 'underline',
- '|',
- 'link', 'insertTable',
- '|',
- 'undo', 'redo'
- ]
- },
- table: {
- contentToolbar: [
- 'tableColumn',
- 'tableRow',
- 'mergeTableCells'
- ]
- },
- // The configuration of the Products plugin. It specifies a function that will allow
- // the editor to render a React <ProductPreview> component inside a product widget.
- products: {
- productRenderer: ( id, domElement ) => {
- const product = this.props.products.find( product => product.id === id );
- ReactDOM.render(
- <ProductPreview id={id} {...product} />,
- domElement
- );
- }
- }
- };
- this.handleEditorDataChange = this.handleEditorDataChange.bind( this );
- this.handleEditorInit = this.handleEditorInit.bind( this );
- }
- // A handler executed when the user types or modifies the editor content.
- // It updates the state of the application.
- handleEditorDataChange( evt, editor ) {
- this.setState( {
- editorData: editor.getData()
- } );
- }
- // A handler executed when the editor has been initialized and is ready.
- // It synchronizes the initial data state and saves the reference to the editor instance.
- handleEditorInit( editor ) {
- this.editor = editor;
- this.setState( {
- editorData: editor.getData()
- } );
- }
- render() {
- return [
- // The application renders two columns:
- // * in the left one, the <CKEditor> and the textarea displaying live
- // editor data are rendered.
- // * in the right column, a <ProductList> is rendered with available <ProductPreviews>
- // to choose from.
- <div className="app__offer-editor" key="offer-editor">
- <h3>Product offer editor</h3>
- <CKEditor
- editor={ClassicEditor}
- data={this.state.editorData}
- config={this.editorConfig}
- onChange={this.handleEditorDataChange}
- onInit={this.handleEditorInit}
- />
- <h4>Editor data</h4>
- <textarea value={this.state.editorData} readOnly={true}></textarea>
- </div>,
- <ProductList
- key="product-list"
- products={this.props.products}
- onClick={( id ) => {
- this.editor.execute( 'insertProduct', id );
- this.editor.editing.view.focus();
- }}
- />
- ];
- }
- }
- // Render the <App> in the <div class="app"></div> element found in the DOM.
- ReactDOM.render(
- <App
- // Feeding the application with predefined products.
- // In a real-life application, this sort of data would be loaded
- // from a database. To keep this tutorial simple, a few
- // hard–coded product definitions will be used.
- products={[
- {
- id: 1,
- name: 'Colors of summer in Poland',
- price: '$1500',
- image: '../../../assets/img/fields.jpg'
- },
- {
- id: 2,
- name: 'Mediterranean sun on Malta',
- price: '$1899',
- image: '../../../assets/img/malta.jpg'
- },
- {
- id: 3,
- name: 'Tastes of Asia',
- price: '$2599',
- image: '../../../assets/img/umbrellas.jpg'
- },
- {
- id: 4,
- name: 'Exotic India',
- price: '$2200',
- image: '../../../assets/img/tajmahal.jpg'
- }
- ]}
- />,
- document.querySelector( '.app' )
- );
- </script>
|