├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── composer.json ├── config └── tiptap-validation.php ├── resources └── lang │ └── en │ └── messages.php └── src ├── Concerns ├── Creatable.php └── DecodesTiptapContent.php ├── Contracts └── TiptapRule.php ├── Enums └── TiptapValidationRuleMode.php ├── Facades └── TiptapValidation.php ├── Helpers ├── TiptapContentHelper.php └── TiptapTextHelper.php ├── Rules ├── TiptapContainsText.php ├── TiptapContent.php └── TiptapNode.php └── TiptapValidationServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-tiptap-validation` will be documented in this file. 4 | 5 | ## 1.1.2 - 2023-05-15 6 | 7 | - Use `blank` and `filled` instead of `empty` 8 | - Tidy up README 9 | 10 | ## 1.1.1 - 2023-05-12 11 | 12 | - Performance improvements (recursive validation is stopped when it comes accross a failure) 13 | - Add config file and move over some parameters 14 | - Add additional tests 15 | 16 | ## 1.1.0 - 2023-05-11 17 | 18 | - Add `TiptapContainsText` rule 19 | - Rename `TiptapValidationRule` to `TiptapContent` 20 | - Add translation files for error messages 21 | - Add additional tests 22 | 23 | ## 1.0.1 - 2023-05-10 24 | 25 | - Update dependancies 26 | - Add additional tests 27 | 28 | ## 1.0.0 - 2023-05-10 29 | 30 | Initial release 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) JacobFitzp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Tiptap validation 2 | 3 | 4 | 5 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/jacobfitzp/laravel-tiptap-validation.svg?style=flat-square)](https://packagist.org/packages/jacobfitzp/laravel-tiptap-validation) 6 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/jacobfitzp/laravel-tiptap-validation/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/jacobfitzp/laravel-tiptap-validation/actions?query=workflow%3Arun-tests+branch%3Amain) 7 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/jacobfitzp/laravel-tiptap-validation/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/jacobfitzp/laravel-tiptap-validation/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/jacobfitzp/laravel-tiptap-validation.svg?style=flat-square)](https://packagist.org/packages/jacobfitzp/laravel-tiptap-validation) 9 | 10 | Configurable Laravel validation rule for [Tiptap editor](https://tiptap.dev/) content. 11 | 12 | ```php 13 | $rules = [ 14 | 'tiptap_content' => [ 15 | 'required', 16 | TiptapValidation::content() 17 | ->whitelist() 18 | ->nodes('text', 'paragraph') 19 | ->marks('bold', 'italic', 'link'), 20 | TiptapValidation::containsText() 21 | ->between(18, 256), 22 | ], 23 | ]; 24 | ``` 25 | 26 | Validate Tiptap content in your back-end to prevent unwanted elements and styling from being used. 27 | 28 | ### Please note 29 | 30 | This package only works with JSON output, not the raw HTML. You can read more about outputting Tiptap JSON content [here](https://tiptap.dev/guide/output#option-1-json). 31 | 32 | ## Installation 33 | 34 | You can install the package via composer: 35 | 36 | ```bash 37 | composer require jacobfitzp/laravel-tiptap-validation 38 | ``` 39 | 40 | And then add the service provider to `config/app.php` 41 | ```php 42 | JacobFitzp\LaravelTiptapValidation\TiptapValidationServiceProvider::class, 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### Content 48 | 49 | The `TiptapContent` rule is used to validate the basic structure and format, as well as limit what nodes and marks are allowed. 50 | 51 | Simply call `TiptapValidation::content()` within your rules. 52 | 53 | ```php 54 | TiptapValidation::content() 55 | ``` 56 | 57 | #### Blacklisting 58 | 59 | Only nodes and marks which are not specified in the blacklist will be allowed, anything else will fail validation. 60 | 61 | ```php 62 | TiptapValidation::content() 63 | ->blacklist() 64 | ->nodes('orderedList', 'listItem') 65 | ->marks('italic', 'link') 66 | ``` 67 | 68 | #### Whitelisting 69 | 70 | Only specified nodes and marks are allowed, anything not in the whitelist will fail validation. 71 | 72 | ```php 73 | TiptapValidation::content() 74 | ->whitelist() 75 | ->nodes('text', 'paragraph') 76 | ->marks('bold') 77 | ``` 78 | 79 | #### Extension 80 | 81 | Instead of having to configure the rule each time, you could simply create an extension that has your default preferences set. 82 | 83 | ```php 84 | class MyCustomTiptapValidationRule extends TiptapContent 85 | { 86 | protected TiptapValidationRuleMode $mode = TiptapValidationRuleMode::WHITELIST; 87 | protected array $nodes = ['text', 'paragraph', 'table']; 88 | protected array $marks = ['italic', 'link']; 89 | } 90 | ``` 91 | 92 | This can then be used without the need for further configuration: 93 | 94 | ```php 95 | MyCustomTiptapValidationRule::make(), 96 | ``` 97 | 98 | ### Contains Text 99 | 100 | The `TiptapContainsText` rule is used for verifying that the content contains text, and meets an optional character count requirements. 101 | 102 | ```php 103 | TiptapValidation::containsText() 104 | ->minimum(12) // Minimum character requirement 105 | ->maximum(156) // Maximum character requirement 106 | ->between(12, 156) // Minimum and maximum character requirement 107 | ``` 108 | 109 | ## Configuration 110 | 111 | ### Error messages 112 | 113 | First publish the translation files: 114 | 115 | ```bash 116 | php artisan vendor:publish --provider="JacobFitzp\LaravelTiptapValidation\TiptapValidationServiceProvider" --tag="tiptap-validation-translations" 117 | ``` 118 | 119 | And then you can configure the error messages in `lang/vendor/tiptap-validation/messages.php` 120 | 121 | ## Testing 122 | 123 | ```bash 124 | composer test 125 | ``` 126 | 127 | ## Changelog 128 | 129 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 130 | 131 | ## Contributing 132 | 133 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 134 | 135 | ## Security Vulnerabilities 136 | 137 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 138 | 139 | ## Credits 140 | 141 | - [Jacob Fitzpatrick](https://github.com/JacobFitzp) 142 | - [All Contributors](../../contributors) 143 | 144 | ## License 145 | 146 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 147 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.1.x | :white_check_mark: | 8 | | 1.0.x | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Report all security vulnerabilities privately, by emailing contact@jacobfitzp.me 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jacobfitzp/laravel-tiptap-validation", 3 | "description": "Laravel validation rules for the Tiptap WYSIWYG editor.", 4 | "keywords": [ 5 | "JacobFitzp", 6 | "laravel", 7 | "laravel-tiptap-validation" 8 | ], 9 | "homepage": "https://github.com/jacobfitzp/laravel-tiptap-validation", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Jacob Fitzpatrick", 14 | "email": "contact@jacobfitzp.me", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "laravel/helpers": "^1.6", 21 | "spatie/laravel-package-tools": "^1.15.0" 22 | }, 23 | "require-dev": { 24 | "roave/security-advisories": "dev-latest", 25 | "laravel/pint": "^1.0", 26 | "nunomaduro/collision": "^7.9", 27 | "nunomaduro/larastan": "^2.0.1", 28 | "orchestra/testbench": "^8.4", 29 | "pestphp/pest": "^2.0", 30 | "pestphp/pest-plugin-arch": "^2.0", 31 | "pestphp/pest-plugin-laravel": "^2.0", 32 | "phpstan/extension-installer": "^1.1", 33 | "phpstan/phpstan-deprecation-rules": "^1.0", 34 | "phpstan/phpstan-phpunit": "^1.0", 35 | "spatie/laravel-ray": "^1.26" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "JacobFitzp\\LaravelTiptapValidation\\": "src/", 40 | "JacobFitzp\\LaravelTiptapValidation\\Database\\Factories\\": "database/factories/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "JacobFitzp\\LaravelTiptapValidation\\Tests\\": "tests/" 46 | } 47 | }, 48 | "scripts": { 49 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 50 | "analyse": "vendor/bin/phpstan analyse", 51 | "test": "vendor/bin/phpunit", 52 | "test-coverage": "vendor/bin/phpunit --coverage", 53 | "format": "vendor/bin/pint" 54 | }, 55 | "config": { 56 | "sort-packages": true, 57 | "allow-plugins": { 58 | "pestphp/pest-plugin": true, 59 | "phpstan/extension-installer": true 60 | } 61 | }, 62 | "extra": { 63 | "laravel": { 64 | "providers": [ 65 | "JacobFitzp\\LaravelTiptapValidation\\TiptapValidationServiceProvider" 66 | ], 67 | "aliases": { 68 | "TiptapValidation": "JacobFitzp\\LaravelTiptapValidation\\Facades\\TiptapValidation" 69 | } 70 | } 71 | }, 72 | "minimum-stability": "dev", 73 | "prefer-stable": true 74 | } 75 | -------------------------------------------------------------------------------- /config/tiptap-validation.php: -------------------------------------------------------------------------------- 1 | ['text'], 10 | ]; 11 | -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'Invalid Tiptap content', 5 | 'tiptapContainsText' => [ 6 | 'noText' => 'Must contain text', 7 | 'minimumChars' => 'Must contain more than :min characters', 8 | 'maximumChars' => 'Must contain less than :max characters', 9 | 'betweenChars' => 'Must contain between :min and :max characters', 10 | ], 11 | 'tiptapNode' => 'Invalid Tiptap node', 12 | ]; 13 | -------------------------------------------------------------------------------- /src/Concerns/Creatable.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | trait Creatable 11 | { 12 | public static function make(): self 13 | { 14 | return new self(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Concerns/DecodesTiptapContent.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | trait DecodesTiptapContent 13 | { 14 | /** 15 | * @see TiptapContentHelper::decode() 16 | */ 17 | protected function decode(mixed $value): ?array 18 | { 19 | return TiptapContentHelper::decode($value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Contracts/TiptapRule.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | enum TiptapValidationRuleMode: string 11 | { 12 | case BLACKLIST = 'blacklist'; 13 | case WHITELIST = 'whitelist'; 14 | } 15 | -------------------------------------------------------------------------------- /src/Facades/TiptapValidation.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TiptapValidation extends Facade 15 | { 16 | /** 17 | * Tiptap content validation rule. 18 | */ 19 | public static function content(): TiptapContent 20 | { 21 | return TiptapContent::make(); 22 | } 23 | 24 | /** 25 | * Tiptap contains text validation rule. 26 | */ 27 | public static function containsText(): TiptapContainsText 28 | { 29 | return TiptapContainsText::make(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Helpers/TiptapContentHelper.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class TiptapContentHelper 13 | { 14 | /** 15 | * Attempt to decode tiptap content to an array that we 16 | * can apply validation rules to. 17 | * 18 | * This does not guarantee that the content is in a 19 | * valid format, simply parses it into a type that 20 | * we can work with further down the line. 21 | */ 22 | public static function decode(mixed $value): ?array 23 | { 24 | // Content is already an array, so we don't need to do anything. 25 | if (is_array($value)) { 26 | return $value; 27 | } 28 | 29 | // Content is an arrayable object, we can convert it as it. 30 | if ($value instanceof Arrayable) { 31 | return $value->toArray(); 32 | } 33 | 34 | // Catch any exceptions and treat them as blank content. 35 | try { 36 | // Cast to string and attempt to json decode 37 | return (array) json_decode($value, true, 512, JSON_THROW_ON_ERROR); 38 | 39 | } catch (\Exception $exception) { 40 | return null; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Helpers/TiptapTextHelper.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class TiptapTextHelper 12 | { 13 | /** 14 | * Count the number of characters in tiptap content. 15 | * 16 | * @param array $nodes The nodes to search for text within. 17 | * @return int The number of characters found within the provided nodes. 18 | */ 19 | public static function countCharacters(array $nodes): int 20 | { 21 | $characters = 0; 22 | $textProperties = config('tiptap-validation.textProperties'); 23 | 24 | foreach ($nodes as $node) { 25 | foreach ($textProperties as $textProperty) { 26 | $characters += ( 27 | strlen(array_get($node, $textProperty)) 28 | - substr_count(array_get($node, $textProperty), ' ') 29 | ); 30 | } 31 | 32 | foreach (array_get($node, 'marks', []) as $mark) { 33 | foreach ($textProperties as $textProperty) { 34 | $characters += ( 35 | strlen(array_get($mark, $textProperty)) 36 | - substr_count(array_get($mark, $textProperty), ' ') 37 | ); 38 | } 39 | } 40 | 41 | // Nested content 42 | if (filled(array_get($node, 'content'))) { 43 | $characters += self::countCharacters(array_get($node, 'content')); 44 | } 45 | } 46 | 47 | return $characters; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rules/TiptapContainsText.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class TiptapContainsText implements TiptapRule 20 | { 21 | use Creatable; 22 | use DecodesTiptapContent; 23 | 24 | protected ?int $minimum = null; 25 | 26 | protected ?int $maximum = null; 27 | 28 | /** 29 | * Minimum number of characters required. 30 | * 31 | * @return $this 32 | */ 33 | public function minimum(int $min): TiptapContainsText 34 | { 35 | $this->minimum = $min; 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * Maximum number of characters required. 42 | * 43 | * @return $this 44 | */ 45 | public function maximum(int $max): TiptapContainsText 46 | { 47 | $this->maximum = $max; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Number of characters required. 54 | * 55 | * @return $this 56 | */ 57 | public function between(int $min, int $max): TiptapContainsText 58 | { 59 | return $this 60 | ->minimum($min) 61 | ->maximum($max); 62 | } 63 | 64 | /** 65 | * Validate content. 66 | */ 67 | public function validate(string $attribute, mixed $value, Closure $fail): void 68 | { 69 | $value = $this->decode($value); 70 | 71 | // Invalid content 72 | if (! is_array($value) || blank($value)) { 73 | $fail(trans('tiptap-validation::messages.tiptapContainsText.noText')); 74 | 75 | return; 76 | } 77 | 78 | // Count text characters in content 79 | $length = TiptapTextHelper::countCharacters(array_get($value, 'content', [])); 80 | 81 | // No text found 82 | if ($length === 0) { 83 | $fail(trans('tiptap-validation::messages.tiptapContainsText.noText')); 84 | 85 | return; 86 | } 87 | 88 | // Out-of-range of character thresholds 89 | if ( 90 | ! is_null($this->minimum) && 91 | ! is_null($this->maximum) && ( 92 | $length < $this->minimum || 93 | $length > $this->maximum 94 | ) 95 | ) { 96 | $fail(trans('tiptap-validation::messages.tiptapContainsText.betweenChars', [ 97 | 'min' => $this->minimum, 98 | 'max' => $this->maximum, 99 | ])); 100 | 101 | return; 102 | } 103 | 104 | // Below minimum character count threshold 105 | if (! is_null($this->minimum) && $length < $this->minimum) { 106 | $fail(trans('tiptap-validation::messages.tiptapContainsText.minimumChars', [ 107 | 'min' => $this->minimum, 108 | ])); 109 | 110 | return; 111 | } 112 | 113 | // Above maximum character count threshold 114 | if (! is_null($this->maximum) && $length > $this->maximum) { 115 | $fail(trans('tiptap-validation::messages.tiptapContainsText.maximumChars', [ 116 | 'max' => $this->maximum, 117 | ])); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Rules/TiptapContent.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class TiptapContent implements TiptapRule 24 | { 25 | use Creatable; 26 | use DecodesTiptapContent; 27 | 28 | /** 29 | * Validation failed. 30 | */ 31 | protected bool $fails = false; 32 | 33 | /** 34 | * List of allowed / disallowed node types. 35 | * 36 | * @var string[] 37 | */ 38 | protected array $nodes = []; 39 | 40 | /** 41 | * List of allowed / disallowed mark types. 42 | * 43 | * @var string[] 44 | */ 45 | protected array $marks = []; 46 | 47 | /** 48 | * Mode used for validation, blacklist, or whitelist. 49 | */ 50 | protected TiptapValidationRuleMode $mode = TiptapValidationRuleMode::BLACKLIST; 51 | 52 | /** 53 | * Nodes to blacklist / whitelist 54 | * 55 | * @param string ...$nodes 56 | */ 57 | public function nodes(...$nodes): static 58 | { 59 | $this->nodes = $nodes; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Marks to blacklist / whitelist 66 | * 67 | * @param string ...$marks 68 | */ 69 | public function marks(...$marks): static 70 | { 71 | $this->marks = $marks; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Enable whitelisting mode 78 | */ 79 | public function whitelist(): static 80 | { 81 | $this->mode = TiptapValidationRuleMode::WHITELIST; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Enable blacklisting mode 88 | */ 89 | public function blacklist(): static 90 | { 91 | $this->mode = TiptapValidationRuleMode::BLACKLIST; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Validate Tiptap content. 98 | */ 99 | public function validate(string $attribute, mixed $value, Closure $fail): void 100 | { 101 | // Skip empty values 102 | if (blank($value)) { 103 | return; 104 | } 105 | 106 | // Attempt to decode content 107 | $value = $this->decode($value); 108 | 109 | // Unable to decode content, invalid format 110 | if (is_null($value)) { 111 | $fail(trans('tiptap-validation::messages.tiptapContent')); 112 | 113 | return; 114 | } 115 | 116 | // Validate root tiptap content. 117 | $validator = Validator::make($value, [ 118 | 'type' => 'required|string', 119 | 'content' => 'array', 120 | ]); 121 | 122 | if ($validator->fails()) { 123 | $fail(trans('tiptap-validation::messages.tiptapContent')); 124 | 125 | return; 126 | } 127 | 128 | // Begin content validation 129 | if ( 130 | filled(array_get($value, 'content')) && 131 | ! $this->validateNodes(array_get($value, 'content')) 132 | ) { 133 | $fail(trans('tiptap-validation::messages.tiptapContent')); 134 | } 135 | } 136 | 137 | /** 138 | * Validate that a set of nodes are formatted correctly 139 | * and of allowed types. 140 | */ 141 | protected function validateNodes(array $nodes): bool 142 | { 143 | // Validate node types 144 | $validator = Validator::make($nodes, [ 145 | '*.type' => ['required', 'string', $this->typeValidationRule($this->nodes)], 146 | '*.marks.*.type' => ['required', 'string', $this->typeValidationRule($this->marks)], 147 | '*' => new TiptapNode(), 148 | ]); 149 | 150 | if ($validator->fails()) { 151 | return false; 152 | } 153 | 154 | $passes = true; 155 | 156 | // Validate nested nodes recursively. 157 | collect($nodes) 158 | ->each(function (array $node) use (&$passes) { 159 | if ( 160 | filled(array_get($node, 'content')) && 161 | ! $this->validateNodes(array_get($node, 'content')) 162 | ) { 163 | return $passes = false; 164 | } 165 | 166 | return true; 167 | }); 168 | 169 | return $passes; 170 | } 171 | 172 | /** 173 | * Get validation rule based on the validation mode used. 174 | * 175 | * Either whitelist (In), or blacklist (NotIn) 176 | */ 177 | protected function typeValidationRule(array $types): NotIn|In 178 | { 179 | return match ($this->mode) { 180 | TiptapValidationRuleMode::WHITELIST => Rule::in($types), 181 | default => Rule::notIn($types), 182 | }; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Rules/TiptapNode.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class TiptapNode implements TiptapRule 19 | { 20 | use Creatable; 21 | use DecodesTiptapContent; 22 | 23 | public function validate(string $attribute, mixed $value, Closure $fail): void 24 | { 25 | $value = $this->decode($value); 26 | 27 | $validator = Validator::make($value, [ 28 | 'type' => 'required|string', 29 | 'text' => 'string', 30 | 'marks' => 'array', 31 | 'marks.*.type' => 'string', 32 | 'marks.*.text' => 'string', 33 | 'content' => 'array', 34 | ]); 35 | 36 | if ($validator->fails()) { 37 | $fail(trans('messages.tiptapNode')); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/TiptapValidationServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('tiptap-validation') 14 | ->hasTranslations() 15 | ->hasConfigFile(); 16 | } 17 | } 18 | --------------------------------------------------------------------------------