forked from e621ng/e621ng
[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:
parent
f7286ccce3
commit
14b0327ffc
@ -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
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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; }
|
||||
|
147
app/javascript/src/styles/common/dtext_input.scss
Normal file
147
app/javascript/src/styles/common/dtext_input.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@
|
||||
|
||||
textarea {
|
||||
width: 80%;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
label {
|
||||
|
21
app/views/application/_dtext_input.html.erb
Normal file
21
app/views/application/_dtext_input.html.erb
Normal 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>
|
Loading…
Reference in New Issue
Block a user