Browse Source

Quill editor basics

imwald
Nuša Pukšič 1 year ago
parent
commit
cd4bf3094b
  1. 39
      assets/controllers/quill_controller.js
  2. 5
      composer.json
  3. 98
      composer.lock
  4. 17
      package.json
  5. 1
      src/Enum/KindsEnum.php
  6. 39
      src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php
  7. 52
      src/Form/DataTransformer/HtmlToMdTransformer.php
  8. 76
      src/Form/EditorType.php
  9. 30
      src/Form/Type/QuillType.php

39
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;
});
}
}

5
composer.json

@ -9,11 +9,13 @@
"php": ">=8.3.13", "php": ">=8.3.13",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"ext-openssl": "*",
"doctrine/dbal": "^4.2", "doctrine/dbal": "^4.2",
"doctrine/doctrine-bundle": "^2.13", "doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.3", "doctrine/orm": "^3.3",
"league/commonmark": "^2.6", "league/commonmark": "^2.6",
"league/html-to-markdown": "*",
"phpdocumentor/reflection-docblock": "^5.6", "phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.0", "phpstan/phpdoc-parser": "^2.0",
"runtime/frankenphp-symfony": "^0.2.0", "runtime/frankenphp-symfony": "^0.2.0",
@ -40,8 +42,7 @@
"symfony/ux-live-component": "^2.21", "symfony/ux-live-component": "^2.21",
"symfony/yaml": "7.1.*", "symfony/yaml": "7.1.*",
"twig/extra-bundle": "^2.12|^3.0", "twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^3.15", "twig/twig": "^3.15"
"ext-openssl": "*"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {

98
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "897e77674e1fcdc323faccc51cf19c34", "content-hash": "78cdd4b60714efe2a7a9fb033411c46b",
"packages": [ "packages": [
{ {
"name": "bitwasp/bech32", "name": "bitwasp/bech32",
@ -1691,6 +1691,95 @@
], ],
"time": "2022-12-11T20:36:23+00:00" "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", "name": "nette/schema",
"version": "v1.3.2", "version": "v1.3.2",
@ -10416,14 +10505,15 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": {}, "stability-flags": [],
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": ">=8.3.13", "php": ">=8.3.13",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*" "ext-iconv": "*",
"ext-openssl": "*"
}, },
"platform-dev": {}, "platform-dev": [],
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

17
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"
}
}

1
src/Enum/KindsEnum.php

@ -8,6 +8,7 @@ enum KindsEnum: int
case TEXT_NOTE = 1; // text note, NIP-01 case TEXT_NOTE = 1; // text note, NIP-01
case REPOST = 6; // Only wraps kind 1, NIP-18 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 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 PINNED_LONGFORM = 10023; // Special purpose curation set, NIP-51, seems deprecated?
case HTTP_AUTH = 27235; // NIP-98, HTTP Auth case HTTP_AUTH = 27235; // NIP-98, HTTP Auth
case CURATION_SET = 30004; // NIP-51 case CURATION_SET = 30004; // NIP-51

39
src/Form/DataTransformer/CommaSeparatedToJsonTransformer.php

@ -0,0 +1,39 @@
<?php
namespace App\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
class CommaSeparatedToJsonTransformer implements DataTransformerInterface
{
/**
* Transforms an array to a comma-separated string.
* @inheritDoc
*/
public function transform(mixed $value): mixed
{
if ($value === null) {
return '';
}
$array = json_decode($value, true);
return implode(',', $array);
}
/**
* Transforms a comma-separated string to an array.
* @inheritDoc
*/
public function reverseTransform(mixed $value): mixed
{
if (!$value) {
return json_encode([]);
}
$array = array_map('trim', explode(',', $value));
return json_encode($array);
}
}

52
src/Form/DataTransformer/HtmlToMdTransformer.php

@ -0,0 +1,52 @@
<?php
namespace App\Form\DataTransformer;
use League\HTMLToMarkdown\HtmlConverter;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class HtmlToMdTransformer implements DataTransformerInterface
{
private $converter;
public function __construct()
{
$this->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');
}
}
}

76
src/Form/EditorType.php

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace App\Form;
use App\Entity\Article;
use App\Form\DataTransformer\CommaSeparatedToJsonTransformer;
use App\Form\DataTransformer\HtmlToMdTransformer;
use App\Form\Type\QuillType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EditorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// create a form with a title field, a QuillType content field and a submit button
$builder
->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,
]);
}
}

30
src/Form/Type/QuillType.php

@ -0,0 +1,30 @@
<?php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Nicolas Assing <nicolas.assing@gmail.com>
*/
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;
}
}
Loading…
Cancel
Save