REFACTOR: various code/UI/UX changes and refactorings
- ability to clear placeholders - builder UI - link to placeholder - improve styles
This commit is contained in:
parent
6f46840fb1
commit
3726aa75e3
|
@ -1,9 +1,53 @@
|
|||
.d-wrap[data-wrap=placeholder] {
|
||||
.placeholder-ui {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 1em;
|
||||
background-color: blend-primary-secondary(5%);
|
||||
border: 1px solid $primary-low;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
.clear-placeholder {
|
||||
svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholders-container {
|
||||
max-width: 90%;
|
||||
margin: -1em 1em 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
a {
|
||||
font-size: $font-down-1;
|
||||
padding: 0.25em;
|
||||
background: $primary-low;
|
||||
border-radius: 3px;
|
||||
color: $primary-medium;
|
||||
margin-top: 1em;
|
||||
|
||||
&:hover {
|
||||
background: $primary-low-mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.d-wrap[data-wrap="placeholder"] {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.d-wrap[data-wrap="placeholder"] {
|
||||
padding: 0.5em;
|
||||
border: 1px solid $primary-medium;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-left: 5px solid $primary-low;
|
||||
background-color: blend-primary-secondary(5%);
|
||||
|
||||
.discourse-placeholder-name {
|
||||
width: 200px;
|
||||
|
@ -12,15 +56,29 @@
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-right: 0.5em;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
p,
|
||||
.discourse-placeholder-name {
|
||||
font-size: $font-down-1;
|
||||
color: dark-light-choose($primary-high, $secondary-low);
|
||||
}
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.discourse-placeholder-value,
|
||||
.discourse-placeholder-select {
|
||||
box-sizing: border-box;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.discourse-placeholder-value {
|
||||
width: 100%;
|
||||
width: 350px;
|
||||
padding: 0.5em;
|
||||
line-height: $line-height-small;
|
||||
color: $primary;
|
||||
|
@ -29,7 +87,7 @@
|
|||
}
|
||||
|
||||
.discourse-placeholder-select {
|
||||
width: 100%;
|
||||
width: 350px;
|
||||
margin: 0.5em 0;
|
||||
line-height: $line-height-small;
|
||||
color: $primary;
|
||||
|
@ -37,3 +95,22 @@
|
|||
border: 1px solid $primary-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.discourse-placeholder-builder-modal {
|
||||
.input {
|
||||
input {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: $font-down-1;
|
||||
color: $primary-medium;
|
||||
margin: 0.25em 0 1em 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,233 +0,0 @@
|
|||
<script type="text/discourse-plugin" version="0.8.30">
|
||||
api.decorateCooked(($cooked, postWidget) => {
|
||||
const VALID_TAGS = "h1, h2, h3, h4, h5, h6, p, code, blockquote, .md-table, li";
|
||||
const DELIMITER = "=";
|
||||
const mappings = [];
|
||||
const placeholders = {};
|
||||
|
||||
// http://davidwalsh.name/javascript-debounce-function
|
||||
function debounce(func, wait, immediate) {
|
||||
let timeout;
|
||||
|
||||
return function() {
|
||||
const context = this,
|
||||
args = arguments;
|
||||
|
||||
const later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
|
||||
const callNow = immediate && !timeout;
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
function processChange($cooked, inputEvent, mappings) {
|
||||
const value = inputEvent.target.value;
|
||||
const key = inputEvent.target.dataset.key;
|
||||
const delimiter = inputEvent.target.dataset.delimiter;
|
||||
|
||||
if (postWidget) {
|
||||
const placeholderIdentifier = `d-placeholder-${postWidget.widget.attrs.topicId}-${postWidget.widget.attrs.id}-${key}`;
|
||||
if (value) {
|
||||
$.cookie(placeholderIdentifier, value);
|
||||
} else {
|
||||
$.removeCookie(placeholderIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
let newValue;
|
||||
if (value && value.length && value !== "none") {
|
||||
newValue = value;
|
||||
} else {
|
||||
newValue = `${delimiter}${key}${delimiter}`;
|
||||
}
|
||||
|
||||
$cooked.find(VALID_TAGS).each((index, elem) => {
|
||||
let replaced = false;
|
||||
const mapping = mappings[index];
|
||||
let newInnnerHTML = elem.innerHTML;
|
||||
let diff = 0;
|
||||
|
||||
if (!mapping) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapping.forEach(m => {
|
||||
if (m.pattern !== `${delimiter}${key}${delimiter}`) {
|
||||
m.position = m.position + diff;
|
||||
return;
|
||||
}
|
||||
|
||||
replaced = true;
|
||||
|
||||
const previousLength = m.length;
|
||||
const prefix = newInnnerHTML.slice(0, m.position + diff);
|
||||
const suffix = newInnnerHTML.slice(
|
||||
m.position + diff + m.length,
|
||||
newInnnerHTML.length
|
||||
);
|
||||
newInnnerHTML = `${prefix}${newValue}${suffix}`;
|
||||
|
||||
m.length = newValue.length;
|
||||
m.position = m.position + diff;
|
||||
diff = diff + newValue.length - previousLength;
|
||||
});
|
||||
|
||||
if (replaced) {
|
||||
elem.innerHTML = newInnnerHTML;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addSelectOption(select, options = {}) {
|
||||
const option = document.createElement("option");
|
||||
option.classList.add("discourse-placeholder-option");
|
||||
option.value = options.value;
|
||||
option.text = options.description || options.value;
|
||||
|
||||
if (options.selected) {
|
||||
option.setAttribute("selected", true);
|
||||
}
|
||||
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
function processPlaceholders(placeholders, $cooked, mappings) {
|
||||
mappings.length = 0;
|
||||
|
||||
const keys = Object.keys(placeholders);
|
||||
const pattern = keys.map(key => {
|
||||
const placeholder = placeholders[key];
|
||||
return `(${placeholder.delimiter}${key}${placeholder.delimiter})`;
|
||||
}).join("|");
|
||||
const regex = new RegExp(pattern, "g");
|
||||
|
||||
$cooked.find(VALID_TAGS).each((index, elem) => {
|
||||
const innerHTML = elem.innerHTML;
|
||||
let match;
|
||||
|
||||
mappings[index] = mappings[index] || [];
|
||||
|
||||
while ((match = regex.exec(innerHTML)) != null) {
|
||||
mappings[index].push({
|
||||
pattern: match[0],
|
||||
position: match.index,
|
||||
length: match[0].length
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$cooked.find(".d-wrap[data-wrap=placeholder]:not(.placeholdered)").each((index, elem) => {
|
||||
const dataKey = elem.dataset.key;
|
||||
if (!dataKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let valueFromCookie;
|
||||
if (postWidget) {
|
||||
const placeholderIdentifier = `d-placeholder-${postWidget.widget.attrs.topicId}-${postWidget.widget.attrs.id}`;
|
||||
valueFromCookie = $.cookie(`${placeholderIdentifier}-${dataKey}`);
|
||||
}
|
||||
|
||||
const defaultValue = valueFromCookie || elem.dataset.default;
|
||||
const defaultValues = (elem.dataset.defaults || "").split(",").filter(x => x);
|
||||
const description = elem.dataset.description;
|
||||
const delimiter = elem.dataset.delimiter || DELIMITER;
|
||||
|
||||
placeholders[dataKey] = {
|
||||
default: defaultValue,
|
||||
defaults: defaultValues,
|
||||
delimiter,
|
||||
description
|
||||
}
|
||||
|
||||
elem.classList.add("placeholdered")
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("discourse-placeholder-name")
|
||||
span.innerText = dataKey;
|
||||
elem.appendChild(span)
|
||||
|
||||
if (defaultValues && defaultValues.length) {
|
||||
const select = document.createElement("select");
|
||||
select.classList.add("discourse-placeholder-select")
|
||||
select.dataset.key = dataKey;
|
||||
select.dataset.delimiter = delimiter;
|
||||
|
||||
if (description) {
|
||||
addSelectOption(select, { value: "none", description });
|
||||
}
|
||||
|
||||
defaultValues.forEach(value =>
|
||||
addSelectOption(select, {
|
||||
value,
|
||||
selected: defaultValue === value
|
||||
})
|
||||
);
|
||||
|
||||
elem.appendChild(select);
|
||||
} else {
|
||||
const input = document.createElement("input");
|
||||
input.classList.add("discourse-placeholder-value")
|
||||
input.dataset.key = dataKey;
|
||||
input.dataset.delimiter = delimiter;
|
||||
|
||||
if (description) {
|
||||
input.setAttribute("placeholder", description);
|
||||
}
|
||||
|
||||
if (valueFromCookie || defaultValue) {
|
||||
input.value = valueFromCookie || defaultValue;
|
||||
}
|
||||
|
||||
elem.appendChild(input)
|
||||
}
|
||||
})
|
||||
|
||||
$cooked
|
||||
.on(
|
||||
"input",
|
||||
".discourse-placeholder-value",
|
||||
debounce(inputEvent => {
|
||||
processChange($cooked, inputEvent, mappings);
|
||||
}, 250)
|
||||
)
|
||||
.on(
|
||||
"change",
|
||||
".discourse-placeholder-select",
|
||||
debounce(inputEvent => {
|
||||
processChange($cooked, inputEvent, mappings);
|
||||
}, 250)
|
||||
);
|
||||
|
||||
Ember.run.later(() => {
|
||||
if (Object.keys(placeholders).length > 0) {
|
||||
processPlaceholders(placeholders, $cooked, mappings);
|
||||
}
|
||||
|
||||
// trigger fake event to setup initial state
|
||||
Object.keys(placeholders).forEach(placeholderKey => {
|
||||
const placeholder = placeholders[placeholderKey];
|
||||
|
||||
const value =
|
||||
placeholder.default ||
|
||||
(placeholder.defaults.length && !placeholder.description
|
||||
? placeholder.defaults[0]
|
||||
: null);
|
||||
|
||||
processChange(
|
||||
$cooked,
|
||||
{ target: { value, dataset: { key: placeholderKey, delimiter: placeholder.delimiter } } },
|
||||
mappings
|
||||
);
|
||||
});
|
||||
}, 500);
|
||||
}, { id: "discourse-placeholder-theme-component" });
|
||||
</script>
|
|
@ -0,0 +1,47 @@
|
|||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { isBlank } from "@ember/utils";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
form: null,
|
||||
|
||||
onShow() {
|
||||
this.set(
|
||||
"form",
|
||||
EmberObject.create({
|
||||
key: null,
|
||||
description: null,
|
||||
values: []
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
onClose() {},
|
||||
|
||||
@action
|
||||
insertPlaceholder() {
|
||||
if (isBlank(this.form.key)) {
|
||||
bootbox.alert(I18n.t(themePrefix("builder.errors.no_key")));
|
||||
return;
|
||||
}
|
||||
|
||||
let output = `[wrap=placeholder key="${this.form.key}"`;
|
||||
|
||||
if (this.form.description) {
|
||||
output = `${output} description="${this.form.description}"`;
|
||||
}
|
||||
|
||||
if (this.form.values.length) {
|
||||
if (this.form.values.length === 1) {
|
||||
output = `${output} default="${this.form.values.firstObject}"`;
|
||||
} else {
|
||||
output = `${output} defaults="${this.form.values.join(",")}"`;
|
||||
}
|
||||
}
|
||||
|
||||
this.model.toolbarEvent.addText(`${output}][/wrap]`);
|
||||
|
||||
this.send("closeModal");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,318 @@
|
|||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { later, debounce } from "@ember/runloop";
|
||||
|
||||
const VALID_TAGS = "h1, h2, h3, h4, h5, h6, p, code, blockquote, .md-table, li";
|
||||
const DELIMITER = "=";
|
||||
|
||||
function buildPlaceholderUI(element, clearButton, placeholderNodes) {
|
||||
const ui = document.createElement("div");
|
||||
ui.classList.add("placeholder-ui");
|
||||
|
||||
const placeholdersContainer = document.createElement("div");
|
||||
placeholdersContainer.classList.add("placeholders-container");
|
||||
|
||||
placeholderNodes.forEach(placeholderNode => {
|
||||
const link = document.createElement("a");
|
||||
link.href = `#placeholder-key-${placeholderNode.dataset.key}`;
|
||||
link.innerText = placeholderNode.dataset.key;
|
||||
placeholdersContainer.append(link);
|
||||
});
|
||||
|
||||
ui.appendChild(placeholdersContainer);
|
||||
ui.appendChild(clearButton);
|
||||
|
||||
return ui;
|
||||
}
|
||||
|
||||
function buildInput(key, placeholder) {
|
||||
const input = document.createElement("input");
|
||||
input.classList.add("discourse-placeholder-value");
|
||||
input.dataset.key = key;
|
||||
input.dataset.delimiter = placeholder.delimiter;
|
||||
|
||||
if (placeholder.description) {
|
||||
input.setAttribute("placeholder", placeholder.description);
|
||||
}
|
||||
|
||||
if (placeholder.default) {
|
||||
input.value = placeholder.default;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
function addSelectOption(select, options = {}) {
|
||||
const option = document.createElement("option");
|
||||
option.classList.add("discourse-placeholder-option");
|
||||
option.value = options.value;
|
||||
option.text = options.description || options.value;
|
||||
|
||||
if (options.selected) {
|
||||
option.setAttribute("selected", true);
|
||||
}
|
||||
|
||||
select.appendChild(option);
|
||||
}
|
||||
|
||||
function buildSelect(key, placeholder) {
|
||||
const select = document.createElement("select");
|
||||
select.classList.add("discourse-placeholder-select");
|
||||
select.dataset.key = key;
|
||||
select.dataset.delimiter = placeholder.delimiter;
|
||||
|
||||
if (placeholder.description) {
|
||||
addSelectOption(select, {
|
||||
value: "none",
|
||||
description: placeholder.description
|
||||
});
|
||||
}
|
||||
|
||||
placeholder.defaults.forEach(value =>
|
||||
addSelectOption(select, {
|
||||
value,
|
||||
selected: placeholder.default === value
|
||||
})
|
||||
);
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
function buildClearButton() {
|
||||
const clearButton = document.createElement("button");
|
||||
clearButton.innerHTML = iconHTML("trash-alt");
|
||||
clearButton.classList.add(
|
||||
"clear-placeholder",
|
||||
"btn",
|
||||
"no-text",
|
||||
"btn-default",
|
||||
"btn-primary"
|
||||
);
|
||||
clearButton.disabled = true;
|
||||
return clearButton;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "discourse-placeholder-theme-component",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("0.8.7", api => {
|
||||
api.decorateCooked(
|
||||
($cooked, postWidget) => {
|
||||
if (!postWidget) return;
|
||||
|
||||
const postIdentifier = `d-placeholder-${postWidget.widget.attrs.topicId}-${postWidget.widget.attrs.id}-`;
|
||||
const clearButton = buildClearButton();
|
||||
clearButton.addEventListener("click", _clearPlaceholders);
|
||||
const mappings = [];
|
||||
const placeholders = {};
|
||||
|
||||
function processChange(inputEvent) {
|
||||
const value = inputEvent.target.value;
|
||||
const key = inputEvent.target.dataset.key;
|
||||
const delimiter = inputEvent.target.dataset.delimiter;
|
||||
const placeholderIdentifier = `${postIdentifier}${key}`;
|
||||
|
||||
if (value) {
|
||||
$.cookie(placeholderIdentifier, value);
|
||||
} else {
|
||||
$.removeCookie(placeholderIdentifier);
|
||||
}
|
||||
|
||||
let newValue;
|
||||
if (value && value.length && value !== "none") {
|
||||
newValue = value;
|
||||
clearButton.disabled = false;
|
||||
} else {
|
||||
newValue = `${delimiter}${key}${delimiter}`;
|
||||
}
|
||||
|
||||
$cooked.find(VALID_TAGS).each((index, elem) => {
|
||||
const mapping = mappings[index];
|
||||
|
||||
if (!mapping) return;
|
||||
|
||||
let diff = 0;
|
||||
let replaced = false;
|
||||
let newInnnerHTML = elem.innerHTML;
|
||||
|
||||
mapping.forEach(m => {
|
||||
if (m.pattern !== `${delimiter}${key}${delimiter}`) {
|
||||
m.position = m.position + diff;
|
||||
return;
|
||||
}
|
||||
|
||||
replaced = true;
|
||||
|
||||
const previousLength = m.length;
|
||||
const prefix = newInnnerHTML.slice(0, m.position + diff);
|
||||
const suffix = newInnnerHTML.slice(
|
||||
m.position + diff + m.length,
|
||||
newInnnerHTML.length
|
||||
);
|
||||
newInnnerHTML = `${prefix}${newValue}${suffix}`;
|
||||
|
||||
m.length = newValue.length;
|
||||
m.position = m.position + diff;
|
||||
diff = diff + newValue.length - previousLength;
|
||||
});
|
||||
|
||||
if (replaced) elem.innerHTML = newInnnerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
function processPlaceholders() {
|
||||
mappings.length = 0;
|
||||
|
||||
const keys = Object.keys(placeholders);
|
||||
const pattern = keys
|
||||
.map(key => {
|
||||
const placeholder = placeholders[key];
|
||||
return `(${placeholder.delimiter}${key}${placeholder.delimiter})`;
|
||||
})
|
||||
.join("|");
|
||||
const regex = new RegExp(pattern, "g");
|
||||
|
||||
$cooked.find(VALID_TAGS).each((index, elem) => {
|
||||
let match;
|
||||
|
||||
mappings[index] = mappings[index] || [];
|
||||
|
||||
while ((match = regex.exec(elem.innerHTML)) != null) {
|
||||
mappings[index].push({
|
||||
pattern: match[0],
|
||||
position: match.index,
|
||||
length: match[0].length
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _fillPlaceholders() {
|
||||
if (Object.keys(placeholders).length > 0) {
|
||||
processPlaceholders(placeholders, $cooked, mappings);
|
||||
|
||||
// trigger fake event to setup initial state
|
||||
Object.keys(placeholders).forEach(placeholderKey => {
|
||||
const placeholder = placeholders[placeholderKey];
|
||||
const placeholderIdentifier = `${postIdentifier}${placeholderKey}`;
|
||||
const value = $.cookie(placeholderIdentifier);
|
||||
|
||||
if (value) {
|
||||
clearButton.disabled = false;
|
||||
}
|
||||
|
||||
processChange({
|
||||
target: {
|
||||
value,
|
||||
dataset: {
|
||||
key: placeholderKey,
|
||||
delimiter: placeholder.delimiter
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _clearPlaceholders(event) {
|
||||
$cooked[0]
|
||||
.querySelectorAll(
|
||||
".discourse-placeholder-value, .discourse-placeholder-select"
|
||||
)
|
||||
.forEach(node => {
|
||||
$.removeCookie(`${postIdentifier}${node.dataset.key}`);
|
||||
node.value =
|
||||
node.parentNode.dataset.default ||
|
||||
(node.tagName === "SELECT" ? "none" : "");
|
||||
});
|
||||
|
||||
event.target.disabled = true;
|
||||
}
|
||||
|
||||
const placeholderNodes = $cooked[0].querySelectorAll(
|
||||
".d-wrap[data-wrap=placeholder]:not(.placeholdered)"
|
||||
);
|
||||
|
||||
if (placeholderNodes.length) {
|
||||
$cooked[0].prepend(
|
||||
buildPlaceholderUI($cooked[0], clearButton, placeholderNodes)
|
||||
);
|
||||
}
|
||||
|
||||
placeholderNodes.forEach(elem => {
|
||||
const dataKey = elem.dataset.key;
|
||||
|
||||
if (!dataKey) return;
|
||||
|
||||
elem.id = `placeholder-key-${dataKey}`;
|
||||
|
||||
const placeholderIdentifier = `${postIdentifier}${dataKey}`;
|
||||
const valueFromCookie = $.cookie(placeholderIdentifier);
|
||||
const defaultValues = (elem.dataset.defaults || "")
|
||||
.split(",")
|
||||
.filter(Boolean);
|
||||
|
||||
placeholders[dataKey] = {
|
||||
default: valueFromCookie || elem.dataset.default,
|
||||
defaults: defaultValues,
|
||||
delimiter: elem.dataset.delimiter || DELIMITER,
|
||||
description: elem.dataset.description
|
||||
};
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("discourse-placeholder-name", "placeholdered");
|
||||
span.innerText = dataKey;
|
||||
|
||||
// content has been set inside the [wrap][/wrap] block
|
||||
if (elem.querySelector("p")) {
|
||||
elem.querySelector("p").prepend(span);
|
||||
} else {
|
||||
elem.prepend(span);
|
||||
}
|
||||
|
||||
if (defaultValues && defaultValues.length) {
|
||||
const select = buildSelect(dataKey, placeholders[dataKey]);
|
||||
elem.appendChild(select);
|
||||
} else {
|
||||
const input = buildInput(dataKey, placeholders[dataKey]);
|
||||
elem.appendChild(input);
|
||||
}
|
||||
});
|
||||
|
||||
$cooked
|
||||
.on("input", ".discourse-placeholder-value", inputEvent =>
|
||||
debounce(this, processChange, inputEvent, 250)
|
||||
)
|
||||
.on("change", ".discourse-placeholder-select", inputEvent =>
|
||||
debounce(this, processChange, inputEvent, 250)
|
||||
);
|
||||
|
||||
later(_fillPlaceholders, 500);
|
||||
},
|
||||
{ onlyStream: true, id: "discourse-placeholder-theme-component" }
|
||||
);
|
||||
|
||||
api.addToolbarPopupMenuOptionsCallback(() => {
|
||||
return {
|
||||
action: "insertPlaceholder",
|
||||
icon: "file",
|
||||
label: themePrefix("toolbar.builder")
|
||||
};
|
||||
});
|
||||
|
||||
api.modifyClass("controller:composer", {
|
||||
actions: {
|
||||
insertPlaceholder() {
|
||||
showModal("discourse-placeholder-builder", {
|
||||
model: {
|
||||
toolbarEvent: this.toolbarEvent
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
{{#d-modal-body
|
||||
title=(theme-prefix "builder.title")
|
||||
class="discourse-placeholder-builder"
|
||||
style="overflow: auto"}}
|
||||
<form>
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
{{theme-i18n "builder.key.label"}}
|
||||
</span>
|
||||
<div class="input">
|
||||
{{input
|
||||
value=(readonly form.key)
|
||||
input=(action (mut form.key) value="target.value")
|
||||
}}
|
||||
</div>
|
||||
<p class="description">{{theme-i18n "builder.key.description"}}</p>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
{{theme-i18n "builder.description.label"}}
|
||||
</span>
|
||||
<div class="input">
|
||||
{{input
|
||||
value=(readonly form.description)
|
||||
input=(action (mut form.description) value="target.value")
|
||||
}}
|
||||
</div>
|
||||
<p class="description">{{theme-i18n "builder.description.description"}}</p>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<span class="label">
|
||||
{{theme-i18n "builder.values.label"}}
|
||||
</span>
|
||||
<div class="input">
|
||||
{{multi-select
|
||||
valueProperty=null
|
||||
nameProperty=null
|
||||
value=form.values
|
||||
content=form.values
|
||||
options=(hash
|
||||
allowAny=true
|
||||
placementStrategy="absolute"
|
||||
)
|
||||
onChange=(action (mut form.values))
|
||||
}}
|
||||
</div>
|
||||
<p class="description">{{theme-i18n "builder.values.description"}}</p>
|
||||
</div>
|
||||
</form>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer discourse-local-dates-create-modal-footer">
|
||||
{{d-button
|
||||
class="btn-primary"
|
||||
action=(action "insertPlaceholder")
|
||||
label=(theme-prefix "builder.insert")
|
||||
}}
|
||||
</div>
|
|
@ -0,0 +1,17 @@
|
|||
en:
|
||||
toolbar:
|
||||
builder: "Add Placeholder"
|
||||
builder:
|
||||
errors:
|
||||
no_key: "A key is required."
|
||||
title: "Add Placeholder"
|
||||
insert: "Insert"
|
||||
key:
|
||||
label: "Key"
|
||||
description: "The =Key= to be replaced in the post."
|
||||
description:
|
||||
label: "Description"
|
||||
description: "Description displayed on input with no value set."
|
||||
values:
|
||||
label: "Default value(s)"
|
||||
description: "Optional value(s) for your placeholder, if multiple values are defined, a select will be used."
|
|
@ -0,0 +1,23 @@
|
|||
.placeholder-ui {
|
||||
flex-direction: column;
|
||||
|
||||
.placeholders-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.d-wrap[data-wrap="placeholder"] {
|
||||
.discourse-placeholder-name {
|
||||
width: 50%;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
.discourse-placeholder-value,
|
||||
.discourse-placeholder-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue