9 changed files with 351 additions and 6 deletions
@ -0,0 +1,39 @@
@@ -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; |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,17 @@
@@ -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" |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -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); |
||||
} |
||||
} |
||||
@ -0,0 +1,52 @@
@@ -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'); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -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, |
||||
]); |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -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…
Reference in new issue