├── .eslintrc.js
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 1.bug_report.yml
│ └── config.yml
├── actions
│ └── setup-project
│ │ └── action.yml
├── dependabot.yml
├── lock.yml
├── no-response.yml
└── workflows
│ ├── publish-cli.yml
│ ├── publish-core-latest.yml
│ ├── publish-core-rc.yml
│ ├── publish-docs.yml
│ ├── publish.yml
│ ├── quality.yml
│ └── test.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
├── .vuepress
│ ├── components
│ │ ├── Demo.vue
│ │ ├── DemoBasicBlocks.vue
│ │ ├── DemoCanvasOnly.vue
│ │ ├── DemoCustomPanels.vue
│ │ ├── DemoDevices.vue
│ │ ├── DemoLayers.vue
│ │ ├── DemoStyle.vue
│ │ ├── DemoTheme.vue
│ │ ├── DemoTraits.vue
│ │ ├── DemoViewer.vue
│ │ └── demos
│ │ │ ├── DemoCanvasOnly.css
│ │ │ ├── DemoCanvasOnly.html
│ │ │ ├── DemoCanvasOnly.js
│ │ │ ├── DemoLayers.css
│ │ │ └── utils.js
│ ├── config.js
│ ├── enhanceApp.js
│ ├── public
│ │ ├── assets-builtin-modal.png
│ │ ├── assets-empty-view.png
│ │ ├── assets-full-dropzone.gif
│ │ ├── assets-svg-view.png
│ │ ├── assets-uploader.png
│ │ ├── block-custom-render.jpg
│ │ ├── block-custom-render2.jpg
│ │ ├── blocks3.jpg
│ │ ├── btn-clicked.png
│ │ ├── canvas-panels.jpg
│ │ ├── canvas-spot-hover.jpg
│ │ ├── canvas-spot-resize.jpg
│ │ ├── canvas-spot-select.jpg
│ │ ├── canvas-spot-target.jpg
│ │ ├── component-type-stack.svg
│ │ ├── cssom-devtools.png
│ │ ├── cssom-result.jpg
│ │ ├── default-gjs.jpg
│ │ ├── default-link-comp.jpg
│ │ ├── default-sm.jpg
│ │ ├── default-traits.png
│ │ ├── demo-view.png
│ │ ├── docs-init-link-trait.jpg
│ │ ├── docs-link-trait-raw.jpg
│ │ ├── empty-gjs.png
│ │ ├── enabled-sm.jpg
│ │ ├── grapes.min.js
│ │ ├── input-custom-traits.png
│ │ ├── layer-manager.png
│ │ ├── logo-icon.png
│ │ ├── logo.png
│ │ ├── margin-strings.jpg
│ │ ├── new-btn.png
│ │ ├── new-panel.png
│ │ ├── selector-manager.jpg
│ │ ├── sm-base-type.jpg
│ │ ├── sm-component-first.jpg
│ │ ├── sm-disable-selector.jpg
│ │ ├── sm-empty-state.jpg
│ │ ├── sm-selected-component.jpg
│ │ ├── sm-type-color.jpg
│ │ ├── sm-type-composite.jpg
│ │ ├── sm-type-number.jpg
│ │ ├── sm-type-radio.jpg
│ │ ├── sm-type-select.jpg
│ │ ├── sm-type-slider.jpg
│ │ ├── sm-type-stack.jpg
│ │ ├── studio-banner.jpg
│ │ ├── style-comp.jpg
│ │ ├── style-manager.jpg
│ │ ├── symbols-model.svg
│ │ └── trait-categories.png
│ ├── styles
│ │ ├── index.styl
│ │ └── palette.styl
│ └── theme
│ │ ├── index.js
│ │ └── layouts
│ │ ├── CarbonAds.vue
│ │ ├── Layout.vue
│ │ ├── StudioSdkBannerSidebar.vue
│ │ └── utils.js
├── Home.md
├── README.md
├── api.mjs
├── api
│ ├── README.md
│ ├── asset.md
│ ├── assets.md
│ ├── block.md
│ ├── block_manager.md
│ ├── canvas.md
│ ├── canvas_spot.md
│ ├── commands.md
│ ├── component.md
│ ├── components.md
│ ├── css_composer.md
│ ├── css_rule.md
│ ├── data_source_manager.md
│ ├── datarecord.md
│ ├── datasource.md
│ ├── datasources.md
│ ├── device.md
│ ├── device_manager.md
│ ├── editor.md
│ ├── frame.md
│ ├── i18n.md
│ ├── keymaps.md
│ ├── layer.md
│ ├── layer_manager.md
│ ├── modal_dialog.md
│ ├── page.md
│ ├── pages.md
│ ├── panels.md
│ ├── parser.md
│ ├── property.md
│ ├── property_composite.md
│ ├── property_number.md
│ ├── property_select.md
│ ├── property_stack.md
│ ├── rich_text_editor.md
│ ├── sector.md
│ ├── selector.md
│ ├── selector_manager.md
│ ├── state.md
│ ├── storage_manager.md
│ ├── style_manager.md
│ ├── trait.md
│ ├── trait_manager.md
│ └── undo_manager.md
├── faq.md
├── getting-started.md
├── guides
│ ├── Custom-CSS-parser.md
│ ├── Replace-Rich-Text-Editor.md
│ ├── Symbols.md
│ └── Telemetry.md
├── modules
│ ├── Assets.md
│ ├── Blocks.md
│ ├── Canvas.md
│ ├── Commands.md
│ ├── Components-js.md
│ ├── Components.md
│ ├── DataSources.md
│ ├── I18n.md
│ ├── Layers.md
│ ├── Modal.md
│ ├── Pages.md
│ ├── Plugins.md
│ ├── Selectors.md
│ ├── Storage.md
│ ├── Style-manager.md
│ └── Traits.md
└── package.json
├── package.json
├── packages
├── cli
│ ├── README.md
│ ├── babel.config.js
│ ├── index.html
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── banner.txt
│ │ ├── build.ts
│ │ ├── cli.ts
│ │ ├── init.ts
│ │ ├── main.ts
│ │ ├── serve.ts
│ │ ├── template
│ │ │ ├── .gitignore-t
│ │ │ ├── .npmignore-t
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ │ ├── blocks.js
│ │ │ │ ├── components.js
│ │ │ │ └── index.js
│ │ │ └── tsconfig.json
│ │ ├── utils.ts
│ │ └── webpack.config.ts
│ ├── test
│ │ └── utils.spec.ts
│ ├── tsconfig.json
│ └── webpack.cli.ts
└── core
│ ├── .npmignore
│ ├── .yarnrc
│ ├── LICENSE
│ ├── README.md
│ ├── babel.config.js
│ ├── index.html
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── abstract
│ │ ├── CollectionWithCategories.ts
│ │ ├── Module.ts
│ │ ├── ModuleCategories.ts
│ │ ├── ModuleCategory.ts
│ │ ├── ModuleCategoryView.ts
│ │ ├── ModuleCollection.ts
│ │ ├── ModuleDomainViews.ts
│ │ ├── ModuleModel.ts
│ │ ├── ModuleView.ts
│ │ └── index.ts
│ ├── asset_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Asset.ts
│ │ │ ├── AssetImage.ts
│ │ │ └── Assets.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── AssetImageView.ts
│ │ │ ├── AssetView.ts
│ │ │ ├── AssetsView.ts
│ │ │ └── FileUploader.ts
│ ├── block_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Block.ts
│ │ │ └── Blocks.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── BlockView.ts
│ │ │ └── BlocksView.ts
│ ├── canvas
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Canvas.ts
│ │ │ ├── CanvasSpot.ts
│ │ │ ├── CanvasSpots.ts
│ │ │ ├── Frame.ts
│ │ │ └── Frames.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── CanvasView.ts
│ │ │ ├── FrameView.ts
│ │ │ ├── FrameWrapView.ts
│ │ │ └── FramesView.ts
│ ├── code_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── CodeMirrorEditor.ts
│ │ │ ├── CssGenerator.ts
│ │ │ ├── HtmlGenerator.ts
│ │ │ ├── JsGenerator.ts
│ │ │ └── JsonGenerator.ts
│ │ └── view
│ │ │ └── EditorView.ts
│ ├── commands
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── CanvasClear.ts
│ │ │ ├── CanvasMove.ts
│ │ │ ├── CommandAbstract.ts
│ │ │ ├── ComponentDelete.ts
│ │ │ ├── ComponentDrag.ts
│ │ │ ├── ComponentEnter.ts
│ │ │ ├── ComponentExit.ts
│ │ │ ├── ComponentNext.ts
│ │ │ ├── ComponentPrev.ts
│ │ │ ├── ComponentStyleClear.ts
│ │ │ ├── CopyComponent.ts
│ │ │ ├── ExportTemplate.ts
│ │ │ ├── Fullscreen.ts
│ │ │ ├── MoveComponent.ts
│ │ │ ├── OpenAssets.ts
│ │ │ ├── OpenBlocks.ts
│ │ │ ├── OpenLayers.ts
│ │ │ ├── OpenStyleManager.ts
│ │ │ ├── OpenTraitManager.ts
│ │ │ ├── PasteComponent.ts
│ │ │ ├── Preview.ts
│ │ │ ├── Resize.ts
│ │ │ ├── SelectComponent.ts
│ │ │ ├── SelectPosition.ts
│ │ │ ├── ShowOffset.ts
│ │ │ └── SwitchVisibility.ts
│ ├── common
│ │ └── index.ts
│ ├── css_composer
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── CssRule.ts
│ │ │ └── CssRules.ts
│ │ └── view
│ │ │ ├── CssGroupRuleView.ts
│ │ │ ├── CssRuleView.ts
│ │ │ └── CssRulesView.ts
│ ├── data_sources
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── ComponentDataVariable.ts
│ │ │ ├── ComponentWithDataResolver.ts
│ │ │ ├── DataRecord.ts
│ │ │ ├── DataRecords.ts
│ │ │ ├── DataResolverListener.ts
│ │ │ ├── DataSource.ts
│ │ │ ├── DataSources.ts
│ │ │ ├── DataVariable.ts
│ │ │ ├── TraitDataVariable.ts
│ │ │ ├── conditional_variables
│ │ │ │ ├── ComponentDataCondition.ts
│ │ │ │ ├── ComponentDataOutput.ts
│ │ │ │ ├── ConditionStatement.ts
│ │ │ │ ├── ConditionalOutputBase.ts
│ │ │ │ ├── DataCondition.ts
│ │ │ │ ├── DataConditionEvaluator.ts
│ │ │ │ ├── LogicalGroupEvaluator.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── operators
│ │ │ │ │ ├── AnyTypeOperator.ts
│ │ │ │ │ ├── BaseOperator.ts
│ │ │ │ │ ├── BooleanOperator.ts
│ │ │ │ │ ├── NumberOperator.ts
│ │ │ │ │ └── StringOperator.ts
│ │ │ │ └── types.ts
│ │ │ └── data_collection
│ │ │ │ ├── ComponentDataCollection.ts
│ │ │ │ ├── constants.ts
│ │ │ │ └── types.ts
│ │ ├── types.ts
│ │ ├── utils.ts
│ │ └── view
│ │ │ ├── ComponentDataCollectionView.ts
│ │ │ ├── ComponentDataConditionView.ts
│ │ │ └── ComponentDataVariableView.ts
│ ├── device_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Device.ts
│ │ │ └── Devices.ts
│ │ └── view
│ │ │ └── DevicesView.ts
│ ├── dom_components
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Component.ts
│ │ │ ├── ComponentComment.ts
│ │ │ ├── ComponentDataResolverWatchers.ts
│ │ │ ├── ComponentFrame.ts
│ │ │ ├── ComponentHead.ts
│ │ │ ├── ComponentImage.ts
│ │ │ ├── ComponentLabel.ts
│ │ │ ├── ComponentLink.ts
│ │ │ ├── ComponentMap.ts
│ │ │ ├── ComponentResolverWatcher.ts
│ │ │ ├── ComponentScript.ts
│ │ │ ├── ComponentSvg.ts
│ │ │ ├── ComponentSvgIn.ts
│ │ │ ├── ComponentTable.ts
│ │ │ ├── ComponentTableBody.ts
│ │ │ ├── ComponentTableCell.ts
│ │ │ ├── ComponentTableFoot.ts
│ │ │ ├── ComponentTableHead.ts
│ │ │ ├── ComponentTableRow.ts
│ │ │ ├── ComponentText.ts
│ │ │ ├── ComponentTextNode.ts
│ │ │ ├── ComponentVideo.ts
│ │ │ ├── ComponentWrapper.ts
│ │ │ ├── Components.ts
│ │ │ ├── SymbolUtils.ts
│ │ │ ├── Symbols.ts
│ │ │ ├── Toolbar.ts
│ │ │ ├── ToolbarButton.ts
│ │ │ └── types.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── ComponentCommentView.ts
│ │ │ ├── ComponentFrameView.ts
│ │ │ ├── ComponentImageView.ts
│ │ │ ├── ComponentLabelView.ts
│ │ │ ├── ComponentLinkView.ts
│ │ │ ├── ComponentMapView.ts
│ │ │ ├── ComponentScriptView.ts
│ │ │ ├── ComponentSvgView.ts
│ │ │ ├── ComponentTableBodyView.ts
│ │ │ ├── ComponentTableCellView.ts
│ │ │ ├── ComponentTableFootView.ts
│ │ │ ├── ComponentTableHeadView.ts
│ │ │ ├── ComponentTableRowView.ts
│ │ │ ├── ComponentTableView.ts
│ │ │ ├── ComponentTextNodeView.ts
│ │ │ ├── ComponentTextView.ts
│ │ │ ├── ComponentVideoView.ts
│ │ │ ├── ComponentView.ts
│ │ │ ├── ComponentWrapperView.ts
│ │ │ ├── ComponentsView.ts
│ │ │ ├── ToolbarButtonView.ts
│ │ │ └── ToolbarView.ts
│ ├── domain_abstract
│ │ ├── model
│ │ │ ├── StyleableModel.ts
│ │ │ └── TypeableCollection.ts
│ │ ├── ui
│ │ │ ├── Input.ts
│ │ │ ├── InputColor.ts
│ │ │ └── InputNumber.ts
│ │ └── view
│ │ │ └── DomainViews.ts
│ ├── editor
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Editor.ts
│ │ │ └── Selected.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ └── EditorView.ts
│ ├── i18n
│ │ ├── config.ts
│ │ ├── index.ts
│ │ ├── locale
│ │ │ ├── ar.js
│ │ │ ├── bs.js
│ │ │ ├── ca.js
│ │ │ ├── de.js
│ │ │ ├── el.js
│ │ │ ├── en.js
│ │ │ ├── es.js
│ │ │ ├── fa.js
│ │ │ ├── fr.js
│ │ │ ├── he.js
│ │ │ ├── id.js
│ │ │ ├── it.js
│ │ │ ├── ko.js
│ │ │ ├── nb.js
│ │ │ ├── nl.js
│ │ │ ├── pl.js
│ │ │ ├── pt.js
│ │ │ ├── ru.js
│ │ │ ├── se.js
│ │ │ ├── tr.js
│ │ │ ├── vi.js
│ │ │ └── zh.js
│ │ └── types.ts
│ ├── index.ts
│ ├── keymaps
│ │ ├── config.ts
│ │ └── index.ts
│ ├── modal_dialog
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ └── Modal.ts
│ │ └── view
│ │ │ └── ModalView.ts
│ ├── navigator
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ └── view
│ │ │ ├── ItemView.ts
│ │ │ └── ItemsView.ts
│ ├── pages
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Page.ts
│ │ │ └── Pages.ts
│ │ └── types.ts
│ ├── panels
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Button.ts
│ │ │ ├── Buttons.ts
│ │ │ ├── Panel.ts
│ │ │ └── Panels.ts
│ │ └── view
│ │ │ ├── ButtonView.ts
│ │ │ ├── ButtonsView.ts
│ │ │ ├── PanelView.ts
│ │ │ └── PanelsView.ts
│ ├── parser
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── BrowserParserCss.ts
│ │ │ ├── BrowserParserHtml.ts
│ │ │ ├── ParserCss.ts
│ │ │ └── ParserHtml.ts
│ │ └── types.ts
│ ├── plugin_manager
│ │ └── index.ts
│ ├── rich_text_editor
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ └── model
│ │ │ └── RichTextEditor.ts
│ ├── selector_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Selector.ts
│ │ │ ├── Selectors.ts
│ │ │ └── State.ts
│ │ └── view
│ │ │ ├── ClassTagView.ts
│ │ │ └── ClassTagsView.ts
│ ├── storage_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── IStorage.ts
│ │ │ ├── LocalStorage.ts
│ │ │ └── RemoteStorage.ts
│ │ └── types.ts
│ ├── style_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Layer.ts
│ │ │ ├── Layers.ts
│ │ │ ├── Properties.ts
│ │ │ ├── Property.ts
│ │ │ ├── PropertyComposite.ts
│ │ │ ├── PropertyFactory.ts
│ │ │ ├── PropertyNumber.ts
│ │ │ ├── PropertyRadio.ts
│ │ │ ├── PropertySelect.ts
│ │ │ ├── PropertySlider.ts
│ │ │ ├── PropertyStack.ts
│ │ │ ├── Sector.ts
│ │ │ └── Sectors.ts
│ │ └── view
│ │ │ ├── LayerView.ts
│ │ │ ├── LayersView.ts
│ │ │ ├── PropertiesView.ts
│ │ │ ├── PropertyColorView.ts
│ │ │ ├── PropertyCompositeView.ts
│ │ │ ├── PropertyFileView.ts
│ │ │ ├── PropertyNumberView.ts
│ │ │ ├── PropertyRadioView.ts
│ │ │ ├── PropertySelectView.ts
│ │ │ ├── PropertySliderView.ts
│ │ │ ├── PropertyStackView.ts
│ │ │ ├── PropertyView.ts
│ │ │ ├── SectorView.ts
│ │ │ └── SectorsView.ts
│ ├── styles
│ │ └── scss
│ │ │ ├── _gjs_assets.scss
│ │ │ ├── _gjs_blocks.scss
│ │ │ ├── _gjs_canvas.scss
│ │ │ ├── _gjs_category_general.scss
│ │ │ ├── _gjs_code_manager.scss
│ │ │ ├── _gjs_commands.scss
│ │ │ ├── _gjs_devices.scss
│ │ │ ├── _gjs_file_uploader.scss
│ │ │ ├── _gjs_inputs.scss
│ │ │ ├── _gjs_layers.scss
│ │ │ ├── _gjs_main_mixins.scss
│ │ │ ├── _gjs_modal.scss
│ │ │ ├── _gjs_panels.scss
│ │ │ ├── _gjs_root.scss
│ │ │ ├── _gjs_rte.scss
│ │ │ ├── _gjs_selectors.scss
│ │ │ ├── _gjs_spectrum.scss
│ │ │ ├── _gjs_status.scss
│ │ │ ├── _gjs_style_manager.scss
│ │ │ ├── _gjs_traits.scss
│ │ │ ├── _gjs_vars.scss
│ │ │ ├── main.scss
│ │ │ └── spectrum.scss
│ ├── trait_manager
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── index.ts
│ │ ├── model
│ │ │ ├── Trait.ts
│ │ │ ├── TraitFactory.ts
│ │ │ └── Traits.ts
│ │ ├── types.ts
│ │ └── view
│ │ │ ├── TraitButtonView.ts
│ │ │ ├── TraitCheckboxView.ts
│ │ │ ├── TraitColorView.ts
│ │ │ ├── TraitNumberView.ts
│ │ │ ├── TraitSelectView.ts
│ │ │ ├── TraitView.ts
│ │ │ └── TraitsView.ts
│ ├── undo_manager
│ │ ├── config.ts
│ │ └── index.ts
│ └── utils
│ │ ├── AutoScroller.ts
│ │ ├── ColorPicker.ts
│ │ ├── Dragger.ts
│ │ ├── Droppable.ts
│ │ ├── Resizer.ts
│ │ ├── cash-dom.ts
│ │ ├── dom.ts
│ │ ├── extender.ts
│ │ ├── fetch.ts
│ │ ├── host-name.ts
│ │ ├── html.ts
│ │ ├── index.ts
│ │ ├── keymaster.ts
│ │ ├── mixins.ts
│ │ ├── polyfills.ts
│ │ ├── promise-polyfill.d.ts
│ │ └── sorter
│ │ ├── BaseComponentNode.ts
│ │ ├── CanvasComponentNode.ts
│ │ ├── CanvasNewComponentNode.ts
│ │ ├── ComponentSorter.ts
│ │ ├── Dimension.ts
│ │ ├── DropLocationDeterminer.ts
│ │ ├── LayerNode.ts
│ │ ├── LayersComponentNode.ts
│ │ ├── PlaceholderClass.ts
│ │ ├── RateLimiter.ts
│ │ ├── SortableTreeNode.ts
│ │ ├── Sorter.ts
│ │ ├── SorterUtils.ts
│ │ ├── StyleManagerSorter.ts
│ │ └── types.ts
│ ├── test
│ ├── common.ts
│ ├── setup.js
│ ├── specs
│ │ ├── asset_manager
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ ├── Asset.ts
│ │ │ │ ├── AssetImage.ts
│ │ │ │ └── Assets.ts
│ │ │ └── view
│ │ │ │ ├── AssetImageView.ts
│ │ │ │ ├── AssetView.ts
│ │ │ │ ├── AssetsView.ts
│ │ │ │ └── FileUploader.ts
│ │ ├── block_manager
│ │ │ ├── index.ts
│ │ │ └── view
│ │ │ │ └── BlocksView.ts
│ │ ├── canvas
│ │ │ └── index.ts
│ │ ├── code_manager
│ │ │ ├── index.js
│ │ │ └── model
│ │ │ │ └── CodeModels.js
│ │ ├── commands
│ │ │ ├── index.ts
│ │ │ └── view
│ │ │ │ ├── CommandAbstract.ts
│ │ │ │ ├── Preview.ts
│ │ │ │ └── SwitchVisibility.ts
│ │ ├── css_composer
│ │ │ ├── e2e
│ │ │ │ └── CssComposer.ts
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ └── CssModels.ts
│ │ │ └── view
│ │ │ │ ├── CssRuleView.ts
│ │ │ │ └── CssRulesView.ts
│ │ ├── data_sources
│ │ │ ├── __snapshots__
│ │ │ │ ├── jsonplaceholder.ts.snap
│ │ │ │ ├── serialization.ts.snap
│ │ │ │ └── storage.ts.snap
│ │ │ ├── dynamic_values
│ │ │ │ ├── attributes.ts
│ │ │ │ └── props.ts
│ │ │ ├── index.ts
│ │ │ ├── jsonplaceholder.ts
│ │ │ ├── model
│ │ │ │ ├── ComponentDataVariable.getters-setters.ts
│ │ │ │ ├── ComponentDataVariable.ts
│ │ │ │ ├── StyleDataVariable.ts
│ │ │ │ ├── TraitDataVariable.ts
│ │ │ │ ├── conditional_variables
│ │ │ │ │ ├── ComponentDataCondition.getters-setters.ts
│ │ │ │ │ ├── ComponentDataCondition.ts
│ │ │ │ │ ├── ConditionalStyles.ts
│ │ │ │ │ ├── ConditionalTraits.ts
│ │ │ │ │ ├── DataCondition.ts
│ │ │ │ │ └── operators
│ │ │ │ │ │ ├── AnyTypeOperator.ts
│ │ │ │ │ │ ├── BooleanOperator.ts
│ │ │ │ │ │ ├── NumberOperator.ts
│ │ │ │ │ │ └── StringOperator.ts
│ │ │ │ └── data_collection
│ │ │ │ │ ├── ComponentDataCollection.getters-setters.ts
│ │ │ │ │ ├── ComponentDataCollection.ts
│ │ │ │ │ ├── ComponentDataCollectionWithDataVariable.ts
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ ├── ComponentDataCollection.ts.snap
│ │ │ │ │ ├── ComponentDataCollectionWithDataVariable.ts.snap
│ │ │ │ │ └── nestedComponentDataCollections.ts.snap
│ │ │ │ │ └── nestedComponentDataCollections.ts
│ │ │ ├── mutable.ts
│ │ │ ├── serialization.ts
│ │ │ ├── storage.ts
│ │ │ └── transformers.ts
│ │ ├── device_manager
│ │ │ ├── index.js
│ │ │ └── view
│ │ │ │ └── DevicesView.js
│ │ ├── dom_components
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ ├── Component.ts
│ │ │ │ ├── ComponentImage.ts
│ │ │ │ ├── ComponentTypes.ts
│ │ │ │ ├── ComponentWrapper.ts
│ │ │ │ └── Symbols.ts
│ │ │ └── view
│ │ │ │ ├── ComponentImageView.ts
│ │ │ │ ├── ComponentTextView.ts
│ │ │ │ ├── ComponentView.ts
│ │ │ │ └── ComponentsView.ts
│ │ ├── editor
│ │ │ ├── index.ts
│ │ │ └── telemetry.ts
│ │ ├── grapesjs
│ │ │ ├── headless.ts
│ │ │ └── index.ts
│ │ ├── i18n
│ │ │ └── index.ts
│ │ ├── issue_replications
│ │ │ └── checkbox-not-working.ts
│ │ ├── keymaps
│ │ │ └── index.js
│ │ ├── modal
│ │ │ ├── index.js
│ │ │ └── view
│ │ │ │ └── ModalView.js
│ │ ├── navigator
│ │ │ └── view
│ │ │ │ └── ItemView.ts
│ │ ├── pages
│ │ │ └── index.ts
│ │ ├── panels
│ │ │ ├── e2e
│ │ │ │ └── PanelsE2e.js
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ └── PanelModels.js
│ │ │ └── view
│ │ │ │ ├── ButtonView.ts
│ │ │ │ ├── ButtonsView.ts
│ │ │ │ ├── PanelView.ts
│ │ │ │ └── PanelsView.ts
│ │ ├── parser
│ │ │ └── model
│ │ │ │ ├── ParserCss.ts
│ │ │ │ └── ParserHtml.ts
│ │ ├── plugin_manager
│ │ │ └── index.js
│ │ ├── selector_manager
│ │ │ ├── e2e
│ │ │ │ └── ClassManager.ts
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ └── SelectorModels.ts
│ │ │ └── view
│ │ │ │ ├── ClassTagView.ts
│ │ │ │ └── ClassTagsView.ts
│ │ ├── storage_manager
│ │ │ ├── index.ts
│ │ │ └── model
│ │ │ │ └── Models.js
│ │ ├── style_manager
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ ├── Models.ts
│ │ │ │ ├── Properties.ts
│ │ │ │ ├── PropertyFactory.ts
│ │ │ │ └── Sectors.ts
│ │ │ └── view
│ │ │ │ ├── PropertyColorView.ts
│ │ │ │ ├── PropertyCompositeView.ts
│ │ │ │ ├── PropertyIntegerView.ts
│ │ │ │ ├── PropertyRadioView.ts
│ │ │ │ ├── PropertySelectView.ts
│ │ │ │ ├── PropertyStackView.ts
│ │ │ │ ├── PropertyView.ts
│ │ │ │ ├── SectorView.ts
│ │ │ │ └── SectorsView.ts
│ │ ├── trait_manager
│ │ │ ├── index.ts
│ │ │ ├── model
│ │ │ │ └── TraitsModel.ts
│ │ │ └── view
│ │ │ │ └── TraitsView.ts
│ │ └── utils
│ │ │ ├── Mixins.ts
│ │ │ └── Sorter.ts
│ └── test_utils.js
│ ├── tsconfig.json
│ └── webpack.config.js
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── scripts
├── common.ts
├── releaseCli.ts
├── releaseCore.ts
└── releaseDocs.ts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # Shows a funding button via Open Collective
2 |
3 | open_collective: grapesjs
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 🚀 Feature Request
4 | url: https://github.com/artf/grapesjs/discussions/new?category=ideas
5 | about: 'Suggest any ideas you have using our discussion forums.'
6 | - name: 🙏 Help
7 | url: https://github.com/artf/grapesjs/discussions/new?category=q-a
8 | about: 'If you have a question or need help, ask a question on the discussion forums.'
9 | - name: 📢 Show and tell
10 | url: https://github.com/artf/grapesjs/discussions/new?category=show-and-tell
11 | about: "Have something nice to say or share about GrapesJS? We'd love to hear it!"
12 |
--------------------------------------------------------------------------------
/.github/actions/setup-project/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup Project
2 | description: 'Sets up the project by installing dependencies and building the project.'
3 |
4 | inputs:
5 | pnpm-version:
6 | description: 'The version of pnpm to use for installing dependencies.'
7 | required: false
8 | default: 9.10.0
9 | node-version:
10 | description: 'The version of Node.js to use for building the project.'
11 | required: false
12 | default: '20.16.0'
13 |
14 | runs:
15 | using: composite
16 | steps:
17 | - uses: pnpm/action-setup@v4
18 | with:
19 | version: ${{ inputs.pnpm-version }}
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: ${{ inputs.node-version }}
23 | cache: 'pnpm'
24 | - name: Install dependencies
25 | run: pnpm install
26 | shell: bash
27 | - name: Build project
28 | run: pnpm build
29 | shell: bash
30 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: 'npm'
5 | directory: '/'
6 | open-pull-requests-limit: 0
7 | schedule:
8 | interval: 'weekly'
9 |
--------------------------------------------------------------------------------
/.github/lock.yml:
--------------------------------------------------------------------------------
1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads
2 |
3 | # Number of days of inactivity before a closed issue or pull request is locked
4 | daysUntilLock: 365
5 |
6 | # Skip issues and pull requests created before a given timestamp. Timestamp must
7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
8 | skipCreatedBefore: false
9 |
10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable
11 | exemptLabels:
12 | - no-locking
13 | - help-wanted
14 |
15 | # Label to add before locking, such as `outdated`. Set to `false` to disable
16 | lockLabel: outdated
17 |
18 | # Comment to post before locking. Set to `false` to disable
19 | lockComment: >
20 | This thread has been automatically locked since there has not been
21 | any recent activity after it was closed. Please open a new issue for
22 | related bugs.
23 |
24 | # Assign `resolved` as the reason for locking. Set to `false` to disable
25 | setLockReason: true
26 | # Limit to only `issues` or `pulls`
27 | # only: issues
28 |
29 | # Optionally, specify configuration settings just for `issues` or `pulls`
30 | # issues:
31 | # exemptLabels:
32 | # - help-wanted
33 | # lockLabel: outdated
34 |
35 | # pulls:
36 | # daysUntilLock: 30
37 |
38 | # Repository to extend settings from
39 | # _extends: repo
40 |
--------------------------------------------------------------------------------
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-no-response - https://github.com/probot/no-response
2 |
3 | # Number of days of inactivity before an Issue is closed for lack of response
4 | daysUntilClose: 10
5 | # Label requiring a response
6 | responseRequiredLabel: more-information-needed
7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable
8 | closeComment: >
9 | This issue has been automatically closed because there has been no response
10 | to our request for more information from the original author. With only the
11 | information that is currently in the issue, we don't have enough information
12 | to take action. Please reach out if you have or find the answers we need so
13 | that we can investigate further.
14 |
--------------------------------------------------------------------------------
/.github/workflows/publish-cli.yml:
--------------------------------------------------------------------------------
1 | name: Publish GrapesJS CLI
2 | on:
3 | push:
4 | branches: [dev]
5 | paths:
6 | - 'packages/cli/**'
7 |
8 | jobs:
9 | publish:
10 | if: "contains(github.event.head_commit.message, 'Release GrapesJS cli:')"
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: ./.github/actions/setup-project
15 | - name: Publish Core
16 | run: pnpm publish --access public
17 | working-directory: ./packages/cli
18 |
--------------------------------------------------------------------------------
/.github/workflows/publish-core-latest.yml:
--------------------------------------------------------------------------------
1 | name: Publish GrapesJS core latest
2 | on:
3 | push:
4 | branches: [dev]
5 | paths:
6 | - 'packages/core/**'
7 |
8 | jobs:
9 | publish:
10 | if: "contains(github.event.head_commit.message, 'Release GrapesJS core latest:')"
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: ./.github/actions/setup-project
15 | - name: Build cli
16 | run: pnpm run build:cli
17 | - name: Build core
18 | run: pnpm run build:core
19 | - name: Check core TS
20 | run: pnpm run ts:check
21 | - name: Publish to npm
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 | run: |
25 | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ./packages/core/.npmrc
26 | pnpm publish:core:latest
27 |
--------------------------------------------------------------------------------
/.github/workflows/publish-core-rc.yml:
--------------------------------------------------------------------------------
1 | name: Publish GrapesJS core rc
2 | on:
3 | push:
4 | branches: [dev]
5 | paths:
6 | - 'packages/core/**'
7 |
8 | jobs:
9 | publish:
10 | if: "contains(github.event.head_commit.message, 'Release GrapesJS core rc:')"
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: ./.github/actions/setup-project
15 | - name: Build cli
16 | run: pnpm run build:cli
17 | - name: Build core
18 | run: pnpm run build:core
19 | - name: Check core TS
20 | run: pnpm run ts:check
21 | - name: Publish to npm
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 | run: |
25 | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ./packages/core/.npmrc
26 | pnpm publish:core:rc
27 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | publish:
9 | if: ${{ false }}
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: '20.x'
16 | registry-url: 'https://registry.npmjs.org'
17 | cache: 'yarn'
18 | - run: yarn --frozen-lockfile
19 | - run: yarn build
20 | - run: npm publish
21 | env:
22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/quality.yml:
--------------------------------------------------------------------------------
1 | name: GrapesJS Qualty Checks
2 | on:
3 | push:
4 | branches: [dev]
5 | pull_request:
6 | branches: [dev]
7 |
8 | jobs:
9 | quality-checks:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: ./.github/actions/setup-project
14 | - name: TS Check
15 | run: pnpm ts:check
16 | - name: Lint
17 | run: pnpm lint
18 | - name: Format Check
19 | run: pnpm format:check
20 | - name: Docs
21 | run: pnpm docs:api
22 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: GrapesJS Tests
2 | on:
3 | push:
4 | branches: [dev]
5 | pull_request:
6 | branches: [dev]
7 |
8 | jobs:
9 | test-core:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: ./.github/actions/setup-project
14 | - name: Core Tests
15 | run: pnpm test
16 | working-directory: ./packages/core
17 | test-cli:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: ./.github/actions/setup-project
22 | - name: CLI Tests
23 | run: pnpm test
24 | working-directory: ./packages/cli
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .settings/
3 | .sass-cache/
4 | .project
5 | .idea
6 | npm-debug.log*
7 | yarn-error.log
8 | package-lock.json
9 | style/.sass-cache/
10 | stats.json
11 | .npmrc
12 |
13 | img/
14 | images/
15 | private/
16 | vendor/
17 | coverage/
18 | locale/
19 | node_modules/
20 | bower_components/
21 | grapesjs-*.tgz
22 | _index.html
23 | docs/.vuepress/dist
24 | dist/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | docs/**/*.md
2 | dist/
3 | pnpm-lock.yaml
4 | packages/cli/src/template/**/*.*
5 | **/locale/**
6 | stats.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "insertPragma": false,
4 | "requirePragma": false,
5 | "trailingComma": "all",
6 | "tabWidth": 2,
7 | "useTabs": false,
8 | "singleQuote": true,
9 | "printWidth": 120
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ./packages/core/LICENSE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ./packages/core/README.md
2 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/Demo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoBasicBlocks.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Hello World Component!
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoCanvasOnly.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoCustomPanels.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
Hello World Component!
8 |
9 |
10 |
11 |
12 |
13 |
33 |
34 |
53 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoLayers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
Hello World Component!
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoStyle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
Hello World Component!
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
54 |
55 |
60 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoTraits.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
Hello World Component!
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
55 |
56 |
61 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/DemoViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
44 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demos/DemoCanvasOnly.css:
--------------------------------------------------------------------------------
1 | /* Let's highlight canvas boundaries */
2 | #gjs {
3 | border: 3px solid #444;
4 | }
5 |
6 | /* Reset some default styling */
7 | .gjs-cv-canvas {
8 | top: 0;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demos/DemoCanvasOnly.html:
--------------------------------------------------------------------------------
1 |
2 |
Hello World Component!
3 |
4 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demos/DemoCanvasOnly.js:
--------------------------------------------------------------------------------
1 | const editor = grapesjs.init({
2 | // Indicate where to init the editor. You can also pass an HTMLElement
3 | container: '#gjs',
4 | // Get the content for the canvas directly from the element
5 | // As an alternative we could use: `components: 'Hello World Component! '`,
6 | fromElement: true,
7 | // Size of the editor
8 | height: '300px',
9 | width: 'auto',
10 | // Disable the storage manager for the moment
11 | storageManager: false,
12 | // Avoid any default panel
13 | panels: { defaults: [] },
14 | });
15 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/demos/DemoLayers.css:
--------------------------------------------------------------------------------
1 | .editor-row {
2 | display: flex;
3 | justify-content: flex-start;
4 | align-items: stretch;
5 | flex-wrap: nowrap;
6 | height: 300px;
7 | }
8 |
9 | .editor-canvas {
10 | flex-grow: 1;
11 | }
12 |
13 | .panel__right {
14 | flex-basis: 230px;
15 | position: relative;
16 | overflow-y: auto;
17 | }
18 |
--------------------------------------------------------------------------------
/docs/.vuepress/enhanceApp.js:
--------------------------------------------------------------------------------
1 | // We can use this hook to install additional Vue plugins, register global components, or add additional router hooks
2 | module.exports = ({
3 | Vue, // the version of Vue being used in the VuePress app
4 | options, // the options for the root Vue instance
5 | router, // the router instance for the app
6 | siteData, // site metadata
7 | }) => {
8 | // ...apply enhancements to the app
9 | };
10 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets-builtin-modal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/assets-builtin-modal.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets-empty-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/assets-empty-view.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets-full-dropzone.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/assets-full-dropzone.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets-svg-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/assets-svg-view.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets-uploader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/assets-uploader.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/block-custom-render.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/block-custom-render.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/block-custom-render2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/block-custom-render2.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/blocks3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/blocks3.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/btn-clicked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/btn-clicked.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/canvas-panels.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/canvas-panels.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/canvas-spot-hover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/canvas-spot-hover.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/canvas-spot-resize.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/canvas-spot-resize.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/canvas-spot-select.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/canvas-spot-select.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/canvas-spot-target.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/canvas-spot-target.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/cssom-devtools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/cssom-devtools.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/cssom-result.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/cssom-result.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/default-gjs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/default-gjs.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/default-link-comp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/default-link-comp.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/default-sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/default-sm.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/default-traits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/default-traits.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/demo-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/demo-view.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/docs-init-link-trait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/docs-init-link-trait.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/docs-link-trait-raw.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/docs-link-trait-raw.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/empty-gjs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/empty-gjs.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/enabled-sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/enabled-sm.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/input-custom-traits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/input-custom-traits.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/layer-manager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/layer-manager.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/logo-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/logo-icon.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/logo.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/margin-strings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/margin-strings.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/new-btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/new-btn.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/new-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/new-panel.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/selector-manager.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/selector-manager.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-base-type.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-base-type.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-component-first.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-component-first.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-disable-selector.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-disable-selector.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-empty-state.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-empty-state.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-selected-component.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-selected-component.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-color.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-color.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-composite.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-composite.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-number.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-number.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-radio.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-radio.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-select.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-select.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-slider.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-slider.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/sm-type-stack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/sm-type-stack.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/studio-banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/studio-banner.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/style-comp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/style-comp.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/style-manager.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/style-manager.jpg
--------------------------------------------------------------------------------
/docs/.vuepress/public/trait-categories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GrapesJS/grapesjs/63c459001e2a0ce85d46c7ab980eb7de50c8516d/docs/.vuepress/public/trait-categories.png
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | .img-ctr, .img-ctr-rad {
2 | margin: 0 auto;
3 | display: block;
4 | }
5 |
6 | .img-ctr-rad {
7 | border-radius: 5px;
8 | width: 100%;
9 | }
10 |
11 | .navbar {
12 | background-color: rgb(111, 41, 67);
13 | background-image: linear-gradient(120deg, rgb(217, 131, 166), rgb(77, 17, 79));
14 | color: $navBarColor;
15 | border: none;
16 |
17 | .logo {
18 | min-width: auto;
19 | }
20 |
21 | .site-name {
22 | color: $navBarColor !important;
23 | }
24 |
25 | .links {
26 | background-color: transparent !important;
27 | }
28 | }
29 |
30 | .token.string {
31 | color: $accentColor;
32 | }
33 |
34 | @media (min-width: 719px) {
35 | .nav-links a:hover,
36 | .nav-links a.router-link-active {
37 | color: #ffeff2 !important;
38 | }
39 | }
40 |
41 | .search-box input {
42 | border: 1px solid transparent;
43 | transition: border 0.25s;
44 | }
45 |
46 | .page-nav,
47 | .page-edit,
48 | .content:not(.custom) {
49 | max-width: $pageWidth;
50 | }
51 |
52 | .page__getting-started {
53 | .language-js .language-js {
54 | max-height: 300px;
55 | }
56 | }
57 |
58 |
59 | // Scrollbars
60 | * {
61 | ::-webkit-scrollbar-track {}
62 |
63 | ::-webkit-scrollbar-thumb {
64 | background-color: alpha(black, 0.1);
65 | }
66 |
67 | ::-webkit-scrollbar {
68 | width: $scrollBarSize;
69 | }
70 | }
71 |
72 | .language-js {
73 | ::-webkit-scrollbar {
74 | height: $scrollBarSize;
75 | }
76 |
77 | ::-webkit-scrollbar-thumb {
78 | background-color: alpha(white, 0.3);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 | $accentColor = #e2627f
2 | $accentColor = #e67891
3 | $navBarColor = white
4 | $scrollBarSize = 8px
5 | $pageWidth = 900px
--------------------------------------------------------------------------------
/docs/.vuepress/theme/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extend: '@vuepress/theme-default',
3 | };
4 |
--------------------------------------------------------------------------------
/docs/.vuepress/theme/layouts/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
26 |
--------------------------------------------------------------------------------
/docs/.vuepress/theme/layouts/StudioSdkBannerSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
23 |
24 |
45 |
--------------------------------------------------------------------------------
/docs/.vuepress/theme/layouts/utils.js:
--------------------------------------------------------------------------------
1 | export const getSdkUtmParams = (medium = '') => {
2 | return `utm_source=grapesjs-docs&utm_medium=${medium}`;
3 | };
4 |
5 | export const getSdkDocsLink = (medium = '') => {
6 | return `https://app.grapesjs.com/docs-sdk/overview/getting-started?${getSdkUtmParams(medium)}`;
7 | };
8 |
--------------------------------------------------------------------------------
/docs/api/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | pageClass: page__apis
3 | ---
4 |
5 | # API Reference
6 |
7 | Here you can find the documentation about GrapesJS' APIs. Mainly, you would use them for your editor to extend the basic functionality of the framework or you could also [create a plugin](/modules/Plugins.html) and make those extensions reusable and available to others.
8 |
--------------------------------------------------------------------------------
/docs/api/asset.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Asset
4 |
5 | ### Properties
6 |
7 | * `type` **[String][1]** Asset type, eg. `'image'`.
8 | * `src` **[String][1]** Asset URL, eg. `'https://.../image.png'`.
9 |
10 | ## getType
11 |
12 | Get asset type.
13 |
14 | ### Examples
15 |
16 | ```javascript
17 | // Asset: { src: 'https://.../image.png', type: 'image' }
18 | asset.getType(); // -> 'image'
19 | ```
20 |
21 | Returns **[String][1]**
22 |
23 | ## getSrc
24 |
25 | Get asset URL.
26 |
27 | ### Examples
28 |
29 | ```javascript
30 | // Asset: { src: 'https://.../image.png' }
31 | asset.getSrc(); // -> 'https://.../image.png'
32 | ```
33 |
34 | Returns **[String][1]**
35 |
36 | ## getFilename
37 |
38 | Get filename of the asset (based on `src`).
39 |
40 | ### Examples
41 |
42 | ```javascript
43 | // Asset: { src: 'https://.../image.png' }
44 | asset.getFilename(); // -> 'image.png'
45 | // Asset: { src: 'https://.../image' }
46 | asset.getFilename(); // -> 'image'
47 | ```
48 |
49 | Returns **[String][1]**
50 |
51 | ## getExtension
52 |
53 | Get extension of the asset (based on `src`).
54 |
55 | ### Examples
56 |
57 | ```javascript
58 | // Asset: { src: 'https://.../image.png' }
59 | asset.getExtension(); // -> 'png'
60 | // Asset: { src: 'https://.../image' }
61 | asset.getExtension(); // -> ''
62 | ```
63 |
64 | Returns **[String][1]**
65 |
66 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
67 |
--------------------------------------------------------------------------------
/docs/api/device.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Device
4 |
5 |
6 |
7 | ### Properties
8 |
9 | * `name` **[String][1]?** Device type, eg. `Mobile`
10 | * `width` **[String][1]?** Width to set for the editor iframe, eg. '900px'
11 | * `height` **[String][1]?** Height to set for the editor iframe, eg. '600px'
12 | * `widthMedia` **[String][1]?** The width which will be used in media queries, If empty the width will be used
13 | * `priority` **[Number][2]?** Setup the order of media queries
14 |
15 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
16 |
17 | [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
18 |
--------------------------------------------------------------------------------
/docs/api/frame.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Frame
4 |
5 |
6 |
7 | ### Properties
8 |
9 | * `component` **([Object][1] | [String][2])** Wrapper component definition. You can also pass an HTML string as components of the default wrapper component.
10 | * `width` **[String][2]?** Width of the frame. By default, the canvas width will be taken.
11 | * `height` **[String][2]?** Height of the frame. By default, the canvas height will be taken.
12 | * `x` **[Number][3]?** Horizontal position of the frame in the canvas.
13 | * `y` **[Number][3]?** Vertical position of the frame in the canvas.
14 |
15 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
16 |
17 | [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
18 |
19 | [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
20 |
--------------------------------------------------------------------------------
/docs/api/layer.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## getId
4 |
5 | Get layer id.
6 |
7 | Returns **[String][1]**
8 |
9 | ## getIndex
10 |
11 | Get layer index.
12 |
13 | Returns **[Number][2]**
14 |
15 | ## getValues
16 |
17 | Get layer values.
18 |
19 | ### Parameters
20 |
21 | * `opts` **[Object][3]** Options (optional, default `{}`)
22 |
23 | * `opts.camelCase` **[Boolean][4]?** Return property names in camelCase.
24 |
25 | Returns **[Object][3]**
26 |
27 | ## getLabel
28 |
29 | Get layer label.
30 |
31 | Returns **[String][1]**
32 |
33 | ## isSelected
34 |
35 | Check if the layer is selected.
36 |
37 | Returns **[Boolean][4]**
38 |
39 | ## select
40 |
41 | Select the layer.
42 |
43 | ## remove
44 |
45 | Remove the layer.
46 |
47 | ## move
48 |
49 | Move layer to a new index.
50 |
51 | ### Parameters
52 |
53 | * `index` **[Number][2]** New index
54 |
55 | ## getStylePreview
56 |
57 | Get style object for the preview.
58 |
59 | ### Parameters
60 |
61 | * `opts` **[Object][3]** Options. Same of `PropertyStack.getStyleFromLayer` (optional, default `{}`)
62 |
63 | Returns **[Object][3]** Style object
64 |
65 | ## hasPreview
66 |
67 | Check if the property has the preview enabled for this layer.
68 |
69 | Returns **[Boolean][4]**
70 |
71 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
72 |
73 | [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
74 |
75 | [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
76 |
77 | [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
78 |
--------------------------------------------------------------------------------
/docs/api/page.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## getId
4 |
5 | Get page id
6 |
7 | Returns **[String][1]**
8 |
9 | ## getName
10 |
11 | Get page name
12 |
13 | Returns **[String][1]**
14 |
15 | ## setName
16 |
17 | Update page name
18 |
19 | ### Parameters
20 |
21 | * `name` **[String][1]** New page name
22 |
23 | ### Examples
24 |
25 | ```javascript
26 | page.setName('New name');
27 | ```
28 |
29 | ## getAllFrames
30 |
31 | Get all frames
32 |
33 | ### Examples
34 |
35 | ```javascript
36 | const arrayOfFrames = page.getAllFrames();
37 | ```
38 |
39 | Returns **[Array][2]\ **
40 |
41 | ## getMainFrame
42 |
43 | Get the first frame of the page (identified always as the main one)
44 |
45 | ### Examples
46 |
47 | ```javascript
48 | const mainFrame = page.getMainFrame();
49 | ```
50 |
51 | Returns **Frame**
52 |
53 | ## getMainComponent
54 |
55 | Get the root component (usually is the `wrapper` component) from the main frame
56 |
57 | ### Examples
58 |
59 | ```javascript
60 | const rootComponent = page.getMainComponent();
61 | console.log(rootComponent.toHTML());
62 | ```
63 |
64 | Returns **Component**
65 |
66 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
67 |
68 | [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
69 |
--------------------------------------------------------------------------------
/docs/api/state.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## State
4 |
5 |
6 |
7 | ### Properties
8 |
9 | * `name` **[String][1]** State name, eg. `hover`, `nth-of-type(2n)`
10 | * `label` **[String][1]** State label, eg. `Hover`, `Even/Odd`
11 |
12 | ### getName
13 |
14 | Get state name
15 |
16 | Returns **[String][1]**
17 |
18 | ### getLabel
19 |
20 | Get state label. If label was not provided, the name will be returned.
21 |
22 | Returns **[String][1]**
23 |
24 | [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
25 |
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Faq
3 | ---
4 |
5 | # FAQ
6 |
7 | Coming soon
8 |
--------------------------------------------------------------------------------
/docs/guides/Telemetry.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: GrapesJS Telemetry
3 | ---
4 |
5 | # GrapesJS Telemetry
6 |
7 | We collect and use data to improve GrapesJS. This page explains what data we collect and how we use it.
8 |
9 | ## What data we collect
10 |
11 | We collect the following data:
12 |
13 | - **domain**: The domain of the website where GrapesJS is used.
14 | - **version**: The version of GrapesJS used.
15 | - **timestamp**: The time when the editor is loaded.
16 |
17 | ## How we use data
18 |
19 | We use data to:
20 |
21 | - **Improve GrapesJS**: We use data to improve GrapesJS. For example, we use data to identify bugs and fix them.
22 | - **Analyze usage**: We use data to analyze how GrapesJS is used. For example, we use data to understand which features are used most often.
23 | - **Provide support**: We use data to provide support to users. For example, we use data to understand how users interact with GrapesJS.
24 |
25 | ## How to opt-out
26 |
27 | You can opt-out of data collection by setting the `telemetry` option to `false` when initializing GrapesJS:
28 |
29 | ```js
30 | const editor = grapesjs.init({
31 | // ...
32 | telemetry: false,
33 | });
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@grapesjs/docs",
3 | "private": true,
4 | "description": "Free and Open Source Web Builder Framework",
5 | "version": "0.22.6",
6 | "license": "BSD-3-Clause",
7 | "homepage": "http://grapesjs.com",
8 | "files": [
9 | "dist",
10 | "locale",
11 | "src/styles"
12 | ],
13 | "sideEffects": [
14 | "*.vue",
15 | "*.css",
16 | "*.scss"
17 | ],
18 | "dependencies": {
19 | "codemirror": "5.63.0",
20 | "codemirror-formatting": "1.0.0",
21 | "html-entities": "~1.4.0"
22 | },
23 | "devDependencies": {
24 | "@types/markdown-it": "14.1.2",
25 | "@vuepress/plugin-google-analytics": "1.8.2",
26 | "@vuepress/types": "1.9.10",
27 | "documentation": "14.0.3",
28 | "postcss": "8",
29 | "sass": "1.80.3",
30 | "vuepress": "1.9.10",
31 | "webpack": "4.0.0",
32 | "whatwg-fetch": "3.6.20"
33 | },
34 | "scripts": {
35 | "docs": "vuepress dev .",
36 | "docs:api": "node ./api.mjs",
37 | "build": "npm run docs:api && vuepress build ."
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/cli/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
3 | };
4 |
--------------------------------------------------------------------------------
/packages/cli/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= title %>
6 |
10 |
11 |
18 |
19 |
20 |
21 |
22 | This is a demo content generated from GrapesJS CLI . For the development, you should create a _index.html
23 | template file (might be a copy of this one) and on the next server start the new file will be served, and it
24 | will be ignored by git.
25 |
26 |
27 |
28 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/packages/cli/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'jest';
2 |
3 | const config: Config = {
4 | testEnvironment: 'node',
5 | verbose: true,
6 | modulePaths: ['/src'],
7 | testMatch: ['/test/**/*.(t|j)s'],
8 | };
9 |
10 | export default config;
11 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grapesjs-cli",
3 | "version": "4.1.3",
4 | "description": "GrapesJS CLI tool for the plugin development",
5 | "bin": {
6 | "grapesjs-cli": "dist/cli.js"
7 | },
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "cross-env BUILD_MODE=production webpack --config ./webpack.cli.ts",
13 | "build:watch": "webpack --config ./webpack.cli.ts --watch",
14 | "lint": "eslint src",
15 | "patch": "npm version patch -m 'Bump v%s'",
16 | "test": "jest"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/GrapesJS/grapesjs.git"
21 | },
22 | "keywords": [
23 | "grapesjs",
24 | "plugin",
25 | "dev",
26 | "cli"
27 | ],
28 | "author": "Artur Arseniev",
29 | "license": "BSD-3-Clause",
30 | "dependencies": {
31 | "@babel/core": "7.25.2",
32 | "@babel/plugin-transform-runtime": "7.26.10",
33 | "@babel/preset-env": "7.25.4",
34 | "@babel/runtime": "7.25.6",
35 | "babel-loader": "9.1.3",
36 | "chalk": "^4.1.2",
37 | "core-js": "3.38.1",
38 | "dts-bundle-generator": "^8.0.1",
39 | "html-webpack-plugin": "5.6.3",
40 | "inquirer": "^8.2.5",
41 | "listr": "^0.14.3",
42 | "lodash.template": "^4.5.0",
43 | "rimraf": "^4.1.2",
44 | "spdx-license-list": "^6.6.0",
45 | "terser-webpack-plugin": "^5.3.14",
46 | "webpack": "5.94.0",
47 | "webpack-cli": "5.1.4",
48 | "webpack-dev-server": "5.1.0",
49 | "yargs": "^17.6.2"
50 | },
51 | "devDependencies": {
52 | "@types/webpack-node-externals": "^3.0.0",
53 | "copy-webpack-plugin": "^11.0.0",
54 | "fork-ts-checker-webpack-plugin": "^8.0.0",
55 | "webpack-node-externals": "^3.0.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/cli/src/banner.txt:
--------------------------------------------------------------------------------
1 | ______ _______ ________ ____
2 | / ____/________ _____ ___ _____ / / ___/ / ____/ / / _/
3 | / / __/ ___/ __ `/ __ \/ _ \/ ___/_ / /\__ \______/ / / / / /
4 | / /_/ / / / /_/ / /_/ / __(__ ) /_/ /___/ /_____/ /___/ /____/ /
5 | \____/_/ \__,_/ .___/\___/____/\____//____/ \____/_____/___/
6 | /_/
--------------------------------------------------------------------------------
/packages/cli/src/main.ts:
--------------------------------------------------------------------------------
1 | export { default as init } from './init';
2 | export { default as build } from './build';
3 | export { default as serve } from './serve';
4 |
--------------------------------------------------------------------------------
/packages/cli/src/serve.ts:
--------------------------------------------------------------------------------
1 | import { printRow, buildWebpackArgs, log, normalizeJsonOpt } from './utils';
2 | import webpack from 'webpack';
3 | import webpackDevServer from 'webpack-dev-server';
4 | import webpackConfig from './webpack.config';
5 | import chalk from 'chalk';
6 |
7 | interface ServeOptions {
8 | host?: string;
9 | port?: number;
10 | verbose?: boolean;
11 | }
12 |
13 | /**
14 | * Start up the development server
15 | * @param {Object} opts
16 | */
17 | export default (opts: ServeOptions = {}) => {
18 | printRow('Start the development server...');
19 | const { host, port } = opts;
20 | const isVerb = opts.verbose;
21 | const resultWebpackConf = {
22 | ...webpackConfig({ args: buildWebpackArgs(opts), cmdOpts: opts }),
23 | ...normalizeJsonOpt(opts, 'webpack'),
24 | };
25 | const devServerConf = {
26 | ...resultWebpackConf.devServer,
27 | open: true,
28 | ...normalizeJsonOpt(opts, 'devServer'),
29 | };
30 |
31 | if (host !== 'localhost') {
32 | devServerConf.host = host;
33 | }
34 |
35 | if (port !== 8080) {
36 | devServerConf.port = port;
37 | }
38 |
39 | if (isVerb) {
40 | log(chalk.yellow('Server config:\n'), opts, '\n');
41 | log(chalk.yellow('DevServer config:\n'), devServerConf, '\n');
42 | }
43 |
44 | const compiler = webpack(resultWebpackConf);
45 | const server = new webpackDevServer(devServerConf, compiler);
46 |
47 | server.start();
48 | };
49 |
--------------------------------------------------------------------------------
/packages/cli/src/template/.gitignore-t:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | private/
3 | /locale
4 | node_modules/
5 | *.log
6 | _index.html
7 | dist/
8 | stats.json
--------------------------------------------------------------------------------
/packages/cli/src/template/.npmignore-t:
--------------------------------------------------------------------------------
1 | .*
2 | *.log
3 | *.html
4 | **/tsconfig.json
5 | **/webpack.config.js
6 | node_modules
7 | src
--------------------------------------------------------------------------------
/packages/cli/src/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= rName %>",
3 | "version": "1.0.0",
4 | "description": "<%= name %>",
5 | "main": "dist/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/<%= user %>/<%= rName %>.git"
9 | },
10 | "scripts": {
11 | "start": "grapesjs-cli serve",
12 | "build": "grapesjs-cli build",
13 | "bump": "npm version patch -m 'Bump v%s'"
14 | },
15 | "keywords": [
16 | "grapesjs",
17 | "plugin"
18 | ],
19 | "devDependencies": {
20 | "grapesjs-cli": "^<%= version %>"
21 | },
22 | "license": "<%= license %>"
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cli/src/template/src/blocks.js:
--------------------------------------------------------------------------------
1 | export default (editor, opts = {}) => {
2 | const bm = editor.BlockManager;
3 |
4 | bm.add('MY-BLOCK', {
5 | label: 'My block',
6 | content: { type: 'MY-COMPONENT' },
7 | // media: '... ',
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/packages/cli/src/template/src/components.js:
--------------------------------------------------------------------------------
1 | export default (editor, opts = {}) => {
2 | const domc = editor.DomComponents;
3 |
4 | domc.addType('MY-COMPONENT', {
5 | model: {
6 | defaults: {
7 | // Default props
8 | },
9 | },
10 | view: {},
11 | });
12 | };
13 |
--------------------------------------------------------------------------------
/packages/cli/src/template/src/index.js:
--------------------------------------------------------------------------------
1 | <% if(components){ %>import loadComponents from './components';<% } %>
2 | <% if(blocks){ %>import loadBlocks from './blocks';<% } %>
3 | <% if(i18n){ %>import en from './locale/en';<% } %>
4 |
5 | export default (editor, opts = {}) => {
6 | const options = { ...{
7 | <% if(i18n){ %>i18n: {},<% } %>
8 | // default options
9 | }, ...opts };
10 |
11 | <% if(components){ %>// Add components
12 | loadComponents(editor, options);<% } %>
13 | <% if(blocks){ %>// Add blocks
14 | loadBlocks(editor, options);<% } %>
15 | <% if(i18n){ %>// Load i18n files
16 | editor.I18n && editor.I18n.addMessages({
17 | en,
18 | ...options.i18n,
19 | });<% } %>
20 |
21 | // TODO Remove
22 | editor.on('load', () =>
23 | editor.addComponents(
24 | `
25 | Content loaded from the plugin
26 |
`,
27 | { at: 0 }
28 | ))
29 | };
30 |
--------------------------------------------------------------------------------
/packages/cli/src/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "sourceMap": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": false
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "noImplicitThis": true,
5 | "moduleResolution": "node",
6 | "noUnusedLocals": true,
7 | "allowUnreachableCode": false,
8 | "module": "commonjs",
9 | "target": "es2016",
10 | "outDir": "dist",
11 | "esModuleInterop": true,
12 | "declaration": true,
13 | "noImplicitReturns": false,
14 | "noImplicitAny": false,
15 | "strictNullChecks": false,
16 | "resolveJsonModule": true,
17 | "emitDecoratorMetadata": true,
18 | "experimentalDecorators": true
19 | },
20 | "include": ["src/cli.ts"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .settings/
3 | .sass-cache/
4 | .project
5 | .idea
6 | npm-debug.log*
7 | yarn-error.log
8 | yarn.lock
9 | style/.sass-cache/
10 | stats.json
11 |
12 | img/
13 | images/
14 | private/
15 | vendor/
16 | coverage/
17 | node_modules/
18 | bower_components/
19 | grapesjs-*.tgz
20 | _index.html
21 | index.html
22 | docs
23 | .github
24 | test
25 | .editorconfig
26 | .eslintrc
27 | .travis.yml
28 | *.md
29 | webpack.config.js
30 | tsconfig.json
31 |
--------------------------------------------------------------------------------
/packages/core/.yarnrc:
--------------------------------------------------------------------------------
1 | registry "https://registry.npmjs.org"
2 |
--------------------------------------------------------------------------------
/packages/core/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017-current, Artur Arseniev
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | - Redistributions in binary form must reproduce the above copyright notice, this
10 | list of conditions and the following disclaimer in the documentation and/or
11 | other materials provided with the distribution.
12 | - Neither the name "GrapesJS" nor the names of its contributors may be
13 | used to endorse or promote products derived from this software without
14 | specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/packages/core/babel.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@babel/core')} */
2 | module.exports = {
3 | env: {
4 | test: {
5 | presets: ['@babel/preset-env', '@babel/preset-typescript'],
6 | },
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/core/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | testEnvironment: 'jsdom',
4 | moduleFileExtensions: ['js', 'ts'],
5 | verbose: true,
6 | testEnvironmentOptions: {
7 | url: 'http://localhost/',
8 | },
9 | modulePaths: ['/src'],
10 | testMatch: ['/test/specs/**/*.(t|j)s'],
11 | setupFilesAfterEnv: ['/test/setup.js'],
12 | };
13 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/CollectionWithCategories.ts:
--------------------------------------------------------------------------------
1 | import { isString } from 'underscore';
2 | import { Collection, Model } from '../common';
3 | import Categories from './ModuleCategories';
4 | import Category, { CategoryProperties } from './ModuleCategory';
5 | import { isObject } from '../utils/mixins';
6 |
7 | interface ModelWithCategoryProps {
8 | category?: string | CategoryProperties;
9 | }
10 |
11 | const CATEGORY_KEY = 'category';
12 |
13 | export abstract class CollectionWithCategories> extends Collection {
14 | abstract getCategories(): Categories;
15 |
16 | initCategory(model: T) {
17 | let category = model.get(CATEGORY_KEY);
18 | const isDefined = category instanceof Category;
19 |
20 | // Ensure the category exists and it's not already initialized
21 | if (category && !isDefined) {
22 | if (isString(category)) {
23 | category = { id: category, label: category };
24 | } else if (isObject(category) && !category.id) {
25 | category.id = category.label;
26 | }
27 |
28 | const catModel = this.getCategories().add(category);
29 | model.set(CATEGORY_KEY, catModel as any, { silent: true });
30 | return catModel;
31 | } else if (isDefined) {
32 | const catModel = category as unknown as Category;
33 | this.getCategories().add(catModel);
34 | return catModel;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/ModuleCategories.ts:
--------------------------------------------------------------------------------
1 | import { isArray, isString } from 'underscore';
2 | import { AddOptions, Collection } from '../common';
3 | import { normalizeKey } from '../utils/mixins';
4 | import EditorModel from '../editor/model/Editor';
5 | import Category, { CategoryProperties } from './ModuleCategory';
6 |
7 | type CategoryCollectionParams = ConstructorParameters>;
8 |
9 | interface CategoryOptions {
10 | events?: { update?: string };
11 | em?: EditorModel;
12 | }
13 |
14 | export default class Categories extends Collection {
15 | constructor(models?: CategoryCollectionParams[0], opts: CategoryOptions = {}) {
16 | super(models, opts);
17 | const { events, em } = opts;
18 | const evUpdate = events?.update;
19 | if (em) {
20 | evUpdate &&
21 | this.on('change', (category, options) =>
22 | em.trigger(evUpdate, { category, changes: category.changedAttributes(), options }),
23 | );
24 | }
25 | }
26 |
27 | /** @ts-ignore */
28 | add(model: (CategoryProperties | Category)[] | CategoryProperties | Category, opts?: AddOptions) {
29 | const models = isArray(model) ? model : [model];
30 | models.forEach((md) => md && (md.id = normalizeKey(`${md.id}`)));
31 | return super.add(model, opts);
32 | }
33 |
34 | get(id: string | Category) {
35 | return super.get(isString(id) ? normalizeKey(id) : id);
36 | }
37 | }
38 |
39 | Categories.prototype.model = Category;
40 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/ModuleCollection.ts:
--------------------------------------------------------------------------------
1 | import { isArray, isUndefined } from 'underscore';
2 | import { AddOptions, Collection } from '../common';
3 | import ModuleModel from './ModuleModel';
4 |
5 | type ModuleExt = TModel extends ModuleModel ? M : unknown;
6 | type ModelConstructor = { new (mod: ModuleExt, attr: any): TModel };
7 |
8 | export default class ModuleCollection extends Collection {
9 | module!: ModuleExt;
10 | private newModel!: ModelConstructor;
11 |
12 | add(model: Array> | TModel, options?: AddOptions): TModel;
13 | add(models: Array> | TModel>, options?: AddOptions): TModel[];
14 | add(model?: unknown, options?: AddOptions): any {
15 | //Note: the undefined case needed because backbonejs not handle the reset() correctly
16 | var models = isArray(model) ? model : !isUndefined(model) ? [model] : undefined;
17 |
18 | models = models?.map((m) => (m instanceof this.newModel ? m : new this.newModel(this.module, m))) ?? [undefined];
19 |
20 | return super.add(isArray(model) ? models : models[0], options);
21 | }
22 |
23 | constructor(
24 | module: ModuleExt,
25 | models: TModel[] | Array>,
26 | modelConstructor: ModelConstructor,
27 | ) {
28 | super(models, { module, modelConstructor });
29 | }
30 |
31 | preinitialize(models?: TModel[] | Array>, options?: any) {
32 | this.newModel = options.modelConstructor;
33 | this.module = options.module;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/ModuleModel.ts:
--------------------------------------------------------------------------------
1 | import { Model, ObjectHash, SetOptions, CombinedModelConstructorOptions } from '../common';
2 | import EditorModel from '../editor/model/Editor';
3 | import Module, { IBaseModule } from './Module';
4 |
5 | export default class ModuleModel<
6 | TModule extends IBaseModule = Module,
7 | T extends ObjectHash = any,
8 | S = SetOptions,
9 | E = any,
10 | > extends Model {
11 | private _module: TModule;
12 |
13 | constructor(module: TModule, attributes?: T, options?: CombinedModelConstructorOptions) {
14 | super(attributes, options);
15 | this._module = module;
16 | }
17 |
18 | public get module() {
19 | return this._module;
20 | }
21 |
22 | public get config(): TModule extends IBaseModule ? C : unknown {
23 | return this._module.config;
24 | }
25 |
26 | public get em(): EditorModel {
27 | return this._module.em;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/ModuleView.ts:
--------------------------------------------------------------------------------
1 | import ModuleCollection from './ModuleCollection';
2 | import ModuleModel from './ModuleModel';
3 | import { IBaseModule } from './Module';
4 | import { View } from '../common';
5 | import EditorModel from '../editor/model/Editor';
6 |
7 | type ModuleFromModel = TModel extends ModuleModel ? M : unknown;
8 | type ModuleModelExt =
9 | TItem extends ModuleCollection ? ModuleFromModel : TItem extends ModuleModel ? M : unknown;
10 |
11 | // type TCollection = TItem extends ModuleCollection ? TItem : unknown;
12 |
13 | export default class ModuleView<
14 | TModel extends ModuleModel | ModuleCollection = ModuleModel,
15 | TElement extends Element = HTMLElement,
16 | > extends View {
17 | protected get pfx() {
18 | return this.ppfx + (this.config as any).stylePrefix || '';
19 | }
20 |
21 | protected get ppfx() {
22 | return this.em.config.stylePrefix || '';
23 | }
24 |
25 | collection!: TModel extends ModuleModel ? ModuleCollection : TModel;
26 |
27 | protected get module(): ModuleModelExt {
28 | return (this.model as any)?.module ?? this.collection.module;
29 | }
30 |
31 | protected get em(): EditorModel {
32 | return this.module.em;
33 | }
34 |
35 | protected get config(): ModuleModelExt extends IBaseModule ? C : unknown {
36 | return this.module.config as any;
37 | }
38 |
39 | public className!: string;
40 |
41 | preinitialize(options?: any) {
42 | this.className = '';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/core/src/abstract/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ModuleModel } from './ModuleModel';
2 | export { default as ModuleCollection } from './ModuleCollection';
3 | export { default as ModuleView } from './ModuleView';
4 | export { default as Module } from './Module';
5 |
--------------------------------------------------------------------------------
/packages/core/src/asset_manager/model/AssetImage.ts:
--------------------------------------------------------------------------------
1 | import Asset from './Asset';
2 |
3 | export default class AssetImage extends Asset {
4 | defaults() {
5 | return {
6 | ...Asset.getDefaults(),
7 | type: 'image',
8 | unitDim: 'px',
9 | height: 0,
10 | width: 0,
11 | };
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/asset_manager/model/Assets.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import Asset from './Asset';
3 | import AssetImage from './AssetImage';
4 | import AssetImageView from '../view/AssetImageView';
5 | import TypeableCollection from '../../domain_abstract/model/TypeableCollection';
6 |
7 | const TypeableCollectionExt = Collection.extend(TypeableCollection);
8 |
9 | export default class Assets extends TypeableCollectionExt {}
10 |
11 | Assets.prototype.types = [
12 | {
13 | id: 'image',
14 | model: AssetImage,
15 | view: AssetImageView,
16 | isType(value: string) {
17 | if (typeof value == 'string') {
18 | return {
19 | type: 'image',
20 | src: value,
21 | };
22 | }
23 | return value;
24 | },
25 | },
26 | ];
27 |
--------------------------------------------------------------------------------
/packages/core/src/block_manager/config/config.ts:
--------------------------------------------------------------------------------
1 | import Editor from '../../editor';
2 | import Block, { BlockProperties } from '../model/Block';
3 |
4 | export interface BlockManagerConfig {
5 | /**
6 | * Specify the element to use as a container, string (query) or HTMLElement.
7 | * With the empty value, nothing will be rendered.
8 | * @default ''
9 | */
10 | appendTo?: HTMLElement | string;
11 | /**
12 | * Default blocks.
13 | * @default []
14 | */
15 | blocks?: BlockProperties[];
16 | /**
17 | * Append blocks to canvas on click.
18 | * With the `true` value, it will try to append the block to the selected component
19 | * If there is no selected component, the block will be appened to the wrapper.
20 | * You can also pass a function to this option, use it as a catch-all for all block
21 | * clicks and implement a custom logic for each block.
22 | * @default false
23 | * @example
24 | * // Example with a function
25 | * appendOnClick: (block, editor) => {
26 | * if (block.get('id') === 'some-id')
27 | * editor.getSelected().append(block.get('content'))
28 | * else
29 | * editor.getWrapper().append(block.get('content'))
30 | * }
31 | */
32 | appendOnClick?: boolean | ((block: Block, editor: Editor, opts: { event: Event }) => void);
33 | /**
34 | * Avoid rendering the default block manager UI.
35 | * More about it here: https://grapesjs.com/docs/modules/Blocks.html#customization
36 | * @default false
37 | */
38 | custom?: boolean;
39 | }
40 |
41 | const config: () => BlockManagerConfig = () => ({
42 | appendTo: '',
43 | blocks: [],
44 | appendOnClick: false,
45 | custom: false,
46 | });
47 |
48 | export default config;
49 |
--------------------------------------------------------------------------------
/packages/core/src/block_manager/model/Blocks.ts:
--------------------------------------------------------------------------------
1 | import { CollectionWithCategories } from '../../abstract/CollectionWithCategories';
2 | import EditorModel from '../../editor/model/Editor';
3 | import Block from './Block';
4 |
5 | export default class Blocks extends CollectionWithCategories {
6 | em: EditorModel;
7 |
8 | constructor(coll: any[], options: { em: EditorModel }) {
9 | super(coll);
10 | this.em = options.em;
11 | this.on('add', this.handleAdd);
12 | }
13 |
14 | getCategories() {
15 | return this.em.Blocks.getCategories();
16 | }
17 |
18 | handleAdd(model: Block) {
19 | this.initCategory(model);
20 | }
21 | }
22 |
23 | Blocks.prototype.model = Block;
24 |
--------------------------------------------------------------------------------
/packages/core/src/canvas/model/Frames.ts:
--------------------------------------------------------------------------------
1 | import { bindAll } from 'underscore';
2 | import CanvasModule from '..';
3 | import { ModuleCollection } from '../../abstract';
4 | import Page from '../../pages/model/Page';
5 | import Frame from './Frame';
6 |
7 | export default class Frames extends ModuleCollection {
8 | loadedItems = 0;
9 | itemsToLoad = 0;
10 | page?: Page;
11 |
12 | constructor(module: CanvasModule, models: Frame[] | Array> = []) {
13 | super(module, models, Frame);
14 | bindAll(this, 'itemLoaded');
15 | this.on('add', this.onAdd);
16 | this.on('reset', this.onReset);
17 | this.on('remove', this.onRemove);
18 | this.forEach((frame) => this.onAdd(frame));
19 | }
20 |
21 | onAdd(frame: Frame) {
22 | this.module.framesById[frame.id] = frame;
23 | }
24 |
25 | onReset(m: Frame, opts?: { previousModels?: Frame[] }) {
26 | const prev = opts?.previousModels || [];
27 | prev.map((p) => this.onRemove(p));
28 | }
29 |
30 | onRemove(frame: Frame) {
31 | frame.onRemove();
32 | delete this.module.framesById[frame.id];
33 | }
34 |
35 | initRefs() {
36 | this.forEach((frame) => frame.initRefs());
37 | }
38 |
39 | itemLoaded() {
40 | this.loadedItems++;
41 |
42 | if (this.loadedItems >= this.itemsToLoad) {
43 | this.trigger('loaded:all');
44 | this.listenToLoadItems(false);
45 | }
46 | }
47 |
48 | listenToLoad() {
49 | this.loadedItems = 0;
50 | this.itemsToLoad = this.length;
51 | this.listenToLoadItems(true);
52 | }
53 |
54 | listenToLoadItems(on: boolean) {
55 | this.forEach((item) => item[on ? 'on' : 'off']('loaded', this.itemLoaded));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/core/src/canvas/view/FramesView.ts:
--------------------------------------------------------------------------------
1 | import CanvasModule from '..';
2 | import ModuleDomainViews from '../../abstract/ModuleDomainViews';
3 | import Frames from '../model/Frames';
4 | import CanvasView from './CanvasView';
5 | import FrameWrapView from './FrameWrapView';
6 |
7 | export default class FramesView extends ModuleDomainViews {
8 | canvasView: CanvasView;
9 | private _module: CanvasModule;
10 |
11 | constructor(opts = {}, config: any) {
12 | super(opts, true);
13 | this.listenTo(this.collection, 'reset', this.render);
14 | this.canvasView = config.canvasView;
15 | this._module = config.module;
16 | }
17 |
18 | onRemoveBefore(items: FrameWrapView[], opts = {}) {
19 | items.forEach((item) => item.remove(opts));
20 | }
21 |
22 | onRender() {
23 | const { $el, ppfx } = this;
24 | $el.attr({ class: `${ppfx}frames` });
25 | }
26 |
27 | clearItems() {
28 | const items = this.viewCollection || [];
29 | items.forEach((item) => item.remove());
30 | this.viewCollection = [];
31 | }
32 |
33 | protected renderView(item: any, type: string) {
34 | return new FrameWrapView(item, this.canvasView);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/code_manager/config/config.ts:
--------------------------------------------------------------------------------
1 | export interface CodeManagerConfig {
2 | /**
3 | * Style prefix.
4 | * @default 'cm-'
5 | */
6 | stylePrefix?: string;
7 |
8 | /**
9 | * Pass default options to code viewer
10 | * @default {}
11 | */
12 | optsCodeViewer?: Record;
13 | }
14 |
15 | const config: () => CodeManagerConfig = () => ({
16 | stylePrefix: 'cm-',
17 | optsCodeViewer: {},
18 | });
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/packages/core/src/code_manager/model/JsonGenerator.ts:
--------------------------------------------------------------------------------
1 | import { each } from 'underscore';
2 | import { Model, Collection } from '../../common';
3 | import Component from '../../dom_components/model/Component';
4 |
5 | type ComponentProps = Record;
6 |
7 | export default class JsonGenerator extends Model {
8 | build(model: Component) {
9 | // @ts-ignore
10 | const json = model.toJSON() as ComponentProps;
11 | this.beforeEach(json);
12 |
13 | each(json, (v, attr) => {
14 | const obj = json[attr];
15 | if (obj instanceof Model) {
16 | // @ts-ignore
17 | json[attr] = this.build(obj);
18 | } else if (obj instanceof Collection) {
19 | const coll = obj;
20 | json[attr] = [];
21 | if (coll.length) {
22 | coll.forEach((el, index) => {
23 | json[attr][index] = this.build(el);
24 | });
25 | }
26 | }
27 | });
28 |
29 | return json;
30 | }
31 |
32 | /**
33 | * Execute on each object
34 | * @param {Object} obj
35 | */
36 | beforeEach(obj: ComponentProps) {
37 | delete obj.status;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/src/code_manager/view/EditorView.ts:
--------------------------------------------------------------------------------
1 | import { View } from '../../common';
2 | import html from '../../utils/html';
3 |
4 | export default class CodeEditorView extends View {
5 | pfx?: string;
6 | config!: Record;
7 |
8 | template({ pfx, codeName, label }: { pfx: string; codeName: string; label: string }) {
9 | return html`
10 |
14 | `;
15 | }
16 |
17 | initialize(o: any) {
18 | this.config = o.config || {};
19 | this.pfx = this.config.stylePrefix;
20 | }
21 |
22 | render() {
23 | const { model, pfx, $el } = this;
24 | const obj = model.toJSON();
25 | const toAppend = model.get('input') || (model as any).getElement?.();
26 | obj.pfx = pfx;
27 | $el.html(this.template(obj));
28 | $el.attr('class', `${pfx}editor-c`);
29 | $el.find(`#${pfx}code`).append(toAppend);
30 | return this;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/CanvasClear.ts:
--------------------------------------------------------------------------------
1 | import { CommandObject } from './CommandAbstract';
2 |
3 | export default {
4 | run(ed) {
5 | ed.Components.clear();
6 | ed.Css.clear();
7 | },
8 | } as CommandObject;
9 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentDelete.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from 'underscore';
2 | import Component from '../../dom_components/model/Component';
3 | import { CommandObject } from './CommandAbstract';
4 |
5 | const command: CommandObject<{ component?: Component }> = {
6 | run(ed, s, opts = {}) {
7 | const removed: Component[] = [];
8 | let components = opts.component || ed.getSelectedAll();
9 | components = isArray(components) ? [...components] : [components];
10 |
11 | components.filter(Boolean).forEach((component) => {
12 | if (!component.get('removable')) {
13 | return this.em.logWarning('The element is not removable', {
14 | component,
15 | });
16 | }
17 |
18 | removed.push(component);
19 | const cmp = component.delegate?.remove?.(component) || component;
20 | cmp.remove();
21 | });
22 |
23 | ed.selectRemove(removed);
24 |
25 | return removed;
26 | },
27 | };
28 |
29 | export default command;
30 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentEnter.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../dom_components/model/Component';
2 | import { CommandObject } from './CommandAbstract';
3 |
4 | export default {
5 | run(ed) {
6 | if (!ed.Canvas.hasFocus()) return;
7 | const toSelect: Component[] = [];
8 |
9 | ed.getSelectedAll().forEach((component) => {
10 | const coll = component.components();
11 | const next = coll && coll.filter((c: any) => c.get('selectable'))[0];
12 | next && toSelect.push(next);
13 | });
14 |
15 | toSelect.length && ed.select(toSelect);
16 | },
17 | } as CommandObject;
18 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentExit.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../dom_components/model/Component';
2 | import { CommandObject } from './CommandAbstract';
3 |
4 | export default {
5 | run(ed, snd, opts = {}) {
6 | if (!ed.Canvas.hasFocus() && !opts.force) return;
7 | const toSelect: Component[] = [];
8 |
9 | ed.getSelectedAll().forEach((component) => {
10 | let next = component.parent();
11 |
12 | // Recurse through the parent() chain until a selectable parent is found
13 | while (next && !next.get('selectable')) {
14 | next = next.parent();
15 | }
16 |
17 | next && toSelect.push(next);
18 | });
19 |
20 | toSelect.length && ed.select(toSelect);
21 | },
22 | } as CommandObject;
23 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentNext.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../dom_components/model/Component';
2 | import { CommandObject } from './CommandAbstract';
3 |
4 | export default {
5 | run(ed) {
6 | if (!ed.Canvas.hasFocus()) return;
7 | const toSelect: Component[] = [];
8 |
9 | ed.getSelectedAll().forEach((cmp) => {
10 | const parent = cmp.parent();
11 | if (!parent) return;
12 |
13 | const len = parent.components().length;
14 | let incr = 0;
15 | let at = 0;
16 | let next: any;
17 |
18 | // Get the next selectable component
19 | do {
20 | incr++;
21 | at = cmp.index() + incr;
22 | next = at <= len ? parent.getChildAt(at) : null;
23 | } while (next && !next.get('selectable'));
24 |
25 | toSelect.push(next || cmp);
26 | });
27 |
28 | toSelect.length && ed.select(toSelect);
29 | },
30 | } as CommandObject;
31 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentPrev.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../dom_components/model/Component';
2 | import { CommandObject } from './CommandAbstract';
3 |
4 | export default {
5 | run(ed) {
6 | if (!ed.Canvas.hasFocus()) return;
7 | const toSelect: Component[] = [];
8 |
9 | ed.getSelectedAll().forEach((cmp) => {
10 | const parent = cmp.parent();
11 | if (!parent) return;
12 |
13 | let incr = 0;
14 | let at = 0;
15 | let next: any;
16 |
17 | // Get the first selectable component
18 | do {
19 | incr++;
20 | at = cmp.index() - incr;
21 | next = at >= 0 ? parent.getChildAt(at) : null;
22 | } while (next && !next.get('selectable'));
23 |
24 | toSelect.push(next || cmp);
25 | });
26 |
27 | toSelect.length && ed.select(toSelect);
28 | },
29 | } as CommandObject;
30 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/ComponentStyleClear.ts:
--------------------------------------------------------------------------------
1 | import { flatten } from 'underscore';
2 | import CssRule from '../../css_composer/model/CssRule';
3 | import { CommandObject } from './CommandAbstract';
4 |
5 | export default {
6 | run(ed, s, opts = {}) {
7 | const { target } = opts;
8 | let toRemove: CssRule[] = [];
9 |
10 | if (!target.get('styles')) return toRemove;
11 |
12 | // Find all components in the project, of the target component type
13 | const type = target.get('type');
14 | const wrappers = ed.Pages.getAllWrappers();
15 | const len = flatten(wrappers.map((wrp) => wrp.findType(type))).length;
16 |
17 | // Remove component related styles only if there are no more components
18 | // of that type in the project
19 | if (!len) {
20 | const rules = ed.CssComposer.getAll();
21 | toRemove = rules.filter((rule) => rule.get('group') === `cmp:${type}`);
22 | rules.remove(toRemove);
23 | }
24 |
25 | return toRemove;
26 | },
27 | } as CommandObject;
28 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/CopyComponent.ts:
--------------------------------------------------------------------------------
1 | import { CommandObject } from './CommandAbstract';
2 |
3 | export default {
4 | run(ed) {
5 | const em = ed.getModel();
6 | const models = [...ed.getSelectedAll()].map((md) => md.delegate?.copy?.(md) || md).filter(Boolean);
7 | models.length && em.set('clipboard', models);
8 | },
9 | } as CommandObject;
10 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/OpenBlocks.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from 'underscore';
2 | import { createEl } from '../../utils/dom';
3 | import { CommandObject } from './CommandAbstract';
4 |
5 | export default {
6 | open() {
7 | const { container, editor, bm, config } = this;
8 | const { custom, appendTo } = config;
9 |
10 | if (isFunction(custom.open)) {
11 | return custom.open(bm.__customData());
12 | }
13 |
14 | if (this.firstRender && !appendTo) {
15 | const id = 'views-container';
16 | const pn = editor.Panels;
17 | const panels = pn.getPanel(id) || pn.addPanel({ id });
18 | panels.set('appendContent', container).trigger('change:appendContent');
19 | if (!custom) container.appendChild(bm.render());
20 | }
21 |
22 | if (container) container.style.display = 'block';
23 | },
24 |
25 | close() {
26 | const { container, config } = this;
27 | const { custom } = config;
28 |
29 | if (isFunction(custom.close)) {
30 | return custom.close(this.bm.__customData());
31 | }
32 |
33 | if (container) container.style.display = 'none';
34 | },
35 |
36 | run(editor) {
37 | const bm = editor.Blocks;
38 | this.config = bm.getConfig();
39 | this.firstRender = !this.container;
40 | this.container = this.container || createEl('div');
41 | this.editor = editor;
42 | this.bm = bm;
43 | const { container } = this;
44 | bm.__behaviour({
45 | container,
46 | });
47 |
48 | if (this.config.custom) {
49 | bm.__trgCustom();
50 | }
51 |
52 | this.open();
53 | },
54 |
55 | stop() {
56 | this.close();
57 | },
58 | } as CommandObject<{}, { [k: string]: any }>;
59 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/OpenLayers.ts:
--------------------------------------------------------------------------------
1 | import { CommandObject } from './CommandAbstract';
2 |
3 | export default {
4 | run(editor) {
5 | const lm = editor.LayerManager;
6 | const pn = editor.Panels;
7 | const lmConfig = lm.getConfig();
8 |
9 | if (lmConfig.appendTo) return;
10 |
11 | if (!this.layers) {
12 | const id = 'views-container';
13 | const layers = document.createElement('div');
14 | // @ts-ignore
15 | const panels = pn.getPanel(id) || pn.addPanel({ id });
16 |
17 | if (lmConfig.custom) {
18 | lm.__trgCustom({ container: layers });
19 | } else {
20 | layers.appendChild(lm.render());
21 | }
22 |
23 | panels.set('appendContent', layers).trigger('change:appendContent');
24 | this.layers = layers;
25 | }
26 |
27 | this.layers.style.display = 'block';
28 | },
29 |
30 | stop() {
31 | const { layers } = this;
32 | layers && (layers.style.display = 'none');
33 | },
34 | } as CommandObject<{}, { [k: string]: any }>;
35 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/Resize.ts:
--------------------------------------------------------------------------------
1 | import Resizer, { ResizerOptions } from '../../utils/Resizer';
2 | import { CommandObject } from './CommandAbstract';
3 |
4 | export default {
5 | run(editor, sender, opts) {
6 | const opt = opts || {};
7 | const canvas = editor.Canvas;
8 | const canvasView = canvas.getCanvasView();
9 | const options: ResizerOptions = {
10 | appendTo: canvas.getResizerEl(),
11 | prefix: editor.getConfig().stylePrefix,
12 | posFetcher: canvasView.getElementPos.bind(canvasView),
13 | mousePosFetcher: canvas.getMouseRelativePos.bind(canvas),
14 | ...(opt.options || {}),
15 | };
16 | let { canvasResizer } = this;
17 |
18 | // Create the resizer for the canvas if not yet created
19 | if (!canvasResizer || opt.forceNew) {
20 | this.canvasResizer = new editor.Utils.Resizer(options);
21 | canvasResizer = this.canvasResizer;
22 | }
23 |
24 | canvasResizer.setOptions(options, true);
25 | canvasResizer.blur();
26 | canvasResizer.focus(opt.el);
27 | return canvasResizer;
28 | },
29 |
30 | stop() {
31 | this.canvasResizer?.blur();
32 | },
33 | } as CommandObject<{ options?: {}; forceNew?: boolean; el: HTMLElement }, { canvasResizer?: Resizer }>;
34 |
--------------------------------------------------------------------------------
/packages/core/src/commands/view/SwitchVisibility.ts:
--------------------------------------------------------------------------------
1 | import { bindAll } from 'underscore';
2 | import Frame from '../../canvas/model/Frame';
3 | import Editor from '../../editor';
4 | import { CommandObject } from './CommandAbstract';
5 | import { isDef } from '../../utils/mixins';
6 |
7 | export default {
8 | init() {
9 | bindAll(this, '_onFramesChange');
10 | },
11 |
12 | run(ed) {
13 | this.toggleVis(ed, true);
14 | },
15 |
16 | stop(ed) {
17 | this.toggleVis(ed, false);
18 | },
19 |
20 | toggleVis(ed: Editor, active = true) {
21 | if (!ed.Commands.isActive('preview')) {
22 | const cv = ed.Canvas;
23 | const mth = active ? 'on' : 'off';
24 | const canvasModel = cv.getModel();
25 | canvasModel[mth]('change:frames', this._onFramesChange);
26 | this.handleFrames(cv.getFrames(), active);
27 | }
28 | },
29 |
30 | handleFrames(frames: Frame[], active?: boolean) {
31 | frames.forEach((frame: Frame & { __ol?: boolean }) => {
32 | frame.view?.loaded && this._upFrame(frame, active);
33 |
34 | if (!frame.__ol) {
35 | frame.on('loaded', () => this._upFrame(frame));
36 | frame.__ol = true;
37 | }
38 | });
39 | },
40 |
41 | _onFramesChange(_: any, frames: Frame[]) {
42 | this.handleFrames(frames);
43 | },
44 |
45 | _upFrame(frame: Frame, active?: boolean) {
46 | const { ppfx, em, id } = this;
47 | const isActive = isDef(active) ? active : em.Commands.isActive(id as string);
48 | const method = isActive ? 'add' : 'remove';
49 | const cls = `${ppfx}dashed`;
50 | frame.view?.getBody().classList[method](cls);
51 | },
52 | } as CommandObject<
53 | {},
54 | {
55 | [key: string]: any;
56 | }
57 | >;
58 |
--------------------------------------------------------------------------------
/packages/core/src/css_composer/config/config.ts:
--------------------------------------------------------------------------------
1 | export interface CssComposerConfig {
2 | /**
3 | * Style prefix.
4 | * @default 'css-'
5 | */
6 | stylePrefix?: string;
7 |
8 | /**
9 | * Default CSS style rules
10 | */
11 | rules?: Array; // TODO
12 | }
13 |
14 | const config: () => CssComposerConfig = () => ({
15 | stylePrefix: 'css-',
16 | rules: [],
17 | });
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/packages/core/src/css_composer/model/CssRules.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import EditorModel from '../../editor/model/Editor';
3 | import CssRule, { CssRuleProperties } from './CssRule';
4 |
5 | export default class CssRules extends Collection {
6 | editor: EditorModel;
7 |
8 | constructor(props: any, opt: any) {
9 | super(props);
10 | // Inject editor
11 | this.editor = opt?.em;
12 |
13 | // This will put the listener post CssComposer.postLoad
14 | setTimeout(() => {
15 | this.on('remove', this.onRemove);
16 | this.on('add', this.onAdd);
17 | });
18 | }
19 |
20 | toJSON(opts?: any) {
21 | const result = Collection.prototype.toJSON.call(this, opts);
22 | return result.filter((rule: CssRuleProperties) => rule.style && !rule.shallow);
23 | }
24 |
25 | onAdd(model: CssRule, c: CssRules, o: any) {
26 | model.ensureSelectors(model, c, o); // required for undo
27 | }
28 |
29 | onRemove(removed: CssRule) {
30 | const em = this.editor;
31 | em.stopListening(removed);
32 | em.UndoManager.remove(removed);
33 | }
34 |
35 | /** @ts-ignore */
36 | add(models: any, opt: any = {}) {
37 | if (typeof models === 'string') {
38 | models = this.editor.get('Parser').parseCss(models);
39 | }
40 | opt.em = this.editor;
41 | return Collection.prototype.add.apply(this, [models, opt]);
42 | }
43 | }
44 |
45 | CssRules.prototype.model = CssRule;
46 |
--------------------------------------------------------------------------------
/packages/core/src/css_composer/view/CssGroupRuleView.ts:
--------------------------------------------------------------------------------
1 | import CssRuleView from './CssRuleView';
2 |
3 | export default class CssGroupRuleView extends CssRuleView {
4 | _createElement() {
5 | return document.createTextNode('');
6 | }
7 |
8 | render() {
9 | const model = this.model;
10 | const important = model.get('important');
11 | this.el.textContent = model.getDeclaration({ important });
12 | return this;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/src/css_composer/view/CssRuleView.ts:
--------------------------------------------------------------------------------
1 | import FrameView from '../../canvas/view/FrameView';
2 | import { View } from '../../common';
3 | import CssRule from '../model/CssRule';
4 |
5 | export default class CssRuleView extends View {
6 | config: any;
7 |
8 | constructor(o: any = {}) {
9 | super(o);
10 | this.config = o.config || {};
11 | const { model } = this;
12 | this.listenTo(model, 'change', this.render);
13 | this.listenTo(model, 'destroy remove', this.remove);
14 | this.listenTo(model.get('selectors'), 'change', this.render);
15 | model.setView(this);
16 | }
17 |
18 | get frameView(): FrameView {
19 | return this.config.frameView;
20 | }
21 |
22 | remove() {
23 | super.remove();
24 | this.model.removeView(this);
25 | return this;
26 | }
27 |
28 | updateStyles() {
29 | this.render();
30 | }
31 |
32 | /** @ts-ignore */
33 | tagName() {
34 | return 'style';
35 | }
36 |
37 | render() {
38 | const { model, el } = this;
39 | const important = model.get('important');
40 | el.innerHTML = model.toCSS({ important });
41 | return this;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/DataRecords.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import { DataRecordProps } from '../types';
3 | import DataRecord from './DataRecord';
4 | import DataSource from './DataSource';
5 |
6 | export default class DataRecords extends Collection> {
7 | dataSource: DataSource;
8 |
9 | constructor(models: DataRecord[] | DataRecordProps[], options: { dataSource: DataSource }) {
10 | super(models, options);
11 | this.dataSource = options.dataSource;
12 | }
13 | }
14 |
15 | DataRecords.prototype.model = DataRecord;
16 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/DataSources.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import EditorModel from '../../editor/model/Editor';
3 | import { DataRecordProps, DataSourceProps } from '../types';
4 | import DataSource from './DataSource';
5 |
6 | export default class DataSources extends Collection {
7 | em: EditorModel;
8 |
9 | constructor(models: DataSource[] | DataSourceProps[], em: EditorModel) {
10 | super(models, em);
11 | this.em = em;
12 |
13 | // @ts-ignore We need to inject `em` for pages created on reset from the Storage load
14 | this.model = (props: DataSourceProps, opts = {}) => {
15 | return new DataSource(props, { ...opts, em });
16 | };
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/TraitDataVariable.ts:
--------------------------------------------------------------------------------
1 | import DataVariable, { DataVariableProps } from './DataVariable';
2 | import Trait from '../../trait_manager/model/Trait';
3 | import { TraitProperties } from '../../trait_manager/types';
4 |
5 | export interface TraitDataVariableProps extends Omit, DataVariableProps {}
6 |
7 | export default class TraitDataVariable extends DataVariable {
8 | trait?: Trait;
9 |
10 | constructor(props: TraitDataVariableProps, options: any) {
11 | super(props, options);
12 | this.trait = options.trait;
13 | }
14 |
15 | onDataSourceChange() {
16 | const newValue = this.getDataValue();
17 | this.trait?.setTargetValue(newValue);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/ComponentDataOutput.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../../dom_components/model/Component';
2 | import { ComponentDefinitionDefined } from '../../../dom_components/model/types';
3 | import { toLowerCase } from '../../../utils/mixins';
4 | import { isComponentDataOutputType } from '../../utils';
5 |
6 | export default class ComponentDataOutput extends Component {
7 | get defaults(): ComponentDefinitionDefined {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | removable: false,
12 | draggable: false,
13 | };
14 | }
15 |
16 | static isComponent(el: HTMLElement) {
17 | return isComponentDataOutputType(toLowerCase(el.tagName));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/ConditionStatement.ts:
--------------------------------------------------------------------------------
1 | import { SimpleOperator } from './operators/BaseOperator';
2 | import { DataConditionSimpleOperation } from './types';
3 |
4 | export class ConditionStatement {
5 | constructor(
6 | private leftValue: any,
7 | private operator: SimpleOperator,
8 | private rightValue: any,
9 | ) {}
10 |
11 | evaluate(): boolean {
12 | return this.operator.evaluate(this.leftValue, this.rightValue);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../../dom_components/model/Component';
2 | import { ComponentDefinitionDefined, ToHTMLOptions } from '../../../dom_components/model/types';
3 | import { toLowerCase } from '../../../utils/mixins';
4 | import { isComponentDataOutputType } from '../../utils';
5 |
6 | export default class ConditionalOutputBase extends Component {
7 | get defaults(): ComponentDefinitionDefined {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | removable: false,
12 | draggable: false,
13 | };
14 | }
15 |
16 | static isComponent(el: HTMLElement) {
17 | return isComponentDataOutputType(toLowerCase(el.tagName));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/LogicalGroupEvaluator.ts:
--------------------------------------------------------------------------------
1 | import EditorModel from '../../../editor/model/Editor';
2 | import { DataCollectionStateMap } from '../data_collection/types';
3 | import { DataConditionEvaluator, ConditionProps } from './DataConditionEvaluator';
4 | import { BooleanOperator } from './operators/BooleanOperator';
5 |
6 | export class LogicalGroupEvaluator {
7 | constructor(
8 | private operator: BooleanOperator,
9 | private statements: ConditionProps[],
10 | private opts: { em: EditorModel; collectionsStateMap: DataCollectionStateMap },
11 | ) {}
12 |
13 | evaluate(): boolean {
14 | const results = this.statements.map((statement) => {
15 | const condition = new DataConditionEvaluator({ condition: statement }, this.opts);
16 | return condition.evaluate();
17 | });
18 |
19 | return this.operator.evaluate(results);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/constants.ts:
--------------------------------------------------------------------------------
1 | export const DataConditionIfTrueType = 'data-condition-true-content';
2 | export const DataConditionIfFalseType = 'data-condition-false-content';
3 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/operators/BaseOperator.ts:
--------------------------------------------------------------------------------
1 | import EditorModel from '../../../../editor/model/Editor';
2 | import { enumToArray } from '../../../utils';
3 | import { DataConditionSimpleOperation } from '../types';
4 |
5 | export abstract class SimpleOperator {
6 | protected em: EditorModel;
7 | protected operationString: OperationType;
8 | protected abstract operationsEnum: Record;
9 |
10 | constructor(operationString: any, opts: { em: EditorModel }) {
11 | this.operationString = operationString;
12 | this.em = opts.em;
13 | }
14 |
15 | abstract evaluate(left: any, right: any): boolean;
16 |
17 | getOperations(): DataConditionSimpleOperation[] {
18 | return enumToArray(this.operationsEnum);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/operators/BooleanOperator.ts:
--------------------------------------------------------------------------------
1 | import { enumToArray } from '../../../utils';
2 | import { DataConditionSimpleOperation } from '../types';
3 | import { SimpleOperator } from './BaseOperator';
4 |
5 | export enum BooleanOperation {
6 | and = 'and',
7 | or = 'or',
8 | xor = 'xor',
9 | }
10 |
11 | export class BooleanOperator extends SimpleOperator {
12 | protected operationsEnum = BooleanOperation;
13 |
14 | evaluate(statements: boolean[]): boolean {
15 | if (!statements?.length) return false;
16 |
17 | switch (this.operationString) {
18 | case BooleanOperation.and:
19 | return statements.every(Boolean);
20 | case BooleanOperation.or:
21 | return statements.some(Boolean);
22 | case BooleanOperation.xor:
23 | return statements.filter(Boolean).length === 1;
24 | default:
25 | this.em.logWarning(`Unsupported boolean operation: ${this.operationString}`);
26 | return false;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/operators/NumberOperator.ts:
--------------------------------------------------------------------------------
1 | import { enumToArray } from '../../../utils';
2 | import { SimpleOperator } from './BaseOperator';
3 |
4 | export enum NumberOperation {
5 | greaterThan = '>',
6 | lessThan = '<',
7 | greaterThanOrEqual = '>=',
8 | lessThanOrEqual = '<=',
9 | equals = '=',
10 | notEquals = '!=',
11 | }
12 |
13 | export class NumberOperator extends SimpleOperator {
14 | protected operationsEnum = NumberOperation;
15 |
16 | evaluate(left: number, right: number): boolean {
17 | if (typeof left !== 'number') return false;
18 |
19 | switch (this.operationString) {
20 | case NumberOperation.greaterThan:
21 | return left > right;
22 | case NumberOperation.lessThan:
23 | return left < right;
24 | case NumberOperation.greaterThanOrEqual:
25 | return left >= right;
26 | case NumberOperation.lessThanOrEqual:
27 | return left <= right;
28 | case NumberOperation.equals:
29 | return left === right;
30 | case NumberOperation.notEquals:
31 | return left !== right;
32 | default:
33 | this.em.logWarning(`Unsupported number operation: ${this.operationString}`);
34 | return false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/operators/StringOperator.ts:
--------------------------------------------------------------------------------
1 | import { SimpleOperator } from './BaseOperator';
2 |
3 | export enum StringOperation {
4 | contains = 'contains',
5 | startsWith = 'startsWith',
6 | endsWith = 'endsWith',
7 | matchesRegex = 'matchesRegex',
8 | equalsIgnoreCase = 'equalsIgnoreCase',
9 | trimEquals = 'trimEquals',
10 | }
11 |
12 | export class StringOperator extends SimpleOperator {
13 | protected operationsEnum = StringOperation;
14 |
15 | evaluate(left: string, right: string) {
16 | if (typeof left !== 'string') return false;
17 |
18 | switch (this.operationString) {
19 | case StringOperation.contains:
20 | return left.includes(right);
21 | case StringOperation.startsWith:
22 | return left.startsWith(right);
23 | case StringOperation.endsWith:
24 | return left.endsWith(right);
25 | case StringOperation.matchesRegex:
26 | if (!right) this.em.logWarning('Regex pattern must be provided.');
27 | return new RegExp(right ?? '').test(left);
28 | case StringOperation.equalsIgnoreCase:
29 | return left.toLowerCase() === right.toLowerCase();
30 | case StringOperation.trimEquals:
31 | return left.trim() === right.trim();
32 | default:
33 | this.em.logWarning(`Unsupported string operation: ${this.operationString}`);
34 | return false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/conditional_variables/types.ts:
--------------------------------------------------------------------------------
1 | import { AnyTypeOperation } from './operators/AnyTypeOperator';
2 | import { BooleanOperation } from './operators/BooleanOperator';
3 | import { NumberOperation } from './operators/NumberOperator';
4 | import { StringOperation } from './operators/StringOperator';
5 |
6 | export type DataConditionSimpleOperation = AnyTypeOperation | StringOperation | NumberOperation | BooleanOperation;
7 | export type DataConditionCompositeOperation = DataConditionSimpleOperation;
8 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/data_collection/constants.ts:
--------------------------------------------------------------------------------
1 | export const DataCollectionType = 'data-collection';
2 | export const DataCollectionItemType = 'data-collection-item';
3 | export const keyCollectionDefinition = 'dataResolver';
4 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/model/data_collection/types.ts:
--------------------------------------------------------------------------------
1 | import { DataCollectionType, keyCollectionDefinition } from './constants';
2 | import { ComponentDefinition } from '../../../dom_components/model/types';
3 | import { DataVariableProps } from '../DataVariable';
4 |
5 | export type DataCollectionDataSource = DataVariableProps;
6 |
7 | export enum DataCollectionStateType {
8 | currentIndex = 'currentIndex',
9 | startIndex = 'startIndex',
10 | currentItem = 'currentItem',
11 | currentKey = 'currentKey',
12 | endIndex = 'endIndex',
13 | collectionId = 'collectionId',
14 | totalItems = 'totalItems',
15 | remainingItems = 'remainingItems',
16 | }
17 |
18 | export interface DataCollectionState {
19 | [DataCollectionStateType.currentIndex]: number;
20 | [DataCollectionStateType.startIndex]: number;
21 | [DataCollectionStateType.currentItem]: DataVariableProps;
22 | [DataCollectionStateType.currentKey]: string | number;
23 | [DataCollectionStateType.endIndex]: number;
24 | [DataCollectionStateType.collectionId]: string;
25 | [DataCollectionStateType.totalItems]: number;
26 | [DataCollectionStateType.remainingItems]: number;
27 | }
28 |
29 | export interface DataCollectionStateMap {
30 | [key: string]: DataCollectionState;
31 | }
32 |
33 | export interface ComponentDataCollectionProps extends ComponentDefinition {
34 | type: typeof DataCollectionType;
35 | [keyCollectionDefinition]: DataCollectionProps;
36 | }
37 |
38 | export interface DataCollectionProps {
39 | collectionId: string;
40 | startIndex?: number;
41 | endIndex?: number;
42 | dataSource: DataCollectionDataSource;
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/view/ComponentDataCollectionView.ts:
--------------------------------------------------------------------------------
1 | import ComponentView from '../../dom_components/view/ComponentView';
2 | import ComponentDataCollection from '../model/data_collection/ComponentDataCollection';
3 |
4 | export default class ComponentDataCollectionView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/view/ComponentDataConditionView.ts:
--------------------------------------------------------------------------------
1 | import ComponentView from '../../dom_components/view/ComponentView';
2 | import ComponentDataCondition from '../model/conditional_variables/ComponentDataCondition';
3 | import DataResolverListener from '../model/DataResolverListener';
4 |
5 | export default class ComponentDataConditionView extends ComponentView {
6 | dataResolverListener!: DataResolverListener;
7 |
8 | initialize(opt = {}) {
9 | super.initialize(opt);
10 |
11 | this.postRender = this.postRender.bind(this);
12 | this.listenTo(this.model.components(), 'reset', this.postRender);
13 | this.dataResolverListener = new DataResolverListener({
14 | em: this.em,
15 | resolver: this.model.dataResolver,
16 | onUpdate: this.postRender,
17 | });
18 | }
19 |
20 | renderDataResolver() {
21 | const componentTrue = this.model.getIfTrueContent();
22 | const componentFalse = this.model.getIfFalseContent();
23 |
24 | const elTrue = componentTrue?.getEl();
25 | const elFalse = componentFalse?.getEl();
26 |
27 | const isTrue = this.model.isTrue();
28 | if (elTrue) {
29 | elTrue.style.display = isTrue ? '' : 'none';
30 | }
31 | if (elFalse) {
32 | elFalse.style.display = isTrue ? 'none' : '';
33 | }
34 | }
35 |
36 | postRender() {
37 | this.renderDataResolver();
38 | super.postRender();
39 | }
40 |
41 | remove() {
42 | this.stopListening(this.model.components(), 'reset', this.postRender);
43 | this.dataResolverListener.destroy();
44 | return super.remove();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/src/data_sources/view/ComponentDataVariableView.ts:
--------------------------------------------------------------------------------
1 | import ComponentView from '../../dom_components/view/ComponentView';
2 | import ComponentDataVariable from '../model/ComponentDataVariable';
3 | import DataResolverListener from '../model/DataResolverListener';
4 |
5 | export default class ComponentDataVariableView extends ComponentView {
6 | dataResolverListener!: DataResolverListener;
7 |
8 | initialize(opt = {}) {
9 | super.initialize(opt);
10 | this.dataResolverListener = new DataResolverListener({
11 | em: this.em,
12 | resolver: this.model.dataResolver,
13 | onUpdate: () => this.postRender(),
14 | });
15 | }
16 |
17 | remove() {
18 | this.dataResolverListener.destroy();
19 | return super.remove();
20 | }
21 |
22 | postRender() {
23 | const model = this.model;
24 | const dataResolver = model.getDataResolver();
25 | const asPlainText = !!dataResolver.asPlainText;
26 |
27 | if (asPlainText) {
28 | this.el.textContent = model.getDataValue();
29 | } else {
30 | this.el.innerHTML = model.getDataValue();
31 | }
32 |
33 | super.postRender();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/device_manager/config/config.ts:
--------------------------------------------------------------------------------
1 | import { DeviceProperties } from '../model/Device';
2 |
3 | export interface DeviceManagerConfig {
4 | /**
5 | * The device `id` to select on start, if not indicated, the first available from `devices` will be used.
6 | * @default ''
7 | */
8 | default?: string;
9 | /**
10 | * Default devices.
11 | * @example
12 | * devices: [{
13 | * id: 'desktop',
14 | * name: 'Desktop',
15 | * width: '',
16 | * }, {
17 | * id: 'tablet',
18 | * name: 'Tablet',
19 | * width: '770px',
20 | * widthMedia: '992px',
21 | * },
22 | * ...
23 | * ]
24 | */
25 | devices?: DeviceProperties[];
26 | }
27 |
28 | const config: () => DeviceManagerConfig = () => ({
29 | default: '',
30 | devices: [
31 | {
32 | id: 'desktop',
33 | name: 'Desktop',
34 | width: '',
35 | },
36 | {
37 | id: 'tablet',
38 | name: 'Tablet',
39 | width: '770px',
40 | widthMedia: '992px',
41 | },
42 | {
43 | id: 'mobileLandscape',
44 | name: 'Mobile landscape',
45 | width: '568px',
46 | widthMedia: '768px',
47 | },
48 | {
49 | id: 'mobilePortrait',
50 | name: 'Mobile portrait',
51 | width: '320px',
52 | widthMedia: '480px',
53 | },
54 | ],
55 | });
56 |
57 | export default config;
58 |
--------------------------------------------------------------------------------
/packages/core/src/device_manager/model/Devices.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import Device from './Device';
3 |
4 | export default class Devices extends Collection {}
5 |
6 | Devices.prototype.model = Device;
7 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentComment.ts:
--------------------------------------------------------------------------------
1 | import ComponentTextNode from './ComponentTextNode';
2 |
3 | export default class ComponentComment extends ComponentTextNode {
4 | get defaults() {
5 | // @ts-ignore
6 | return { ...super.defaults };
7 | }
8 |
9 | toHTML() {
10 | return ``;
11 | }
12 |
13 | static isComponent(el: HTMLElement) {
14 | if (el.nodeType == 8) {
15 | return {
16 | type: 'comment',
17 | content: el.textContent ?? '',
18 | };
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentFrame.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'iframe';
5 |
6 | export default class ComponentFrame extends Component {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | droppable: false,
14 | resizable: true,
15 | traits: ['id', 'title', 'src'],
16 | attributes: { frameborder: '0' },
17 | };
18 | }
19 |
20 | static isComponent(el: HTMLElement) {
21 | return toLowerCase(el.tagName) === type;
22 | }
23 | }
24 |
25 | // ComponentFrame.isComponent = el => toLowerCase(el.tagName) === type;
26 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentHead.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 | import { DraggableDroppableFn } from './types';
4 |
5 | export const type = 'head';
6 | const droppable = ['title', 'style', 'base', 'link', 'meta', 'script', 'noscript'];
7 |
8 | export default class ComponentHead extends Component {
9 | get defaults() {
10 | return {
11 | // @ts-ignore
12 | ...super.defaults,
13 | type,
14 | tagName: type,
15 | draggable: false,
16 | highlightable: false,
17 | droppable: (({ tagName }) => !tagName || droppable.includes(toLowerCase(tagName))) as DraggableDroppableFn,
18 | };
19 | }
20 |
21 | static isComponent(el: HTMLElement) {
22 | return toLowerCase(el.tagName) === type;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentLabel.ts:
--------------------------------------------------------------------------------
1 | import ComponentText from './ComponentText';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'label';
5 |
6 | export default class ComponentLabel extends ComponentText {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | traits: ['id', 'title', 'for'],
14 | };
15 | }
16 |
17 | static isComponent(el: HTMLElement) {
18 | return toLowerCase(el.tagName) === type;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentLink.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from 'underscore';
2 | import { toLowerCase } from '../../utils/mixins';
3 | import ComponentText from './ComponentText';
4 |
5 | const type = 'link';
6 |
7 | export default class ComponentLink extends ComponentText {
8 | get defaults() {
9 | return {
10 | // @ts-ignore
11 | ...super.defaults,
12 | type,
13 | tagName: 'a',
14 | traits: ['title', 'href', 'target'],
15 | };
16 | }
17 |
18 | static isComponent(el: HTMLElement, opts: any = {}) {
19 | let result: any;
20 |
21 | if (toLowerCase(el.tagName) === 'a') {
22 | const textTags = opts.textTags || [];
23 | result = { type, editable: false };
24 |
25 | // The link is editable only if, at least, one of its
26 | // children is a text node (not empty one)
27 | const children = el.childNodes;
28 | const len = children.length;
29 | if (!len) delete result.editable;
30 |
31 | forEach(children, (child) => {
32 | const { tagName } = child as HTMLElement;
33 | if (
34 | (child.nodeType == 3 && (child as any).textContent.trim() !== '') ||
35 | (tagName && textTags.indexOf(toLowerCase(tagName)) >= 0)
36 | ) {
37 | delete result.editable;
38 | }
39 | });
40 | }
41 |
42 | return result;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentScript.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'script';
5 |
6 | export default class ComponentScript extends Component {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | droppable: false,
14 | draggable: false,
15 | layerable: false,
16 | highlightable: false,
17 | };
18 | }
19 |
20 | static isComponent(el: HTMLScriptElement) {
21 | return toLowerCase(el.tagName) === type;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentSvg.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'svg';
5 |
6 | export default class ComponentSvg extends Component {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | highlightable: false,
14 | resizable: { ratioDefault: true },
15 | };
16 | }
17 |
18 | getName() {
19 | let name = this.get('tagName')!;
20 | const customName = this.get('custom-name');
21 | name = name.charAt(0).toUpperCase() + name.slice(1);
22 | return customName || name;
23 | }
24 |
25 | static isComponent(el: HTMLElement) {
26 | return toLowerCase(el.tagName) === type;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentSvgIn.ts:
--------------------------------------------------------------------------------
1 | import ComponentSvg from './ComponentSvg';
2 |
3 | /**
4 | * Component for inner SVG elements
5 | */
6 | export default class ComponentSvgIn extends ComponentSvg {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | selectable: false,
12 | hoverable: false,
13 | layerable: false,
14 | };
15 | }
16 |
17 | static isComponent(el: any, opts: any = {}) {
18 | return !!opts.inSvg;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTable.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 | import { ComponentOptions, ComponentProperties } from './types';
4 |
5 | const type = 'table';
6 |
7 | export default class ComponentTable extends Component {
8 | get defaults() {
9 | return {
10 | // @ts-ignore
11 | ...super.defaults,
12 | type,
13 | tagName: type,
14 | droppable: ['tbody', 'thead', 'tfoot'],
15 | };
16 | }
17 |
18 | constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
19 | super(props, opt);
20 | const components = this.get('components')!;
21 | !components.length && components.add({ type: 'tbody' });
22 | }
23 |
24 | static isComponent(el: HTMLElement) {
25 | return toLowerCase(el.tagName) === type;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTableBody.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 | import { ComponentOptions, ComponentProperties } from './types';
4 |
5 | const type = 'tbody';
6 |
7 | export default class ComponentTableBody extends Component {
8 | get defaults() {
9 | return {
10 | // @ts-ignore
11 | ...super.defaults,
12 | type,
13 | tagName: type,
14 | draggable: ['table'],
15 | droppable: ['tr'],
16 | columns: 1,
17 | rows: 1,
18 | };
19 | }
20 |
21 | constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
22 | super(props, opt);
23 | const components = this.get('components')!;
24 | let columns = this.get('columns');
25 | let rows = this.get('rows');
26 |
27 | // Init components if empty
28 | if (!components.length) {
29 | const rowsToAdd = [];
30 |
31 | while (rows--) {
32 | const columnsToAdd = [];
33 | let clm = columns;
34 |
35 | while (clm--) {
36 | columnsToAdd.push({
37 | type: 'cell',
38 | classes: ['cell'],
39 | });
40 | }
41 |
42 | rowsToAdd.push({
43 | type: 'row',
44 | classes: ['row'],
45 | components: columnsToAdd,
46 | });
47 | }
48 |
49 | components.add(rowsToAdd);
50 | }
51 | }
52 |
53 | static isComponent(el: HTMLElement) {
54 | return toLowerCase(el.tagName) === type;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTableCell.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | export default class ComponentTableCell extends Component {
5 | get defaults() {
6 | return {
7 | // @ts-ignore
8 | ...super.defaults,
9 | type: 'cell',
10 | tagName: 'td',
11 | draggable: ['tr'],
12 | };
13 | }
14 |
15 | static isComponent(el: HTMLElement) {
16 | return ['td', 'th'].indexOf(toLowerCase(el.tagName)) >= 0;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTableFoot.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableBody from './ComponentTableBody';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'tfoot';
5 |
6 | export default class ComponentTableFoot extends ComponentTableBody {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | };
14 | }
15 |
16 | static isComponent(el: HTMLElement) {
17 | return toLowerCase(el.tagName) === type;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTableHead.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableBody from './ComponentTableBody';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const type = 'thead';
5 |
6 | export default class ComponentTableHead extends ComponentTableBody {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | type,
12 | tagName: type,
13 | };
14 | }
15 |
16 | static isComponent(el: HTMLElement) {
17 | return toLowerCase(el.tagName) === type;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTableRow.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { toLowerCase } from '../../utils/mixins';
3 |
4 | const tagName = 'tr';
5 |
6 | export default class ComponentTableRow extends Component {
7 | get defaults() {
8 | return {
9 | // @ts-ignore
10 | ...super.defaults,
11 | tagName,
12 | draggable: ['thead', 'tbody', 'tfoot'],
13 | droppable: ['th', 'td'],
14 | };
15 | }
16 |
17 | static isComponent(el: HTMLElement) {
18 | return toLowerCase(el.tagName) === tagName;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentText.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from 'underscore';
2 | import Component from './Component';
3 | import { ComponentOptions, ComponentProperties } from './types';
4 |
5 | export default class ComponentText extends Component {
6 | get defaults() {
7 | return {
8 | // @ts-ignore
9 | ...super.defaults,
10 | type: 'text',
11 | droppable: false,
12 | editable: true,
13 | };
14 | }
15 |
16 | constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
17 | super(props, opt);
18 | this.__checkInnerChilds();
19 | }
20 |
21 | __checkInnerChilds() {
22 | const { disableTextInnerChilds } = this.em.Components.config;
23 | if (disableTextInnerChilds) {
24 | const disableChild = (child: Component) => {
25 | if (!child.isInstanceOf('textnode')) {
26 | child.set({
27 | locked: true,
28 | layerable: false,
29 | });
30 | }
31 | };
32 |
33 | if (isFunction(disableTextInnerChilds)) {
34 | this.forEachChild((child) => {
35 | disableTextInnerChilds(child) && disableChild(child);
36 | });
37 | } else {
38 | this.forEachChild(disableChild);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ComponentTextNode.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import { escapeNodeContent } from '../../utils/mixins';
3 |
4 | export default class ComponentTextNode extends Component {
5 | get defaults() {
6 | return {
7 | // @ts-ignore
8 | ...super.defaults,
9 | tagName: '',
10 | droppable: false,
11 | layerable: false,
12 | selectable: false,
13 | editable: true,
14 | };
15 | }
16 |
17 | toHTML() {
18 | const { content } = this;
19 | const parent = this.parent();
20 | return parent?.is('script') ? content : this.__escapeContent(content);
21 | }
22 |
23 | __escapeContent(content: string) {
24 | return escapeNodeContent(content);
25 | }
26 |
27 | static isComponent(el: HTMLElement) {
28 | if (el.nodeType === 3) {
29 | return {
30 | type: 'textnode',
31 | content: el.textContent ?? '',
32 | };
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/Toolbar.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import ToolbarButton from './ToolbarButton';
3 |
4 | export default class Toolbar extends Collection {}
5 |
6 | Toolbar.prototype.model = ToolbarButton;
7 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/model/ToolbarButton.ts:
--------------------------------------------------------------------------------
1 | import { CommandFunction } from '../../commands/view/CommandAbstract';
2 | import { Model, ObjectAny } from '../../common';
3 |
4 | export interface ToolbarButtonProps {
5 | /**
6 | * Command name.
7 | */
8 | command: CommandFunction | string;
9 |
10 | /**
11 | * Button label.
12 | */
13 | label?: string;
14 |
15 | id?: string;
16 | attributes?: ObjectAny;
17 | events?: ObjectAny;
18 | }
19 |
20 | export default class ToolbarButton extends Model {
21 | defaults() {
22 | return {
23 | command: '',
24 | attributes: {},
25 | };
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentCommentView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTextNodeView from './ComponentTextNodeView';
2 |
3 | export default class ComponentCommentView extends ComponentTextNodeView {
4 | _createElement() {
5 | return document.createComment(this.model.content) as Text;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentFrameView.ts:
--------------------------------------------------------------------------------
1 | import ComponentView from './ComponentView';
2 | import { createEl, find, attrUp } from '../../utils/dom';
3 | import ComponentFrame from '../model/ComponentFrame';
4 |
5 | export default class ComponentFrameView extends ComponentView {
6 | tagName() {
7 | return 'div';
8 | }
9 |
10 | initialize(props: any) {
11 | super.initialize(props);
12 | this.listenTo(this.model, 'change:attributes:src', this.updateSrc);
13 | }
14 |
15 | updateSrc() {
16 | const frame = find(this.el, 'iframe')[0] as HTMLElement;
17 | frame && attrUp(frame, { src: this.__getSrc() });
18 | }
19 |
20 | render() {
21 | super.render();
22 | const frame = createEl('iframe', {
23 | class: `${this.ppfx}no-pointer`,
24 | style: 'width: 100%; height: 100%; border: none',
25 | src: this.__getSrc(),
26 | });
27 | this.el.appendChild(frame);
28 | return this;
29 | }
30 |
31 | __getSrc() {
32 | return this.model.getAttributes().src || '';
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentLabelView.ts:
--------------------------------------------------------------------------------
1 | import ComponentLabel from '../model/ComponentLabel';
2 | import ComponentLinkView from './ComponentLinkView';
3 |
4 | export default class ComponentLabelView extends ComponentLinkView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentLinkView.ts:
--------------------------------------------------------------------------------
1 | import ComponentLink from '../model/ComponentLink';
2 | import ComponentTextView from './ComponentTextView';
3 |
4 | export default class ComponentLinkView extends ComponentTextView {
5 | render() {
6 | super.render();
7 | // I need capturing instead of bubbling as bubbled clicks from other
8 | // children will execute the link event
9 | this.el.addEventListener('click', this.prevDef, true);
10 |
11 | return this;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentMapView.ts:
--------------------------------------------------------------------------------
1 | import ComponentImageView from './ComponentImageView';
2 |
3 | export default class ComponentMapView extends ComponentImageView {
4 | iframe?: HTMLIFrameElement;
5 |
6 | tagName() {
7 | return 'div';
8 | }
9 |
10 | events() {
11 | return {};
12 | }
13 |
14 | initialize(props: any) {
15 | super.initialize(props);
16 | this.classEmpty = this.ppfx + 'plh-map';
17 | }
18 |
19 | /**
20 | * Update the map on the canvas
21 | * @private
22 | */
23 | updateSrc() {
24 | this.getIframe().src = this.model.get('src');
25 | }
26 |
27 | getIframe() {
28 | if (!this.iframe) {
29 | const ifrm = document.createElement('iframe');
30 | ifrm.src = this.model.get('src');
31 | ifrm.frameBorder = '0';
32 | ifrm.style.height = '100%';
33 | ifrm.style.width = '100%';
34 | ifrm.className = this.ppfx + 'no-pointer';
35 | this.iframe = ifrm;
36 | }
37 | return this.iframe;
38 | }
39 |
40 | render() {
41 | super.render();
42 | this.updateClasses();
43 | this.el.appendChild(this.getIframe());
44 | return this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentScriptView.ts:
--------------------------------------------------------------------------------
1 | import ComponentScript from '../model/ComponentScript';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentScriptView extends ComponentView {
5 | tagName() {
6 | return 'script';
7 | }
8 |
9 | events() {
10 | return {};
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentSvgView.ts:
--------------------------------------------------------------------------------
1 | import ComponentSvg from '../model/ComponentSvg';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentSvgView extends ComponentView {
5 | _createElement(tagName: string) {
6 | return document.createElementNS('http://www.w3.org/2000/svg', tagName);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableBodyView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableBody from '../model/ComponentTableBody';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableBodyView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableCellView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableCell from '../model/ComponentTableCell';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableCellView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableFootView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableFoot from '../model/ComponentTableFoot';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableFootView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableHeadView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableHead from '../model/ComponentTableHead';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableHeadView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableRowView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTableRow from '../model/ComponentTableRow';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableRowView extends ComponentView {}
5 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTableView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTable from '../model/ComponentTable';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTableView extends ComponentView {
5 | events() {
6 | return {};
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentTextNodeView.ts:
--------------------------------------------------------------------------------
1 | import ComponentTextNode from '../model/ComponentTextNode';
2 | import ComponentView from './ComponentView';
3 |
4 | export default class ComponentTextNodeView<
5 | TComp extends ComponentTextNode = ComponentTextNode,
6 | > extends ComponentView {
7 | // Clear methods used on Nodes with attributes
8 | _setAttributes() {}
9 | renderAttributes() {}
10 | updateStatus() {}
11 | updateClasses() {}
12 | setAttribute() {}
13 | updateAttributes() {}
14 | initClasses() {}
15 | initComponents() {}
16 | delegateEvents() {
17 | return this;
18 | }
19 |
20 | _createElement() {
21 | return document.createTextNode('');
22 | }
23 |
24 | render() {
25 | const { model, el } = this;
26 | if (model.opt.temporary) return this;
27 | el.textContent = model.content;
28 | return this;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ComponentWrapperView.ts:
--------------------------------------------------------------------------------
1 | import ComponentView from './ComponentView';
2 |
3 | export default class ComponentWrapperView extends ComponentView {
4 | tagName() {
5 | return 'div';
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/dom_components/view/ToolbarView.ts:
--------------------------------------------------------------------------------
1 | import DomainViews from '../../domain_abstract/view/DomainViews';
2 | import EditorModel from '../../editor/model/Editor';
3 | import ToolbarButtonView, { ToolbarViewProps } from './ToolbarButtonView';
4 |
5 | export default class ToolbarView extends DomainViews {
6 | em: EditorModel;
7 |
8 | constructor(opts: ToolbarViewProps) {
9 | super(opts);
10 | const { em } = opts;
11 | this.em = em;
12 | this.config = { em };
13 | this.listenTo(this.collection, 'reset', this.render);
14 | }
15 |
16 | onRender() {
17 | const pfx = this.em.config.stylePrefix!;
18 | this.el.className = `${pfx}toolbar-items`;
19 | }
20 | }
21 |
22 | // @ts-ignore
23 | ToolbarView.prototype.itemView = ToolbarButtonView;
24 |
--------------------------------------------------------------------------------
/packages/core/src/editor/model/Selected.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from 'underscore';
2 | import { Collection, Model } from '../../common';
3 | import Component from '../../dom_components/model/Component';
4 |
5 | export class Selectable extends Model {}
6 |
7 | export default class Selected extends Collection {
8 | getByComponent(component: Component) {
9 | return this.filter((s) => this.getComponent(s) === component)[0];
10 | }
11 |
12 | addComponent(component: Component, opts: any) {
13 | const toAdd = (isArray(component) ? component : [component])
14 | .filter((c) => !this.hasComponent(c))
15 | .map((component) => new Selectable({ component }))[0];
16 | return this.push(toAdd, opts);
17 | }
18 |
19 | getComponent(model: Selectable): Component {
20 | return model.get('component');
21 | }
22 |
23 | hasComponent(component: Component) {
24 | const model = this.getByComponent(component);
25 | return model && this.contains(model);
26 | }
27 |
28 | lastComponent() {
29 | const last = this.last();
30 | return last ? this.getComponent(last) : undefined;
31 | }
32 |
33 | allComponents() {
34 | return this.map((s) => this.getComponent(s)).filter((i) => i);
35 | }
36 |
37 | removeComponent(component: Component | Component[], opts: any) {
38 | const toRemove = (isArray(component) ? component : [component]).map((c) => this.getByComponent(c));
39 | return this.remove(toRemove, opts);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/i18n/config.ts:
--------------------------------------------------------------------------------
1 | import en from './locale/en';
2 |
3 | export interface I18nConfig {
4 | /**
5 | * Locale value.
6 | * @default 'en'
7 | */
8 | locale?: string;
9 |
10 | /**
11 | * Fallback locale.
12 | * @default 'en'
13 | */
14 | localeFallback?: string;
15 |
16 | /**
17 | * Detect locale by checking browser language.
18 | * @default true
19 | */
20 | detectLocale?: boolean;
21 |
22 | /**
23 | * Show warnings when some of the i18n resources are missing.
24 | * @default false
25 | */
26 | debug?: boolean;
27 |
28 | /**
29 | * Messages to translate.
30 | * @default { en: {...} }
31 | */
32 | messages?: Record;
33 |
34 | /**
35 | * Additional messages. This allows extending the default `messages` set directly from the configuration.
36 | */
37 | messagesAdd?: Record;
38 | }
39 |
40 | const config: () => I18nConfig = () => ({
41 | locale: 'en',
42 | localeFallback: 'en',
43 | detectLocale: true,
44 | debug: false,
45 | messages: { en },
46 | messagesAdd: undefined,
47 | });
48 |
49 | export default config;
50 |
--------------------------------------------------------------------------------
/packages/core/src/i18n/types.ts:
--------------------------------------------------------------------------------
1 | import { I18nConfig } from './config';
2 |
3 | export type Messages = Required['messages'];
4 |
5 | /**{START_EVENTS}*/
6 | export enum I18nEvents {
7 | /**
8 | * @event `i18n:add` New set of messages is added.
9 | * @example
10 | * editor.on('i18n:add', (messages) => { ... });
11 | */
12 | add = 'i18n:add',
13 |
14 | /**
15 | * @event `i18n:update` The set of messages is updated.
16 | * @example
17 | * editor.on('i18n:update', (messages) => { ... });
18 | */
19 | update = 'i18n:update',
20 |
21 | /**
22 | * @event `i18n:locale` Locale changed.
23 | * @example
24 | * editor.on('i18n:locale', ({ value, valuePrev }) => { ... });
25 | */
26 | locale = 'i18n:locale',
27 | }
28 | /**{END_EVENTS}*/
29 |
30 | // need this to avoid the TS documentation generator to break
31 | export default I18nEvents;
32 |
--------------------------------------------------------------------------------
/packages/core/src/modal_dialog/config/config.ts:
--------------------------------------------------------------------------------
1 | export interface ModalConfig {
2 | stylePrefix?: string;
3 | title?: string;
4 | content?: string;
5 | /**
6 | * Close modal on interact with backdrop.
7 | * @default true
8 | */
9 | backdrop?: boolean;
10 |
11 | /**
12 | * Avoid rendering the default modal.
13 | * @default false
14 | */
15 | custom?: boolean;
16 |
17 | /**
18 | * Extend ModalView object (view/ModalView.js)
19 | * @example
20 | * extend: {
21 | * template() {
22 | * return '...New modal template...
';
23 | * },
24 | * },
25 | */
26 | extend?: Record;
27 | }
28 |
29 | const config: () => ModalConfig = () => ({
30 | stylePrefix: 'mdl-',
31 | title: '',
32 | content: '',
33 | backdrop: true,
34 | custom: false,
35 | extend: {},
36 | });
37 |
38 | export default config;
39 |
--------------------------------------------------------------------------------
/packages/core/src/modal_dialog/model/Modal.ts:
--------------------------------------------------------------------------------
1 | import ModalModule from '..';
2 | import { ModuleModel } from '../../abstract';
3 |
4 | export default class Modal extends ModuleModel {
5 | defaults() {
6 | return {
7 | title: '',
8 | content: '',
9 | attributes: {},
10 | open: false,
11 | };
12 | }
13 |
14 | open() {
15 | this.set('open', true);
16 | }
17 |
18 | close() {
19 | this.set('open', false);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/src/pages/model/Pages.ts:
--------------------------------------------------------------------------------
1 | import { Collection, RemoveOptions } from '../../common';
2 | import EditorModel from '../../editor/model/Editor';
3 | import Page from './Page';
4 |
5 | export default class Pages extends Collection {
6 | constructor(models: any, em: EditorModel) {
7 | super(models);
8 | this.on('reset', this.onReset);
9 | this.on('remove', this.onRemove);
10 |
11 | // @ts-ignore We need to inject `em` for pages created on reset from the Storage load
12 | this.model = (props: {}, opts = {}) => {
13 | return new Page(props, { ...opts, em });
14 | };
15 | }
16 |
17 | onReset(m: Page, opts?: RemoveOptions & { previousModels?: Pages }) {
18 | opts?.previousModels?.map((p) => this.onRemove(p, this, opts));
19 | }
20 |
21 | onRemove(removed?: Page, _p?: this, opts: RemoveOptions = {}) {
22 | // Avoid removing frames if triggered from undo #6142
23 | if (opts.fromUndo || opts.temporary) return;
24 | removed?.onRemove();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/panels/model/Button.ts:
--------------------------------------------------------------------------------
1 | import PanelManager from '..';
2 | import { ModuleModel } from '../../abstract';
3 | import Buttons from './Buttons';
4 |
5 | export default class Button extends ModuleModel {
6 | defaults() {
7 | return {
8 | id: '',
9 | label: '',
10 | tagName: 'span',
11 | className: '',
12 | command: '',
13 | context: '',
14 | buttons: [],
15 | attributes: {},
16 | options: {},
17 | active: false,
18 | dragDrop: false,
19 | togglable: true,
20 | runDefaultCommand: true,
21 | stopDefaultCommand: false,
22 | disable: false,
23 | };
24 | }
25 |
26 | get className(): string {
27 | return this.get('className');
28 | }
29 |
30 | get command(): string {
31 | return this.get('command');
32 | }
33 |
34 | get active(): boolean {
35 | return this.get('active');
36 | }
37 | set active(isActive: boolean) {
38 | this.set('active', isActive);
39 | }
40 |
41 | get togglable(): boolean {
42 | return this.get('togglable');
43 | }
44 |
45 | get runDefaultCommand(): boolean {
46 | return this.get('runDefaultCommand');
47 | }
48 | get stopDefaultCommand(): boolean {
49 | return this.get('stopDefaultCommand');
50 | }
51 | get disable(): boolean {
52 | return this.get('disable');
53 | }
54 |
55 | constructor(module: PanelManager, options: any) {
56 | super(module, options);
57 | if (this.get('buttons').length) {
58 | this.set('buttons', new Buttons(this.module, this.get('buttons')));
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/core/src/panels/model/Panels.ts:
--------------------------------------------------------------------------------
1 | import PanelManager from '..';
2 | import { ModuleCollection } from '../../abstract';
3 | import Panel from './Panel';
4 |
5 | export default class Panels extends ModuleCollection {
6 | constructor(module: PanelManager, models: Panel[] | Array>) {
7 | super(module, models, Panel);
8 | }
9 | }
10 |
11 | Panels.prototype.model = Panel;
12 |
--------------------------------------------------------------------------------
/packages/core/src/panels/view/ButtonsView.ts:
--------------------------------------------------------------------------------
1 | import { result } from 'underscore';
2 | import { ModuleView } from '../../abstract';
3 | import Button from '../model/Button';
4 | import Buttons from '../model/Buttons';
5 | import ButtonView from './ButtonView';
6 |
7 | export default class ButtonsView extends ModuleView {
8 | constructor(collection: Buttons) {
9 | super({ collection });
10 | this.listenTo(this.collection, 'add', this.addTo);
11 | this.listenTo(this.collection, 'reset remove', this.render);
12 | this.className = this.pfx + 'buttons';
13 | }
14 |
15 | /**
16 | * Add to collection
17 | * @param Object Model
18 | *
19 | * @return Object
20 | * */
21 | private addTo(model: Button) {
22 | this.addToCollection(model);
23 | }
24 |
25 | /**
26 | * Add new object to collection
27 | * @param Object Model
28 | * @param Object Fragment collection
29 | *
30 | * @return Object Object created
31 | * */
32 | private addToCollection(model: Button, fragmentEl?: DocumentFragment) {
33 | const fragment = fragmentEl || null;
34 | const el = model.get('el');
35 | const view = new ButtonView({
36 | el,
37 | model,
38 | });
39 | const rendered = view.render().el;
40 |
41 | if (fragment) {
42 | fragment.appendChild(rendered);
43 | } else {
44 | this.$el.append(rendered);
45 | }
46 |
47 | return rendered;
48 | }
49 |
50 | public render() {
51 | var fragment = document.createDocumentFragment();
52 | this.$el.empty();
53 |
54 | this.collection.each((model) => this.addToCollection(model, fragment));
55 |
56 | this.$el.append(fragment);
57 | this.$el.attr('class', result(this, 'className'));
58 | return this;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/packages/core/src/selector_manager/model/State.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '../../common';
2 |
3 | /**
4 | * @typedef State
5 | * @property {String} name State name, eg. `hover`, `nth-of-type(2n)`
6 | * @property {String} label State label, eg. `Hover`, `Even/Odd`
7 | */
8 | export default class State extends Model {
9 | defaults() {
10 | return {
11 | name: '',
12 | label: '',
13 | };
14 | }
15 |
16 | /**
17 | * Get state name
18 | * @returns {String}
19 | */
20 | getName(): string {
21 | return this.get('name');
22 | }
23 |
24 | /**
25 | * Get state label. If label was not provided, the name will be returned.
26 | * @returns {String}
27 | */
28 | getLabel(): string {
29 | return this.get('label') || this.getName();
30 | }
31 | }
32 | State.prototype.idAttribute = 'name';
33 |
--------------------------------------------------------------------------------
/packages/core/src/storage_manager/model/IStorage.ts:
--------------------------------------------------------------------------------
1 | export interface StorageOptions {
2 | [key: string]: any;
3 | }
4 |
5 | export interface ProjectData {
6 | [key: string]: any;
7 | }
8 |
9 | export default interface IStorage {
10 | load: (options: T) => Promise;
11 | store: (data: ProjectData, options: T) => Promise;
12 | [key: string]: any;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/storage_manager/model/LocalStorage.ts:
--------------------------------------------------------------------------------
1 | import { hasWin } from '../../utils/mixins';
2 | import IStorage, { ProjectData } from './IStorage';
3 |
4 | export interface LocalStorageConfig {
5 | /**
6 | * Local key identifier of the project.
7 | * @default 'gjsProject'
8 | */
9 | key?: string;
10 |
11 | /**
12 | * If enabled, checks if browser supports LocalStorage.
13 | * @default true
14 | */
15 | checkLocal?: boolean;
16 | }
17 |
18 | export default class LocalStorage implements IStorage {
19 | async store(data: ProjectData, opts: LocalStorageConfig = {}) {
20 | if (this.hasLocal(opts, true)) {
21 | localStorage.setItem(opts.key!, JSON.stringify(data));
22 | }
23 | return data;
24 | }
25 |
26 | async load(opts: LocalStorageConfig = {}) {
27 | let result = {};
28 |
29 | if (this.hasLocal(opts, true)) {
30 | result = JSON.parse(localStorage.getItem(opts.key!) || '{}');
31 | }
32 |
33 | return result;
34 | }
35 |
36 | hasLocal(opts: LocalStorageConfig = {}, thr?: boolean) {
37 | if (opts.checkLocal && (!hasWin() || !localStorage)) {
38 | if (thr) throw new Error('localStorage not available');
39 | return false;
40 | }
41 |
42 | return true;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/model/Layers.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import LayersView from '../view/LayersView';
3 | import Layer from './Layer';
4 |
5 | export default class Layers extends Collection {
6 | prop: any;
7 | view?: LayersView;
8 |
9 | initialize(p: any, opts: { prop?: any } = {}) {
10 | this.prop = opts.prop;
11 | }
12 | }
13 |
14 | Layers.prototype.model = Layer;
15 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/model/PropertyRadio.ts:
--------------------------------------------------------------------------------
1 | import PropertySelect from './PropertySelect';
2 |
3 | export default class PropertyRadio extends PropertySelect {
4 | defaults() {
5 | return {
6 | ...PropertySelect.getDefaults(),
7 | full: 1,
8 | };
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/model/PropertySlider.ts:
--------------------------------------------------------------------------------
1 | import PropertyNumber from './PropertyNumber';
2 |
3 | export default class PropertySlider extends PropertyNumber {
4 | defaults() {
5 | return {
6 | ...PropertyNumber.getDefaults(),
7 | showInput: 1,
8 | };
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/model/Sectors.ts:
--------------------------------------------------------------------------------
1 | import { Collection } from '../../common';
2 | import EditorModel from '../../editor/model/Editor';
3 | import Sector from './Sector';
4 |
5 | export default class Sectors extends Collection {
6 | em!: EditorModel;
7 | module!: any;
8 |
9 | initialize(prop: any, opts: { em?: EditorModel; module?: any } = {}) {
10 | const { module, em } = opts;
11 | this.em = em!;
12 | this.module = module;
13 | this.listenTo(this, 'reset', this.onReset);
14 | }
15 |
16 | /** @ts-ignore */
17 | model(props, opts = {}) {
18 | // @ts-ignore
19 | const { em } = opts.collection;
20 | return new Sector(props, { ...opts, em });
21 | }
22 |
23 | onReset(models: any, opts: { previousModels?: Sector[] } = {}) {
24 | const prev = opts.previousModels || [];
25 | // @ts-ignore
26 | prev.forEach((sect) => sect.get('properties').reset());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/view/PropertyColorView.ts:
--------------------------------------------------------------------------------
1 | import PropertyNumberView from './PropertyNumberView';
2 | import InputColor from '../../domain_abstract/ui/InputColor';
3 |
4 | export default class PropertyColorView extends PropertyNumberView {
5 | setValue(value: string) {
6 | this.inputInst?.setValue(value, {
7 | fromTarget: 1,
8 | def: this.model.getDefaultValue(),
9 | });
10 | }
11 |
12 | remove() {
13 | PropertyNumberView.prototype.remove.apply(this, arguments as any);
14 | const inp = this.inputInst;
15 | inp && inp.remove && inp.remove();
16 | // @ts-ignore
17 | ['inputInst', '$color'].forEach((i) => (this[i] = null));
18 | return this;
19 | }
20 |
21 | __handleChange(value: string, partial: boolean) {
22 | this.model.upValue(value, { partial });
23 | }
24 |
25 | onRender() {
26 | if (!this.inputInst) {
27 | this.__handleChange = this.__handleChange.bind(this);
28 | const { ppfx, model, em, el } = this;
29 | const inputColor = new InputColor({
30 | target: em,
31 | model,
32 | ppfx,
33 | onChange: this.__handleChange,
34 | });
35 | const input = inputColor.render();
36 | el.querySelector(`.${ppfx}fields`)!.appendChild(input.el);
37 | this.input = input.inputEl?.get(0) as HTMLInputElement;
38 | this.inputInst = input;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/view/PropertyCompositeView.ts:
--------------------------------------------------------------------------------
1 | import PropertyView from './PropertyView';
2 | import PropertiesView from './PropertiesView';
3 | import PropertyComposite from '../model/PropertyComposite';
4 |
5 | export default class PropertyCompositeView extends PropertyView {
6 | props?: PropertiesView;
7 |
8 | templateInput() {
9 | const { pfx } = this;
10 | return `
11 |
12 |
13 |
14 | `;
15 | }
16 |
17 | remove() {
18 | this.props?.remove();
19 | PropertyView.prototype.remove.apply(this, arguments as any);
20 | return this;
21 | }
22 |
23 | onValueChange() {}
24 |
25 | onRender() {
26 | const { pfx } = this;
27 | const model = this.model as PropertyComposite;
28 | const props = model.get('properties')!;
29 |
30 | if (props.length && !this.props) {
31 | const detached = model.isDetached();
32 | const propsView = new PropertiesView({
33 | config: {
34 | ...this.config,
35 | highlightComputed: detached,
36 | highlightChanged: detached,
37 | },
38 | // @ts-ignore
39 | collection: props,
40 | parent: this,
41 | });
42 | propsView.render();
43 | this.$el.find(`#${pfx}input-holder`).append(propsView.el);
44 | this.props = propsView;
45 | }
46 | }
47 |
48 | clearCached() {
49 | PropertyView.prototype.clearCached.apply(this, arguments as any);
50 | delete this.props;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/core/src/style_manager/view/PropertyNumberView.ts:
--------------------------------------------------------------------------------
1 | import PropertyView from './PropertyView';
2 |
3 | export default class PropertyNumberView extends PropertyView {
4 | inputInst?: any;
5 |
6 | templateInput(m: any) {
7 | return '';
8 | }
9 |
10 | init() {
11 | const model = this.model;
12 | this.listenTo(model, 'change:unit', this.onValueChange);
13 | this.listenTo(model, 'change:units', this.render);
14 | }
15 |
16 | setValue(v: string) {
17 | // handled by this.inputInst
18 | }
19 |
20 | onRender() {
21 | const { ppfx, model, el } = this;
22 |
23 | if (!this.inputInst) {
24 | const { input } = model as any;
25 | input.ppfx = ppfx;
26 | input.render();
27 | const fields = el.querySelector(`.${ppfx}fields`)!;
28 | fields.appendChild(input.el);
29 | this.input = input.inputEl.get(0);
30 | this.inputInst = input;
31 | }
32 | }
33 |
34 | clearCached() {
35 | PropertyView.prototype.clearCached.apply(this, arguments as any);
36 | this.inputInst = null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_code_manager.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 |
3 | .#{gjs_vars.$cm-prefix}editor-c {
4 | float: left;
5 | box-sizing: border-box;
6 | width: 50%;
7 |
8 | .CodeMirror {
9 | height: 450px;
10 | }
11 | }
12 | .#{gjs_vars.$cm-prefix}editor {
13 | font-size: 12px;
14 |
15 | #{gjs_vars.$cm-prefix}htmlmixed {
16 | padding-right: 10px;
17 | border-right: 1px solid var(--gjs-main-dark-color);
18 | ##{gjs_vars.$cm-prefix}title {
19 | color: #a97d44;
20 | }
21 | }
22 | #{gjs_vars.$cm-prefix}css {
23 | padding-left: 10px;
24 | ##{gjs_vars.$cm-prefix}title {
25 | color: #ddca7e;
26 | }
27 | }
28 | ##{gjs_vars.$cm-prefix}title {
29 | background-color: var(--gjs-main-dark-color);
30 | font-size: 12px;
31 | padding: 5px 10px 3px;
32 | text-align: right;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_devices.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 |
3 | .#{gjs_vars.$app-prefix}devices-c {
4 | display: flex;
5 | align-items: center;
6 | padding: 2px 3px 3px 3px;
7 |
8 | .#{gjs_vars.$app-prefix}device-label {
9 | flex-grow: 2;
10 | text-align: left;
11 | margin-right: 10px;
12 | }
13 |
14 | .#{gjs_vars.$app-prefix}select {
15 | flex-grow: 20;
16 | }
17 |
18 | .#{gjs_vars.$app-prefix}add-trasp {
19 | flex-grow: 1;
20 | margin-left: 5px;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_file_uploader.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_main_mixins';
2 | @use 'gjs_vars';
3 |
4 | .#{gjs_vars.$am-prefix}file-uploader {
5 | width: 55%;
6 | float: left;
7 |
8 | > form {
9 | background-color: var(--gjs-secondary-dark-color);
10 | border: 2px dashed;
11 | border-radius: 3px;
12 | position: relative;
13 | text-align: center;
14 | margin-bottom: 15px;
15 |
16 | &.#{gjs_vars.$am-prefix}hover {
17 | border: 2px solid var(--gjs-color-green);
18 | color: gjs_main_mixins.lighten-color(var(--gjs-color-green), 5%);
19 | }
20 |
21 | &.#{gjs_vars.$am-prefix}disabled {
22 | border-color: red;
23 | }
24 |
25 | ##{gjs_vars.$am-prefix}uploadFile {
26 | @include gjs_main_mixins.opacity(0);
27 | padding: var(--gjs-upload-padding);
28 | width: 100%;
29 | box-sizing: border-box;
30 | }
31 | }
32 |
33 | ##{gjs_vars.$am-prefix}title {
34 | position: absolute;
35 | padding: var(--gjs-upload-padding);
36 | width: 100%;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_main_mixins.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 |
3 | $gjs-is-prefix: '.#{gjs_vars.$app-prefix}is__';
4 |
5 | @function gjs-is($name) {
6 | @return '#{$gjs-is-prefix}#{$name}';
7 | }
8 |
9 | @function darken-color($color, $percentage) {
10 | @return color-mix(in srgb, $color, black $percentage);
11 | }
12 |
13 | @function lighten-color($color, $percentage) {
14 | @return color-mix(in srgb, $color, white $percentage);
15 | }
16 |
17 | @mixin user-select($v) {
18 | -moz-user-select: $v;
19 | -khtml-user-select: $v;
20 | -webkit-user-select: $v;
21 | -ms-user-select: $v;
22 | -o-user-select: $v;
23 | user-select: $v;
24 | }
25 |
26 | @mixin opacity($v) {
27 | opacity: $v;
28 | filter: alpha(opacity=$v * 100);
29 | }
30 |
31 | @mixin appearance($v) {
32 | -webkit-appearance: $v;
33 | -moz-appearance: $v;
34 | appearance: $v;
35 | }
36 |
37 | @mixin transform($v) {
38 | -ms-transform: $v;
39 | -webkit-transform: $v;
40 | -moz-transform: $v;
41 | transform: $v;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_modal.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 | @use 'gjs_category_general';
3 |
4 | .#{gjs_vars.$mdl-prefix} {
5 | &container {
6 | font-family: var(--gjs-main-font);
7 | overflow-y: auto;
8 | position: fixed;
9 | background-color: rgba(0, 0, 0, 0.5);
10 | display: flex;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | bottom: 0;
15 | z-index: 100;
16 | }
17 |
18 | &dialog {
19 | text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.05);
20 | animation: #{gjs_vars.$app-prefix}slide-down 0.215s;
21 | margin: auto;
22 | max-width: 850px;
23 | width: 90%;
24 | border-radius: 3px;
25 | font-weight: lighter;
26 | position: relative;
27 | z-index: 2;
28 | }
29 |
30 | &title {
31 | font-size: 1rem;
32 | }
33 |
34 | &btn-close {
35 | @extend .btn-cl;
36 |
37 | position: absolute;
38 | right: 15px;
39 | top: 5px;
40 | }
41 |
42 | &active .#{gjs_vars.$mdl-prefix}dialog {
43 | animation: #{gjs_vars.$mdl-prefix}slide-down 0.216s;
44 | }
45 |
46 | &header,
47 | &content {
48 | padding: 10px 15px;
49 | clear: both;
50 | }
51 |
52 | &header {
53 | position: relative;
54 | border-bottom: 1px solid var(--gjs-main-dark-color);
55 | padding: 15px 15px 7px;
56 | }
57 | }
58 |
59 | .#{gjs_vars.$app-prefix}export-dl::after {
60 | content: '';
61 | clear: both;
62 | display: block;
63 | margin-bottom: 10px;
64 | }
65 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_rte.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 | @use 'gjs_category_general';
3 |
4 | .#{gjs_vars.$rte-prefix} {
5 | &toolbar {
6 | @extend .#{gjs_vars.$app-prefix}no-user-select;
7 |
8 | position: absolute;
9 | z-index: 10;
10 | }
11 |
12 | &toolbar-ui {
13 | border: 1px solid var(--gjs-main-dark-color);
14 | border-radius: 3px;
15 | }
16 |
17 | &actionbar {
18 | display: flex;
19 | }
20 |
21 | &action {
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | padding: 5px;
26 | width: 25px;
27 | border-right: 1px solid var(--gjs-main-dark-color);
28 | text-align: center;
29 | cursor: pointer;
30 | outline: none;
31 |
32 | &:last-child {
33 | border-right: none;
34 | }
35 |
36 | &:hover {
37 | background-color: var(--gjs-main-light-color);
38 | }
39 | }
40 |
41 | &active {
42 | background-color: var(--gjs-main-light-color);
43 | }
44 | &disabled {
45 | color: var(--gjs-main-light-color);
46 | cursor: not-allowed;
47 | &:hover {
48 | background-color: unset;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_status.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_main_mixins';
2 | @use 'gjs_vars';
3 |
4 | @function gjs-is($name) {
5 | @return '#{gjs_main_mixins.$gjs-is-prefix}#{$name}';
6 | }
7 |
8 | #{gjs_main_mixins.$gjs-is-prefix} {
9 | &grab,
10 | &grab * {
11 | cursor: grab !important;
12 | }
13 |
14 | &grabbing,
15 | &grabbing * {
16 | @include gjs_main_mixins.user-select(none);
17 | cursor: grabbing !important;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/styles/scss/_gjs_traits.scss:
--------------------------------------------------------------------------------
1 | @use 'gjs_vars';
2 | @use 'gjs_category_general';
3 |
4 | .#{gjs_vars.$app-prefix} {
5 | &traits-label {
6 | border-bottom: 1px solid var(--gjs-main-dark-color);
7 | font-weight: lighter;
8 | margin-bottom: 5px;
9 | padding: 10px;
10 | text-align: left;
11 | }
12 |
13 | &label {
14 | &-wrp {
15 | width: 30%;
16 | min-width: 30%;
17 | }
18 | }
19 |
20 | &field {
21 | &-wrp {
22 | flex-grow: 1;
23 | }
24 | }
25 |
26 | &traits-c,
27 | &traits-cs {
28 | display: flex;
29 | flex-direction: column;
30 | }
31 |
32 | &trait-categories {
33 | display: flex;
34 | flex-direction: column;
35 | }
36 |
37 | &trait-category {
38 | width: 100%;
39 |
40 | &.#{gjs_vars.$app-prefix}open {
41 | @extend .#{gjs_vars.$app-prefix}category-open;
42 | }
43 |
44 | .#{gjs_vars.$app-prefix}title {
45 | @extend .#{gjs_vars.$app-prefix}category-title;
46 | }
47 |
48 | .#{gjs_vars.$app-prefix}caret-icon {
49 | margin-right: 5px;
50 | }
51 | }
52 | }
53 |
54 | .#{gjs_vars.$trt-prefix}header {
55 | font-weight: lighter;
56 | padding: 10px;
57 | }
58 |
59 | .#{gjs_vars.$trt-prefix}trait {
60 | display: flex;
61 | justify-content: flex-start;
62 | padding: 5px 10px;
63 | font-weight: lighter;
64 | align-items: center;
65 | text-align: left;
66 | gap: 5px;
67 |
68 | &s {
69 | font-size: var(--gjs-font-size);
70 | }
71 |
72 | .#{gjs_vars.$app-prefix}label {
73 | text-align: left;
74 | text-overflow: ellipsis;
75 | overflow: hidden;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/config/config.ts:
--------------------------------------------------------------------------------
1 | export interface TraitManagerConfig {
2 | /**
3 | * Style prefix.
4 | * @default 'trt-'
5 | */
6 | stylePrefix?: string;
7 |
8 | /**
9 | * Specify the element to use as a container, string (query) or HTMLElement.
10 | * With the empty value, nothing will be rendered.
11 | * @default ''
12 | */
13 | appendTo?: string | HTMLElement;
14 |
15 | /**
16 | * Avoid rendering the default Trait Manager UI.
17 | * More about it here: [Custom Trait Manager](https://grapesjs.com/docs/modules/Traits.html#custom-trait-manager).
18 | * @default false
19 | */
20 | custom?: boolean;
21 |
22 | optionsTarget?: Record[];
23 | }
24 |
25 | const config: () => TraitManagerConfig = () => ({
26 | stylePrefix: 'trt-',
27 | appendTo: '',
28 | optionsTarget: [{ value: false }, { value: '_blank' }],
29 | custom: false,
30 | });
31 |
32 | export default config;
33 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/model/TraitFactory.ts:
--------------------------------------------------------------------------------
1 | import { isString } from 'underscore';
2 | import EditorModel from '../../editor/model/Editor';
3 | import { TraitManagerConfig } from '../config/config';
4 | import { TraitProperties } from '../types';
5 | import Trait from './Trait';
6 |
7 | export default class TraitFactory {
8 | config: Partial;
9 |
10 | constructor(config: Partial = {}) {
11 | this.config = config;
12 | }
13 |
14 | /**
15 | * Build props object by their name
16 | */
17 | build(prop: string | TraitProperties, em: EditorModel): Trait {
18 | return isString(prop) ? this.buildFromString(prop, em) : new Trait(prop, em);
19 | }
20 |
21 | private buildFromString(name: string, em: EditorModel): Trait {
22 | const obj: TraitProperties = {
23 | name: name,
24 | type: 'text',
25 | };
26 |
27 | switch (name) {
28 | case 'target':
29 | obj.type = 'select';
30 | obj.default = false;
31 | obj.options = this.config.optionsTarget as any;
32 | break;
33 | }
34 | return new Trait(obj, em);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/view/TraitButtonView.ts:
--------------------------------------------------------------------------------
1 | import { isString } from 'underscore';
2 | import TraitView from './TraitView';
3 |
4 | export default class TraitButtonView extends TraitView {
5 | templateInput() {
6 | return '';
7 | }
8 |
9 | onChange() {
10 | this.handleClick();
11 | }
12 |
13 | handleClick() {
14 | this.model.runCommand();
15 | }
16 |
17 | renderLabel() {
18 | if (this.model.get('label')) {
19 | TraitView.prototype.renderLabel.apply(this);
20 | }
21 | }
22 |
23 | getInputEl() {
24 | const { model, ppfx } = this;
25 | const { labelButton, text, full } = model.props();
26 | const label = labelButton || text;
27 | const className = `${ppfx}btn`;
28 | const input: any = `${label} `;
31 | return input;
32 | }
33 | }
34 |
35 | // Fix #4388
36 | TraitButtonView.prototype.eventCapture = ['click button'];
37 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/view/TraitCheckboxView.ts:
--------------------------------------------------------------------------------
1 | import { isUndefined } from 'underscore';
2 | import TraitView from './TraitView';
3 |
4 | export default class TraitCheckboxView extends TraitView {
5 | appendInput = false;
6 |
7 | templateInput() {
8 | const { ppfx, clsField } = this;
9 | return `
10 |
11 | `;
12 | }
13 |
14 | /**
15 | * Fires when the input is changed
16 | * @private
17 | */
18 | onChange() {
19 | this.model.set('value', this.getInputElem().checked);
20 | }
21 |
22 | setInputValue(value: any) {
23 | const el = this.getInputElem();
24 | el && (el.checked = !!value);
25 | }
26 |
27 | /**
28 | * Returns input element
29 | * @return {HTMLElement}
30 | * @private
31 | */
32 | getInputEl(...args: any) {
33 | const toInit = !this.$input;
34 | const el = TraitView.prototype.getInputEl.apply(this, args);
35 |
36 | if (toInit) {
37 | let checked, targetValue;
38 | const { model, target } = this;
39 | const { valueFalse } = model.attributes;
40 | const name = model.getName();
41 |
42 | if (model.changeProp) {
43 | checked = target.get(name);
44 | targetValue = checked;
45 | } else {
46 | targetValue = target.get('attributes')![name];
47 | checked = targetValue || targetValue === '' ? !0 : !1;
48 | }
49 |
50 | if (!isUndefined(valueFalse) && targetValue === valueFalse) {
51 | checked = !1;
52 | }
53 |
54 | el!.checked = checked;
55 | }
56 |
57 | return el;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/view/TraitColorView.ts:
--------------------------------------------------------------------------------
1 | import TraitView from './TraitView';
2 | import InputColor from '../../domain_abstract/ui/InputColor';
3 |
4 | export default class TraitColorView extends TraitView {
5 | templateInput() {
6 | return '';
7 | }
8 |
9 | /**
10 | * Returns input element
11 | * @return {HTMLElement}
12 | * @private
13 | */
14 | getInputEl() {
15 | if (!this.input) {
16 | const model = this.model;
17 | const value = this.getModelValue();
18 | const inputColor = new InputColor({
19 | model,
20 | target: this.config.em,
21 | contClass: this.ppfx + 'field-color',
22 | ppfx: this.ppfx,
23 | });
24 | const input = inputColor.render();
25 | input.setValue(value, { fromTarget: 1 });
26 | this.input = input.el as HTMLInputElement;
27 | }
28 |
29 | return this.input;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/src/trait_manager/view/TraitNumberView.ts:
--------------------------------------------------------------------------------
1 | import { isUndefined } from 'underscore';
2 | import InputNumber from '../../domain_abstract/ui/InputNumber';
3 | import TraitView from './TraitView';
4 |
5 | export default class TraitNumberView extends TraitView {
6 | $unit?: HTMLElement;
7 | getValueForTarget() {
8 | const { model } = this;
9 | const { value, unit } = model.attributes;
10 | return !isUndefined(value) && value !== '' ? value + unit : model.get('default');
11 | }
12 |
13 | /**
14 | * Returns input element
15 | * @return {HTMLElement}
16 | * @private
17 | */
18 | getInputEl() {
19 | if (!this.input) {
20 | const { ppfx, model } = this;
21 | const value = this.getModelValue();
22 | const inputNumber = new InputNumber({
23 | contClass: `${ppfx}field-int`,
24 | type: 'number',
25 | model: model,
26 | ppfx,
27 | });
28 | inputNumber.render();
29 | this.$input = inputNumber.inputEl as JQuery;
30 | this.$unit = inputNumber.unitEl as HTMLElement;
31 | // @ts-ignore
32 | model.set('value', value, { fromTarget: true });
33 | this.$input.val(value);
34 | this.input = inputNumber.el as HTMLInputElement;
35 | }
36 | return this.input;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/src/undo_manager/config.ts:
--------------------------------------------------------------------------------
1 | export interface UndoManagerConfig {
2 | /**
3 | * Maximum number of undo items.
4 | * @default 500
5 | */
6 | maximumStackLength?: number;
7 | /**
8 | * Track component selection.
9 | * @default true
10 | */
11 | trackSelection?: boolean;
12 | }
13 |
14 | const config: () => UndoManagerConfig = () => ({
15 | maximumStackLength: 500,
16 | trackSelection: true,
17 | });
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/packages/core/src/utils/fetch.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore avoid errors during TS build
2 | import Promise from 'promise-polyfill';
3 | import { hasWin } from './mixins';
4 |
5 | if (hasWin()) {
6 | window.Promise = window.Promise || Promise;
7 | }
8 |
9 | export default typeof fetch == 'function'
10 | ? // @ts-ignore
11 | fetch.bind()
12 | : (url: string, options: Record) => {
13 | // @ts-ignore avoid errors during TS build
14 | return new Promise((res, rej) => {
15 | const req = new XMLHttpRequest();
16 | req.open(options.method || 'get', url);
17 | req.withCredentials = options.credentials == 'include';
18 |
19 | for (let k in options.headers || {}) {
20 | req.setRequestHeader(k, options.headers[k]);
21 | }
22 |
23 | req.onload = (e) =>
24 | res({
25 | status: req.status,
26 | statusText: req.statusText,
27 | text: () => Promise.resolve(req.responseText),
28 | });
29 | req.onerror = rej;
30 |
31 | // Actually, fetch doesn't support onProgress feature
32 | if (req.upload && options.onProgress) {
33 | req.upload.onprogress = options.onProgress;
34 | }
35 |
36 | // Include body only if present
37 | options.body ? req.send(options.body) : req.send();
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/packages/core/src/utils/host-name.ts:
--------------------------------------------------------------------------------
1 | export function getHostName() {
2 | return window.location.hostname;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/core/src/utils/html.ts:
--------------------------------------------------------------------------------
1 | import { escape } from './mixins';
2 |
3 | /**
4 | * Safe ES6 tagged template strings
5 | * @param {Array} literals
6 | * @param {Array} substs
7 | * @returns {String}
8 | * @example
9 | * const str = 'Hello ';
10 | * const strHtml = html`Escaped ${str}, unescaped $${str}`;
11 | */
12 | export default function html(literals: TemplateStringsArray, ...substs: string[]) {
13 | const { raw } = literals;
14 |
15 | return raw.reduce((acc, lit, i) => {
16 | let subst = substs[i - 1];
17 | const last = raw[i - 1];
18 |
19 | if (Array.isArray(subst)) {
20 | subst = subst.join('');
21 | } else if (last && last.slice(-1) === '$') {
22 | // If the interpolation is preceded by a dollar sign, it won't be escaped
23 | acc = acc.slice(0, -1);
24 | } else {
25 | subst = escape(subst);
26 | }
27 |
28 | return acc + subst + lit;
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import Dragger from './Dragger';
2 | import Resizer from './Resizer';
3 | import * as mixins from './mixins';
4 | import { Module } from '../abstract';
5 | import EditorModel from '../editor/model/Editor';
6 | import ComponentSorter from './sorter/ComponentSorter';
7 | import StyleManagerSorter from './sorter/StyleManagerSorter';
8 |
9 | export default class UtilsModule extends Module {
10 | Sorter = ComponentSorter;
11 | Resizer = Resizer;
12 | Dragger = Dragger;
13 | ComponentSorter = ComponentSorter;
14 | StyleManagerSorter = StyleManagerSorter;
15 | helpers = { ...mixins };
16 |
17 | constructor(em: EditorModel) {
18 | super(em, 'Utils');
19 | }
20 |
21 | destroy() {}
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/utils/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * File made for IE/Edge support
3 | * https://github.com/GrapesJS/grapesjs/issues/214
4 | */
5 | import { hasWin } from './mixins';
6 |
7 | export default () => {
8 | /**
9 | * Check if IE/Edge
10 | * @return {Boolean}
11 | */
12 | const isIE = () => {
13 | let match;
14 | const agent = window.navigator.userAgent;
15 | const rules: [string, RegExp][] = [
16 | ['edge', /Edge\/([0-9\._]+)/],
17 | ['ie', /MSIE\s(7\.0)/],
18 | ['ie', /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],
19 | ['ie', /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],
20 | ];
21 |
22 | for (let i = 0; i < rules.length; i++) {
23 | const rule = rules[i];
24 | match = rule[1].exec(agent);
25 | if (match) break;
26 | }
27 |
28 | return !!match;
29 | };
30 |
31 | if (hasWin() && isIE()) {
32 | const originalCreateHTMLDocument = DOMImplementation.prototype.createHTMLDocument;
33 | DOMImplementation.prototype.createHTMLDocument = (title) => {
34 | if (!title) title = '';
35 | return originalCreateHTMLDocument.apply(document.implementation, [title]);
36 | };
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/packages/core/src/utils/promise-polyfill.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'promise-polyfill' {
2 | const defType: PromiseConstructor;
3 | export default defType;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/src/utils/sorter/CanvasComponentNode.ts:
--------------------------------------------------------------------------------
1 | import { BaseComponentNode } from './BaseComponentNode';
2 |
3 | export default class CanvasComponentNode extends BaseComponentNode {
4 | protected _dropAreaConfig = {
5 | ratio: 0.8,
6 | minUndroppableDimension: 1, // In px
7 | maxUndroppableDimension: 15, // In px
8 | };
9 | /**
10 | * Get the associated view of this component.
11 | * @returns The view associated with the component, or undefined if none.
12 | */
13 | get view() {
14 | return this.model.getView?.();
15 | }
16 |
17 | /**
18 | * Get the associated element of this component.
19 | * @returns The Element associated with the component, or undefined if none.
20 | */
21 | get element() {
22 | return this.model.getEl?.();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/utils/sorter/LayersComponentNode.ts:
--------------------------------------------------------------------------------
1 | import { BaseComponentNode } from './BaseComponentNode';
2 |
3 | export default class LayersComponentNode extends BaseComponentNode {
4 | protected _dropAreaConfig = {
5 | ratio: 0.4,
6 | minUndroppableDimension: 3, // In px
7 | maxUndroppableDimension: 20, // In px
8 | };
9 | /**
10 | * Get the associated view of this component.
11 | * @returns The view associated with the component, or undefined if none.
12 | */
13 | get view(): any {
14 | return this.model.viewLayer;
15 | }
16 |
17 | /**
18 | * Get the associated element of this component.
19 | * @returns The Element associated with the component, or undefined if none.
20 | */
21 | get element(): HTMLElement | undefined {
22 | return this.model.viewLayer?.el;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/utils/sorter/RateLimiter.ts:
--------------------------------------------------------------------------------
1 | export class RateLimiter {
2 | private threshold: number;
3 | private lastArgs: T | undefined;
4 | private timeout: NodeJS.Timeout | null = null;
5 |
6 | constructor(threshold: number) {
7 | this.threshold = threshold;
8 | }
9 |
10 | updateArgs(args: T) {
11 | this.lastArgs = args;
12 | }
13 |
14 | execute(callback: (args: T) => void) {
15 | if (!this.timeout) {
16 | this.timeout = setTimeout(() => {
17 | if (this.lastArgs) {
18 | callback(this.lastArgs);
19 | }
20 | this.timeout = null;
21 | }, this.threshold);
22 | }
23 | }
24 |
25 | clearTimeout() {
26 | if (this.timeout) {
27 | clearTimeout(this.timeout);
28 | this.timeout = null;
29 | }
30 | delete this.lastArgs;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/test/setup.js:
--------------------------------------------------------------------------------
1 | import 'regenerator-runtime/runtime';
2 | import 'whatwg-fetch';
3 | import _ from 'underscore';
4 |
5 | const localStorage = {
6 | getItem(key) {
7 | return this[key];
8 | },
9 | setItem(key, value) {
10 | this[key] = value;
11 | },
12 | removeItem(key, value) {
13 | delete this[key];
14 | },
15 | };
16 |
17 | global._ = _;
18 | global.__GJS_VERSION__ = '';
19 | global.grapesjs = require('./../src').default;
20 | global.$ = global.grapesjs.$;
21 | global.localStorage = localStorage;
22 |
--------------------------------------------------------------------------------
/packages/core/test/specs/asset_manager/model/Asset.ts:
--------------------------------------------------------------------------------
1 | import Asset from '../../../../src/asset_manager/model/Asset';
2 |
3 | describe('Asset', () => {
4 | test('Object exists', () => {
5 | expect(Asset).toBeTruthy();
6 | });
7 |
8 | test('Has default values', () => {
9 | const asset = new Asset({});
10 | expect(asset.get('type')).toBeFalsy();
11 | expect(asset.get('src')).toBeFalsy();
12 | expect(asset.getExtension()).toBeFalsy();
13 | expect(asset.getFilename()).toBeFalsy();
14 | });
15 |
16 | test('Test getFilename', () => {
17 | const asset = new Asset({ type: 'image', src: 'ch/eck/t.e.s.t' });
18 | expect(asset.getFilename()).toEqual('t.e.s.t');
19 | const asset2 = new Asset({ type: 'image', src: 'ch/eck/1234abc' });
20 | expect(asset2.getFilename()).toEqual('1234abc');
21 | });
22 |
23 | test('Test getExtension', () => {
24 | const asset = new Asset({ type: 'image', src: 'ch/eck/t.e.s.t' });
25 | expect(asset.getExtension()).toEqual('t');
26 | const asset2 = new Asset({ type: 'image', src: 'ch/eck/1234abc.' });
27 | expect(asset2.getExtension()).toEqual('');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/packages/core/test/specs/asset_manager/model/AssetImage.ts:
--------------------------------------------------------------------------------
1 | import AssetImage from '../../../../src/asset_manager/model/AssetImage';
2 |
3 | describe('AssetImage', () => {
4 | test('Object exists', () => {
5 | expect(AssetImage).toBeTruthy();
6 | });
7 |
8 | test('Has default values', () => {
9 | const obj = new AssetImage({});
10 | expect(obj.get('type')).toEqual('image');
11 | expect(obj.get('src')).toBeFalsy();
12 | expect(obj.get('unitDim')).toEqual('px');
13 | expect(obj.get('height')).toEqual(0);
14 | expect(obj.get('width')).toEqual(0);
15 | expect(obj.getExtension()).toBeFalsy();
16 | expect(obj.getFilename()).toBeFalsy();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/packages/core/test/specs/asset_manager/model/Assets.ts:
--------------------------------------------------------------------------------
1 | import Assets from '../../../../src/asset_manager/model/Assets';
2 |
3 | describe('Assets', () => {
4 | let obj: Assets;
5 |
6 | beforeEach(() => {
7 | obj = new Assets();
8 | });
9 |
10 | test('Object exists', () => {
11 | expect(obj).toBeTruthy();
12 | });
13 |
14 | test('Collection is empty', () => {
15 | expect(obj.length).toEqual(0);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/packages/core/test/specs/asset_manager/view/AssetView.ts:
--------------------------------------------------------------------------------
1 | import Assets from '../../../../src/asset_manager/model/Assets';
2 | import AssetView from '../../../../src/asset_manager/view/AssetView';
3 |
4 | describe('AssetView', () => {
5 | let testContext: { view?: AssetView };
6 |
7 | beforeEach(() => {
8 | testContext = {};
9 | });
10 |
11 | beforeEach(() => {
12 | const coll = new Assets();
13 | const model = coll.add({ src: 'test' });
14 | testContext.view = new AssetView({
15 | config: {},
16 | model,
17 | } as any);
18 | document.body.innerHTML = '
';
19 | document.body.querySelector('#fixtures')!.appendChild(testContext.view.render().el);
20 | });
21 |
22 | afterEach(() => {
23 | testContext.view!.remove();
24 | });
25 |
26 | test('Object exists', () => {
27 | expect(AssetView).toBeTruthy();
28 | });
29 |
30 | test('Has correct prefix', () => {
31 | expect(testContext.view!.pfx).toEqual('');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/packages/core/test/specs/code_manager/index.js:
--------------------------------------------------------------------------------
1 | import CodeManager from 'code_manager';
2 | import Editor from 'editor/model/Editor';
3 |
4 | describe('Code Manager', () => {
5 | describe('Main', () => {
6 | let obj;
7 |
8 | beforeEach(() => {
9 | const em = new Editor({});
10 | obj = new CodeManager(em);
11 | });
12 |
13 | afterEach(() => {
14 | obj = null;
15 | });
16 |
17 | test('Object exists', () => {
18 | expect(CodeManager).toBeTruthy();
19 | });
20 |
21 | test('Add and get code generator', () => {
22 | obj.addGenerator('test', 'gen');
23 | expect(obj.getGenerator('test')).toEqual('gen');
24 | });
25 |
26 | test('Add and get code viewer', () => {
27 | obj.addViewer('test', 'view');
28 | expect(obj.getViewer('test')).toEqual('view');
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/core/test/specs/commands/view/SwitchVisibility.ts:
--------------------------------------------------------------------------------
1 | import SwitchVisibility from '../../../../src/commands/view/SwitchVisibility';
2 |
3 | describe('SwitchVisibility command', () => {
4 | let fakeEditor: any;
5 | let fakeFrames: any;
6 | let fakeIsActive: any;
7 |
8 | beforeEach(() => {
9 | fakeFrames = [];
10 | fakeIsActive = false;
11 |
12 | fakeEditor = {
13 | Canvas: {
14 | getFrames: jest.fn(() => fakeFrames),
15 | },
16 |
17 | Commands: {
18 | isActive: jest.fn(() => fakeIsActive),
19 | },
20 | };
21 | });
22 |
23 | describe('.toggleVis', () => {
24 | it('should do nothing if the preview command is active', () => {
25 | expect(fakeEditor.Canvas.getFrames).not.toHaveBeenCalled();
26 | fakeIsActive = true;
27 | SwitchVisibility.toggleVis(fakeEditor);
28 | expect(fakeEditor.Canvas.getFrames).not.toHaveBeenCalled();
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Collection component Nested collections are correctly serialized 1`] = `
4 | {
5 | "components": [
6 | {
7 | "components": [
8 | {
9 | "components": [
10 | {
11 | "components": [
12 | {
13 | "name": {
14 | "collectionId": "nested_collection",
15 | "path": "user",
16 | "type": "data-variable",
17 | "variableType": "currentItem",
18 | },
19 | "type": "default",
20 | },
21 | ],
22 | "type": "data-collection-item",
23 | },
24 | ],
25 | "dataResolver": {
26 | "collectionId": "nested_collection",
27 | "dataSource": {
28 | "path": "nested_data_source_id",
29 | "type": "data-variable",
30 | },
31 | },
32 | "type": "data-collection",
33 | },
34 | ],
35 | "type": "data-collection-item",
36 | },
37 | ],
38 | "dataResolver": {
39 | "collectionId": "parent_collection",
40 | "dataSource": {
41 | "path": "my_data_source_id",
42 | "type": "data-variable",
43 | },
44 | },
45 | "type": "data-collection",
46 | }
47 | `;
48 |
--------------------------------------------------------------------------------
/packages/core/test/specs/dom_components/model/ComponentWrapper.ts:
--------------------------------------------------------------------------------
1 | import Component from '../../../../src/dom_components/model/Component';
2 | import ComponentHead from '../../../../src/dom_components/model/ComponentHead';
3 | import Editor from '../../../../src/editor';
4 |
5 | describe('ComponentWrapper', () => {
6 | let em: Editor;
7 |
8 | beforeEach(() => {
9 | em = new Editor({ avoidDefaults: true });
10 | em.Pages.onLoad();
11 | });
12 |
13 | describe('.clone', () => {
14 | test('clones the component and returns a new instance for head and document element', () => {
15 | const originalComponent = em.Pages.getSelected()?.getMainComponent();
16 | const clonedComponent = originalComponent?.clone();
17 | em.Pages.add(
18 | {
19 | id: 'PAGE_ID',
20 | clonedComponent,
21 | },
22 | {
23 | select: true,
24 | },
25 | );
26 | const newPageComponent = em.Pages.get('PAGE_ID')?.getMainComponent();
27 |
28 | expect(clonedComponent?.head).toBeInstanceOf(ComponentHead);
29 | expect(clonedComponent?.head.cid).not.toEqual(originalComponent?.head.cid);
30 |
31 | expect(clonedComponent?.docEl).toBeInstanceOf(Component);
32 | expect(clonedComponent?.docEl.cid).not.toEqual(originalComponent?.docEl.cid);
33 | expect(newPageComponent?.head.cid).not.toEqual(originalComponent?.head.cid);
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/packages/core/test/specs/dom_components/view/ComponentImageView.ts:
--------------------------------------------------------------------------------
1 | import ComponentImageView from '../../../../src/dom_components/view/ComponentImageView';
2 | import Component from '../../../../src/dom_components/model/ComponentImage';
3 | import Editor from '../../../../src/editor/model/Editor';
4 |
5 | describe('ComponentImageView', () => {
6 | let em: Editor;
7 | let model: Component;
8 | let view: ComponentImageView;
9 |
10 | beforeEach(() => {
11 | em = new Editor();
12 | model = new Component({}, { em, config: em.Components.config });
13 | const cmpViewOpts = {
14 | model,
15 | config: { em },
16 | };
17 | view = new ComponentImageView(cmpViewOpts);
18 | document.body.innerHTML = '
';
19 | document.body.querySelector('#fixtures')!.appendChild(view.render().el);
20 | });
21 |
22 | afterEach(() => {
23 | em.destroy();
24 | });
25 |
26 | test('Component empty', () => {
27 | expect(view.el.getAttribute('class')).toEqual(view.classEmpty);
28 | });
29 |
30 | test('TagName is ', () => {
31 | expect(view.el.tagName).toEqual('IMG');
32 | });
33 |
34 | test('Update src attribute', () => {
35 | model.set('src', './');
36 | expect(view.el.getAttribute('src')).toEqual('./');
37 | });
38 |
39 | test('Renders correctly', () => {
40 | expect(view.render()).toBeTruthy();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/packages/core/test/specs/dom_components/view/ComponentsView.ts:
--------------------------------------------------------------------------------
1 | import Components from '../../../../src/dom_components/model/Components';
2 | import ComponentsView from '../../../../src/dom_components/view/ComponentsView';
3 | import Editor from '../../../../src/editor/model/Editor';
4 |
5 | describe('ComponentsView', () => {
6 | let model: Components;
7 | let view: ComponentsView;
8 | let dcomp: Editor['Components'];
9 | let compOpts: any;
10 | let em: Editor;
11 |
12 | beforeEach(() => {
13 | em = new Editor();
14 | dcomp = em.Components;
15 | compOpts = {
16 | em,
17 | componentTypes: dcomp.componentTypes,
18 | };
19 | model = new Components([], compOpts);
20 | view = new ComponentsView({
21 | // @ts-ignore
22 | collection: model,
23 | componentTypes: dcomp.componentTypes,
24 | config: { em },
25 | });
26 | document.body.innerHTML = '
';
27 | document.body.querySelector('#fixtures')!.appendChild(view.render().el);
28 | });
29 |
30 | afterEach(() => {
31 | view.collection.reset();
32 | });
33 |
34 | test('Collection is empty', () => {
35 | expect(view.$el.html()).toBeFalsy();
36 | });
37 |
38 | test('Add new component', () => {
39 | const addSpy = jest.spyOn(view, 'addToCollection');
40 | view.collection.add({});
41 | expect(addSpy).toBeCalledTimes(1);
42 | });
43 |
44 | test('Render new component', () => {
45 | view.collection.add({});
46 | expect(view.$el.html()).toBeTruthy();
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/packages/core/test/specs/modal/index.js:
--------------------------------------------------------------------------------
1 | import Modal from 'modal_dialog';
2 | import Editor from 'editor';
3 |
4 | describe('Modal dialog', () => {
5 | describe('Main', () => {
6 | var em;
7 | var obj;
8 |
9 | beforeEach(() => {
10 | em = new Editor({});
11 | obj = new Modal(em);
12 | });
13 |
14 | afterEach(() => {
15 | obj = null;
16 | });
17 |
18 | test('Object exists', () => {
19 | expect(obj).toBeTruthy();
20 | });
21 |
22 | test('Is close by default', () => {
23 | expect(obj.isOpen()).toEqual(false);
24 | });
25 |
26 | test('Title is empty', () => {
27 | expect(obj.getTitle()).toEqual('');
28 | });
29 |
30 | test('Content is empty', () => {
31 | expect(obj.getContent()).toEqual('');
32 | });
33 |
34 | test('Set title', () => {
35 | obj.setTitle('Test');
36 | expect(obj.getTitle()).toEqual('Test');
37 | });
38 |
39 | test('Set content', () => {
40 | obj.setContent('Test');
41 | expect(obj.getContent()).toEqual('Test');
42 | });
43 |
44 | test('Set HTML content', () => {
45 | obj.setContent('Test ');
46 | expect(obj.getContent()).toEqual('Test ');
47 | });
48 |
49 | test('Open modal', () => {
50 | obj.open();
51 | expect(obj.isOpen()).toEqual(true);
52 | });
53 |
54 | test('Close modal', () => {
55 | obj.open();
56 | obj.close();
57 | expect(obj.isOpen()).toEqual(false);
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/packages/core/test/specs/modal/view/ModalView.js:
--------------------------------------------------------------------------------
1 | import ModalView from 'modal_dialog/view/ModalView';
2 | import Modal from 'modal_dialog/model/Modal';
3 | import Editor from 'editor';
4 |
5 | describe('ModalView', () => {
6 | var model;
7 | var view;
8 | var em;
9 |
10 | beforeEach(() => {
11 | em = new Editor({});
12 | model = new Modal(em);
13 | view = new ModalView({
14 | model,
15 | });
16 | document.body.innerHTML = '
';
17 | document.body.querySelector('#fixtures').appendChild(view.render().el);
18 | });
19 |
20 | afterEach(() => {
21 | view = null;
22 | model = null;
23 | });
24 |
25 | test('The content is not empty', () => {
26 | expect(view.el.innerHTML).toBeTruthy();
27 | });
28 |
29 | test('Get content', () => {
30 | expect(view.getContent()).toBeTruthy();
31 | });
32 |
33 | test('Update content', () => {
34 | model.set('content', 'test');
35 | expect(view.getContent().get(0).innerHTML).toEqual('test');
36 | });
37 |
38 | test('Get title', () => {
39 | expect(view.getTitle()).toBeTruthy();
40 | });
41 |
42 | test('Update title', () => {
43 | model.set('title', 'test');
44 | expect(view.getTitle().innerHTML).toEqual('test');
45 | });
46 |
47 | test('Close by default', () => {
48 | view.updateOpen();
49 | expect(view.el.style.display).toEqual('none');
50 | });
51 |
52 | test('Open dialog', () => {
53 | model.set('open', 1);
54 | expect(view.el.style.display).toEqual('');
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/packages/core/test/specs/navigator/view/ItemView.ts:
--------------------------------------------------------------------------------
1 | import defConfig from '../../../../src/navigator/config/config';
2 | import EditorModel from '../../../../src/editor/model/Editor';
3 | import ItemView from '../../../../src/navigator/view/ItemView';
4 |
5 | describe('ItemView', () => {
6 | let itemView: ItemView;
7 |
8 | const isVisible = (itemView: ItemView) => {
9 | return itemView.module.isVisible(itemView.model);
10 | };
11 |
12 | beforeEach(() => {
13 | const em = new EditorModel();
14 | const defCmp = em.get('DomComponents').getType('default').model;
15 |
16 | itemView = new ItemView({
17 | model: new defCmp({}, { em }),
18 | module: em.get('LayerManager'),
19 | config: { ...defConfig(), em },
20 | } as any);
21 | });
22 |
23 | describe('.isVisible', () => {
24 | it("should return `false` if the model's `style` object has a `display` property set to `none`, `true` otherwise", () => {
25 | expect(isVisible(itemView)).toEqual(true);
26 | itemView.model.addStyle({ display: '' });
27 | expect(isVisible(itemView)).toEqual(true);
28 | itemView.model.addStyle({ display: 'none' });
29 | expect(isVisible(itemView)).toEqual(false);
30 | itemView.model.addStyle({ display: 'block' });
31 | expect(isVisible(itemView)).toEqual(true);
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/packages/core/test/specs/panels/view/ButtonsView.ts:
--------------------------------------------------------------------------------
1 | import ButtonsView from '../../../../src/panels/view/ButtonsView';
2 | import Buttons from '../../../../src/panels/model/Buttons';
3 | import EditorModel from '../../../../src/editor/model/Editor';
4 |
5 | describe('ButtonsView', () => {
6 | let fixtures: HTMLElement;
7 | let em: EditorModel;
8 | let model: Buttons;
9 | let view: ButtonsView;
10 |
11 | beforeEach(() => {
12 | em = new EditorModel({});
13 | model = new Buttons(em.Panels, []);
14 | view = new ButtonsView(model);
15 | document.body.innerHTML = '
';
16 | fixtures = document.body.querySelector('#fixtures')!;
17 | fixtures.appendChild(view.render().el);
18 | });
19 |
20 | afterEach(() => {
21 | view.collection.reset();
22 | });
23 |
24 | test('Collection is empty', () => {
25 | expect(view.$el.html()).toEqual('');
26 | });
27 |
28 | test('Add new button', () => {
29 | const spy = jest.spyOn(view, 'addToCollection' as any);
30 | view.collection.add([{}]);
31 | expect(spy).toBeCalledTimes(1);
32 | });
33 |
34 | test('Render new button', () => {
35 | view.collection.add([{}]);
36 | expect(view.$el.html()).toBeTruthy();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/packages/core/test/specs/panels/view/PanelsView.ts:
--------------------------------------------------------------------------------
1 | import PanelsView from '../../../../src/panels/view/PanelsView';
2 | import Panels from '../../../../src/panels/model/Panels';
3 | import Editor from '../../../../src/editor';
4 |
5 | describe('PanelsView', () => {
6 | let fixtures: HTMLElement;
7 | let editor: Editor;
8 | let model: Panels;
9 | let view: PanelsView;
10 |
11 | beforeEach(() => {
12 | editor = new Editor({});
13 | model = new Panels(editor.Panels, []);
14 | view = new PanelsView(model);
15 | document.body.innerHTML = '
';
16 | fixtures = document.body.querySelector('#fixtures')!;
17 | fixtures.appendChild(view.render().el);
18 | });
19 |
20 | afterEach(() => {
21 | view.collection.reset();
22 | });
23 |
24 | test('Collection is empty', () => {
25 | expect(view.$el.html()).toEqual('');
26 | });
27 |
28 | test('Add new panel', () => {
29 | const spy = jest.spyOn(view, 'addToCollection' as any);
30 | view.collection.add([{}]);
31 | expect(spy).toBeCalledTimes(1);
32 | });
33 |
34 | test('Render new panel', () => {
35 | view.collection.add([{}]);
36 | expect(view.$el.html()).toBeTruthy();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/packages/core/test/specs/plugin_manager/index.js:
--------------------------------------------------------------------------------
1 | import PluginManager from 'plugin_manager';
2 |
3 | describe('PluginManager', () => {
4 | describe('Main', () => {
5 | var obj;
6 | var val;
7 | var testPlugin = (e) => {
8 | val = e;
9 | };
10 |
11 | beforeEach(() => {
12 | obj = new PluginManager();
13 | });
14 |
15 | afterEach(() => {
16 | obj = null;
17 | });
18 |
19 | test('Object exists', () => {
20 | expect(obj).toBeTruthy();
21 | });
22 |
23 | test('No plugins inside', () => {
24 | expect(obj.getAll()).toEqual({});
25 | });
26 |
27 | test('Add new plugin', () => {
28 | obj.add('test', testPlugin);
29 | expect(obj.get('test')).toBeTruthy();
30 | });
31 |
32 | test('Added plugin is working', () => {
33 | obj.add('test', testPlugin);
34 | var plugin = obj.get('test');
35 | plugin('tval');
36 | expect(val).toEqual('tval');
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/packages/core/test/specs/style_manager/view/SectorsView.ts:
--------------------------------------------------------------------------------
1 | import SectorsView from '../../../../src/style_manager/view/SectorsView';
2 | import Sectors from '../../../../src/style_manager/model/Sectors';
3 |
4 | describe('SectorsView', () => {
5 | let fixtures: HTMLElement;
6 | let model: Sectors;
7 | let view: SectorsView;
8 |
9 | beforeEach(() => {
10 | model = new Sectors([]);
11 | view = new SectorsView({
12 | collection: model,
13 | });
14 | document.body.innerHTML = '
';
15 | fixtures = document.body.firstChild as HTMLElement;
16 | fixtures.appendChild(view.render().el);
17 | });
18 |
19 | afterEach(() => {
20 | view.collection.reset();
21 | });
22 |
23 | test('Collection is empty', () => {
24 | expect(view.el.innerHTML).toEqual('');
25 | });
26 |
27 | test('Add new sectors', () => {
28 | view.collection.add([{}, {}]);
29 | expect(view.el.children.length).toEqual(2);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/core/test/specs/trait_manager/index.ts:
--------------------------------------------------------------------------------
1 | import Editor from '../../../src/editor/model/Editor';
2 | import TraitManager from '../../../src/trait_manager';
3 |
4 | describe('TraitManager', () => {
5 | let em: Editor;
6 | let tm: TraitManager;
7 |
8 | beforeEach(() => {
9 | em = new Editor({
10 | mediaCondition: 'max-width',
11 | avoidInlineStyle: true,
12 | });
13 | tm = em.Traits;
14 | // em.Pages.onLoad();
15 | });
16 |
17 | afterEach(() => {
18 | em.destroy();
19 | });
20 |
21 | test('TraitManager exists', () => {
22 | expect(tm).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/packages/core/test/specs/trait_manager/model/TraitsModel.ts:
--------------------------------------------------------------------------------
1 | import Trait from '../../../../src/trait_manager/model/Trait';
2 | import Traits from '../../../../src/trait_manager/model/Traits';
3 | import Component from '../../../../src/dom_components/model/Component';
4 | import Editor from '../../../../src/editor';
5 | import EditorModel from '../../../../src/editor/model/Editor';
6 |
7 | describe('TraitModels', () => {
8 | var trait: Trait;
9 | var target: Component;
10 | var modelName = 'title';
11 | var em: EditorModel;
12 |
13 | beforeEach(() => {
14 | em = new Editor().getModel();
15 | target = new Component({}, { em, config: em.Components.config });
16 | trait = new Trait(
17 | {
18 | name: modelName,
19 | target,
20 | },
21 | em,
22 | );
23 | });
24 |
25 | afterEach(() => {});
26 |
27 | test('Object exists', () => {
28 | expect(trait).toBeTruthy();
29 | });
30 | test('Traits undo property', () => {
31 | em.loadOnStart();
32 | const wrapper = em.Components.getWrapper();
33 | wrapper!.append(target);
34 | const traits = new Traits([], { em });
35 | traits.add(modelName);
36 | traits.setTarget(target);
37 | const trait = traits.models[0];
38 | trait.setTargetValue('TitleValue');
39 |
40 | expect(target.getAttributes()[modelName]).toBe('TitleValue');
41 | em.UndoManager.undo();
42 | expect(target.getAttributes()[modelName]).toBeUndefined;
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/packages/core/test/specs/utils/Mixins.ts:
--------------------------------------------------------------------------------
1 | import { buildBase64UrlFromSvg } from '../../../src/utils/mixins';
2 |
3 | describe('.buildBase64UrlFromSvg', () => {
4 | it('returns original when a none svg is provided', () => {
5 | const input = 'something else';
6 | expect(buildBase64UrlFromSvg(input)).toEqual(input);
7 | });
8 |
9 | it('returns base64 image when an svg is provided', () => {
10 | const input = `
21 |
22 |
23 |
24 | `;
25 |
26 | const output =
27 | // eslint-disable-next-line max-len
28 | 'data:image/svg+xml;base64,PHN2ZwogICAgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgICAgIHdpZHRoPSIyNCIKICAgICAgaGVpZ2h0PSIyNCIKICAgICAgdmlld0JveD0iMCAwIDI0IDI0IgogICAgICBmaWxsPSJub25lIgogICAgICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICAgICAgc3Ryb2tlLXdpZHRoPSIyIgogICAgICBzdHJva2UtbGluZWNhcD0icm91bmQiCiAgICAgIHN0cm9rZS1saW5lam9pbj0icm91bmQiCiAgICA+CiAgICAgIDxwb2x5Z29uIHBvaW50cz0iMSA2IDEgMjIgOCAxOCAxNiAyMiAyMyAxOCAyMyAyIDE2IDYgOCAyIDEgNiIgLz4KICAgICAgPGxpbmUgeDE9IjgiIHkxPSIyIiB4Mj0iOCIgeTI9IjE4IiAvPgogICAgICA8bGluZSB4MT0iMTYiIHkxPSI2IiB4Mj0iMTYiIHkyPSIyMiIgLz4KICAgIDwvc3ZnPg==';
29 | expect(buildBase64UrlFromSvg(input)).toEqual(output);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/packages/core/test/test_utils.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | storageMock() {
3 | var db = {};
4 | return {
5 | id: 'testStorage',
6 | store(data) {
7 | db = data;
8 | },
9 | load(keys) {
10 | return db;
11 | },
12 | getDb() {
13 | return db;
14 | },
15 | };
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "sourceMap": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": false,
18 | "outDir": "./dist"
19 | },
20 | "include": ["src", "test"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const rootDir = path.resolve(__dirname);
3 |
4 | module.exports = ({ config, pkg, webpack }) => {
5 | const { BUILD_MODULE } = process.env;
6 |
7 | return {
8 | ...config,
9 | output: {
10 | ...config.output,
11 | filename: BUILD_MODULE ? 'grapes.mjs' : 'grapes.min.js',
12 | ...(BUILD_MODULE
13 | ? {
14 | libraryTarget: 'module',
15 | library: { type: 'module' },
16 | }
17 | : {
18 | libraryExport: 'default',
19 | }),
20 | },
21 | optimization: {
22 | ...config.optimization,
23 | minimize: !BUILD_MODULE,
24 | },
25 | devServer: {
26 | ...config.devServer,
27 | static: [rootDir],
28 | headers: { 'Access-Control-Allow-Origin': '*' },
29 | allowedHosts: 'all',
30 | },
31 | experiments: {
32 | outputModule: !!BUILD_MODULE,
33 | },
34 | resolve: {
35 | ...config.resolve,
36 | modules: [...(config.resolve && config.resolve.modules), 'src'],
37 | alias: {
38 | ...(config.resolve && config.resolve.alias),
39 | jquery: `${rootDir}/src/utils/cash-dom`,
40 | backbone: `${rootDir}/node_modules/backbone`,
41 | underscore: `${rootDir}/node_modules/underscore`,
42 | },
43 | },
44 | plugins: [
45 | new webpack.DefinePlugin({
46 | __GJS_VERSION__: `'${pkg.version}'`,
47 | }),
48 | ...config.plugins,
49 | ],
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/cli'
3 | - 'packages/core'
4 | - 'docs/'
5 |
--------------------------------------------------------------------------------
/scripts/common.ts:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process';
2 |
3 | export function runCommand(command: string, error?: string) {
4 | try {
5 | return (execSync(command, { stdio: 'inherit' }) || '').toString().trim();
6 | } catch (err) {
7 | console.error(error || `Error while running command: ${command}`);
8 | throw err;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/scripts/releaseCli.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { resolve } from 'path';
3 | import { runCommand } from './common';
4 |
5 | const pathLib = resolve(__dirname, '../packages/cli');
6 |
7 | async function prepareCoreRelease() {
8 | try {
9 | const releaseTag = process.argv[2] || 'rc';
10 | console.log('Prepare release cli tag:', releaseTag);
11 |
12 | // Check if the current branch is clean (no staged changes)
13 | runCommand(
14 | 'git diff-index --quiet HEAD --',
15 | 'You have uncommitted changes. Please commit or stash them before running the release script.',
16 | );
17 |
18 | // Increment the CLI version
19 | const versionCmd = releaseTag === 'latest' ? 'patch' : `prerelease --preid ${releaseTag}`;
20 | runCommand(`pnpm --filter grapesjs-cli exec npm version ${versionCmd} --no-git-tag-version --no-commit-hooks`);
21 |
22 | // Create a new release branch
23 | const newVersion = JSON.parse(fs.readFileSync(`${pathLib}/package.json`, 'utf8')).version;
24 | const newBranch = `release-v${newVersion}`;
25 | runCommand(`git checkout -b ${newBranch}`);
26 | runCommand('git add .');
27 | runCommand(`git commit -m "Release GrapesJS cli ${releaseTag}: v${newVersion}"`);
28 |
29 | console.log(`Release prepared! Push the current "${newBranch}" branch and open a new PR targeting 'dev'`);
30 | } catch (error) {
31 | console.error(error);
32 | process.exit(1);
33 | }
34 | }
35 |
36 | prepareCoreRelease();
37 |
--------------------------------------------------------------------------------
/scripts/releaseCore.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { resolve } from 'path';
3 | import { runCommand } from './common';
4 |
5 | const pathLib = resolve(__dirname, '../packages/core');
6 |
7 | async function prepareCoreRelease() {
8 | try {
9 | const releaseTag = process.argv[2] || 'rc';
10 | console.log('Prepare release core tag:', releaseTag);
11 |
12 | // Check if the current branch is clean (no staged changes)
13 | runCommand(
14 | 'git diff-index --quiet HEAD --',
15 | 'You have uncommitted changes. Please commit or stash them before running the release script.',
16 | );
17 |
18 | // Increment the Core version
19 | const versionCmd = releaseTag === 'latest' ? 'patch' : `prerelease --preid ${releaseTag}`;
20 | runCommand(`pnpm --filter grapesjs exec npm version ${versionCmd} --no-git-tag-version --no-commit-hooks`);
21 |
22 | // Create a new release branch
23 | const newVersion = JSON.parse(fs.readFileSync(`${pathLib}/package.json`, 'utf8')).version;
24 | const newBranch = `release-v${newVersion}`;
25 | runCommand(`git checkout -b ${newBranch}`);
26 | runCommand('git add .');
27 | runCommand(`git commit -m "Release GrapesJS core ${releaseTag}: v${newVersion}"`);
28 |
29 | console.log(`Release prepared! Push the current "${newBranch}" branch and open a new PR targeting 'dev'`);
30 | } catch (error) {
31 | console.error(error);
32 | process.exit(1);
33 | }
34 | }
35 |
36 | prepareCoreRelease();
37 |
--------------------------------------------------------------------------------
/scripts/releaseDocs.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { resolve } from 'path';
3 | import { runCommand } from './common';
4 |
5 | const pathLib = resolve(__dirname, '../docs');
6 |
7 | async function prepareCoreRelease() {
8 | try {
9 | // Check if the current branch is clean (no staged changes)
10 | runCommand(
11 | 'git diff-index --quiet HEAD --',
12 | 'You have uncommitted changes. Please commit or stash them before running the release script.',
13 | );
14 |
15 | // Increment the docs version
16 | runCommand(`pnpm --filter @grapesjs/docs exec npm version patch --no-git-tag-version --no-commit-hooks`);
17 |
18 | // Create a new release branch
19 | const newVersion = JSON.parse(fs.readFileSync(`${pathLib}/package.json`, 'utf8')).version;
20 | const newBranch = `release-docs-v${newVersion}`;
21 | runCommand(`git checkout -b ${newBranch}`);
22 | runCommand('git add .');
23 | runCommand(`git commit -m "Release GrapesJS docs: v${newVersion}"`);
24 |
25 | console.log(`Release prepared! Push the current "${newBranch}" branch and open a new PR targeting 'dev'`);
26 | } catch (error) {
27 | console.error(error);
28 | process.exit(1);
29 | }
30 | }
31 |
32 | prepareCoreRelease();
33 |
--------------------------------------------------------------------------------