feat: update for nodeb 3.2 and up

This commit is contained in:
Barış Soner Uşaklı 2024-10-30 18:13:32 -04:00
parent 35a7576fcd
commit be542b5159
14 changed files with 304 additions and 183 deletions

View file

@ -11,6 +11,7 @@ Controllers.renderAdminPage = function (req, res, next) {
} }


res.render('admin/plugins/composer-quill', { res.render('admin/plugins/composer-quill', {
title: 'Quill Composer',
checks: checks, checks: checks,
}); });
}); });

View file

@ -23,7 +23,7 @@
}, },
"readmeFilename": "README.md", "readmeFilename": "README.md",
"nbbpm": { "nbbpm": {
"compatibility": "^1.17.0 || ^2.0.0" "compatibility": "^3.2.0"
}, },
"dependencies": { "dependencies": {
"async": "^3.2.0", "async": "^3.2.0",

View file

@ -15,11 +15,11 @@
{ "hook": "filter:messaging.getFields", "method": "handleMessageEdit" }, { "hook": "filter:messaging.getFields", "method": "handleMessageEdit" },
{ "hook": "filter:messaging.checkContent", "method": "handleMessageCheck" } { "hook": "filter:messaging.checkContent", "method": "handleMessageCheck" }
], ],
"less": [ "scss": [
"../nodebb-plugin-composer-default/static/less/composer.less", "../nodebb-plugin-composer-default/static/scss/composer.scss",
"./static/less/quill.less", "./static/scss/quill.scss",
"./static/less/post.less", "./static/scss/post.scss",
"./static/less/overrides.less" "./static/scss/overrides.scss"
], ],
"modules": { "modules": {
"quill.js": "./node_modules/quill/dist/quill.js", "quill.js": "./node_modules/quill/dist/quill.js",
@ -36,6 +36,7 @@
"composer/tags.js": "../nodebb-plugin-composer-default/static/lib/composer/tags.js", "composer/tags.js": "../nodebb-plugin-composer-default/static/lib/composer/tags.js",
"composer/uploads.js": "../nodebb-plugin-composer-default/static/lib/composer/uploads.js", "composer/uploads.js": "../nodebb-plugin-composer-default/static/lib/composer/uploads.js",
"composer/autocomplete.js": "../nodebb-plugin-composer-default/static/lib/composer/autocomplete.js", "composer/autocomplete.js": "../nodebb-plugin-composer-default/static/lib/composer/autocomplete.js",
"composer/post-queue.js": "../nodebb-plugin-composer-default/static/lib/composer/post-queue.js",
"../admin/plugins/composer-quill.js": "./static/lib/admin.js" "../admin/plugins/composer-quill.js": "./static/lib/admin.js"
}, },
"scripts": [ "scripts": [

View file

@ -23,21 +23,36 @@ $(document).ready(() => {
$(window).on('action:composer.topic.new', (ev, data) => { $(window).on('action:composer.topic.new', (ev, data) => {
composer.newTopic({ composer.newTopic({
cid: data.cid, cid: data.cid,
title: data.title || '',
body: data.body || '',
tags: data.tags || [],
});
});

$(window).on('action:composer.post.edit', (ev, data) => {
composer.editPost({ pid: data.pid });
});

$(window).on('action:composer.post.new', (ev, data) => {
data.body = data.body || data.text;
data.title = data.title || data.topicName;
composer.newReply({
tid: data.tid,
toPid: data.pid,
title: data.title, title: data.title,
body: data.body, body: data.body,
}); });
}); });


$(window).on('action:composer.post.edit', (ev, data) => {
composer.editPost(data.pid);
});

$(window).on('action:composer.post.new', (ev, data) => {
composer.newReply(data.tid, data.pid, data.topicName, data.text);
});

$(window).on('action:composer.addQuote', (ev, data) => { $(window).on('action:composer.addQuote', (ev, data) => {
composer.newReply(data.tid, data.pid, data.topicName, wrapWithBlockquote(data.text)); data.title = data.title || data.topicName;
data.body = data.body || data.text;
composer.newReply({
tid: data.tid,
toPid: data.pid,
title: data.title,
body: wrapWithBlockquote(data.body),
});
}); });
}); });
}); });

View file

@ -281,7 +281,7 @@ window.quill.init = function (targetEl, data, callback) {
if (className === 'picture') { if (className === 'picture') {
buttonEl.html('<i class="fa fa-file-image-o"></i>'); buttonEl.html('<i class="fa fa-file-image-o"></i>');
} else { } else {
buttonEl.html('<span class="fa-stack"><i class="fa fa-file-o fa-stack-1x"></i><i class="fa fa-arrow-up fa-stack-1x"></i></span>'); buttonEl.html('<i class="fa fa-file-o"></i>');
} }
} }
}); });

View file

@ -0,0 +1,31 @@
.composer {
.formatting-bar {
display: none!important;
}

.write-container {
width: 100%!important;
min-width: 0px; // fixes negation of overflow-wrap due this being a flex-item

textarea {
display: none;
}
}
}

.ql-toolbar {
button > i {
float: left;
height: 100%;
}
}

[component="chat/composer"] {
.ql-editor {
padding: 0;
}

[component="chat/input"] {
display: none;
}
}

19
static/scss/post.scss Normal file
View file

@ -0,0 +1,19 @@
[component="post/content"], [component="chat/messages"] {
.ql-align-center {
text-align: center;
}
.ql-align-right {
text-align: right;
}
.ql-align-justify {
text-align: justify;
}

// Fonts
.ql-font-serif {
font-family: Georgia, "Times New Roman", serif;
}
.ql-font-monospace {
font-family: Monaco, "Courier New", monospace;
}
}

4
static/scss/quill.scss Normal file
View file

@ -0,0 +1,4 @@
// @import './quill/dist/quill.snow';
// @import './quill/dist/quill.bubble';
@import 'nodebb-plugin-composer-quill/node_modules/quill/dist/quill.snow';
@import 'nodebb-plugin-composer-quill/node_modules/quill/dist/quill.bubble';

View file

@ -1,54 +1,60 @@
<div class="row"> <div class="acp-page-container">
<div class="col-lg-8 col-sm-6"> <style>#save {display: none;}</style>
<div class="panel panel-default"> <!-- IMPORT admin/partials/settings/header.tpl -->
<div class="panel-heading">Quill Composer</div>
<div class="panel-body"> <div class="row m-0">
<p> <div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
<a href="https://quilljs.com/">Quill</a> is a free, open source WYSIWYG editor built for the modern web. With its modular architecture and expressive API, it is completely customizable to fit any need. <p>
</p> <a href="https://quilljs.com/">Quill</a> is a free, open source WYSIWYG editor built for the modern web. With its modular architecture and expressive API, it is completely customizable to fit any need.
</div> </p>
</div>
</div> <hr/>
<div class="col-lg-4 col-sm-6">
<div class="panel panel-default"> <div>
<div class="panel-heading">Migration</div> <h5>Migration</h5>
<div class="panel-body">
<p> <p>
If you are switching to Quill from a different composer (i.e. composer-default/markdown), you will need to convert your existing posts to Quill's format. You may use the utilities below to do so. If you are switching to Quill from a different composer (i.e. composer-default/markdown), you will need to convert your existing posts to Quill's format. You may use the utilities below to do so.
</p> </p>


<button class="btn btn-block btn-default" data-action="migrate/in">Migrate to Quill <i class="fa fa-arrow-circle-o-left"></i></button> <button class="btn btn-light" data-action="migrate/in">Migrate to Quill <i class="fa fa-arrow-circle-o-left"></i></button>
<button class="btn btn-block btn-default" data-action="migrate/out">Migrate from Quill <i class="fa fa-arrow-circle-o-right"></i></button> <button class="btn btn-light" data-action="migrate/out">Migrate from Quill <i class="fa fa-arrow-circle-o-right"></i></button>
</div> </div>
</div><div class="panel panel-default">
<div class="panel-heading">Compatibility Checks</div> <hr />
<div class="panel-body">
<div>
<h5>Compatibility Checks</h5>

<ul class="list-group"> <ul class="list-group">
<li class="list-group-item list-group-item-<!-- IF checks.markdown -->success<!-- ELSE -->danger<!-- ENDIF checks.markdown -->"> <li class="list-group-item list-group-item-{{{ if checks.markdown }}}success{{{ else }}}danger{{{ end }}}">
<strong>Markdown Compatibility</strong> <strong>Markdown Compatibility</strong>
<!-- IF checks.markdown --> {{{ if checks.markdown }}}
<span class="badge"><i class="fa fa-check"></i></span> <i class="fa fa-check"></i>
<p>The Markdown plugin is either disabled, or HTML sanitization is disabled</p> <p>The Markdown plugin is either disabled, or HTML sanitization is disabled</p>
<!-- ELSE --> {{{ else }}}
<span class="badge"><i class="fa fa-times"></i></span> <i class="fa fa-times"></i>
<p> <p>
In order to render post content correctly, the Markdown plugin needs to have HTML sanitization disabled, In order to render post content correctly, the Markdown plugin needs to have HTML sanitization disabled,
or the entire plugin should be disabled altogether. or the entire plugin should be disabled altogether.
</p> </p>
<!-- ENDIF checks.markdown --> {{{ end }}}
</li> </li>
<li class="list-group-item list-group-item-<!-- IF checks.composer -->success<!-- ELSE -->danger<!-- ENDIF checks.composer -->"> <li class="list-group-item list-group-item-{{{ if checks.composer }}}success{{{ else }}}danger{{{ end }}}">
<strong>Composer Conflicts</strong> <strong>Composer Conflicts</strong>
<!-- IF checks.composer --> {{{ if checks.composer }}}
<span class="badge"><i class="fa fa-check"></i></span> <i class="fa fa-check"></i>
<p>Great! Looks like Quill is the only composer active</p> <p>Great! Looks like Quill is the only composer active</p>
<!-- ELSE --> {{{ else }}}
<span class="badge"><i class="fa fa-times"></i></span> <i class="fa fa-times"></i>
<p>Quill must be the only composer active. Please disable other composers and reload NodeBB.</p> <p>Quill must be the only composer active. Please disable other composers and reload NodeBB.</p>
<!-- ENDIF checks.composer --> {{{ end }}}
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>

<!-- IMPORT admin/partials/settings/toc.tpl -->
</div> </div>
</div> </div>


View file

@ -1,155 +1,46 @@
<div component="composer" class="composer<!-- IF resizable --> resizable<!-- ENDIF resizable --><!-- IF !isTopicOrMain --> reply<!-- ENDIF !isTopicOrMain -->"> <div component="composer" class="composer {{{ if resizable }}} resizable{{{ end }}}{{{ if !isTopicOrMain }}} reply{{{ end }}}">

<div class="composer-container d-flex flex-column gap-1 h-100">
<div class="composer-container"> <!-- mobile header -->
<nav class="navbar navbar-fixed-top mobile-navbar hidden-md hidden-lg"> <nav class="navbar fixed-top mobile-navbar hidden-md hidden-lg text-bg-primary flex-nowrap gap-1 px-1">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-sm btn-primary composer-discard" data-action="discard" tabindex="-1"><i class="fa fa-times"></i></button> <button class="btn btn-sm btn-primary composer-discard" data-action="discard" tabindex="-1"><i class="fa fa-fw fa-times"></i></button>
<button class="btn btn-sm btn-primary composer-minimize" data-action="minimize" tabindex="-1"><i class="fa fa-minus"></i></button> <button class="btn btn-sm btn-primary composer-minimize" data-action="minimize" tabindex="-1"><i class="fa fa-fw fa-minus"></i></button>
</div> </div>
<!-- IF isTopic --> {{{ if isTopic }}}
<div class="category-name-container"> <div class="category-name-container">
<span class="category-name"></span> <i class="fa fa-sort"></i> <span class="category-name"></span> <i class="fa fa-sort"></i>
</div> </div>
<!-- ENDIF isTopic --> {{{ end }}}
<!-- IF !isTopicOrMain --> {{{ if !isTopicOrMain }}}
<h4 class="title">[[topic:composer.replying_to, "{title}"]]</h4> <h4 class="title text-bg-primary">{{{ if isEditing }}}[[topic:composer.editing-in, "{topicTitle}"]]{{{ else }}}[[topic:composer.replying-to, "{topicTitle}"]]{{{ end }}}</h4>
<!-- ENDIF !isTopicOrMain --> {{{ end }}}
<div class="display-scheduler pull-right{{{ if !canSchedule }}} hidden{{{ end }}}"> <div class="display-scheduler p-2 {{{ if !canSchedule }}} hidden{{{ end }}}">
<i class="fa fa-clock-o"></i> <i class="fa fa-clock-o"></i>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-sm btn-primary composer-submit" data-action="post" tabindex="-1"><i class="fa fa-chevron-right"></i></button> <button class="btn btn-sm btn-primary composer-submit" data-action="post" tabindex="-1"><i class="fa fa-fw fa-chevron-right"></i></button>
</div> </div>
</nav> </nav>
<div class="row title-container"> <div class="p-2 d-flex flex-column gap-1 h-100">
<!-- IF showHandleInput --> <!-- IMPORT partials/composer-title-container.tpl -->
<div data-component="composer/handle">
<input class="handle form-control" type="text" tabindex="1" placeholder="[[topic:composer.handle_placeholder]]" value="{handle}" />
</div>
<!-- ENDIF showHandleInput -->
<div data-component="composer/title">
<!-- IF isTopicOrMain -->
<input class="title form-control" type="text" tabindex="1" placeholder="[[topic:composer.title_placeholder]]" value="{title}"/>
<!-- ELSE -->
<span class="title form-control">[[topic:composer.replying_to, "{title}"]]</span>
<!-- ENDIF isTopicOrMain -->
<div id="quick-search-container" class="quick-search-container hidden">
<div class="quick-search-results-container"></div>
</div>
</div>


{{{ if isTopic }}} <!-- IMPORT partials/composer-formatting.tpl -->
<div class="category-list-container hidden-sm hidden-xs">
<!-- IMPORT partials/category-selector.tpl --> <!-- IMPORT partials/composer-write-preview.tpl -->
</div>
{{{ if isTopicOrMain }}}
<!-- IMPORT partials/composer-tags.tpl -->
{{{ end }}} {{{ end }}}


<div class="pull-right draft-icon hidden-xs hidden-sm"></div> <div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div>


<div class="display-scheduler pull-right hidden-sm hidden-xs{{{ if !canSchedule }}} hidden{{{ end }}}"> <div class="resizer position-absolute w-100 bottom-100 pe-3 border-bottom">
<i class="fa fa-clock-o"></i> <div class="trigger text-center">
</div> <div class="handle d-inline-block px-2 py-1 border bg-body">

<i class="fa fa-fw fa-up-down"></i>
<div class="btn-group pull-right action-bar hidden-sm hidden-xs">
<button class="btn btn-default composer-discard" data-action="discard" tabindex="-1"><i class="fa fa-times"></i> [[topic:composer.discard]]</button>

<button class="btn btn-primary composer-submit" data-action="post" tabindex="6" data-text-variant=" [[topic:composer.schedule]]"><i class="fa fa-check"></i> [[topic:composer.submit]]</button>
</div>
</div>

<div class="category-tag-row">
<div class="btn-toolbar formatting-bar">
<ul class="formatting-group">
<!-- BEGIN formatting -->
<!-- IF formatting.spacer -->
<li class="spacer"></li>
<!-- ELSE -->
<!-- IF !formatting.mobile -->
<li tabindex="-1" data-format="{formatting.name}" title="{formatting.title}"><i class="{formatting.className}"></i></li>
<!-- ENDIF !formatting.mobile -->
<!-- ENDIF formatting.spacer -->
<!-- END formatting -->

<!--[if gte IE 9]><!-->
<!-- IF privileges.upload:post:image -->
<li class="img-upload-btn hide" data-format="picture" tabindex="-1" title="[[modules:composer.upload-picture]]">
<i class="fa fa-file-image-o"></i>
</li>
<!-- ENDIF privileges.upload:post:image -->
<!-- IF privileges.upload:post:file -->
<li class="file-upload-btn hide" data-format="upload" tabindex="-1" title="[[modules:composer.upload-file]]">
<span class="fa-stack">
<i class="fa fa-file-o fa-stack-1x"></i>
<i class="fa fa-arrow-up fa-stack-1x"></i>
</span>
</li>
<!-- ENDIF privileges.upload:post:file -->
<!--<![endif]-->

<!-- IF allowTopicsThumbnail -->
<li tabindex="-1">
<i class="fa fa-th-large topic-thumb-btn topic-thumb-toggle-btn hide" title="[[topic:composer.thumb_title]]"></i>
</li>
<div class="topic-thumb-container center-block hide">
<form id="thumbForm" method="post" class="topic-thumb-form form-inline" enctype="multipart/form-data">
<img class="topic-thumb-preview"></img>
<div class="form-group">
<label for="topic-thumb-url">[[topic:composer.thumb_url_label]]</label>
<input type="text" id="topic-thumb-url" class="form-control" placeholder="[[topic:composer.thumb_url_placeholder]]" />
</div>
<div class="form-group">
<label for="topic-thumb-file">[[topic:composer.thumb_file_label]]</label>
<input type="file" id="topic-thumb-file" class="form-control" />
</div>
<div class="form-group topic-thumb-ctrl">
<i class="fa fa-spinner fa-spin hide topic-thumb-spinner" title="[[topic:composer.uploading]]"></i>
<i class="fa fa-times topic-thumb-btn hide topic-thumb-clear-btn" title="[[topic:composer.thumb_remove]]"></i>
</div>
</form>
</div> </div>
<!-- ENDIF allowTopicsThumbnail -->

<form id="fileForm" method="post" enctype="multipart/form-data">
<!--[if gte IE 9]><!-->
<input type="file" id="files" name="files[]" multiple class="gte-ie9 hide"/>
<!--<![endif]-->
<!--[if lt IE 9]>
<input type="file" id="files" name="files[]" class="lt-ie9 hide" value="Upload"/>
<![endif]-->
</form>
</ul>
</div>
</div>

<div class="row write-preview-container">
<div class="write-container">
<div></div>
<textarea></textarea>
</div>
</div>

<!-- IF isTopicOrMain -->
<div class="tag-row">
<div class="tags-container">
<div class="btn-group dropup <!-- IF !tagWhitelist.length -->hidden<!-- ENDIF !tagWhitelist.length -->" component="composer/tag/dropdown">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">
<span class="visible-sm-inline visible-md-inline visible-lg-inline"><i class="fa fa-tags"></i></span>
[[tags:select_tags]]
</button>

<ul class="dropdown-menu">
<!-- BEGIN tagWhitelist -->
<li data-tag="{@value}"><a href="#">{@value}</a></li>
<!-- END tagWhitelist -->
</ul>
</div> </div>
<input class="tags" type="text" class="form-control" placeholder="[[tags:enter_tags_here, {minimumTagLength}, {maximumTagLength}]]" tabindex="5"/>
</div> </div>
</div> </div>
<!-- ENDIF isTopicOrMain -->

<div class="imagedrop"><div>[[topic:composer.drag_and_drop_images]]</div></div>

<div class="resizer"><div class="trigger text-center"><i class="fa"></i></div></div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,75 @@
<div class="d-flex justify-content-between gap-2 align-items-center formatting-bar m-0">
<ul class="list-unstyled mb-0 d-flex formatting-group gap-2 overflow-auto">
{{{ each formatting }}}
{{{ if ./spacer }}}
<li class="small spacer"></li>
{{{ else }}}
{{{ if (./visibility.desktop && ((isTopicOrMain && ./visibility.main) || (!isTopicOrMain && ./visibility.reply))) }}}
{{{ if ./dropdownItems.length }}}
<li class="dropdown bottom-sheet" title="{./title}">
<button class="btn btn-sm btn-link text-reset" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{./title}">
<i class="{./className}"></i>
</button>
<ul class="dropdown-menu p-1" role="menu">
{{{ each ./dropdownItems }}}
<li>
<a href="#" data-format="{./name}" class="dropdown-item rounded-1 position-relative" role="menuitem">
<i class="{./className} text-secondary"></i> {./text}
{{{ if ./badge }}}
<span class="px-1 position-absolute top-0 start-100 translate-middle badge rounded text-bg-info"></span>
{{{ end }}}
</a>
</li>
{{{ end }}}
</ul>
</li>
{{{ else }}}
<li title="{./title}">
<button data-format="{./name}" class="btn btn-sm btn-link text-reset position-relative" aria-label="{./title}">
<i class="{./className}"></i>
{{{ if ./badge }}}
<span class="px-1 position-absolute top-0 start-100 translate-middle badge rounded text-bg-info"></span>
{{{ end }}}
</button>
</li>
{{{ end }}}
{{{ end }}}
{{{ end }}}
{{{ end }}}

{{{ if privileges.upload:post:image }}}
<li title="[[modules:composer.upload-picture]]">
<button data-format="picture" class="img-upload-btn btn btn-sm btn-link text-reset" aria-label="[[modules:composer.upload-picture]]">
<i class="fa fa-file-image-o"></i>
</button>
</li>
{{{ end }}}

{{{ if privileges.upload:post:file }}}
<li title="[[modules:composer.upload-file]]">
<button data-format="upload" class="file-upload-btn btn btn-sm btn-link text-reset" aria-label="[[modules:composer.upload-file]]">
<i class="fa fa-file-o"></i>
</button>
</li>
{{{ end }}}

<form id="fileForm" method="post" enctype="multipart/form-data">
<input type="file" id="files" name="files[]" multiple class="hide"/>
</form>
</ul>
<div class="d-flex align-items-center gap-1">
<div class="draft-icon m-2 hidden-xs hidden-sm"></div>
<button class="btn btn-sm btn-link py-2 text-body fw-semibold text-nowrap" data-action="preview">
<i class="fa fa-eye"></i>
<span class="d-none d-md-inline show-text">[[modules:composer.show-preview]]</span>
<span class="d-none d-md-inline hide-text">[[modules:composer.hide-preview]]</span>
</button>
{{{ if composer:showHelpTab }}}
<button class="btn btn-sm btn-link py-2 text-body fw-semibold text-nowrap" data-action="help">
<i class="fa fa-question"></i>
<span class="d-none d-md-inline">[[modules:composer.help]]</span>
</button>
{{{ end }}}
</div>
</div>

View file

@ -0,0 +1,17 @@
<div class="tag-row">
<div class="tags-container d-flex align-items-center {{{ if tagWhitelist.length }}}haswhitelist{{{ end }}}">
<div class="btn-group dropup me-2 {{{ if !tagWhitelist.length }}}hidden{{{ end }}}" component="composer/tag/dropdown">
<button class="btn btn-sm btn-link text-body dropdown-toggle" data-bs-toggle="dropdown" type="button" aria-haspopup="true" aria-expanded="false">
<span class="visible-sm-inline visible-md-inline visible-lg-inline"><i class="fa fa-tags"></i></span>
[[tags:select-tags]]
</button>

<ul class="dropdown-menu" role="menu">
<!-- BEGIN tagWhitelist -->
<li data-tag="{@value}"><a class="dropdown-item" href="#" role="menuitem">{@value}</a></li>
<!-- END tagWhitelist -->
</ul>
</div>
<input class="tags" type="text" class="" placeholder="[[tags:enter-tags-here, {minimumTagLength}, {maximumTagLength}]]" />
</div>
</div>

View file

@ -0,0 +1,46 @@
<div class="title-container align-items-center gap-2 d-flex">
{{{ if isTopic }}}
<div class="category-list-container {{{ if !template.compose }}}d-none d-md-block{{{ end }}} align-self-center">
<!-- IMPORT partials/category/selector-dropdown-left.tpl -->
</div>
{{{ end }}}

{{{ if showHandleInput }}}
<div data-component="composer/handle">
<input class="handle form-control h-100 border-0 shadow-none" type="text" placeholder="[[topic:composer.handle-placeholder]]" value="{handle}" />
</div>
{{{ end }}}

<div data-component="composer/title" class="position-relative flex-1" style="min-width: 0;">
{{{ if isTopicOrMain }}}
<input class="title form-control h-100 rounded-1 shadow-none" type="text" placeholder="[[topic:composer.title-placeholder]]" value="{topicTitle}" />
{{{ else }}}
<span class="{{{ if !template.compose }}}d-none d-md-block{{{ else }}}d-block{{{ end }}} title h-100 text-truncate">{{{ if isEditing }}}[[topic:composer.editing-in, "{topicTitle}"]]{{{ else }}}[[topic:composer.replying-to, "{topicTitle}"]]{{{ end }}}</span>
{{{ end }}}
<div id="quick-search-container" class="quick-search-container mt-2 dropdown-menu d-block p-2 hidden">
<div class="text-center loading-indicator"><i class="fa fa-spinner fa-spin"></i></div>
<div class="quick-search-results-container"></div>
</div>
</div>

<div class="{{{ if !template.compose }}}d-none d-md-flex{{{ else }}}d-flex{{{ end }}} action-bar gap-1 align-items-center">
<button class="btn btn-sm btn-link text-body fw-semibold composer-minimize" data-action="hide"><i class="fa fa-angle-down"></i> <span class="d-none d-md-inline">[[topic:composer.hide]]</span></button>
<button class="btn btn-sm btn-link composer-discard text-body fw-semibold" data-action="discard"><i class="fa fa-trash"></i> <span class="d-none d-md-inline">[[topic:composer.discard]]</button>
<div class="btn-group btn-group-sm" component="composer/submit/container">
<button class="btn btn-primary composer-submit fw-bold {{{ if !(submitOptions.length || canSchedule) }}}rounded-1{{{ end }}}" data-action="post" data-text-variant=" [[topic:composer.schedule]]"><i class="fa fa-check"></i> <span class="d-none d-md-inline">[[topic:composer.submit]]</span></button>
<div component="composer/submit/options/container" data-submit-options="{submitOptions.length}" class="btn-group btn-group-sm {{{ if !(submitOptions.length || canSchedule) }}}hidden{{{ end }}}">
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-caret-down"></i>
<span class="sr-only">[[topic:composer.additional-options]]</span>
</button>
<ul class="dropdown-menu dropdown-menu-end p-1" role="menu">
<li><a class="dropdown-item rounded-1 display-scheduler {{{ if !canSchedule }}}hidden{{{ end }}}" role="menuitem">[[topic:composer.post-later]]</a></li>
<li><a class="dropdown-item rounded-1 cancel-scheduling hidden" role="menuitem">[[modules:composer.cancel-scheduling]]</a></li>
{{{ each submitOptions }}}
<li><a class="dropdown-item rounded-1" href="#" data-action="{./action}" role="menuitem">{./text}</a></li>
{{{ end }}}
</ul>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,15 @@
<div class="write-preview-container d-flex gap-2 flex-grow-1 overflow-auto">
<div class="write-container d-flex flex-column w-100 position-relative">
<div></div>
<!--
<div component="composer/post-queue/alert" class="m-2 alert alert-info fade pe-none position-absolute top-0 start-0 alert-dismissible">[[modules:composer.post-queue-alert]]<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>
<div class="draft-icon position-absolute end-0 top-0 mx-2 my-1 hidden-md hidden-lg"></div>
-->
<textarea class="write shadow-none rounded-1 w-100 form-control" placeholder="[[modules:composer.textarea.placeholder]]">{body}</textarea>
</div>
<!-- no need for preview container in quill -->
<div class="preview-container d-none d-md-flex w-50" style="display:none!important;">
<div class="preview w-100 overflow-auto"></div>
</div>

</div>