init
This commit is contained in:
commit
412875b25b
|
@ -0,0 +1,2 @@
|
||||||
|
.discourse-site
|
||||||
|
HELP
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 joffreyjaffeux
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "discourse-placeholder-theme-component",
|
||||||
|
"about_url": "https://github.com/discourse/discourse-placeholder-theme-component",
|
||||||
|
"license_url": "https://github.com/discourse/discourse-placeholder-theme-component/blob/master/LICENSE"
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
.d-wrap[data-wrap=placeholder] {
|
||||||
|
padding: 0.5em;
|
||||||
|
border: 1px solid $primary-medium;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.discourse-placeholder-name {
|
||||||
|
width: 200px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-placeholder-value,
|
||||||
|
.discourse-placeholder-select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-placeholder-value {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
line-height: $line-height-small;
|
||||||
|
color: $primary;
|
||||||
|
background-color: $secondary;
|
||||||
|
border: 1px solid $primary-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-placeholder-select {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
line-height: $line-height-small;
|
||||||
|
color: $primary;
|
||||||
|
background-color: $secondary;
|
||||||
|
border: 1px solid $primary-medium;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
<script type="text/discourse-plugin" version="0.8.30">
|
||||||
|
api.decorateCooked($cooked => {
|
||||||
|
const VALID_TAGS = "h1, h2, h3, h4, h5, h6, p, code, blockquote, .md-table, li";
|
||||||
|
const DELIMITER = "=";
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const keys = Object.keys(placeholders);
|
||||||
|
const pattern = `(${DELIMITER}(?:${keys.join("|")})${DELIMITER})`;
|
||||||
|
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[1],
|
||||||
|
position: match.index,
|
||||||
|
length: match[1].length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappings = [];
|
||||||
|
const placeholders = {};
|
||||||
|
|
||||||
|
$cooked.find(".d-wrap[data-wrap=placeholder]:not(.placeholdered)").each((index, elem) => {
|
||||||
|
const dataKey = elem.dataset.key;
|
||||||
|
if (!dataKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValue = elem.dataset.default;
|
||||||
|
const defaultValues = (elem.dataset.defaults || "").split(",").filter(x => x);
|
||||||
|
const description = elem.dataset.description;
|
||||||
|
|
||||||
|
placeholders[dataKey] = {
|
||||||
|
default: defaultValue,
|
||||||
|
defaults: defaultValues,
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
input.setAttribute("placeholder", description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue) {
|
||||||
|
input.value = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.appendChild(input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Object.keys(placeholders).length > 0) {
|
||||||
|
processPlaceholders(placeholders, $cooked, mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 } } },
|
||||||
|
mappings
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in New Issue