Skip to content

Commit

Permalink
feat(editor): added phpMyFAQ internal link plugin, closes #2896
Browse files Browse the repository at this point in the history
  • Loading branch information
thorsten committed Jan 1, 2025
1 parent b9510e5 commit 2acc2ea
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 22 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This is a log of major user-visible changes in each phpMyFAQ release.

- added configuration to edit robots.txt (Thorsten)
- added Symfony Routing for administration backend (Thorsten)
- WIP: migrated from WYSIWYG editor from TinyMCE to Jodit Editor (Thorsten)
- migrated from WYSIWYG editor from TinyMCE to Jodit Editor (Thorsten)
- removed Webpack, now using Vite v6 (Thorsten)
- migrated from Jest to vitest (Thorsten)

Expand Down
86 changes: 83 additions & 3 deletions phpmyfaq/admin/assets/src/content/editor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* TinyMCE for phpMyFAQ
* Jodit Editor for phpMyFAQ
*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
Expand Down Expand Up @@ -38,6 +38,81 @@ import 'jodit/esm/plugins/symbols/symbols.js';
import 'jodit/esm/modules/uploader/uploader.js';
import 'jodit/esm/plugins/video/video.js';

// Define the phpMyFAQ plugin
Jodit.plugins.add('phpMyFAQ', (editor) => {
// Register the button
editor.registerButton({
name: 'phpMyFAQ',
});

// Register the command
editor.registerCommand('phpMyFAQ', () => {
const dialog = editor.dlg({ closeOnClickOverlay: true });

const content = `<form class="row row-cols-lg-auto g-3 align-items-center m-4">
<div class="col-12">
<label class="visually-hidden" for="pmf-search-internal-links">Search</label>
<input type="text" class="form-control" id="pmf-search-internal-links" placeholder="Search">
</div>
</form>
<div class="m-4" id="pmf-search-results"></div>
<div class="m-4">
<button type="button" class="btn btn-primary" id="select-faq-button">Select FAQ</button>
</div>`;

dialog.setMod('theme', editor.o.theme).setHeader('phpMyFAQ Plugin').setContent(content);

dialog.open();

const searchInput = document.getElementById('pmf-search-internal-links');
const resultsContainer = document.getElementById('pmf-search-results');
const csrfToken = document.getElementById('pmf-csrf-token').value;
const selectLink = document.getElementById('select-faq-button');

searchInput.addEventListener('keyup', () => {
const query = searchInput.value;
if (query.length > 0) {
fetch('api/faq/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
search: query,
csrf: csrfToken,
}),
})
.then((response) => response.json())
.then((data) => {
resultsContainer.innerHTML = '';
data.success.forEach((result) => {
resultsContainer.innerHTML += `<label class="form-check-label">
<input class="form-check-input" type="radio" name="faqURL" value="${result.url}">
${result.question}
</label><br>`;
});
})
.catch((error) => console.error('Error:', error));
} else {
resultsContainer.innerHTML = '';
}
});

selectLink.addEventListener('click', () => {
const selected = document.querySelector('input[name=faqURL]:checked');
if (selected) {
const url = selected.value;
const question = selected.parentNode.textContent.trim();
const anchor = `<a href="${url}">${question}</a>`;
editor.selection.insertHTML(anchor);
dialog.close();
} else {
alert('Please select an FAQ.');
}
});
});
});

export const renderEditor = () => {
const editor = document.getElementById('editor');
if (!editor) {
Expand Down Expand Up @@ -158,6 +233,7 @@ export const renderEditor = () => {
imageDefaultWidth: 300,
removeButtons: [],
disablePlugins: [],
extraPlugins: ['phpMyFAQ'],
extraButtons: [],
buttons: [
'source',
Expand All @@ -172,7 +248,6 @@ export const renderEditor = () => {
'superscript',
'subscript',
'|',
'justify',
'outdent',
'indent',
'|',
Expand All @@ -181,13 +256,16 @@ export const renderEditor = () => {
'lineHeight',
'brush',
'paragraph',
'left',
'center',
'right',
'justify',
'|',
'copy',
'cut',
'paste',
'selectall',
'|',
'file',
'image',
'video',
'table',
Expand All @@ -205,6 +283,8 @@ export const renderEditor = () => {
'fullsize',
'preview',
'print',
'|',
'phpMyFAQ',
],
events: {},
textIcons: false,
Expand Down
2 changes: 0 additions & 2 deletions phpmyfaq/faq.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@
$question = $faq->getQuestion($faqId);
if ($faqConfig->get('main.enableMarkdownEditor')) {
$answer = $converter->convert($faq->faqRecord['content'])->getContent();
} else {
$answer = $faqHelper->rewriteLanguageMarkupClass($faq->faqRecord['content']);
}

// Cleanup answer content first
Expand Down
8 changes: 0 additions & 8 deletions phpmyfaq/src/phpMyFAQ/Helper/FaqHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,6 @@ public function __construct(Configuration $configuration)
$this->configuration = $configuration;
}

/**
* Rewrites the CSS class generated by TinyMCE for HighlightJS.
*/
public function rewriteLanguageMarkupClass(string $answer): string
{
return str_replace('class="language-markup"', 'class="language-html"', $answer);
}

/**
* Extends URL fragments (e.g. <a href="#foo">) with the full default URL.
*/
Expand Down
8 changes: 0 additions & 8 deletions tests/phpMyFAQ/Helper/FaqHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ protected function setUp(): void
$this->faqHelper = new FaqHelper($this->configuration);
}

public function testRewriteLanguageMarkupClass(): void
{
$this->assertEquals(
'<div class="language-html">Foobar</div>',
$this->faqHelper->rewriteLanguageMarkupClass('<div class="language-markup">Foobar</div>')
);
}

public function testRewriteUrlFragments(): void
{
$content = '<a href="#Foobar">Hello, World</a>';
Expand Down

0 comments on commit 2acc2ea

Please sign in to comment.