шаблон анкеты
название темы — имя фамилия на латиницей, возраст цифрой, раса латиницей (подсказка ниже);
вторым сообщение не забывайте отправлять коды
|
информация о персонаже в свободной форме заполнения. можете заполнить поле мемами или аестетиками, если не планируете плотно участвовать в квестах или общем сюжете. если хотите в квесты - нужно хотя бы немного информации о вашем персонаже. |
[html]<style>
/* ==== Виджет анкеты (rf-anketa) ==== */
/* Все селекторы ограничены корневым блоком .rf-anketa-widget,
чтобы не конфликтовать со стилями темы форума */
.rf-anketa-widget {
max-width: 660px;
margin: 12px auto;
padding: 14px;
box-sizing: border-box;
font-family: inherit;
font-size: 13px;
border: solid #b4c6d9;
border-radius: 27px;
border-width: 1.5px;
background-color: #e4e4e4;
}
.rf-anketa-widget * {
box-sizing: border-box;
}
.rf-anketa-field {
margin-bottom: 10px;
}
.rf-anketa-field label {
display: block;
margin-bottom: 4px;
font-size: 12px;
opacity: 0.8;
}
.rf-anketa-input {
width: 100%;
padding: 6px 8px;
font-size: 13px;
border: 1px solid #b4c6d9!important;
border-radius: 4px;
background: #f7f7f7!important;
}
.rf-anketa-img-list {
margin-bottom: 6px;
}
.rf-anketa-img-row {
display: flex;
gap: 6px;
margin-bottom: 6px;
}
.rf-anketa-img-row .rf-anketa-input {
flex: 1 1 auto;
}
.rf-anketa-btn-remove {
flex: 0 0 auto;
width: 28px;
padding: 0;
font-size: 14px;
line-height: 1;
border: 1px solid rgba(0,0,0,0.3);
border-radius: 4px;
background: rgba(0,0,0,0.05);
cursor: pointer;
}
.rf-anketa-btn-remove:hover {
background: rgba(0,0,0,0.1);
}
.rf-anketa-btn-add {
padding: 5px 10px;
font-size: 12px;
border: none;
border-radius: 4px;
background: #151515;
cursor: pointer;
}
.rf-anketa-btn-add:hover {
background: #5959a1;
}
.rf-anketa-bio {
width: 100%;
min-height: 90px;
max-height: 300px;
overflow-y: auto;
padding: 6px 8px;
border: 1px solid rgba(0,0,0,0.25);
border-radius: 4px;
background: rgba(255,255,255,0.6);
white-space: pre-wrap;
word-wrap: break-word;
}
.rf-anketa-buttons {
display: flex;
gap: 8px;
margin: 12px 0;
}
.rf-anketa-btn {
flex: 1 1 auto;
padding: 7px 10px;
font-size: 13px;
border: none;
border-radius: 4px;
background: #000;
cursor: pointer;
}
.rf-anketa-btn:hover {
background: #5959a1;
}
.rf-anketa-output {
width: 100%;
min-height: 140px;
max-height: 320px;
overflow-y: auto;
padding: 8px;
border: 1px dashed rgba(0,0,0,0.3);
border-radius: 4px;
background: rgba(0,0,0,0.03);
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
word-wrap: break-word;
}
.rf-anketa-status {
margin-top: 6px;
font-size: 12px;
min-height: 16px;
opacity: 0.85;
}
</style>
<!-- rf-anketa: форма для заполнения анкеты и генерации готового BB-кода -->
<div class="rf-anketa-widget" data-role="root">
<div class="rf-anketa-field">
<label>Ссылки на картинки</label>
<div class="rf-anketa-img-list" data-role="img-list">
<div class="rf-anketa-img-row" data-role="img-row">
<input type="text" class="rf-anketa-input" data-role="input-img" placeholder="https://..." />
<button type="button" class="rf-anketa-btn-remove" data-action="remove-image" title="Удалить">×</button>
</div>
</div>
<button type="button" class="rf-anketa-btn-add" data-action="add-image">+ Добавить картинку</button>
</div>
<div class="rf-anketa-field">
<label>Внешность</label>
<input type="text" class="rf-anketa-input" data-role="input-appearance" placeholder="name surname" />
</div>
<div class="rf-anketa-field">
<label>Возраст</label>
<input type="text" class="rf-anketa-input" data-role="input-age" placeholder="напр. 27" />
</div>
<div class="rf-anketa-field">
<label>Раса</label>
<input type="text" class="rf-anketa-input" data-role="input-race" placeholder="человек, вампир, оборотень, мэрроу и т.д" />
</div>
<div class="rf-anketa-field">
<label>Род деятельности</label>
<input type="text" class="rf-anketa-input" data-role="input-occupation" placeholder="чем занимается персонаж" />
</div>
<div class="rf-anketa-field">
<label>Локация проживания</label>
<input type="text" class="rf-anketa-input" data-role="input-location" placeholder="напр. лондон, юк" />
</div>
<div class="rf-anketa-field">
<label>Имя Фамилия на латинице</label>
<input type="text" class="rf-anketa-input" data-role="input-name-en" placeholder="name surname" />
</div>
<div class="rf-anketa-field">
<label>Имя Фамилия на русском</label>
<input type="text" class="rf-anketa-input" data-role="input-name-ru" placeholder="имя фамилия" />
</div>
<div class="rf-anketa-field">
<label>Информация о персонаже в свободной форме</label>
<div class="rf-anketa-bio" contenteditable="true" data-role="input-bio"></div>
</div>
<div class="rf-anketa-buttons">
<button type="button" class="rf-anketa-btn" data-action="generate">Сгенерировать шаблон</button>
<button type="button" class="rf-anketa-btn" data-action="copy">Скопировать</button>
</div>
<div class="rf-anketa-field">
<label>Готовый BB-код (можно скопировать вручную)</label>
<div class="rf-anketa-output" contenteditable="true" data-role="output"></div>
</div>
<div class="rf-anketa-status" data-role="status"></div>
</div>
<script>
(function () {
"use strict";
var ROOT_SELECTOR = ".rf-anketa-widget";
var READY_ATTR = "data-rf-anketa-ready";
// --- сборка BB-тегов, чтобы конвертер форума не съел их раньше времени ---
function bbTag(name) {
return String.fromCharCode(91) + name + String.fromCharCode(93);
}
// убираем квадратные скобки из введённого текста, чтобы не поломать разметку
function clean(str) {
if (!str) {
return "";
}
return String(str).split("[").join("(").split("]").join(")");
}
function trim(str) {
return String(str).replace(/^\s+|\s+$/g, "");
}
function getVal(root, role) {
var el = root.querySelector('[data-role="' + role + '"]');
if (!el) {
return "";
}
if (el.tagName === "INPUT") {
return clean(el.value);
}
return clean(el.textContent);
}
// читаем contenteditable-блок с учётом переносов строк.
// Разные браузеры по-разному оформляют Enter (<div> на строку, <br>,
// иногда пустой <div><br></div> для пустой строки), поэтому обходим
// узлы напрямую, а не полагаемся на textContent или regex по HTML.
function walkBioNode(node, state) {
var children = node.childNodes;
var i;
var child;
var tag;
var isBlock;
for (i = 0; i < children.length; i++) {
child = children[i];
if (child.nodeType === 3) {
state.text += child.nodeValue;
} else if (child.nodeType === 1) {
tag = child.tagName.toLowerCase();
if (tag === "br") {
state.text += "\n";
} else {
isBlock = (tag === "div" || tag === "p");
if (isBlock && state.text.length > 0 && state.text.charAt(state.text.length - 1) !== "\n") {
state.text += "\n";
}
walkBioNode(child, state);
if (isBlock && state.text.length > 0 && state.text.charAt(state.text.length - 1) !== "\n") {
state.text += "\n";
}
}
}
}
}
function getBioValue(root) {
var el = root.querySelector('[data-role="input-bio"]');
if (!el) {
return "";
}
var state = { text: "" };
walkBioNode(el, state);
var text = state.text.replace(/\n+$/, "");
text = clean(text);
return text;
}
function getImages(root) {
var inputs = root.querySelectorAll('[data-role="input-img"]');
var result = [];
var i;
var val;
for (i = 0; i < inputs.length; i++) {
val = trim(clean(inputs[i].value));
if (val) {
result.push(val);
}
}
return result;
}
function buildTemplate(v) {
var out = "";
var i;
var imgParts;
out += bbTag("table layout=fixed width=100%");
out += bbTag("tr") + bbTag("td") + bbTag("/td") + bbTag("/tr");
out += bbTag("tr");
out += bbTag("td");
out += bbTag("align=center");
imgParts = [];
for (i = 0; i < v.images.length; i++) {
imgParts.push(bbTag("img") + v.images[i] + bbTag("/img"));
}
out += imgParts.join(" ");
if (v.images.length > 0) {
out += "\n";
}
out += bbTag("size=9");
out += v.appearance;
out += " " + bbTag("b") + "#" + bbTag("/b") + " " + v.age;
out += " " + bbTag("b") + "#" + bbTag("/b") + " " + v.race;
out += " " + bbTag("b") + "#" + bbTag("/b") + " " + v.occupation;
out += " " + bbTag("b") + "#" + bbTag("/b") + " " + v.location;
out += bbTag("/size");
out += bbTag("/align");
out += bbTag("align=center");
out += bbTag("size=24") + bbTag("b") + bbTag("i") + bbTag("font=Georgia");
out += v.nameEn;
out += bbTag("/font") + bbTag("/i") + bbTag("/b") + bbTag("/size");
out += "\n" + v.nameRu;
out += bbTag("/align");
out += bbTag("/td");
out += bbTag("/tr");
out += bbTag("/table");
out += bbTag("table layout=fixed width=100%");
out += bbTag("tr");
out += bbTag("td width=10%") + bbTag("/td");
out += bbTag("td");
out += "\n" + v.bio + "\n";
out += bbTag("/td");
out += bbTag("td width=10%") + bbTag("/td");
out += bbTag("/tr");
out += bbTag("/table");
return out;
}
function setStatus(root, text) {
var el = root.querySelector('[data-role="status"]');
if (el) {
el.textContent = text;
}
}
function generate(root) {
var values = {
images: getImages(root),
appearance: getVal(root, "input-appearance"),
age: getVal(root, "input-age"),
race: getVal(root, "input-race"),
occupation: getVal(root, "input-occupation"),
location: getVal(root, "input-location"),
nameEn: getVal(root, "input-name-en"),
nameRu: getVal(root, "input-name-ru"),
bio: getBioValue(root)
};
var output = root.querySelector('[data-role="output"]');
if (!output) {
return;
}
output.textContent = buildTemplate(values);
setStatus(root, "Шаблон готов. Можно скопировать.");
}
function fallbackCopy(root, output) {
var range = document.createRange();
range.selectNodeContents(output);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
var ok = false;
try {
ok = document.execCommand("copy");
} catch (e) {
ok = false;
}
selection.removeAllRanges();
if (ok) {
setStatus(root, "Скопировано.");
} else {
setStatus(root, "Не удалось скопировать автоматически — выделите текст и нажмите Ctrl+C.");
}
}
function copyOutput(root) {
var output = root.querySelector('[data-role="output"]');
if (!output) {
return;
}
var text = output.textContent;
if (!text) {
setStatus(root, "Сначала нажмите «Сгенерировать шаблон».");
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(
function () {
setStatus(root, "Скопировано.");
},
function () {
fallbackCopy(root, output);
}
);
} else {
fallbackCopy(root, output);
}
}
// свой closest() по тегу BUTTON, чтобы не зависеть от Element.prototype.closest
function closestButton(target, root) {
var node = target;
while (node && node !== root) {
if (node.nodeName === "BUTTON" && node.getAttribute("data-action")) {
return node;
}
node = node.parentNode;
}
return null;
}
// свой closest() по классу (для поиска строки картинки, которую нужно удалить)
function closestByClass(target, className, root) {
var node = target;
while (node && node !== root) {
if (node.className && (" " + node.className + " ").indexOf(" " + className + " ") !== -1) {
return node;
}
node = node.parentNode;
}
return null;
}
function addImageRow(root) {
var list = root.querySelector('[data-role="img-list"]');
if (!list) {
return;
}
var row = document.createElement("div");
row.className = "rf-anketa-img-row";
row.setAttribute("data-role", "img-row");
var input = document.createElement("input");
input.type = "text";
input.className = "rf-anketa-input";
input.setAttribute("data-role", "input-img");
input.setAttribute("placeholder", "https://...");
var removeBtn = document.createElement("button");
removeBtn.type = "button";
removeBtn.className = "rf-anketa-btn-remove";
removeBtn.setAttribute("data-action", "remove-image");
removeBtn.setAttribute("title", "Удалить");
removeBtn.textContent = String.fromCharCode(215); // ×
row.appendChild(input);
row.appendChild(removeBtn);
list.appendChild(row);
}
function removeImageRow(root, button) {
var row = closestByClass(button, "rf-anketa-img-row", root);
if (row && row.parentNode) {
row.parentNode.removeChild(row);
}
}
function bindRoot(root) {
if (root.getAttribute(READY_ATTR) === "true") {
return;
}
root.setAttribute(READY_ATTR, "true");
root.addEventListener("click", function (event) {
var btn = closestButton(event.target, root);
if (!btn) {
return;
}
var action = btn.getAttribute("data-action");
if (action === "generate") {
generate(root);
} else if (action === "copy") {
copyOutput(root);
} else if (action === "add-image") {
addImageRow(root);
} else if (action === "remove-image") {
removeImageRow(root, btn);
}
});
}
function init() {
var roots = document.querySelectorAll(ROOT_SELECTOR);
for (var i = 0; i < roots.length; i++) {
bindRoot(roots[i]);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
// повторная инициализация на случай отложенной вставки HTML конвертером
setTimeout(init, 300);
setTimeout(init, 800);
setTimeout(init, 1500);
})();
</script>
[/html]
Код:[table layout=fixed width=100%] [tr][td][/td][/tr] [tr] [td] [align=center][img]картинку сюда[/img] [size=9]внешность [b]#[/b] возраст [b]#[/b] раса [b]#[/b] род деятельности [b]#[/b] локация проживания[/size][/align] [align=center][size=24][b][i][font=Georgia]name surname[/font][/i][/b][/size] имя фамилия на русском[/align][/td] [/tr] [/table] [table layout=fixed width=100%] [tr][td width=10%][/td] [td] информация о персонаже в свободной форме заполнения. можете заполнить поле мемами или аестетиками, если не планируете плотно участвовать в квестах или общем сюжете. если хотите в квесты - нужно хотя бы немного информации о вашем персонаже. [/td] [td width=10%][/td][/tr] [/table]















