fix(md-editor): build new markdown editor with lit + github/markdown-toolbar-element

- create markdown-write-preview + markdown-preview webcomponents using lit
- create
form_markdown_editor helper form component
- simplify form_dropdown and form_multiselect
components
- fix partner fields display

fixes #93, #94, #120
This commit is contained in:
Yassine Doghri 2021-07-29 09:41:36 +00:00
parent 910d457cf8
commit 9ec1cb93da
36 changed files with 426 additions and 1075 deletions

View File

@ -25,19 +25,20 @@
}
},
"extensions": [
"mikestead.dotenv",
"bierner.lit-html",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"wayou.vscode-todo-highlight",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"jamesbirtles.svelte-vscode",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"eamodio.gitlens",
"breezelin.phpstan",
"kasik96.latte"
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"heybourn.headwind",
"jamesbirtles.svelte-vscode",
"kasik96.latte",
"mikestead.dotenv",
"naumovs.color-highlight",
"streetsidesoftware.code-spell-checker",
"stylelint.vscode-stylelint",
"wayou.vscode-todo-highlight"
]
}

View File

@ -157,17 +157,9 @@ if (! function_exists('form_multiselect')) {
): string {
$defaultExtra = [
'data-class' => $customExtra['class'],
'data-select-text' => lang('Common.forms.multiSelect.selectText'),
'data-loading-text' => lang('Common.forms.multiSelect.loadingText'),
'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'),
'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'),
'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'),
'multiple' => 'multiple',
];
$extra = stringify_attributes(array_merge($defaultExtra, $customExtra));
if (stripos($extra, 'multiple') === false) {
$extra .= ' multiple="multiple"';
}
$extra = array_merge($defaultExtra, $customExtra);
return form_dropdown($name, $options, $selected, $extra);
}
@ -179,43 +171,31 @@ if (! function_exists('form_dropdown')) {
/**
* Drop-down Menu (based on html select tag)
*
* @param array<string, mixed>|string $data
* @param array<string, string> $options
* @param string|string[] $selected
* @param array<string, mixed>|string $extra
* @param array<string, mixed> $options
* @param string[] $selected
* @param array<string, mixed> $customExtra
*/
function form_dropdown(
string | array $data = '',
string $name = '',
array $options = [],
string | array $selected = [],
string | array $extra = ''
array $selected = [],
array $customExtra = []
): string {
$defaults = [];
if (is_array($data)) {
if (isset($data['selected'])) {
$selected = $data['selected'];
unset($data['selected']); // select tags don't have a selected attribute
}
if (isset($data['options'])) {
$options = $data['options'];
unset($data['options']); // select tags don't use an options attribute
}
} else {
$defaults = [
'name' => $data,
];
}
if (! is_array($selected)) {
$selected = [$selected];
}
if (! is_array($options)) {
$options = [$options];
}
$defaultExtra = [
'data-select-text' => lang('Common.forms.multiSelect.selectText'),
'data-loading-text' => lang('Common.forms.multiSelect.loadingText'),
'data-no-results-text' => lang('Common.forms.multiSelect.noResultsText'),
'data-no-choices-text' => lang('Common.forms.multiSelect.noChoicesText'),
'data-max-item-text' => lang('Common.forms.multiSelect.maxItemText'),
];
$extra = array_merge($defaultExtra, $customExtra);
$defaults = [
'name' => $name,
];
// standardize selected as strings, like the option keys will be.
foreach ($selected as $key => $item) {
$selected[$key] = (string) $item;
$selected[$key] = $item;
}
$placeholderOption = '';
@ -230,11 +210,10 @@ if (! function_exists('form_dropdown')) {
$extra = stringify_attributes($extra);
$multiple = (count($selected) > 1 && stripos($extra, 'multiple') === false) ? ' multiple="multiple"' : '';
$form = '<select ' . rtrim(parse_form_attributes($data, $defaults)) . $extra . $multiple . ">\n";
$form = '<select ' . rtrim(parse_form_attributes($name, $defaults)) . $extra . $multiple . ">\n";
$form .= $placeholderOption;
foreach ($options as $key => $val) {
$key = (string) $key;
if (is_array($val)) {
if ($val === []) {
continue;
@ -257,4 +236,80 @@ if (! function_exists('form_dropdown')) {
}
}
//--------------------------------------------------------------------
if (! function_exists('form_editor')) {
/**
* Markdown editor
*
* @param array<string, mixed> $data
* @param array<string, mixed>|string $extra
*/
function form_markdown_editor(array $data = [], string $value = '', string | array $extra = ''): string
{
$editorClass = 'w-full flex flex-col bg-white border border-gray-500 focus-within:ring-1 focus-within:ring-blue-600';
if (array_key_exists('class', $data) && $data['class'] !== '') {
$editorClass .= ' ' . $data['class'];
unset($data['class']);
}
$data['class'] = 'border-none outline-none focus:border-none focus:outline-none w-full h-full';
return '<div class="' . $editorClass . '">' .
'<header class="sticky top-0 z-20 flex flex-wrap justify-between bg-white border-b border-gray-500">' .
'<markdown-write-preview for="' . $data['id'] . '" class="relative inline-flex h-8">' .
'<button type="button" slot="write" class="px-2 font-semibold focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.write'
) . '</button>' .
'<button type="button" slot="preview" class="px-2 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.preview'
) . '</button>' .
'</markdown-write-preview>' .
'<markdown-toolbar for="' . $data['id'] . '" class="flex gap-4 px-2 py-1">' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-header class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'heading'
) . '</md-header>' .
'<md-bold class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'bold'
) . '</md-bold>' .
'<md-italic class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'italic'
) . '</md-italic>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-unordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-unordered'
) . '</md-unordered-list>' .
'<md-ordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-ordered'
) . '</md-ordered-list>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-quote class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'quote'
) . '</md-quote>' .
'<md-link class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'link'
) . '</md-link>' .
'<md-image class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'image-add'
) . '</md-image>' .
'</div>' .
'</markdown-toolbar>' .
'</header>' .
'<div class="relative">' .
form_textarea($data, $value, $extra) .
'<markdown-preview for="' . $data['id'] . '" class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white"></markdown-preview>' .
'</div>' .
'<footer class="flex px-2 py-1 bg-gray-100 border-t">' .
'<a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-gray-500 hover:text-gray-700" target="_blank" rel="noopener noreferrer">' . icon(
'markdown',
'mr-1 text-lg text-gray-400'
) . lang('Common.forms.editor.help') . '</a>' .
'</footer>' .
'</div>';
}
}
// ------------------------------------------------------------------------

View File

@ -24,6 +24,11 @@ return [
'pageInfo' => 'Page {currentPage} out of {pageCount}',
'go_back' => 'Go back',
'forms' => [
'editor' => [
'write' => 'Write',
'preview' => 'Preview',
'help' => 'Powered by markdown',
],
'multiSelect' => [
'selectText' => 'Press to select',
'loadingText' => 'Loading...',

View File

@ -24,6 +24,11 @@ return [
'pageInfo' => 'Page {currentPage} sur {pageCount}',
'go_back' => 'Retour en arrière',
'forms' => [
'editor' => [
'write' => 'Écrire',
'preview' => 'Aperçu',
'help' => 'Propulsé par markdown',
],
'multiSelect' => [
'selectText' => 'Cliquez pour selectionner',
'loadingText' => 'Chargement...',

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M8 11h4.5a2.5 2.5 0 1 0 0-5H8v5zm10 4.5a4.5 4.5 0 0 1-4.5 4.5H6V4h6.5a4.5 4.5 0 0 1 3.256 7.606A4.498 4.498 0 0 1 18 15.5zM8 13v5h5.5a2.5 2.5 0 1 0 0-5H8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M17 11V4h2v17h-2v-8H7v8H5V4h2v7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M21 15v3h3v2h-3v3h-2v-3h-3v-2h3v-3h2zm.008-12c.548 0 .992.445.992.993v9.349A5.99 5.99 0 0 0 20 13V5H4l.001 14 9.292-9.293a.999.999 0 0 1 1.32-.084l.093.085 3.546 3.55a6.003 6.003 0 0 0-3.91 7.743L2.992 21A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016zM8 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 445 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M15 20H7v-2h2.927l2.116-12H9V4h8v2h-2.927l-2.116 12H15z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 207 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M8 4h13v2H8V4zM5 3v3h1v1H3V6h1V4H3V3h2zM3 14v-2.5h2V11H3v-1h3v2.5H4v.5h2v1H3zm2 5.5H3v-1h2V18H3v-1h3v4H3v-1h2v-.5zM8 11h13v2H8v-2zm0 7h13v2H8v-2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M8 4h13v2H8V4zM4.5 6.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 6.9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM8 11h13v2H8v-2zm0 7h13v2H8v-2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm4 12.5v-4l2 2 2-2v4h2v-7h-2l-2 2-2-2H5v7h2zm11-3v-4h-2v4h-2l3 3 3-3h-2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 295 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M4.583 17.321C3.553 16.227 3 15 3 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.804.167 3.226 1.648 3.226 3.489a3.5 3.5 0 0 1-3.5 3.5c-1.073 0-2.099-.49-2.748-1.179zm10 0C13.553 16.227 13 15 13 13.011c0-3.5 2.457-6.637 6.03-8.188l.893 1.378c-3.335 1.804-3.987 4.145-4.247 5.621.537-.278 1.24-.375 1.929-.311 1.804.167 3.226 1.648 3.226 3.489a3.5 3.5 0 0 1-3.5 3.5c-1.073 0-2.099-.49-2.748-1.179z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 617 B

View File

@ -1,8 +1,10 @@
import "@github/markdown-toolbar-element";
import ClientTimezone from "./modules/ClientTimezone";
import Clipboard from "./modules/Clipboard";
import DateTimePicker from "./modules/DateTimePicker";
import Dropdown from "./modules/Dropdown";
import MarkdownEditor from "./modules/MarkdownEditor";
import "./modules/markdown-preview";
import "./modules/markdown-write-preview";
import MultiSelect from "./modules/MultiSelect";
import PublishMessageWarning from "./modules/PublishMessageWarning";
import Select from "./modules/Select";
@ -15,7 +17,6 @@ import Tooltip from "./modules/Tooltip";
Dropdown();
Tooltip();
MarkdownEditor();
Select();
MultiSelect();
Slugify();

View File

@ -1,159 +0,0 @@
import { exampleSetup } from "prosemirror-example-setup";
import "prosemirror-example-setup/style/style.css";
import {
defaultMarkdownParser,
defaultMarkdownSerializer,
schema,
} from "prosemirror-markdown";
import "prosemirror-menu/style/menu.css";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import "prosemirror-view/style/prosemirror.css";
class MarkdownView {
textarea: HTMLTextAreaElement;
constructor(target: HTMLTextAreaElement) {
this.textarea = target;
this.textarea.classList.add("w-full", "h-full");
}
content() {
return this.textarea.innerHTML;
}
focus() {
this.textarea.focus();
}
show() {
this.textarea.classList.remove("hidden");
}
hide() {
this.textarea.classList.add("hidden");
}
}
class ProseMirrorView {
editorContainer: HTMLDivElement;
view: EditorView;
constructor(target: HTMLTextAreaElement, content: string) {
this.editorContainer = document.createElement("div");
this.editorContainer.classList.add("bg-white", "border");
this.editorContainer.style.minHeight = "200px";
const editor = target.parentNode?.insertBefore(
this.editorContainer,
target.nextSibling
);
this.view = new EditorView(editor, {
state: EditorState.create({
doc: defaultMarkdownParser.parse(content),
plugins: exampleSetup({ schema }),
}),
dispatchTransaction: (transaction) => {
const newState = this.view.state.apply(transaction);
this.view.updateState(newState);
if (transaction.docChanged) {
target.innerHTML = this.content();
}
},
attributes: {
class: "prose-sm px-3 py-2 overflow-y-auto focus:ring",
style: "min-height: 200px; max-height: 500px",
},
});
}
content(): string {
return defaultMarkdownSerializer.serialize(this.view.state.doc) || "";
}
focus() {
this.view.focus();
}
show() {
this.editorContainer.classList.remove("hidden");
}
hide() {
this.editorContainer.classList.add("hidden");
}
}
const MarkdownEditor = (): void => {
const targets: NodeListOf<HTMLTextAreaElement> = document.querySelectorAll(
"textarea[data-editor='markdown']"
);
const activeClass = "font-semibold";
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
const wysiwygBtn = document.createElement("button");
wysiwygBtn.classList.add(
activeClass,
"py-1",
"px-2",
"bg-white",
"border",
"text-xs",
"outline-none",
"focus:ring"
);
wysiwygBtn.setAttribute("type", "button");
wysiwygBtn.innerHTML = "Wysiwyg";
const markdownBtn = document.createElement("button");
markdownBtn.classList.add(
"py-1",
"px-2",
"bg-white",
"border",
"text-xs",
"outline-none",
"focus:ring"
);
markdownBtn.setAttribute("type", "button");
markdownBtn.innerHTML = "Markdown";
const viewButtons = document.createElement("div");
viewButtons.appendChild(wysiwygBtn);
viewButtons.appendChild(markdownBtn);
viewButtons.classList.add(
"inline-flex",
"absolute",
"top-0",
"right-0",
"-mt-6"
);
const markdownEditorContainer = document.createElement("div");
markdownEditorContainer.classList.add("relative");
markdownEditorContainer.style.minHeight = "200px";
target.parentNode?.appendChild(markdownEditorContainer);
markdownEditorContainer.appendChild(target);
// show WYSIWYG editor by default
target.classList.add("hidden");
const markdownView = new MarkdownView(target);
const wysiwygView = new ProseMirrorView(target, markdownView.content());
markdownEditorContainer.appendChild(viewButtons);
markdownBtn.addEventListener("click", () => {
if (markdownBtn.classList.contains(activeClass)) return;
markdownBtn.classList.add(activeClass);
wysiwygBtn.classList.remove(activeClass);
wysiwygView.hide();
markdownView.show();
});
wysiwygBtn.addEventListener("click", () => {
if (wysiwygBtn.classList.contains(activeClass)) return;
wysiwygBtn.classList.add(activeClass);
markdownBtn.classList.remove(activeClass);
markdownView.hide();
wysiwygView.show();
});
}
};
export default MarkdownEditor;

View File

@ -10,8 +10,11 @@ const MultiSelect = (): void => {
new Choices(multiSelect, {
maxItemCount: parseInt(multiSelect.dataset.maxItemCount || "-1"),
loadingText: multiSelect.dataset.loadingText,
itemSelectText: multiSelect.dataset.selectText,
maxItemText: multiSelect.dataset.maxItemText,
noChoicesText: multiSelect.dataset.noChoicesText,
noResultsText: multiSelect.dataset.noResultsText,
removeItemButton: true,
classNames: {
containerOuter: "choices",

View File

@ -10,6 +10,11 @@ const Select = (): void => {
const select = selects[i];
new Choices(select, {
loadingText: select.dataset.loadingText,
itemSelectText: select.dataset.selectText,
maxItemText: select.dataset.maxItemText,
noChoicesText: select.dataset.noChoicesText,
noResultsText: select.dataset.noResultsText,
classNames: {
containerOuter: "choices",
containerInner: "choices__inner",

View File

@ -0,0 +1,60 @@
import MarkdownToolbarElement from "@github/markdown-toolbar-element";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import marked from "marked";
@customElement("markdown-preview")
export class MarkdownPreview extends LitElement {
@property()
for!: string;
@property()
_textarea!: HTMLTextAreaElement;
@property()
_markdownToolbar!: MarkdownToolbarElement;
@property()
_show = false;
connectedCallback(): void {
super.connectedCallback();
this._textarea = document.getElementById(this.for) as HTMLTextAreaElement;
this._markdownToolbar = document.querySelector(
`markdown-toolbar[for=${this.for}]`
) as MarkdownToolbarElement;
}
hide(): void {
this._show = false;
this.classList.add("hidden");
this._markdownToolbar.classList.remove("hidden");
}
show(): void {
this._show = true;
this.classList.remove("hidden");
this._markdownToolbar.classList.add("hidden");
}
markdownToHtml(): string {
const renderer = new marked.Renderer();
renderer.link = function () {
// eslint-disable-next-line prefer-rest-params
const link = marked.Renderer.prototype.link.apply(this, arguments as any);
return link.replace("<a", "<a target='_blank' rel='noopener noreferrer'");
};
return marked(this._textarea.value, {
renderer: renderer,
});
}
render(): TemplateResult<1> {
return html`${this._show
? html`${unsafeHTML(this.markdownToHtml())}`
: html``}`;
}
}

View File

@ -0,0 +1,47 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, queryAssignedNodes } from "lit/decorators.js";
import { MarkdownPreview } from "./markdown-preview";
@customElement("markdown-write-preview")
export class MarkdownWritePreview extends LitElement {
@property()
for!: string;
@property()
_textarea: HTMLTextAreaElement | null = null;
@property()
_markdownPreview!: MarkdownPreview;
@queryAssignedNodes("write", true)
_write!: NodeListOf<HTMLButtonElement>;
@queryAssignedNodes("preview", true)
_preview!: NodeListOf<HTMLButtonElement>;
connectedCallback(): void {
super.connectedCallback();
this._textarea = document.getElementById(this.for) as HTMLTextAreaElement;
this._markdownPreview = document.querySelector(
`markdown-preview[for=${this.for}]`
) as MarkdownPreview;
}
write(): void {
this._markdownPreview.hide();
this._write[0].classList.add("font-semibold");
this._preview[0].classList.remove("font-semibold");
}
preview(): void {
this._markdownPreview.show();
this._preview[0].classList.add("font-semibold");
this._write[0].classList.remove("font-semibold");
}
render(): TemplateResult<1> {
return html`<slot name="write" @click="${this.write}"></slot>
<slot name="preview" @click="${this.preview}"></slot>`;
}
}

View File

@ -1 +1,3 @@
export {};
import "@github/markdown-toolbar-element";
import "./modules/markdown-preview";
import "./modules/markdown-write-preview";

View File

View File

@ -1,5 +0,0 @@
import "prosemirror-example-setup/style/style.css";
import "prosemirror-menu/style/menu.css";
import "prosemirror-view/style/prosemirror.css";
declare const MarkdownEditor: () => void;
export default MarkdownEditor;

View File

@ -0,0 +1,13 @@
import MarkdownToolbarElement from "@github/markdown-toolbar-element";
import { LitElement, TemplateResult } from "lit";
export declare class MarkdownPreview extends LitElement {
for: string;
_textarea: HTMLTextAreaElement;
_markdownToolbar: MarkdownToolbarElement;
_show: boolean;
connectedCallback(): void;
hide(): void;
show(): void;
markdownToHtml(): string;
render(): TemplateResult<1>;
}

View File

@ -0,0 +1,13 @@
import { LitElement, TemplateResult } from "lit";
import { MarkdownPreview } from "./markdown-preview";
export declare class MarkdownWritePreview extends LitElement {
for: string;
_textarea: HTMLTextAreaElement | null;
_markdownPreview: MarkdownPreview;
_write: NodeListOf<HTMLButtonElement>;
_preview: NodeListOf<HTMLButtonElement>;
connectedCallback(): void;
write(): void;
preview(): void;
render(): TemplateResult<1>;
}

View File

@ -20,7 +20,7 @@
<?= $this->include('admin/_sidebar') ?>
<?php endif; ?>
</aside>
<main class="overflow-hidden holy-grail-main">
<main class="holy-grail-main">
<header class="text-white bg-pine-900">
<div class="container flex flex-wrap items-end justify-between px-2 py-10 mx-auto md:px-12 gap-y-6 gap-x-6">
<div class="flex flex-col">

View File

@ -17,7 +17,7 @@
<?= csrf_field() ?>
<?= form_label(lang('Contributor.form.user'), 'user') ?>
<?= form_dropdown('user', $userOptions, old('user', ''), [
<?= form_dropdown('user', $userOptions, [old('user', '')], [
'id' => 'user',
'class' => 'form-select mb-4',
'required' => 'required',
@ -25,7 +25,7 @@
]) ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role', ''), [
<?= form_dropdown('role', $roleOptions, [old('role', '')], [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',

View File

@ -17,7 +17,7 @@
<?= csrf_field() ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role', $contributorGroupId), [
<?= form_dropdown('role', $roleOptions, [old('role', $contributorGroupId)], [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',

View File

@ -201,15 +201,13 @@
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', '', false),
'data-editor="markdown"',
) ?>
</div>
@ -219,19 +217,18 @@
'description_footer',
[],
lang('Episode.form.description_footer_hint'),
true
) ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'description_footer',
'name' => 'description_footer',
'class' => 'form-textarea',
],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
'data-editor="markdown"',
) ?>
</div>

View File

@ -211,15 +211,13 @@
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', $episode->description_markdown, false),
'data-editor="markdown"',
) ?>
</div>
@ -229,19 +227,18 @@
'description_footer',
[],
lang('Episode.form.description_footer_hint'),
true
) ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'description_footer',
'name' => 'description_footer',
'class' => 'form-textarea',
],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
'data-editor="markdown"',
) ?>
</div>

View File

@ -38,15 +38,14 @@
<div class="mb-4">
<?= form_label(lang('Page.form.content'), 'content') ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'content',
'name' => 'content',
'class' => 'form-textarea',
'required' => 'required',
],
old('content', '', false),
'data-editor="markdown"',
['rows' => '20']
) ?>
</div>

View File

@ -38,15 +38,13 @@
<div class="mb-4">
<?= form_label(lang('Page.form.content'), 'content') ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'content',
'name' => 'content',
'class' => 'form-textarea',
'required' => 'required',
],
old('content', $page->content_markdown, false),
'data-editor="markdown"',
) ?>
</div>

View File

@ -82,15 +82,13 @@
<div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_textarea(
<?= form_markdown_editor(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', '', false),
'data-editor="markdown"',
) ?>
</div>
@ -103,14 +101,14 @@
) ?>
<?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown('language', $languageOptions, old('language', $browserLang), [
<?= form_dropdown('language', $languageOptions, [old('language', $browserLang)], [
'id' => 'language',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_label(lang('Podcast.form.category'), 'category') ?>
<?= form_dropdown('category', $categoryOptions, old('category', ''), [
<?= form_dropdown('category', $categoryOptions, [old('category', '')], [
'id' => 'category',
'class' => 'form-select mb-4',
'required' => 'required',
@ -127,7 +125,7 @@
<?= form_multiselect(
'other_categories[]',
$categoryOptions,
old('other_categories', []),
[old('other_categories', '')],
[
'id' => 'other_categories',
'class' => 'mb-4',
@ -282,11 +280,11 @@
<?= form_label(lang('Podcast.form.partnership')) ?>
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
<div class="flex flex-col flex-shrink w-32">
<div class="flex flex-col flex-shrink">
<?= form_label(
lang('Podcast.form.partner_id'),
'partner_id',
[],
['class' => 'text-sm'],
lang('Podcast.form.partner_id_hint'),
true,
) ?>
@ -297,11 +295,11 @@
'value' => old('partner_id'),
]) ?>
</div>
<div class="flex flex-col flex-1">
<div class="flex flex-col">
<?= form_label(
lang('Podcast.form.partner_link_url'),
'partner_link_url',
[],
['class' => 'text-sm'],
lang('Podcast.form.partner_link_url_hint'),
true,
) ?>
@ -312,11 +310,11 @@
'value' => old('partner_link_url'),
]) ?>
</div>
<div class="flex flex-col flex-1">
<div class="flex flex-col">
<?= form_label(
lang('Podcast.form.partner_image_url'),
'partner_image_url',
[],
['class' => 'text-sm'],
lang('Podcast.form.partner_image_url_hint'),
true,
) ?>

View File

@ -68,15 +68,12 @@
<div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_textarea(
[
<?= form_markdown_editor([
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', $podcast->description_markdown, false),
'data-editor="markdown"',
old('description', $podcast->description_markdown, false)
) ?>
</div>
@ -92,7 +89,7 @@
<?= form_dropdown(
'language',
$languageOptions,
old('language', $podcast->language_code),
[old('language', $podcast->language_code)],
[
'id' => 'language',
'class' => 'form-select mb-4',
@ -104,7 +101,7 @@
<?= form_dropdown(
'category',
$categoryOptions,
old('category', (string) $podcast->category_id),
[old('category', (string) $podcast->category_id)],
[
'id' => 'category',
'class' => 'form-select mb-4',

View File

@ -68,14 +68,14 @@
</div>
<?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown('language', $languageOptions, old('language', $browserLang), [
<?= form_dropdown('language', $languageOptions, [old('language', $browserLang)], [
'id' => 'language',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_label(lang('Podcast.form.category'), 'category') ?>
<?= form_dropdown('category', $categoryOptions, old('category', ''), [
<?= form_dropdown('category', $categoryOptions, [old('category', '')], [
'id' => 'category',
'class' => 'form-select mb-4',
'required' => 'required',

View File

@ -23,7 +23,7 @@
'redis' => lang('Install.form.cacheHandlerOptions.redis'),
'predis' => lang('Install.form.cacheHandlerOptions.predis'),
],
old('cache_handler', 'file'),
[old('cache_handler', 'file')],
[
'id' => 'cache_handler',
'name' => 'cache_handler',

868
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,19 +28,16 @@
"prepare": "is-ci || husky install"
},
"dependencies": {
"@amcharts/amcharts4": "^4.10.20",
"@amcharts/amcharts4-geodata": "^4.1.21",
"@amcharts/amcharts4": "^4.10.20",
"@github/markdown-toolbar-element": "^1.5.1",
"@popperjs/core": "^2.9.2",
"@rollup/plugin-multi-entry": "^4.0.0",
"choices.js": "^9.0.1",
"flatpickr": "^4.6.9",
"leaflet": "^1.7.1",
"leaflet.markercluster": "^1.5.1",
"leaflet": "^1.7.1",
"lit": "^2.0.0-rc.2",
"prosemirror-example-setup": "^1.1.2",
"prosemirror-markdown": "^1.5.1",
"prosemirror-state": "^1.3.4",
"prosemirror-view": "^1.18.11"
"marked": "^2.1.3"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
@ -53,31 +50,33 @@
"@tailwindcss/line-clamp": "^0.2.1",
"@tailwindcss/typography": "^0.4.1",
"@types/leaflet": "^1.7.5",
"@types/marked": "^2.0.4",
"@types/prosemirror-markdown": "^1.5.2",
"@types/prosemirror-view": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"@typescript-eslint/eslint-plugin": "^4.28.5",
"@typescript-eslint/parser": "^4.28.5",
"cpy-cli": "^3.1.1",
"cross-env": "^7.0.3",
"cssnano": "^5.0.7",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^7.31.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint": "^7.31.0",
"husky": "^7.0.1",
"is-ci": "^3.0.0",
"lint-staged": "^11.1.1",
"lit": "^2.0.0-rc.2",
"postcss-import": "^14.0.2",
"postcss-preset-env": "^6.7.0",
"prettier": "2.3.2",
"prettier-plugin-organize-imports": "^2.3.3",
"prettier": "2.3.2",
"semantic-release": "^17.4.4",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"stylelint": "^13.13.1",
"svgo": "^2.3.1",
"tailwindcss": "^2.2.7",
"typescript": "^4.3.5",
"vite": "^2.4.3"
"vite": "^2.4.4"
},
"lint-staged": {
"*.{js,ts,css,md,json}": "prettier --write",