From cd4bf3094bfba668c7be2810fdb3e0876e91ee2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Sat, 4 Jan 2025 13:22:25 +0100 Subject: [PATCH] Quill editor basics --- assets/controllers/quill_controller.js | 39 ++++++++ composer.json | 5 +- composer.lock | 98 ++++++++++++++++++- package.json | 17 ++++ src/Enum/KindsEnum.php | 1 + .../CommaSeparatedToJsonTransformer.php | 39 ++++++++ .../DataTransformer/HtmlToMdTransformer.php | 52 ++++++++++ src/Form/EditorType.php | 76 ++++++++++++++ src/Form/Type/QuillType.php | 30 ++++++ 9 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 assets/controllers/quill_controller.js create mode 100644 package.json create mode 100644 src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php create mode 100644 src/Form/DataTransformer/HtmlToMdTransformer.php create mode 100644 src/Form/EditorType.php create mode 100644 src/Form/Type/QuillType.php diff --git a/assets/controllers/quill_controller.js b/assets/controllers/quill_controller.js new file mode 100644 index 0000000..60ba30b --- /dev/null +++ b/assets/controllers/quill_controller.js @@ -0,0 +1,39 @@ +import {Controller} from '@hotwired/stimulus'; +import Quill from 'quill'; + +import('quill/dist/quill.core.css'); +import('quill/dist/quill.snow.css'); + + +export default class extends Controller { + + connect() { + const toolbarOptions = [ + ['bold', 'italic', 'underline', 'strike'], + ['link', 'blockquote', 'code-block', 'image'], + [{ 'header': 1 }, { 'header': 2 }], + [{ list: 'ordered' }, { list: 'bullet' }], + ]; + + const options = { + theme: 'snow', + modules: { + toolbar: toolbarOptions, + } + } + + let quill = new Quill('#editor', options); + let target = document.querySelector('#editor_content'); + + + quill.on('text-change', function(delta, oldDelta, source) { + console.log('Text change!'); + console.log(delta); + console.log(oldDelta); + console.log(source); + // save as html + target.value = quill.root.innerHTML; + }); + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index 1432a51..3ca1b19 100644 --- a/composer.json +++ b/composer.json @@ -9,11 +9,13 @@ "php": ">=8.3.13", "ext-ctype": "*", "ext-iconv": "*", + "ext-openssl": "*", "doctrine/dbal": "^4.2", "doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.3", "league/commonmark": "^2.6", + "league/html-to-markdown": "*", "phpdocumentor/reflection-docblock": "^5.6", "phpstan/phpdoc-parser": "^2.0", "runtime/frankenphp-symfony": "^0.2.0", @@ -40,8 +42,7 @@ "symfony/ux-live-component": "^2.21", "symfony/yaml": "7.1.*", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^3.15", - "ext-openssl": "*" + "twig/twig": "^3.15" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 115b23a..0b3ab34 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "897e77674e1fcdc323faccc51cf19c34", + "content-hash": "78cdd4b60714efe2a7a9fb033411c46b", "packages": [ { "name": "bitwasp/bech32", @@ -1691,6 +1691,95 @@ ], "time": "2022-12-11T20:36:23+00:00" }, + { + "name": "league/html-to-markdown", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/html-to-markdown.git", + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd", + "reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xml": "*", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "mikehaertl/php-shellcommand": "^1.1.0", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^8.5 || ^9.2", + "scrutinizer/ocular": "^1.6", + "unleashedtech/php-coding-standard": "^2.7 || ^3.0", + "vimeo/psalm": "^4.22 || ^5.0" + }, + "bin": [ + "bin/html-to-markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\HTMLToMarkdown\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + }, + { + "name": "Nick Cernis", + "email": "nick@cern.is", + "homepage": "http://modernnerd.net", + "role": "Original Author" + } + ], + "description": "An HTML-to-markdown conversion helper for PHP", + "homepage": "https://github.com/thephpleague/html-to-markdown", + "keywords": [ + "html", + "markdown" + ], + "support": { + "issues": "https://github.com/thephpleague/html-to-markdown/issues", + "source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown", + "type": "tidelift" + } + ], + "time": "2023-07-12T21:21:09+00:00" + }, { "name": "nette/schema", "version": "v1.3.2", @@ -10416,14 +10505,15 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=8.3.13", "ext-ctype": "*", - "ext-iconv": "*" + "ext-iconv": "*", + "ext-openssl": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/package.json b/package.json new file mode 100644 index 0000000..6ecb0a4 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "newsroom", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/decent-newsroom/newsroom.git" + }, + "private": true, + "dependencies": { + "quill": "^2.0.3" + } +} diff --git a/src/Enum/KindsEnum.php b/src/Enum/KindsEnum.php index 5f87680..992fbb6 100644 --- a/src/Enum/KindsEnum.php +++ b/src/Enum/KindsEnum.php @@ -8,6 +8,7 @@ enum KindsEnum: int case TEXT_NOTE = 1; // text note, NIP-01 case REPOST = 6; // Only wraps kind 1, NIP-18 case GENERIC_REPOST = 16; // Generic repost, original kind signalled in a "k" tag, NIP-18 + case FILE_METADATA = 1063; // NIP-94 case PINNED_LONGFORM = 10023; // Special purpose curation set, NIP-51, seems deprecated? case HTTP_AUTH = 27235; // NIP-98, HTTP Auth case CURATION_SET = 30004; // NIP-51 diff --git a/src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php b/src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php new file mode 100644 index 0000000..3b5ed31 --- /dev/null +++ b/src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php @@ -0,0 +1,39 @@ +converter = new HtmlConverter(); + } + + /** + * Transforms Markdown into HTML (for displaying in the form). + * @inheritDoc + */ + public function transform(mixed $value): mixed + { + dump($value); + if ($value === null) { + return ''; + } + + // Optional: You can add a markdown-to-html conversion if needed + return $value; // You could return rendered markdown here. + } + + /** + * Transforms a HTML string to Markdown. + * @inheritDoc + */ + public function reverseTransform(mixed $value): mixed + { + dump($value); + if (!$value) { + return ''; + } + + try { + // Convert HTML to Markdown + return $this->converter->convert($value); + } catch (\Exception $e) { + throw new TransformationFailedException('Failed to convert HTML to Markdown'); + } + } +} diff --git a/src/Form/EditorType.php b/src/Form/EditorType.php new file mode 100644 index 0000000..4cc3262 --- /dev/null +++ b/src/Form/EditorType.php @@ -0,0 +1,76 @@ +add('title', TextType::class, [ + 'required' => false, + 'sanitize_html' => true, + 'attr' => ['placeholder' => 'Enter title', 'class' => 'form-control']]) + ->add('summary', TextareaType::class, [ + 'required' => false, + 'sanitize_html' => true, + 'attr' => ['placeholder' => 'Enter summary', 'class' => 'form-control']]) + ->add('content', QuillType::class, [ + 'required' => false, + 'attr' => ['placeholder' => 'Enter content', 'class' => 'form-control']]) + ->add('imgUrl', UrlType::class, [ + 'required' => false, + 'label' => 'Image URL', + 'attr' => ['placeholder' => 'Enter image URL', 'class' => 'form-control']]) + ->add('topics', TextType::class, [ + 'required' => false, + 'sanitize_html' => true, + 'help' => 'Separate tags with commas', + 'attr' => ['placeholder' => 'Enter tags', 'class' => 'form-control']]) + ->add( + $builder->create('actions', FormType::class, + ['row_attr' => ['class' => 'actions'], 'label' => false, 'mapped' => false]) + ->add('submit', SubmitType::class, [ + 'label' => 'Submit', + 'attr' => ['class' => 'btn btn-primary']]) + ->add('draft', SubmitType::class, [ + 'label' => 'Save as draft', + 'attr' => ['class' => 'btn btn-secondary']]) + ->add('preview', SubmitType::class, [ + 'label' => 'Preview', + 'attr' => ['class' => 'btn btn-secondary']]) + ); + + + + // Apply the custom transformer + $builder->get('topics') + ->addModelTransformer(new CommaSeparatedToJsonTransformer()); + $builder->get('content') + ->addModelTransformer(new HtmlToMdTransformer()); + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Article::class, + ]); + } +} diff --git a/src/Form/Type/QuillType.php b/src/Form/Type/QuillType.php new file mode 100644 index 0000000..73e0d4e --- /dev/null +++ b/src/Form/Type/QuillType.php @@ -0,0 +1,30 @@ + + */ +class QuillType extends AbstractType +{ + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([]); + } + + public function getParent(): string + { + return TextareaType::class; + } +} \ No newline at end of file