├── .editorconfig
├── .eslintrc.yml
├── .gitattributes
├── .github
└── workflows
│ ├── auto-format-code.yml
│ └── test-code.yml
├── .gitignore
├── CHANGELOG.md
├── EXTENDING.md
├── README.md
├── UPGRADING.md
├── composer.json
├── dist
├── css
│ └── field.css
├── js
│ ├── field.js
│ ├── field.js.LICENSE.txt
│ ├── index.js
│ └── index.js.LICENSE.txt
└── mix-manifest.json
├── nova.mix.js
├── package-lock.json
├── package.json
├── phpunit.xml
├── pint.json
├── resources
├── css
│ └── field.css
├── js
│ ├── blocks
│ │ ├── checklist.js
│ │ ├── code.js
│ │ ├── delimiter.js
│ │ ├── embed.js
│ │ ├── heading.js
│ │ ├── image.js
│ │ ├── inline-code.js
│ │ ├── link.js
│ │ ├── list.js
│ │ ├── marker.js
│ │ ├── paragraph.js
│ │ ├── raw.js
│ │ └── table.js
│ ├── components
│ │ ├── DetailField.vue
│ │ ├── FormField.vue
│ │ └── IndexField.vue
│ ├── field.js
│ ├── index.js
│ └── nova-editor.js
└── views
│ ├── checklist.blade.php
│ ├── code.blade.php
│ ├── content.blade.php
│ ├── delimiter.blade.php
│ ├── embed.blade.php
│ ├── heading.blade.php
│ ├── image.blade.php
│ ├── link.blade.php
│ ├── list.blade.php
│ ├── paragraph.blade.php
│ ├── raw.blade.php
│ └── table.blade.php
├── routes
└── api.php
├── src
├── Events
│ ├── EditorJsImageUploaded.php
│ └── EditorJsThumbnailCreated.php
├── FieldServiceProvider.php
├── Http
│ └── Controllers
│ │ ├── EditorJsImageUploadController.php
│ │ └── EditorJsLinkController.php
├── NovaEditorJs.php
├── NovaEditorJsCast.php
├── NovaEditorJsConverter.php
├── NovaEditorJsData.php
├── NovaEditorJsField.php
└── config
│ └── nova-editor-js.php
├── tests
├── Feature
│ ├── Http
│ │ └── Controllers
│ │ │ ├── EditorJsImageUploadControllerTest.php
│ │ │ └── EditorJsLinkControllerTest.php
│ └── Views
│ │ ├── LinkViewTest.php
│ │ └── ViewTestHelpers.php
├── Fixtures
│ ├── Models
│ │ └── Dummy.php
│ ├── TestServiceProvider.php
│ ├── database
│ │ └── migrations
│ │ │ └── 2022_07_17_153928_create_dummies_table.php
│ ├── nova
│ │ ├── composer.json
│ │ └── src
│ │ │ ├── Events
│ │ │ └── ServingNova.php
│ │ │ ├── StaticallyUselessClass.php
│ │ │ ├── UselessServiceProvider.php
│ │ │ └── aliases.php
│ └── resources
│ │ ├── html
│ │ └── editorjs.html
│ │ └── json
│ │ └── editorjs.json
├── TestCase.php
├── Unit
│ ├── JsonContentTest.php
│ └── NovaEditorJsCastTest.php
├── helpers.php
└── resources
│ └── responses
│ ├── image.gif
│ ├── image.jpg
│ ├── image.png
│ ├── image.svg
│ ├── image.txt
│ ├── image.webp
│ ├── simple.html
│ └── with-image.html
└── webpack.mix.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{yaml,yml,md}]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends:
2 | - airbnb-base
3 | - plugin:vue/vue3-recommended
4 |
5 | globals:
6 | _: readonly
7 | Nova: readonly
8 | NovaEditorJS: writable
9 |
10 | settings:
11 | import/resolver:
12 | node:
13 | extensions: ['.js', '.vue']
14 |
15 | rules:
16 | global-require: off
17 | indent:
18 | - error
19 | - 4
20 | no-param-reassign:
21 | - off
22 | - props: false
23 | vue/require-prop-types: off
24 | vue/html-indent:
25 | - error
26 | - 4
27 | import/extensions: off
28 | import/no-unresolved:
29 | - error
30 | - ignore:
31 | - laravel-nova
32 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /dist/js/* -text merge=binary
2 | /dist/css/* -text merge=binary
3 |
--------------------------------------------------------------------------------
/.github/workflows/auto-format-code.yml:
--------------------------------------------------------------------------------
1 | name: Verify code formatting
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - develop
7 | - master
8 | push:
9 | branches:
10 | - develop
11 |
12 | permissions:
13 | # Give the default GITHUB_TOKEN write permission to commit and push the
14 | # added or changed files to the repository.
15 | contents: write
16 |
17 | jobs:
18 | test-formatting:
19 | name: Auto-format code using php-cs-fixer
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | with:
26 | fetch-depth: 0
27 |
28 | - name: Setup NodeJS
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: lts/*
32 | cache: 'npm'
33 |
34 | - name: Install NodeJS dependencies
35 | run: npm install --verbose --foreground-scripts
36 |
37 | - name: Setup PHP
38 | uses: shivammathur/setup-php@v2
39 | with:
40 | php-version: '8.3'
41 | extensions: exif,json,mbstring
42 | coverage: none
43 |
44 | - name: Configure local Laravel Nova dummy package
45 | run: |
46 | composer config repositories.0 path ./tests/Fixtures/nova
47 | git update-index --assume-unchanged composer.json
48 |
49 | - name: Install Composer dependencies
50 | uses: ramsey/composer-install@v3
51 |
52 | - name: Lint code
53 | run: |
54 | composer run format
55 | npm run format
56 |
57 | - name: Report changes
58 | id: report-changes
59 | run: |
60 | git diff --color=always
61 | echo " > git diff --shortstat" >> $GITHUB_STEP_SUMMARY
62 | echo " $( git diff --shortstat )" >> $GITHUB_STEP_SUMMARY
63 | echo "HAS_CHANGES=$( git diff --quiet && echo 'no' || echo 'yes' )" >> $GITHUB_OUTPUT
64 |
65 | - name: Fail on changes (pull request only)
66 | if: ${{ github.event_name == 'pull_request' && steps.report-changes.outputs.HAS_CHANGES == 'yes' }}
67 | run: |
68 | echo '::error title=Linting caused changes::Some files were modified by the linter, please run `composer format` to fix these'
69 | exit 1
70 |
71 | - name: Commit changes (push only)
72 | if: github.event_name == 'push'
73 | uses: stefanzweifel/git-auto-commit-action@v5
74 | with:
75 | commit_message: 'chore: fixed code formatting issues'
76 |
77 |
--------------------------------------------------------------------------------
/.github/workflows/test-code.yml:
--------------------------------------------------------------------------------
1 | name: "Run unit tests"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | test-laravel:
11 | name: Test Laravel ${{ matrix.laravel }} on PHP ${{ matrix.php }}
12 | runs-on: ubuntu-latest
13 | continue-on-error: ${{ matrix.experimental == true }}
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | php:
19 | - '8.2'
20 | - '8.3'
21 | - nightly
22 |
23 | laravel:
24 | - '10.0'
25 | - '11.0'
26 | - '12.0'
27 |
28 | include:
29 | - laravel: '10.0'
30 | testbench: '8.0'
31 | - laravel: '11.0'
32 | testbench: '9.0'
33 | - laravel: '12.0'
34 | testbench: '10.0'
35 | - php: 8.3
36 | laravel: '11.0'
37 | stable: true
38 | - php: nightly
39 | experimental: true
40 |
41 | steps:
42 | - name: Checkout
43 | uses: actions/checkout@v4
44 |
45 | - name: Setup PHP
46 | uses: shivammathur/setup-php@v2
47 | with:
48 | php-version: ${{ matrix.php }}
49 | extensions: exif,json,mbstring
50 | coverage: pcov
51 |
52 | - name: Configure to use Laravel ${{ matrix.laravel }} with Testbench ${{ matrix.testbench }}
53 | run: |
54 | composer require --no-update laravel/laravel:^${{ matrix.laravel }}
55 | composer require --no-update --dev orchestra/testbench:^${{ matrix.testbench }}
56 |
57 | - name: Configure local Laravel Nova dummy package
58 | run: composer config repositories.0 path ./tests/Fixtures/nova
59 |
60 | - name: Install Composer dependencies
61 | uses: ramsey/composer-install@v3
62 | with:
63 | composer-options: "--ignore-platform-req=php"
64 |
65 | - name: Run unit tests with coverage and printer
66 | id: phpunit
67 | run: |
68 | echo "phpunit_version=$( vendor/bin/phpunit --version | cut -d ' ' -f 2 )" >> $GITHUB_OUTPUT
69 | vendor/bin/phpunit \
70 | --log-junit ./report-junit.xml \
71 | --coverage-clover ./coverage-clover.xml
72 |
73 | - name: Report test results
74 | if: success() || failure()
75 | uses: mikepenz/action-junit-report@v4
76 | with:
77 | report_paths: ./report-junit.xml
78 | check_name: Laravel ${{ matrix.laravel }}, PHP ${{ matrix.php }} (PHPUnit ${{ steps.phpunit.outputs.phpunit_version }})
79 | summary: |
80 | PHP version: `${{ matrix.php }}`
81 | Laravel version: `${{ matrix.laravel }}`
82 | Testbench version: `${{ matrix.testbench }}`
83 | PHPUnit version: `${{ steps.phpunit.outputs.phpunit_version }}`
84 |
85 | - name: Determine coverage
86 | uses: slavcodev/coverage-monitor-action@1.9.0
87 | if: github.event_name == 'pull_request' && matrix.stable == true
88 | continue-on-error: true
89 | with:
90 | github_token: ${{ secrets.GITHUB_TOKEN }}
91 | coverage_path: ./coverage-clover.xml
92 | threshold_alert: 60
93 | threshold_warning: 85
94 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor
3 | /node_modules
4 | .phpunit.result.cache
5 | .DS_Store
6 | Thumbs.db
7 | .php-cs-fixer.cache
8 | /composer.lock
9 | /auth.json
10 | /report-junit.xml
11 | /.env
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [UNRELEASED]
9 |
10 | ## [4.0.0]
11 |
12 | ### Added
13 |
14 | - Added Laravel 11 support, via #104 by @woeler.
15 | - Added `EditorJsImageUploaded` event that triggers when an image is uploaded, via #98 by @woeler.
16 | - Added `EditorJsThumbnailCreated` event that triggers when a thumbnail is generated, via #98 by @woeler.
17 |
18 | ### Changed
19 |
20 | - Changed GitHub Actions to Node v20, via #106 by @roelofr.
21 | - Changed auto-formatter to use PHP 8.3, via #106 by @roelofr.
22 |
23 | ### Removed
24 |
25 | - Removed Laravel 9 support, via #104 by @woeler.
26 |
27 | ## [3.3.0]
28 |
29 | - Fixed invalid image reference `image.url` in `views/link.blade.php`, via #101 by @roelofr.
30 |
31 | ### Deprecated
32 |
33 | - Deprecated support for Laravel 8.x. It might still work, but we're not testing it anymore.
34 |
35 | ## [3.2.1]
36 |
37 | ### Changed
38 |
39 | - Improve GitHub Actions and test results, via #95 by @roelofr.
40 |
41 | ## [3.2.0]
42 |
43 | ### Added
44 |
45 | - Suport for Laravel 10, via #93 by @Woeler.
46 |
47 | ### Fixed
48 |
49 | - Fixed ignoring the resolveCallbacks on a field, via #83, by @Woeler.
50 |
51 | ## [3.1.0]
52 |
53 | ### Added
54 |
55 | - Guzzle is now a dependency of this project.
56 | - Added `php-cs-fixer` for code standards.
57 | - Added `php-parallel-lint` to ensure all files are actually valid PHP code.
58 | - Added `pretttier` for consistent Markdown files.
59 | - Added RTL support (`editorSettings.rtl`)
60 |
61 | ### Changed
62 |
63 | - Improved image upload handling, using Laravel-native libraries
64 | - Improved link metadata retrieval, using Laravel-native libraries
65 |
66 | ### Deprecated
67 |
68 | - Deprecated `editorSettings.initialBlock` in favor of `editorSettings.defaultBlock` to match EditorJS
69 |
70 | ### Fixed
71 |
72 | - Fixed HTML escaping on list and raw HTML fields. (#80 by @Harrk)
73 |
74 | ## [3.0.5]
75 |
76 | ### Fixed
77 |
78 | - When no changes are made to the editor, the value is left as-is, instead of double-encoding it (thanks @waelelsawy)
79 | - Templates for `list`', `paragraph` and `table` to use raw-html statements on cleaned fields.
80 |
81 | ## [3.0.4]
82 |
83 | ### Fixed
84 |
85 | - NovaEditorJsCast now properly handles JSON, not double-encoding stuff and decoding double-encoded properties.
86 |
87 | ## [3.0.3]
88 |
89 | ### Fixed
90 |
91 | - Constructor of `NovaEditorJsData` now accepts null values and non-iterables.
92 | - PHPDoc return type of `NovaEditorJsData::toHtml()`.
93 |
94 | ## [3.0.2]
95 |
96 | ### Added
97 |
98 | - Support for `spatie/image` version 2.x.
99 |
100 | ## [3.0.1]
101 |
102 | ### Fixed
103 |
104 | - `composer.json` didn't require PHP 8.1+, but the codebase did.
105 |
106 | ## [3.0.0]
107 |
108 | ### Added
109 |
110 | - Nova 4 support
111 | - `NovaEditorJsConverter` to split HTML conversion from the Nova Field
112 | - `NovaEditorJsData` model to store JSON data and allow easy HTML conversion
113 | - `NovaEditorJsCast` to easily convert between raw data and the `NovaEditorJsData` model
114 | - JS linter, EditorConfig and other tools for better development
115 |
116 | ### Changed
117 |
118 | - **PHP version requirements changed**, now requires PHP 8.1 or higher
119 | - `NovaEditorJs` facade for better separation of concerns
120 | - Improved README and separated extending docs to separate file
121 | - Updated Laravel Mix to new version
122 | - Updated Vue to version 3
123 | - The `NovaEditorJsField::displayUsing` now recieves a `NovaEditorJsData` model
124 | - More robust conversion between the model data and the Nova editor field
125 |
126 | ## Deprecated
127 |
128 | - `NovaEditorJs::make`, use `NovaEditorJsField::make` instead
129 |
130 | ## [2.0.3] - 2020-12-07
131 |
132 | ### Fixed
133 |
134 | - Fix for Amazon S3 file support (#49)
135 |
136 | ## [2.0.2] - 2020-11-29
137 |
138 | ### Changed
139 |
140 | - Reduced minimum height of editor (#47)
141 |
142 | ### Fixed
143 |
144 | - Fix for when using an S3 disk (#46)
145 |
146 | ## [2.0.0] - 2020-08-03
147 |
148 | ### Added
149 |
150 | - Added support for extending the EditorJS field with custom plugins
151 |
152 | ---
153 |
154 | For older changes before v2.0.0, please see the [releases page](https://github.com/advoor/nova-editor-js/releases).
155 |
156 | [unreleased]: https://github.com/advoor/nova-editor-js/compare/v3.3.0..master
157 | [4.0.0]: https://github.com/advoor/nova-editor-js/releases/v4.0.0
158 | [3.3.0]: https://github.com/advoor/nova-editor-js/releases/v3.3.0
159 | [3.2.1]: https://github.com/advoor/nova-editor-js/releases/v3.2.1
160 | [3.2.0]: https://github.com/advoor/nova-editor-js/releases/v3.2.0
161 | [3.1.0]: https://github.com/advoor/nova-editor-js/releases/v3.1.0
162 | [3.0.5]: https://github.com/advoor/nova-editor-js/releases/v3.0.5
163 | [3.0.4]: https://github.com/advoor/nova-editor-js/releases/v3.0.4
164 | [3.0.3]: https://github.com/advoor/nova-editor-js/releases/v3.0.3
165 | [3.0.2]: https://github.com/advoor/nova-editor-js/releases/v3.0.2
166 | [3.0.1]: https://github.com/advoor/nova-editor-js/releases/v3.0.1
167 | [3.0.0]: https://github.com/advoor/nova-editor-js/releases/v3.0.0
168 | [2.0.3]: https://github.com/advoor/nova-editor-js/releases/v2.0.3
169 | [2.0.2]: https://github.com/advoor/nova-editor-js/releases/v2.0.2
170 | [2.0.0]: https://github.com/advoor/nova-editor-js/releases/v2.0.0
171 |
--------------------------------------------------------------------------------
/EXTENDING.md:
--------------------------------------------------------------------------------
1 | # Extending Nova EditorJS
2 |
3 | Extending NovaEditorJS is a bit of work, but shouldn't be too hard once you're known with Laravel.
4 |
5 | In this demonstration we will be incorporating the [warning component](https://github.com/editor-js/warning) in our
6 | Laravel application.
7 |
8 | There are two steps to extending the editor. The first consists of creating a JavaScript file and passing it onto Nova.
9 | The second step allows you to create a blade view file and pass it to the field to allow your block to render in the Nova `show` page.
10 |
11 | ## Creating the Javascript file
12 |
13 | `resources/js/editor-js-plugins/warning.js`
14 |
15 | ```js
16 | /*
17 | * The editorConfig variable is used by you to add your tools,
18 | * or any additional configuration you might want to add to the editor.
19 | *
20 | * The fieldConfig variable is the VueJS field exposed to you. You may
21 | * fetch any value that is contained in your laravel config file from there.
22 | */
23 | NovaEditorJS.booting(function (editorConfig, fieldConfig) {
24 | if (fieldConfig.toolSettings.warning.activated === true) {
25 | editorConfig.tools.warning = {
26 | class: require("@editorjs/warning"),
27 | shortcut: fieldConfig.toolSettings.warning.shortcut,
28 | config: {
29 | titlePlaceholder: fieldConfig.toolSettings.warning.titlePlaceholder,
30 | messagePlaceholder: fieldConfig.toolSettings.warning.messagePlaceholder,
31 | },
32 | };
33 | }
34 | });
35 | ```
36 |
37 | `webpack.mix.js`
38 |
39 | ```js
40 | const mix = require("laravel-mix");
41 |
42 | mix.js(
43 | "resources/js/editor-js-plugins/warning.js",
44 | "public/js/editor-js-plugins/warning.js"
45 | );
46 | ```
47 |
48 | `app/Providers/NovaServiceProvider.php`
49 |
50 | ```php
51 | // ...
52 | public function boot()
53 | {
54 | parent::boot();
55 |
56 | Nova::serving(function () {
57 | Nova::script('editor-js-warning', public_path('js/editor-js-plugins/warning.js'));
58 | });
59 | }
60 | // ...
61 | ```
62 |
63 | `config/nova-editor-js.php`
64 |
65 | ```php
66 | return [
67 | // ...
68 | 'toolSettings' => [
69 | 'warning' => [
70 | 'activated' => true,
71 | 'titlePlaceholder' => 'Title',
72 | 'messagePlaceholder' => 'Message',
73 | 'shortcut' => 'CMD+SHIFT+L'
74 | ],
75 | ]
76 | // ...
77 | ];
78 | ```
79 |
80 | ## Creating the blade view file
81 |
82 | `resources/views/editorjs/warning.blade.php`
83 |
84 | _CSS classes taken from [here](https://github.com/editor-js/warning/blob/master/src/index.css)._
85 |
86 | ```html
87 |
88 |
89 |
{{ $title }}
90 |
{{ $message }}
91 |
92 |
93 | ```
94 |
95 | `app/Providers/NovaServiceProvider.php`
96 |
97 | ```php
98 | use Advoor\NovaEditorJs\NovaEditorJs;
99 |
100 | // ...
101 | public function boot()
102 | {
103 | parent::boot();
104 |
105 | NovaEditorJs::addRender('warning', function($block) {
106 | return view('editorjs.warning', $block['data'])->render();
107 | });
108 |
109 | // ...
110 | }
111 | // ...
112 | ```
113 |
114 | That's it for extending the Nova EditorJS package!
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Nova Editor JS Field
2 |
3 | [](https://packagist.org/packages/advoor/nova-editor-js)
4 | [](https://packagist.org/packages/advoor/nova-editor-js)
5 |
6 | A Laravel Nova implementation of [Editor.js](https://github.com/codex-team/editor.js)
7 | by [@advoor](https://github.com/advoor).
8 |
9 | ## Installation
10 |
11 | Install via composer:
12 |
13 | ```
14 | composer require advoor/nova-editor-js
15 | ```
16 |
17 | Publish the config file
18 |
19 | ```
20 | php artisan vendor:publish --provider="Advoor\NovaEditorJs\FieldServiceProvider"
21 | ```
22 |
23 | ## Version Compatibility
24 |
25 | Laravel Nova 4.x isn't backwards compatible with 3.x, so we had to make a version split.
26 | Please use the below table to find which versions are suitable for your installation.
27 |
28 | | Package version | Nova Version | Laravel Version | PHP version |
29 | | --------------- | ------------ | --------------- | ----------- |
30 | | `4.x` | 4.x - 5.x | 10.x - 12.x | 8.2+ |
31 | | `3.x` | 4.x | 8.x - 10.x | 8.1+ |
32 | | `2.x` | 2.x - 3.x | 5.x - 8.x | 5.6 - 7.4 |
33 |
34 | Note that we really pushed the PHP version up. If you're staying on
35 | new versions of Laravel and Nova, we're expecting your PHP version to match that behaviour.
36 |
37 | ## Upgrade
38 |
39 | See [the upgrade guide](./UPGRADING.md).
40 |
41 | ## Usage
42 |
43 | To add EditorJS to your application, you'll need to modify your Nova resource.
44 | For ease-of-use we also recommend to update your models, but that's optional.
45 |
46 | ### Updating your Nova resource
47 |
48 | This package exposes a `NovaEditorJsField` that takes care of displaying the HTML contents
49 | and providing the user with the EditorJS field.
50 |
51 | To use it, simply import the field,
52 |
53 | ```php
54 | use Advoor\NovaEditorJs\NovaEditorJsField;
55 | ```
56 |
57 | use it in your fields array,
58 |
59 | ```php
60 | return [
61 | // …
62 | NovaEditorJsField::make('about'),
63 | ];
64 | ```
65 |
66 | And boom, you've got yourself a fancy editor.
67 |
68 | ### Updating your models (optional)
69 |
70 | For ease-of-use, we recommend you add the `NovaEditorJsCast` to the `$casts` on your models.
71 | This will map the value to a `NovaEditorJsData` model, which can be returned in Blade (rendering HTML), or sent
72 | via API calls (rendering JSON, unless you call `toHtml` on it or cast it to a string).
73 |
74 | ```php
75 | use Advoor\NovaEditorJs\NovaEditorJsCast;
76 |
77 | class User extends Model {
78 | protected $casts = [
79 | 'about' => NovaEditorJsCast::class,
80 | ];
81 | }
82 | ```
83 |
84 | Since the `NovaEditorJsData` model is an `Htmlable`, Blade will recognize it as
85 | safe HTML. This means you don't have to use Blade "unescaped statements".
86 |
87 | ```blade
88 |
89 |
About {{ $user->name }}
90 | {{ $user->about }}
91 |
92 | ```
93 |
94 | ### Rendering HTML without model changes
95 |
96 | You can also use the `NovaEditorJs` facade to render HTML from stored data.
97 |
98 | ```php
99 | NovaEditorJs::generateHtmlOutput($user->about);
100 | ```
101 |
102 | The return value of `generateHtmlOutput` is an `HtmlString`, which is treated as
103 | safe by Blade. This means you don't have to use Blade "unescaped statements".
104 |
105 | ```blade
106 |
107 |
About {{ $user->name }}
108 | {{ NovaEditorJs::generateHtmlOutput($user->about) }}
109 |
110 | ```
111 |
112 | ## Customizing
113 |
114 | You can configure the editor settings and what tools the Editor should use, by
115 | updating the `editorSettings` and `toolSettings` property in the config file
116 | respectively.
117 |
118 | From the config, you can define the following editor settings:
119 |
120 | - `placeholder` ([docs][placeholder-docs]) - The placeholder to show in an empty editor
121 | - `defaultBlock` ([docs][defaultblock-docs]) - The block that's used by default
122 | - `autofocus` ([docs][autofocus-docs]) - If the editor should auto-focus, only use if you never have multiple editors on
123 | a page and after considering the
124 | [accessibility implications][autofocus-accessibility]
125 | - `rtl` ([docs][rtl-docs]) - Set to true to enable right-to-left mode, for languages like Arabic and Hebrew
126 |
127 | [placeholder-docs]: https://editorjs.io/configuration#placeholder
128 | [defaultblock-docs]: https://editorjs.io/configuration#change-the-default-block
129 | [autofocus-docs]: https://editorjs.io/configuration#autofocus
130 | [autofocus-accessibility]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus#accessibility_considerations
131 | [rtl-docs]: https://editorjs.io/i18n#rtl-support
132 |
133 | Furthermore, you can customize the tools the editor should use. The following tools are enabled by default:
134 |
135 | - [Header](https://github.com/editor-js/header)
136 | - [Image](https://github.com/editor-js/image)
137 | - [Link](https://github.com/editor-js/link)
138 | - [List](https://github.com/editor-js/list)
139 | - [Code block](https://github.com/editor-js/code)
140 | - [Inline code](https://github.com/editor-js/inline-code)
141 | - [Checklist](https://github.com/editor-js/checklist)
142 | - [Marker](https://github.com/editor-js/marker)
143 | - [Embeds](https://github.com/editor-js/embed)†
144 | - [Delimiter](https://github.com/editor-js/delimiter)
145 | - [Table](https://github.com/editor-js/table)
146 | - [Raw](https://github.com/editor-js/raw)
147 |
148 | You can customize the views for each component, by changing the view in `resources/views/vendor/nova-editor-js/`.
149 |
150 | † The _Embeds_ tool is triggered by pasting URLs to embeddable
151 | content. It does not have an entry in the "Add" menu.
152 |
153 | ### Registering custom components
154 |
155 | Please refer to the [extending Nova EditorJS](./EXTENDING.md) guide on instructions on how to register custom
156 | components.
157 |
--------------------------------------------------------------------------------
/UPGRADING.md:
--------------------------------------------------------------------------------
1 | # Upgrade Guide
2 |
3 | This guide describes how to upgrade this application.
4 |
5 | ## 3.x to 4.x
6 |
7 | The following deprecations from 3.x have been removed in 4.x:
8 |
9 | - `Advoor\NovaEditorJs\NovaEditorJs::make`, use `Advoor\NovaEditorJs\NovaEditorJsField::make` instead
10 | - Config setting `editorSettings.initialBlock`, use `editorSettings.defaultBlock` instead
11 | - Support for Laravel 8.x and Laravel 9.x has been dropped.
12 |
13 | ## From 2.x to 3.x (Laravel Nova 4.x)
14 |
15 | To be more in line with the separation of concerns, a bunch of code has moved.
16 | The changes are somewhat backwards compatible, but you're advices to quickly fix these deprecations.
17 |
18 | ### High impact changes
19 |
20 | - The HTML rendering has been split from the field, `NovaEditorJs::make` is deprecated.
21 | - Update your Nova resources to use the `NovaEditorJsField` in the `fields()`
22 | - `NovaEditorJs` is now a facade, containing the `generateHtmlOutput` and `addRender` methods
23 | - PHP requirement is now 8.1+
24 | - Laravel requirement is now 8.0+
25 |
26 | ### Medium impact changes
27 |
28 | - `NovaEditorJsField::displayUsing` now recieves a `NovaEditorJsData` instance, instead of a `string|array`.
29 | - `NovaEditorJsData` is a Fluent type, can be treated as an `iterable`.
30 |
31 | ### Low impact changes
32 |
33 | - The Table component has been updated. While this shouldn't affect the data model, you're best off checking it.
34 | - Using the `NovaEditorJsCast` on your Eloquent models is now recommended over casting fields to an array.
35 |
36 | ## From 1.x to 2.x
37 |
38 | _No significant changes written down._
39 |
40 | ## From 0.4 to 1.x
41 |
42 | If upgrading from v0.4.0, re-publish the config file!
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "advoor/nova-editor-js",
3 | "description": "A Laravel Nova field bringing EditorJs magic to Nova.",
4 | "license": "MIT",
5 | "keywords": [
6 | "laravel",
7 | "nova",
8 | "editor",
9 | "editorjs",
10 | "wysiwyg"
11 | ],
12 | "require": {
13 | "php": "^8.2",
14 | "ext-exif": "*",
15 | "ext-json": "*",
16 | "codex-team/editor.js": "*",
17 | "guzzlehttp/guzzle": "^7.0",
18 | "illuminate/events": "^10.0 || ^11.0 || ^12.0",
19 | "illuminate/support": "^10.0 || ^11.0 || ^12.0",
20 | "laravel/laravel": "^12",
21 | "laravel/nova": "^4.0 || ^5.0",
22 | "spatie/image": "^3.0"
23 | },
24 | "require-dev": {
25 | "laravel/pint": "^1.15",
26 | "orchestra/testbench": "^10",
27 | "php-parallel-lint/php-parallel-lint": "^1.3"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Advoor\\NovaEditorJs\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Tests\\": "tests"
37 | },
38 | "files": [
39 | "tests/helpers.php"
40 | ]
41 | },
42 | "scripts": {
43 | "test": "phpunit",
44 | "lint": [
45 | "parallel-lint --exclude .git --exclude vendor ."
46 | ],
47 | "format": [
48 | "pint"
49 | ]
50 | },
51 | "scripts-descriptions": {
52 | "test": "Test application using PHPUnit.",
53 | "lint": "Lint all php files",
54 | "format": "Run php-cs-fixer formatter"
55 | },
56 | "extra": {
57 | "laravel": {
58 | "providers": [
59 | "Advoor\\NovaEditorJs\\FieldServiceProvider"
60 | ]
61 | }
62 | },
63 | "config": {
64 | "sort-packages": true
65 | },
66 | "minimum-stability": "dev",
67 | "prefer-stable": true,
68 | "repositories": [
69 | {
70 | "type": "composer",
71 | "url": "https://nova.laravel.com"
72 | }
73 | ]
74 | }
75 |
--------------------------------------------------------------------------------
/dist/css/field.css:
--------------------------------------------------------------------------------
1 | .editor-js{background-color:#fff;border-color:#bacad6;border-radius:.5rem;border-width:1px;box-shadow:0 2px 4px 0 rgba(0,0,0,.05);color:#7c858e;padding-left:.75rem;width:100%}.editor-js h1{font-size:26px;font-weight:400;margin-bottom:1.35em}.editor-js h2{font-size:21px;font-weight:400}.editor-js h3{font-size:20px;font-weight:400}.editor-js h4{font-size:19px;font-weight:400}.editor-js-content .editor-js-block{padding:.7em 0}.editor-js-content h2{line-height:1.5em;margin:0 0 -.9em;padding:1em 0}.editor-js-content p{line-height:1.6em}.editor-js-content li{line-height:1.6em;padding:5.5px 0 5.5px 3px}.editor-js-content .editor-js-code{word-wrap:normal;background:#f8f7fa;border:1px solid #f1f1f4;box-shadow:none;color:#41314e;font-size:12px;line-height:1.6em;min-height:200px;overflow-x:auto;resize:vertical;white-space:pre}.editor-js-content .editor-js-link{background:#fff;border:1px solid hsla(240,3%,79%,.48);border-radius:6px;box-shadow:0 1px 3px rgba(0,0,0,.1);display:block;padding:25px}.editor-js-content .editor-js-link h4{font-size:17px;font-weight:600;line-height:1.5em;margin:0 0 10px}.editor-js-content .editor-js-link small{color:#888;display:block;font-size:15px;line-height:1em;margin-top:25px}.editor-js-content .editor-js-link .editor-js-link-image{background-position:50%;background-repeat:no-repeat;background-size:cover;border-radius:3px;float:right;height:65px;margin:0 0 0 30px;width:65px}.editor-js-content .editor-js-checklist .checklist-item{box-sizing:content-box;display:flex;padding:0 10px}.editor-js-content .editor-js-checklist .checklist-item .checkbox{background:#fff;border:1px solid #d0d0d0;border-radius:50%;display:inline-block;flex-shrink:0;height:20px;margin:10px 10px 10px 0;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:20px}.editor-js-content .editor-js-checklist .checklist-item .checkbox:after{background:transparent;border:2px solid #fcfff4;border-right:none;border-top:none;content:"";height:5px;left:5px;opacity:1;position:absolute;top:5px;transform:rotate(-45deg);width:8px}.editor-js-content .editor-js-checklist .checklist-item .checkbox-checked{background:#388ae5;border-color:#388ae5}.editor-js-content .editor-js-checklist .checklist-item .checkbox-text{flex-grow:1;outline:none;padding:10px 0}.editor-js-content .editor-js-delimiter{line-height:1.6em;text-align:center;width:100%}.editor-js-content .editor-js-delimiter:before{content:"***";display:inline-block;font-size:30px;height:30px;letter-spacing:.2em;line-height:65px}.editor-js-content .editor-js-table{border-collapse:collapse;height:100%;table-layout:fixed;width:100%}.editor-js-content .editor-js-table td{border:1px solid #dbdbe2;padding:10px;vertical-align:top}
2 |
--------------------------------------------------------------------------------
/dist/js/field.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * vuex v4.0.2
3 | * (c) 2021 Evan You
4 | * @license MIT
5 | */
6 |
7 | /*! For license information please see editor.js.LICENSE.txt */
8 |
9 | /**
10 | * @license
11 | * Lodash
12 | * Copyright OpenJS Foundation and other contributors
13 | * Released under MIT license
14 | * Based on Underscore.js 1.8.3
15 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
16 | */
17 |
18 | /**
19 | * Base Paragraph Block for the Editor.js.
20 | * Represents simple paragraph
21 | *
22 | * @author CodeX (team@codex.so)
23 | * @copyright CodeX 2018
24 | * @license The MIT License (MIT)
25 | */
26 |
27 | /**
28 | * CodeTool for Editor.js
29 | *
30 | * @author CodeX (team@ifmo.su)
31 | * @copyright CodeX 2018
32 | * @license MIT
33 | * @version 2.0.0
34 | */
35 |
36 | /**
37 | * Delimiter Block for the Editor.js.
38 | *
39 | * @author CodeX (team@ifmo.su)
40 | * @copyright CodeX 2018
41 | * @license The MIT License (MIT)
42 | * @version 2.0.0
43 | */
44 |
45 | /**
46 | * Header block for the Editor.js.
47 | *
48 | * @author CodeX (team@ifmo.su)
49 | * @copyright CodeX 2018
50 | * @license MIT
51 | * @version 2.0.0
52 | */
53 |
54 | /**
55 | * Image Tool for the Editor.js
56 | *
57 | * @author CodeX
58 | * @license MIT
59 | * @see {@link https://github.com/editor-js/image}
60 | *
61 | * To developers.
62 | * To simplify Tool structure, we split it to 4 parts:
63 | * 1) index.js — main Tool's interface, public API and methods for working with data
64 | * 2) uploader.js — module that has methods for sending files via AJAX: from device, by URL or File pasting
65 | * 3) ui.js — module for UI manipulations: render, showing preloader, etc
66 | * 4) tunes.js — working with Block Tunes: render buttons, handle clicks
67 | *
68 | * For debug purposes there is a testing server
69 | * that can save uploaded files and return a Response {@link UploadResponseFormat}
70 | *
71 | * $ node dev/server.js
72 | *
73 | * It will expose 8008 port, so you can pass http://localhost:8008 with the Tools config:
74 | *
75 | * image: {
76 | * class: ImageTool,
77 | * config: {
78 | * endpoints: {
79 | * byFile: 'http://localhost:8008/uploadFile',
80 | * byUrl: 'http://localhost:8008/fetchUrl',
81 | * }
82 | * },
83 | * },
84 | */
85 |
86 | /**
87 | * Raw HTML Tool for CodeX Editor
88 | *
89 | * @author CodeX (team@codex.so)
90 | * @copyright CodeX 2018
91 | * @license The MIT License (MIT)
92 | */
93 |
--------------------------------------------------------------------------------
/dist/js/index.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * vuex v4.0.2
3 | * (c) 2021 Evan You
4 | * @license MIT
5 | */
6 |
7 | /*! For license information please see editor.js.LICENSE.txt */
8 |
9 | /**
10 | * @license
11 | * Lodash
12 | * Copyright OpenJS Foundation and other contributors
13 | * Released under MIT license
14 | * Based on Underscore.js 1.8.3
15 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
16 | */
17 |
18 | /**
19 | * Base Paragraph Block for the Editor.js.
20 | * Represents simple paragraph
21 | *
22 | * @author CodeX (team@codex.so)
23 | * @copyright CodeX 2018
24 | * @license The MIT License (MIT)
25 | */
26 |
27 | /**
28 | * CodeTool for Editor.js
29 | *
30 | * @author CodeX (team@ifmo.su)
31 | * @copyright CodeX 2018
32 | * @license MIT
33 | * @version 2.0.0
34 | */
35 |
36 | /**
37 | * Delimiter Block for the Editor.js.
38 | *
39 | * @author CodeX (team@ifmo.su)
40 | * @copyright CodeX 2018
41 | * @license The MIT License (MIT)
42 | * @version 2.0.0
43 | */
44 |
45 | /**
46 | * Header block for the Editor.js.
47 | *
48 | * @author CodeX (team@ifmo.su)
49 | * @copyright CodeX 2018
50 | * @license MIT
51 | * @version 2.0.0
52 | */
53 |
54 | /**
55 | * Image Tool for the Editor.js
56 | *
57 | * @author CodeX
58 | * @license MIT
59 | * @see {@link https://github.com/editor-js/image}
60 | *
61 | * To developers.
62 | * To simplify Tool structure, we split it to 4 parts:
63 | * 1) index.js — main Tool's interface, public API and methods for working with data
64 | * 2) uploader.js — module that has methods for sending files via AJAX: from device, by URL or File pasting
65 | * 3) ui.js — module for UI manipulations: render, showing preloader, etc
66 | * 4) tunes.js — working with Block Tunes: render buttons, handle clicks
67 | *
68 | * For debug purposes there is a testing server
69 | * that can save uploaded files and return a Response {@link UploadResponseFormat}
70 | *
71 | * $ node dev/server.js
72 | *
73 | * It will expose 8008 port, so you can pass http://localhost:8008 with the Tools config:
74 | *
75 | * image: {
76 | * class: ImageTool,
77 | * config: {
78 | * endpoints: {
79 | * byFile: 'http://localhost:8008/uploadFile',
80 | * byUrl: 'http://localhost:8008/fetchUrl',
81 | * }
82 | * },
83 | * },
84 | */
85 |
86 | /**
87 | * Raw HTML Tool for CodeX Editor
88 | *
89 | * @author CodeX (team@codex.so)
90 | * @copyright CodeX 2018
91 | * @license The MIT License (MIT)
92 | */
93 |
--------------------------------------------------------------------------------
/dist/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/js/field.js": "/js/field.js",
3 | "/css/field.css": "/css/field.css"
4 | }
5 |
--------------------------------------------------------------------------------
/nova.mix.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies,class-methods-use-this */
2 |
3 | const mix = require('laravel-mix');
4 | const webpack = require('webpack');
5 | const path = require('path');
6 | const fs = require('fs');
7 |
8 | const novaLocation = [
9 | path.join(__dirname, '../../vendor/laravel/nova/resources/js/mixins/packages.js'),
10 | path.join(__dirname, 'vendor/laravel/nova/resources/js/mixins/packages.js'),
11 | ].filter(fs.existsSync)[0] ?? '';
12 |
13 | if (!novaLocation) {
14 | throw new Error('Unable to locate Nova resources. Mount the extension in a Laravel installation, or run `composer install` in the extension directory.');
15 | }
16 |
17 | class NovaExtension {
18 | name() {
19 | return 'nova-extension';
20 | }
21 |
22 | register(name) {
23 | this.name = name;
24 | }
25 |
26 | webpackPlugins() {
27 | return new webpack.ProvidePlugin({
28 | _: 'lodash',
29 | Errors: 'form-backend-validation',
30 | });
31 | }
32 |
33 | webpackConfig(webpackConfig) {
34 | webpackConfig.externals = {
35 | vue: 'Vue',
36 | };
37 |
38 | webpackConfig.resolve.alias = {
39 | ...(webpackConfig.resolve.alias || {}),
40 | 'laravel-nova': novaLocation,
41 | };
42 |
43 | webpackConfig.output = {
44 | uniqueName: this.name,
45 | };
46 | }
47 | }
48 |
49 | mix.extend('nova', new NovaExtension());
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "mix build",
6 | "watch": "mix watch",
7 | "watch-poll": "mix watch -- --watch-options-poll=1000",
8 | "hot": "mix watch --hot",
9 | "prod": "npm run production",
10 | "production": "mix build --production",
11 | "format": "eslint --fix resources/js/**/*.{js,vue} *.js && prettier --write *.md",
12 | "nova:install": "npm --prefix='./vendor/laravel/nova' ci"
13 | },
14 | "dependencies": {
15 | "@editorjs/checklist": "^1.3.0",
16 | "@editorjs/code": "^2.7.0",
17 | "@editorjs/delimiter": "^1.2.0",
18 | "@editorjs/editorjs": "^2.24.3",
19 | "@editorjs/embed": "^2.5.1",
20 | "@editorjs/header": "^2.6.2",
21 | "@editorjs/image": "^2.6.2",
22 | "@editorjs/inline-code": "^1.3.1",
23 | "@editorjs/link": "^2.4.1",
24 | "@editorjs/list": "^1.7.0",
25 | "@editorjs/marker": "^1.2.2",
26 | "@editorjs/paragraph": "^2.8.0",
27 | "@editorjs/raw": "^2.3.1",
28 | "@editorjs/table": "^2.0.2"
29 | },
30 | "devDependencies": {
31 | "@vue/compiler-sfc": "^3.2.36",
32 | "eslint": "^8.16.0",
33 | "eslint-config-airbnb-base": "^15.0.0",
34 | "eslint-plugin-vue": "^9.1.0",
35 | "laravel-mix": "^6.0.44",
36 | "postcss": "^8.4.14",
37 | "prettier": "^2.7.1",
38 | "vue-loader": "^16.8.3"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/Feature
6 |
7 |
8 | ./tests/Unit
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ./src
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/pint.json:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "laravel",
3 | "rules": {
4 | "php_unit_method_casing": {
5 | "case": "camel_case"
6 | },
7 | "php_unit_test_annotation": {
8 | "style": "prefix"
9 | },
10 | "no_superfluous_phpdoc_tags": true,
11 | "declare_strict_types": true,
12 | "strict_param": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/resources/css/field.css:
--------------------------------------------------------------------------------
1 | /* Nova Tool CSS */
2 | .editor-js {
3 | width: 100%;
4 | background-color: #fff;
5 | border-width: 1px;
6 | border-color: #bacad6;
7 | padding-left: .75rem;
8 | color: #7c858e;
9 | border-radius: .5rem;
10 | -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .05);
11 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .05);
12 | }
13 |
14 | .editor-js h1 {
15 | font-size: 26px;
16 | margin-bottom: 1.35em;
17 | font-weight: 400;
18 | }
19 |
20 | .editor-js h2 {
21 | font-size: 21px;
22 | font-weight: 400;
23 | }
24 |
25 | .editor-js h3 {
26 | font-size: 20px;
27 | font-weight: 400;
28 | }
29 |
30 | .editor-js h4 {
31 | font-size: 19px;
32 | font-weight: 400;
33 | }
34 |
35 | .editor-js-content .editor-js-block {
36 | padding: .7em 0;
37 | }
38 |
39 | .editor-js-content h2 {
40 | padding: 1em 0;
41 | margin: 0;
42 | margin-bottom: -0.9em;
43 | line-height: 1.5em;
44 | }
45 |
46 | .editor-js-content p {
47 | line-height: 1.6em;
48 | }
49 |
50 | .editor-js-content li {
51 | padding: 5.5px 0 5.5px 3px;
52 | line-height: 1.6em;
53 | }
54 |
55 | .editor-js-content .editor-js-code {
56 | min-height: 200px;
57 | color: #41314e;
58 | line-height: 1.6em;
59 | font-size: 12px;
60 | background: #f8f7fa;
61 | border: 1px solid #f1f1f4;
62 | -webkit-box-shadow: none;
63 | box-shadow: none;
64 | white-space: pre;
65 | word-wrap: normal;
66 | overflow-x: auto;
67 | resize: vertical;
68 | }
69 |
70 | .editor-js-content .editor-js-link {
71 | display: block;
72 | background: #fff;
73 | border: 1px solid rgba(201, 201, 204, 0.48);
74 | box-shadow: 0 1px 3px rgba(0, 0, 0, .1);
75 | border-radius: 6px;
76 | padding: 25px;
77 | }
78 |
79 | .editor-js-content .editor-js-link h4 {
80 | font-size: 17px;
81 | font-weight: 600;
82 | line-height: 1.5em;
83 | margin: 0 0 10px 0;
84 | }
85 |
86 | .editor-js-content .editor-js-link small {
87 | margin-top: 25px;
88 | display: block;
89 | font-size: 15px;
90 | line-height: 1em;
91 | color: #888;
92 | }
93 |
94 | .editor-js-content .editor-js-link .editor-js-link-image {
95 | background-position: center center;
96 | background-repeat: no-repeat;
97 | background-size: cover;
98 | margin: 0 0 0 30px;
99 | width: 65px;
100 | height: 65px;
101 | border-radius: 3px;
102 | float: right;
103 | }
104 |
105 | .editor-js-content .editor-js-checklist .checklist-item {
106 | display: flex;
107 | padding: 0 10px;
108 | box-sizing: content-box;
109 | }
110 |
111 | .editor-js-content .editor-js-checklist .checklist-item .checkbox {
112 | display: inline-block;
113 | flex-shrink: 0;
114 | position: relative;
115 | width: 20px;
116 | height: 20px;
117 | margin: 10px 10px 10px 0;
118 | border-radius: 50%;
119 | border: 1px solid #d0d0d0;
120 | background: #fff;
121 | user-select: none;
122 | }
123 |
124 | .editor-js-content .editor-js-checklist .checklist-item .checkbox::after {
125 | position: absolute;
126 | top: 5px;
127 | left: 5px;
128 | width: 8px;
129 | height: 5px;
130 | border: 2px solid #fcfff4;
131 | border-top: none;
132 | border-right: none;
133 | background: transparent;
134 | content: '';
135 | opacity: 1;
136 | transform: rotate(-45deg);
137 | }
138 |
139 | .editor-js-content .editor-js-checklist .checklist-item .checkbox-checked {
140 | background: #388ae5;
141 | border-color: #388ae5;
142 |
143 |
144 | }
145 |
146 | .editor-js-content .editor-js-checklist .checklist-item .checkbox-text {
147 | outline: none;
148 | flex-grow: 1;
149 | padding: 10px 0;
150 | }
151 |
152 | .editor-js-content .editor-js-delimiter {
153 | line-height: 1.6em;
154 | width: 100%;
155 | text-align: center;
156 | }
157 |
158 | .editor-js-content .editor-js-delimiter::before {
159 | display: inline-block;
160 | content: "***";
161 | font-size: 30px;
162 | line-height: 65px;
163 | height: 30px;
164 | letter-spacing: 0.2em;
165 | }
166 |
167 | .editor-js-content .editor-js-table {
168 | width: 100%;
169 | height: 100%;
170 | border-collapse: collapse;
171 | table-layout: fixed;
172 | }
173 |
174 | .editor-js-content .editor-js-table td {
175 | border: 1px solid #dbdbe2;
176 | padding: 10px;
177 | vertical-align: top;
178 | }
179 |
--------------------------------------------------------------------------------
/resources/js/blocks/checklist.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.checklist.activated === true) {
3 | editorConfig.tools.checklist = {
4 | class: require('@editorjs/checklist'),
5 | inlineToolbar: fieldConfig.toolSettings.checklist.inlineToolbar,
6 | shortcut: fieldConfig.toolSettings.checklist.shortcut,
7 | };
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/resources/js/blocks/code.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.code.activated === true) {
3 | editorConfig.tools.code = {
4 | class: require('@editorjs/code'),
5 | shortcut: fieldConfig.toolSettings.code.shortcut,
6 | config: {
7 | placeholder: fieldConfig.toolSettings.code.placeholder,
8 | },
9 | };
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/resources/js/blocks/delimiter.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.delimiter.activated === true) {
3 | editorConfig.tools.delimiter = {
4 | class: require('@editorjs/delimiter'),
5 | };
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/resources/js/blocks/embed.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.embed.activated === true) {
3 | editorConfig.tools.embed = {
4 | class: require('@editorjs/embed'),
5 | inlineToolbar: fieldConfig.toolSettings.embed.inlineToolbar,
6 | config: {
7 | services: {
8 | codepen: fieldConfig.toolSettings.embed.services.codepen,
9 | imgur: fieldConfig.toolSettings.embed.services.imgur,
10 | vimeo: fieldConfig.toolSettings.embed.services.vimeo,
11 | youtube: fieldConfig.toolSettings.embed.services.youtube,
12 | },
13 | },
14 | };
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/resources/js/blocks/heading.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.header.activated === true) {
3 | editorConfig.tools.header = {
4 | class: require('@editorjs/header'),
5 | config: {
6 | placeholder: fieldConfig.toolSettings.header.placeholder,
7 | },
8 | shortcut: fieldConfig.toolSettings.header.shortcut,
9 | };
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/resources/js/blocks/image.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.image.activated === true) {
3 | editorConfig.tools.image = {
4 | class: require('@editorjs/image'),
5 | config: {
6 | endpoints: {
7 | byFile: fieldConfig.uploadImageByFileEndpoint,
8 | byUrl: fieldConfig.uploadImageByUrlEndpoint,
9 | },
10 | additionalRequestHeaders: {
11 | 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
12 | },
13 | },
14 | };
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/resources/js/blocks/inline-code.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.inlineCode.activated === true) {
3 | editorConfig.tools.inlineCode = {
4 | class: require('@editorjs/inline-code'),
5 | shortcut: fieldConfig.toolSettings.inlineCode.shortcut,
6 | };
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/resources/js/blocks/link.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.link.activated === true) {
3 | editorConfig.tools.linkTool = {
4 | class: require('@editorjs/link'),
5 | config: {
6 | endpoint: fieldConfig.fetchUrlEndpoint,
7 | },
8 | };
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/resources/js/blocks/list.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.list.activated === true) {
3 | editorConfig.tools.list = {
4 | class: require('@editorjs/list'),
5 | inlineToolbar: fieldConfig.toolSettings.list.inlineToolbar,
6 | shortcut: fieldConfig.toolSettings.list.shortcut,
7 | };
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/resources/js/blocks/marker.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.marker.activated === true) {
3 | editorConfig.tools.marker = {
4 | class: require('@editorjs/marker'),
5 | shortcut: fieldConfig.toolSettings.marker.shortcut,
6 | };
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/resources/js/blocks/paragraph.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig) => {
2 | editorConfig.tools.paragraph = {
3 | class: require('@editorjs/paragraph'),
4 | inlineToolbar: true,
5 | };
6 | });
7 |
--------------------------------------------------------------------------------
/resources/js/blocks/raw.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.raw.activated === true) {
3 | editorConfig.tools.raw = {
4 | class: require('@editorjs/raw'),
5 | config: {
6 | placeholder: fieldConfig.toolSettings.raw.placeholder,
7 | },
8 | };
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/resources/js/blocks/table.js:
--------------------------------------------------------------------------------
1 | NovaEditorJS.booting((editorConfig, fieldConfig) => {
2 | if (fieldConfig.toolSettings.table.activated === true) {
3 | editorConfig.tools.table = {
4 | class: require('@editorjs/table'),
5 | inlineToolbar: fieldConfig.toolSettings.table.inlineToolbar,
6 | };
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/resources/js/components/DetailField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/resources/js/components/FormField.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
109 |
--------------------------------------------------------------------------------
/resources/js/components/IndexField.vue:
--------------------------------------------------------------------------------
1 |
2 | Dynamic content. Click 'View' to see contents.
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/resources/js/field.js:
--------------------------------------------------------------------------------
1 | Nova.booting((Vue) => {
2 | Vue.component('IndexNovaEditorJs', require('./components/IndexField').default);
3 | Vue.component('DetailNovaEditorJs', require('./components/DetailField').default);
4 | Vue.component('FormNovaEditorJs', require('./components/FormField').default);
5 | });
6 |
--------------------------------------------------------------------------------
/resources/js/index.js:
--------------------------------------------------------------------------------
1 | // Import the Nova Editor class
2 | import NovaEditorJS from './nova-editor';
3 |
4 | // Expose it for other plugins
5 | window.NovaEditorJS = new NovaEditorJS();
6 |
7 | // Import the blocks
8 | require('./blocks/checklist');
9 | require('./blocks/code');
10 | require('./blocks/delimiter');
11 | require('./blocks/embed');
12 | require('./blocks/heading');
13 | require('./blocks/image');
14 | require('./blocks/inline-code');
15 | require('./blocks/link');
16 | require('./blocks/list');
17 | require('./blocks/marker');
18 | require('./blocks/paragraph');
19 | require('./blocks/raw');
20 | require('./blocks/table');
21 |
22 | // Import the Nova field declaration
23 | require('./field');
24 |
--------------------------------------------------------------------------------
/resources/js/nova-editor.js:
--------------------------------------------------------------------------------
1 | const EditorJS = require('@editorjs/editorjs');
2 |
3 | export default class NovaEditorJS {
4 | constructor() {
5 | this.defaultConfigObject = {
6 | tools: {},
7 | };
8 |
9 | this.persistentConfigObject = {};
10 | this.bootingCallbacks = [];
11 | }
12 |
13 | /**
14 | * Callback for registering a plugin
15 | *
16 | * @callback novaEditorJSBooting
17 | * @param {Object} editorConfig Editor Config
18 | * @param {Object} fieldConfig Field Config
19 | */
20 |
21 | /**
22 | * Register a callback to load your block plugin.
23 | *
24 | * @param {novaEditorJSBooting} callback Callback to register your plugin
25 | */
26 | booting(callback) {
27 | // Only callables are allowed
28 | if (!(callback instanceof Function)) {
29 | return;
30 | }
31 |
32 | this.bootingCallbacks.push(callback);
33 | }
34 |
35 | getInstance(config, field) {
36 | const editorConfig = _.merge({}, this.defaultConfigObject, config);
37 | const fieldObject = _.cloneDeep(field);
38 |
39 | // Plugins should not modify the field config.
40 | // If a key should be changed, other plugins loaded later
41 | // would have an unsynchronized version of the field configuration.
42 | Object.freeze(fieldObject);
43 |
44 | // We boot each block plugin by passing the editorConfig and the fieldObject
45 | this.bootingCallbacks.forEach((callback) => callback(editorConfig, fieldObject));
46 |
47 | // We apply the persistent config and return the editor instance
48 | return new EditorJS(
49 | _.merge(editorConfig, this.persistentConfigObject),
50 | );
51 | }
52 |
53 | /**
54 | * Sets a default configuration for the editor. The values set here will
55 | * be overriden by the form field, if a key of the same name exists.
56 | *
57 | * @param config
58 | */
59 | defaultConfig(config) {
60 | // If it's not an object, we discard the information
61 | if (!(config instanceof Object)) {
62 | return;
63 | }
64 |
65 | // We use lodash to perform a deep merge, instead of overwriting
66 | // root values with the spread operator
67 | this.defaultConfigObject = _.merge(this.defaultConfigObject, config);
68 | }
69 |
70 | /**
71 | * Sets a persistent configuration for the editor. The values set here will
72 | * be overwrite any and all keys set by the form field, if a key of the
73 | * same name exists.
74 | *
75 | * @param config
76 | */
77 | persistentConfig(config) {
78 | // If it's not an object, we discard the information
79 | if (!(config instanceof Object)) {
80 | return;
81 | }
82 |
83 | // We use lodash to perform a deep merge, instead of overwriting
84 | // root values with the spread operator
85 | this.persistentConfigObject = _.merge(this.persistentConfigObject, config);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/resources/views/checklist.blade.php:
--------------------------------------------------------------------------------
1 |