[DText] Rework how the input gets constructed

I want to switch over to svg icons but the current one doesn't allow
for it because it uses unicode to set the icons.
Make it instead use rails rendered html which can then be switched out
for the svg icon helper
This commit is contained in:
Earlopain 2023-08-12 21:00:30 +02:00
parent f7286ccce3
commit 14b0327ffc
No known key found for this signature in database
GPG Key ID: 48860312319ADF61
7 changed files with 209 additions and 259 deletions

View File

@ -2,18 +2,15 @@ class DtextInput < SimpleForm::Inputs::TextInput
def input(wrapper_options = nil)
input_html_options[:cols] = "80"
input_html_options[:rows] = "10"
input_html_options["data-limit"] ||= @options[:limit]
input_html_options["data-initialized"] = false
if object
input_html_options[:id] ||= "#{object.model_name.param_key}_#{attribute_name}_for_#{object.id}"
end
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
@builder.template.render("dtext_input", textarea: super(merged_input_options), limit: @options[:limit])
end
link = ApplicationController.helpers.link_to "DText", Rails.application.routes.url_helpers.help_page_path(id: "dtext"), target: "_blank", tabindex: "-1"
%(
#{super(merged_input_options)}
<span class="hint">All text is formatted using #{link}</span>
).html_safe
def input_html_classes
super.push("dtext-formatter-input")
end
end

View File

@ -2,137 +2,47 @@ import { SendQueue } from './send_queue';
const DText = {};
DText.buttons = [
{ icon: "f032", title: "Bold", content: "[b]%selection%[/b]" },
{ icon: "f033", title: "Italics", content: "[i]%selection%[/i]" },
{ icon: "f0cc", title: "Strikethrough", content: "[s]%selection%[/s]" },
{ icon: "f0cd", title: "Underline", content: "[u]%selection%[/u]" },
null,
{ icon: "f1dc", title: "Header", content: "h2.%selection%" },
{ icon: "f070", title: "Spoiler", content: "[spoiler]%selection%[/spoiler]" },
{ icon: "f121", title: "Code", content: "[code]%selection%[/code]" },
{ icon: "f10e", title: "Quote", content: "[quote]%selection%[/quote]" },
];
DText.initialze_input = function($element) {
const $preview = $(".dtext-formatter-preview", $element);
const $textarea = $(".dtext-formatter-input", $element);
const $charcount = $(".dtext-formatter-charcount", $element);
/**
* Set up the wrapper for the target input with
* DText preview and formatting buttons.
* @param {JQuery<HTMLElement>} textarea Target input
*/
DText.create_wrapper = function(textarea) {
const wrapper = $("<div>")
.addClass("dtext-formatter")
.attr({ "data-editing": "true", })
.insertBefore(textarea);
build_tabs(wrapper);
build_buttons(wrapper, textarea);
textarea
.addClass("dtext-formatter-input")
.appendTo(wrapper);
build_preview(wrapper, textarea);
build_charcounter(wrapper, textarea);
textarea.attr("data-initialized", "true");
}
/**
* Unwraps the textarea and restores it back
* to its original state.
* @param {JQuery<HTMLElement>} textarea Target input
*/
DText.destroy_wrapper = function(textarea) {
const wrapper = textarea.parents(".dtext-formatter");
if(!wrapper.length) return;
textarea
.insertAfter(wrapper)
.removeClass("dtext-formatter-input")
.off("input.danbooru.formatter");
wrapper.remove();
}
function build_tabs(wrapper) {
$("<div>")
.addClass("dtext-formatter-tabs")
.html(
`<a data-action="edit" role="tab">Write</a>` +
`<a data-action="show" role="tab">Preview</a>`
)
.on("click", "a", (event) => {
event.preventDefault();
wrapper.trigger("e621:toggle");
})
.appendTo(wrapper);
}
function build_buttons(wrapper, textarea) {
const container = $("<div>")
.addClass("dtext-formatter-buttons")
.attr({ "role": "toolbar", })
.appendTo(wrapper);
wrapper.on("e621:reload", () => {
container.html("");
for(const button of DText.buttons) {
// Spacer
if(button == null) {
$("<span>").appendTo(container);
continue;
}
// Normal button
$("<a>")
.html("&#x" + button.icon)
.attr({
"title": button.title,
"role": "button",
})
.on("click", (event) => {
event.preventDefault();
DText.process_formatting(button.content, textarea);
})
.appendTo(container);
}
});
wrapper.trigger("e621:reload");
}
function build_preview(wrapper, textarea) {
const preview = $("<div>")
.addClass("dtext-formatter-preview dtext-container")
.appendTo(wrapper);
wrapper.on("e621:toggle", () => {
if(wrapper.attr("data-editing") == "true") {
preview.css("min-height", textarea.outerHeight());
wrapper.attr("data-editing", "false");
update_preview(textarea, preview);
// Tab switching
$(".dtext-formatter-tabs a", $element).on("click", event => {
event.preventDefault();
if($element.attr("data-editing") == "true") {
$preview.css("min-height", $textarea.outerHeight());
$element.attr("data-editing", "false");
update_preview($textarea, $preview);
} else {
wrapper.attr("data-editing", "true");
preview.attr("loading", "false");
$element.attr("data-editing", "true");
$preview.attr("loading", "false");
}
});
// Character count limit
const limit = $charcount.attr("data-limit") || 0;
$textarea.on("input.danbooru.formatter", () => {
const length = ($textarea.val() + "").length;
$charcount.toggleClass("overfill", length >= limit).attr("data-count", length);
});
DText.initialize_formatting_buttons($element);
$element.attr("data-initialized", "true");
}
function build_charcounter(wrapper, textarea) {
const limit = textarea.attr("data-limit") || 0;
const charcount = $("<div>")
.addClass("dtext-formatter-charcount")
.attr({
"data-limit": limit,
"data-count": (textarea.val() + "").length,
})
.appendTo(wrapper);
DText.initialize_formatting_buttons = function(element) {
const $textarea = $(".dtext-formatter-input", element);
textarea.on("input.danbooru.formatter", () => {
const length = (textarea.val() + "").length;
charcount
.toggleClass("overfill", length >= limit)
.attr("data-count", length);
});
for(const button of $(".dtext-formatter-buttons a", element)) {
const $button = $(button);
const content = $button.attr("data-content");
$button.off("click");
$button.on("click", event => {
event.preventDefault();
DText.process_formatting(content, $textarea);
});
}
}
/** Refreshes the preview field to match the provided input */
@ -213,8 +123,8 @@ DText.process_formatting = function (content, input) {
/** Add formatters to all appropriate inputs */
DText.initialize_all_inputs = function() {
$("textarea.dtext[data-initialized='false']").each((index, element) => {
DText.create_wrapper($(element));
$(".dtext-formatter[data-initialized='false']").each((index, element) => {
DText.initialze_input($(element));
});
}

View File

@ -15,6 +15,7 @@
@import "common/comment_container";
@import "common/diffs.scss";
@import "common/dtext.scss";
@import "common/dtext_input.scss";
@import "common/forms.scss";
@import "common/inline.scss";
@import "common/jquery_ui_custom.scss";

View File

@ -137,128 +137,3 @@ div.post-thumbnail.dtext {
position: relative;
display: inline-block;
}
div.dtext-formatter {
display: grid;
grid-template-columns: min-content auto;
background: #00000035;
border-radius: 4px;
position: relative;
div.dtext-formatter-tabs {
grid-area: 1 / 1 / 1 / 1;
white-space: nowrap;
padding: 0.5rem 0.75rem 0;
a {
display: inline-block;
padding: 0.5rem 0.75rem;
margin-right: 0.5rem;
border-radius: 4px 4px 0 0;
outline: 0;
font-weight: 500;
cursor: pointer;
@include nonselectable;
&:hover { background: #ffffff15; }
}
}
div.dtext-formatter-buttons {
grid-area: 1 / 2 / 1 / 2;
display: flex;
overflow: hidden;
white-space: nowrap;
padding-top: 0.5rem;
a {
box-sizing: border-box;
padding: 0.5rem 0.625rem;
border-radius: 6px 6px 0 0;
margin-right: 0.25rem;
cursor: pointer;
line-height: 1;
@include nonselectable;
@include font-awesome-icon;
&:hover { background: #ffffff15; }
}
span { width: 1em; }
}
textarea.dtext-formatter-input,
div.dtext-formatter-preview {
grid-area: 2 / 1 / 2 / 3;
width: 100% !important;
max-width: unset !important;
height: 100%;
min-height: 8rem;
box-sizing: border-box;
border-top: 1px solid #00000035;
padding: 0.25rem 0.5rem;
font-family: inherit;
font-size: 1em;
}
div.dtext-formatter-preview[loading="true"] {
position: relative;
&::before {
content: "Loading . . .";
font-size: 1.25rem;
opacity: 0.20;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
animation: loading-pulsate 4s linear infinite;
}
@keyframes loading-pulsate {
from { opacity: 0; }
50% { opacity: 0.6; }
to { opacity: 0; }
}
}
div.dtext-formatter-charcount {
grid-area: 3 / 1 / 3 / 3;
font-size: 0.75rem;
line-height: 0.75rem;
height: 0.75rem;
position: absolute;
right: 1.25rem;
bottom: 0.15rem;
pointer-events: none;
&::before { content: attr(data-count) " / " attr(data-limit); }
&[data-limit="0"]::before { content: attr(data-count); }
&[data-count="0"] { visibility: hidden; }
&:not([data-limit="0"]).overfill {
color: orangered !important;
font-weight: bold;
}
}
// Preview toggle states
&[data-editing="true"] {
div.dtext-formatter-tabs {
a[data-action="edit"] {
pointer-events: none;
background: #00000040;
}
}
div.dtext-formatter-preview { display: none !important; }
div.dtext-formatter-charcount { color: black; }
}
&[data-editing="false"] {
div.dtext-formatter-tabs {
a[data-action="show"] {
pointer-events: none;
background: #00000040;
}
}
div.dtext-formatter-buttons { visibility: hidden; }
textarea.dtext-formatter-input { display: none !important; }
}
}
form.simple_form div.dtext-formatter textarea.dtext-formatter-input { font-size: 1em; }

View File

@ -0,0 +1,147 @@
.dtext-formatter {
display: grid;
grid-template-columns: min-content auto;
background: #00000035;
border-radius: 4px;
position: relative;
.dtext-formatter-tabs {
grid-area: 1 / 1 / 1 / 1;
white-space: nowrap;
padding: 0.5rem 0.75rem 0;
a {
display: inline-block;
padding: 0.5rem 0.75rem;
margin-right: 0.5rem;
border-radius: 4px 4px 0 0;
outline: 0;
font-weight: 500;
cursor: pointer;
@include nonselectable;
&:hover {
background: #ffffff15;
}
}
}
.dtext-formatter-buttons {
grid-area: 1 / 2 / 1 / 2;
display: flex;
overflow: hidden;
white-space: nowrap;
padding-top: 0.5rem;
a {
box-sizing: border-box;
padding: 0.5rem 0.625rem;
border-radius: 6px 6px 0 0;
margin-right: 0.25rem;
cursor: pointer;
line-height: 1;
@include nonselectable;
@include font-awesome-icon;
&:hover {
background: #ffffff15;
}
}
.spacer {
width: 1em;
}
}
.dtext-formatter-input, .dtext-formatter-preview {
grid-area: 2 / 1 / 2 / 3;
width: 100% !important;
max-width: unset !important;
height: 100%;
min-height: 8rem;
box-sizing: border-box;
border-top: 1px solid #00000035;
padding: 0.25rem 0.5rem;
font-family: inherit;
font-size: 1em;
}
.dtext-formatter-preview[loading="true"] {
position: relative;
&::before {
content: "Loading . . .";
font-size: 1.25rem;
opacity: 0.20;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
animation: loading-pulsate 4s linear infinite;
}
@keyframes loading-pulsate {
from {
opacity: 0;
}
50% {
opacity: 0.6
}
to {
opacity: 0;
}
}
}
.dtext-formatter-charcount {
grid-area: 3 / 1 / 3 / 3;
font-size: 0.75rem;
line-height: 0.75rem;
height: 0.75rem;
position: absolute;
right: 1.25rem;
bottom: 0.15rem;
pointer-events: none;
&::before {
content: attr(data-count) " / " attr(data-limit);
}
&[data-limit="0"]::before {
content: attr(data-count);
}
&[data-count="0"] {
visibility: hidden;
}
&:not([data-limit="0"]).overfill {
color: orangered !important;
font-weight: bold;
}
}
// Preview toggle states
&[data-editing="true"] {
.dtext-formatter-tabs {
a[data-action="edit"] {
pointer-events: none;
background: #00000040;
}
}
.dtext-formatter-preview {
display: none !important;
}
.dtext-formatter-charcount {
color: black;
}
}
&[data-editing="false"] {
.dtext-formatter-tabs {
a[data-action="show"] {
pointer-events: none;
background: #00000040;
}
}
.dtext-formatter-buttons {
visibility: hidden;
}
.dtext-formatter-input {
display: none !important;
}
}
}

View File

@ -20,7 +20,6 @@
textarea {
width: 80%;
font-size: 1.2em;
}
label {

View File

@ -0,0 +1,21 @@
<div class="dtext-formatter" data-editing="true" data-initialized="false">
<div class="dtext-formatter-tabs">
<a data-action="edit" role="tab">Write</a>
<a data-action="show" role="tab">Preview</a>
</div>
<div class="dtext-formatter-buttons" role="toolbar">
<%= tag.a(tag.i(class: "fa-solid fa-bold"), title: "Bold", data: { content: "[b]%selection%[/b]" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-italic"), title: "Italics", data: { content: "[i]%selection%[/i]" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-strikethrough"), title: "Strikethrough", data: { content: "[s]%selection%[/s]" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-underline"), title: "Underline", data: { content: "[u]%selection%[/u]" }) %>
<span class="spacer"></span>
<%= tag.a(tag.i(class: "fa-solid fa-heading"), title: "Header", data: { content: "h2.%selection%" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-eye-slash"), title: "Spoiler", data: { content: "[spoiler]%selection%[/spoiler]" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-code"), title: "Code", data: { content: "[code]%selection%[/code]" }) %>
<%= tag.a(tag.i(class: "fa-solid fa-quote-right"), title: "Quote", data: { content: "[quote]%selection%[/quote]" }) %>
</div>
<%= textarea %>
<div class="dtext-formatter-preview dtext-container"></div>
<div class="dtext-formatter-charcount" data-limit="50000" data-count="0"></div>
</div>
<span class="hint">All text is formatted using <%= link_to "DText", help_page_path(id: "dtext"), target: "_blank", rel: "noopener", tabindex: "-1" %></span>