</option>
<option name="internal_hostnames">
<categoryname>general.page</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<option name="head_code">
<categoryname>general.page</categoryname>
</option>
<option name="url_title_component_replacement">
<categoryname>general.page.seo</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<option name="sitemap_index_time_frame">
<categoryname>general.page.sitemap</categoryname>
</option>
<option name="image_external_source_whitelist">
<categoryname>message.general.image</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<option name="message_force_secure_images">
<categoryname>message.general.image</categoryname>
</option>
<option name="image_proxy_host_whitelist">
<categoryname>message.general.imageProxy</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<!-- /message.general.image -->
<!-- message.censorship -->
</option>
<option name="censored_words">
<categoryname>message.censorship</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<!-- /message.censorship -->
<option name="password_min_score">
<!-- user.ban -->
<option name="register_forbidden_usernames">
<categoryname>user.ban</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<option name="register_forbidden_emails">
<categoryname>user.ban</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<option name="register_allowed_emails">
<categoryname>user.ban</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
</option>
<!-- /user.ban -->
<!-- user.register -->
</option>
<option name="user_forbidden_titles">
<categoryname>user.title</categoryname>
- <optiontype>textarea</optiontype>
+ <optiontype>lineBreakSeparatedText</optiontype>
<options>module_user_rank</options>
</option>
<!-- /user.title -->
--- /dev/null
+/**
+ * UI element that shows individual lines of text as distinct list items but saves them as line
+ * break-separated text.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/ItemList/LineBreakSeparatedText
+ * @since 5.4
+ */
+
+import * as UiConfirmation from "../Confirmation";
+import * as Language from "../../Language";
+import DomUtil from "../../Dom/Util";
+
+export interface LineBreakSeparatedTextOptions {
+ submitFieldName: string;
+}
+
+export class UiItemListLineBreakSeparatedText {
+ protected clearButton?: HTMLAnchorElement = undefined;
+ protected itemInput?: HTMLInputElement = undefined;
+ protected readonly itemList: HTMLUListElement;
+ protected readonly items = new Set<string>();
+ protected readonly options: LineBreakSeparatedTextOptions;
+
+ constructor(itemList: HTMLUListElement, options: LineBreakSeparatedTextOptions) {
+ this.itemList = itemList;
+ this.options = options;
+
+ this.itemList.closest("form")!.addEventListener("submit", () => this.submit());
+
+ this.initValues();
+ this.buildUi();
+ }
+
+ /**
+ * Adds an item to the list after clicking on the "add" button.
+ */
+ protected addItem(event: Event): void {
+ event.preventDefault();
+
+ const itemInput = this.itemInput!;
+ const item = itemInput.value.trim();
+
+ if (item === "") {
+ DomUtil.innerError(itemInput.parentElement!, Language.get("wcf.global.form.error.empty"));
+ } else if (!this.items.has(item)) {
+ this.insertItem(item);
+
+ this.resetInput();
+ } else {
+ DomUtil.innerError(
+ itemInput.parentElement!,
+ Language.get("wcf.acp.option.type.lineBreakSeparatedText.error.duplicate", {
+ item,
+ }),
+ true,
+ );
+ }
+
+ itemInput.focus();
+ }
+
+ /**
+ * Builds the user interface during setup.
+ */
+ protected buildUi(): void {
+ const container = document.createElement("div");
+ container.classList.add("itemListFilter");
+
+ this.itemList.insertAdjacentElement("beforebegin", container);
+ container.appendChild(this.itemList);
+
+ const inputAddon = document.createElement("div");
+ inputAddon.classList.add("inputAddon");
+ container.appendChild(inputAddon);
+
+ this.itemInput = document.createElement("input");
+ this.itemInput.classList.add("long");
+ this.itemInput.type = "text";
+ this.itemInput.placeholder = Language.get("wcf.acp.option.type.lineBreakSeparatedText.placeholder");
+ this.itemInput.addEventListener("keydown", (ev) => this.keydown(ev));
+ this.itemInput.addEventListener("paste", (ev) => this.paste(ev));
+ inputAddon.appendChild(this.itemInput);
+
+ const addButton = document.createElement("a");
+ addButton.href = "#";
+ addButton.classList.add("button", "inputSuffix", "jsTooltip");
+ addButton.title = Language.get("wcf.global.button.add");
+ addButton.innerHTML = '<span class="icon icon16 fa-plus"></span>';
+ addButton.addEventListener("click", (ev) => this.addItem(ev));
+ inputAddon.appendChild(addButton);
+
+ this.clearButton = document.createElement("a");
+ this.clearButton.href = "#";
+ this.clearButton.classList.add("button", "inputSuffix", "jsTooltip");
+ this.clearButton.title = Language.get("wcf.global.button.delete");
+ this.clearButton.innerHTML = '<span class="icon icon16 fa-times"></span>';
+ this.clearButton.addEventListener("click", (ev) => this.clearList(ev));
+ inputAddon.appendChild(this.clearButton);
+ if (this.items.size === 0) {
+ DomUtil.hide(this.clearButton);
+ }
+ }
+
+ /**
+ * Clears the item list after clicking on the clear button.
+ */
+ protected clearList(ev: Event): void {
+ ev.preventDefault();
+
+ UiConfirmation.show({
+ confirm: () => {
+ this.itemList.innerHTML = "";
+ this.items.clear();
+
+ this.hideList();
+ },
+ message: Language.get("wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage"),
+ messageIsHtml: true,
+ });
+ }
+
+ /**
+ * Deletes an item from the list after clicking on its delete icon.
+ */
+ protected deleteItem(event: Event): void {
+ const button = event.currentTarget as HTMLElement;
+ const item = button.closest("li")!.dataset.value!;
+
+ UiConfirmation.show({
+ confirm: () => {
+ button.closest("li")!.remove();
+
+ if (this.itemList.childElementCount === 0) {
+ this.hideList();
+ }
+
+ this.items.delete(item);
+ },
+ message: Language.get("wcf.button.delete.confirmMessage", {
+ objectTitle: item,
+ }),
+ messageIsHtml: true,
+ });
+ }
+
+ /**
+ * Hides the item list and clear button.
+ */
+ protected hideList(): void {
+ DomUtil.hide(this.itemList);
+ DomUtil.hide(this.clearButton!);
+ }
+
+ /**
+ * Adds the initial values to the list.
+ */
+ protected initValues(): void {
+ Array.from(this.itemList.children).forEach((el: HTMLElement) => {
+ this.items.add(el.dataset.value!);
+
+ el.querySelector(".jsDeleteItem")!.addEventListener("click", (ev) => this.deleteItem(ev));
+ });
+ }
+
+ /**
+ * Inserts the given item to the list.
+ */
+ protected insertItem(item: string): void {
+ this.items.add(item);
+
+ const itemElement = document.createElement("li");
+ itemElement.dataset.value = item;
+
+ const deleteButton = document.createElement("span");
+ deleteButton.classList.add("icon", "icon16", "fa-times", "jsDeleteItem", "jsTooltip", "pointer");
+ deleteButton.title = Language.get("wcf.global.button.delete");
+ deleteButton.addEventListener("click", (ev) => this.deleteItem(ev));
+ itemElement.append(deleteButton);
+
+ itemElement.append(document.createTextNode(" "));
+
+ const label = document.createElement("span");
+ label.innerText = item;
+ itemElement.append(label);
+
+ const nextElement = Array.from(this.itemList.children).find((el: HTMLElement) => el.dataset.value! > item);
+
+ if (nextElement) {
+ this.itemList.insertBefore(itemElement, nextElement);
+ } else {
+ this.itemList.append(itemElement);
+ }
+
+ this.showList();
+ }
+
+ /**
+ * Adds an item to the list when pressing "Enter" in the input field.
+ */
+ protected keydown(event: KeyboardEvent): void {
+ if (event.key === "Enter") {
+ this.addItem(event);
+ }
+ }
+
+ /**
+ * Adds multiple items at one to the list when pasting multiple lines of text into the input
+ * field.
+ */
+ protected paste(event: ClipboardEvent): void {
+ const items = event.clipboardData!.getData("text/plain").split("\n");
+ if (items.length > 1) {
+ event.preventDefault();
+
+ items.forEach((item) => this.insertItem(item));
+
+ this.resetInput();
+ }
+ }
+
+ /**
+ * Resets the input field.
+ */
+ protected resetInput(): void {
+ DomUtil.innerError(this.itemInput!.parentElement!, "");
+ this.itemInput!.value = "";
+ }
+
+ /**
+ * Shows the item list and clear button.
+ */
+ protected showList(): void {
+ DomUtil.show(this.itemList);
+ DomUtil.show(this.clearButton!);
+ }
+
+ /**
+ * Adds a hidden input field with the data to the form before it is submitted.
+ */
+ protected submit(): void {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = this.options.submitFieldName;
+ input.value = Array.from(this.items).join("\n");
+ this.itemList.parentElement!.append(input);
+ }
+}
+
+export default UiItemListLineBreakSeparatedText;
--- /dev/null
+<ul class="scrollableCheckboxList" {*
+ *}id="lineBreakSeparatedTextOption_{@$option->optionID}"{*
+ *}{if $values|empty} style="display: none"{/if}{*
+*}>
+ {foreach from=$values item=value}
+ <li data-value="{$value}">
+ <span class="icon icon16 fa-times jsDeleteItem jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}"></span>
+ <span>{$value}</span>
+ </li>
+ {/foreach}
+</ul>
+
+<script data-relocate="true">
+ require(['Language', 'WoltLabSuite/Core/Ui/ItemList/LineBreakSeparatedText'], (Language, { UiItemListLineBreakSeparatedText }) => {
+ Language.addObject({
+ 'wcf.acp.option.type.lineBreakSeparatedText.placeholder': '{jslang}wcf.acp.option.type.lineBreakSeparatedText.placeholder{/jslang}',
+ 'wcf.acp.option.type.lineBreakSeparatedText.error.duplicate': '{jslang __literal=true}wcf.acp.option.type.lineBreakSeparatedText.error.duplicate{/jslang}',
+ 'wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage': '{jslang}wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage{/jslang}',
+ });
+
+ new UiItemListLineBreakSeparatedText(
+ document.getElementById("lineBreakSeparatedTextOption_{@$option->optionID}"),
+ {
+ submitFieldName: "values[{$option->optionName}]"
+ }
+ );
+ });
+</script>
--- /dev/null
+/**
+ * UI element that shows individual lines of text as distinct list items but saves them as line
+ * break-separated text.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/ItemList/LineBreakSeparatedText
+ * @since 5.4
+ */
+define(["require", "exports", "tslib", "../Confirmation", "../../Language", "../../Dom/Util"], function (require, exports, tslib_1, UiConfirmation, Language, Util_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.UiItemListLineBreakSeparatedText = void 0;
+ UiConfirmation = tslib_1.__importStar(UiConfirmation);
+ Language = tslib_1.__importStar(Language);
+ Util_1 = tslib_1.__importDefault(Util_1);
+ class UiItemListLineBreakSeparatedText {
+ constructor(itemList, options) {
+ this.clearButton = undefined;
+ this.itemInput = undefined;
+ this.items = new Set();
+ this.itemList = itemList;
+ this.options = options;
+ this.itemList.closest("form").addEventListener("submit", () => this.submit());
+ this.initValues();
+ this.buildUi();
+ }
+ /**
+ * Adds an item to the list after clicking on the "add" button.
+ */
+ addItem(event) {
+ event.preventDefault();
+ const itemInput = this.itemInput;
+ const item = itemInput.value.trim();
+ if (item === "") {
+ Util_1.default.innerError(itemInput.parentElement, Language.get("wcf.global.form.error.empty"));
+ }
+ else if (!this.items.has(item)) {
+ this.insertItem(item);
+ this.resetInput();
+ }
+ else {
+ Util_1.default.innerError(itemInput.parentElement, Language.get("wcf.acp.option.type.lineBreakSeparatedText.error.duplicate", {
+ item,
+ }), true);
+ }
+ itemInput.focus();
+ }
+ /**
+ * Builds the user interface during setup.
+ */
+ buildUi() {
+ const container = document.createElement("div");
+ container.classList.add("itemListFilter");
+ this.itemList.insertAdjacentElement("beforebegin", container);
+ container.appendChild(this.itemList);
+ const inputAddon = document.createElement("div");
+ inputAddon.classList.add("inputAddon");
+ container.appendChild(inputAddon);
+ this.itemInput = document.createElement("input");
+ this.itemInput.classList.add("long");
+ this.itemInput.type = "text";
+ this.itemInput.placeholder = Language.get("wcf.acp.option.type.lineBreakSeparatedText.placeholder");
+ this.itemInput.addEventListener("keydown", (ev) => this.keydown(ev));
+ this.itemInput.addEventListener("paste", (ev) => this.paste(ev));
+ inputAddon.appendChild(this.itemInput);
+ const addButton = document.createElement("a");
+ addButton.href = "#";
+ addButton.classList.add("button", "inputSuffix", "jsTooltip");
+ addButton.title = Language.get("wcf.global.button.add");
+ addButton.innerHTML = '<span class="icon icon16 fa-plus"></span>';
+ addButton.addEventListener("click", (ev) => this.addItem(ev));
+ inputAddon.appendChild(addButton);
+ this.clearButton = document.createElement("a");
+ this.clearButton.href = "#";
+ this.clearButton.classList.add("button", "inputSuffix", "jsTooltip");
+ this.clearButton.title = Language.get("wcf.global.button.delete");
+ this.clearButton.innerHTML = '<span class="icon icon16 fa-times"></span>';
+ this.clearButton.addEventListener("click", (ev) => this.clearList(ev));
+ inputAddon.appendChild(this.clearButton);
+ if (this.items.size === 0) {
+ Util_1.default.hide(this.clearButton);
+ }
+ }
+ /**
+ * Clears the item list after clicking on the clear button.
+ */
+ clearList(ev) {
+ ev.preventDefault();
+ UiConfirmation.show({
+ confirm: () => {
+ this.itemList.innerHTML = "";
+ this.items.clear();
+ this.hideList();
+ },
+ message: Language.get("wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage"),
+ messageIsHtml: true,
+ });
+ }
+ /**
+ * Deletes an item from the list after clicking on its delete icon.
+ */
+ deleteItem(event) {
+ const button = event.currentTarget;
+ const item = button.closest("li").dataset.value;
+ UiConfirmation.show({
+ confirm: () => {
+ button.closest("li").remove();
+ if (this.itemList.childElementCount === 0) {
+ this.hideList();
+ }
+ this.items.delete(item);
+ },
+ message: Language.get("wcf.button.delete.confirmMessage", {
+ objectTitle: item,
+ }),
+ messageIsHtml: true,
+ });
+ }
+ /**
+ * Hides the item list and clear button.
+ */
+ hideList() {
+ Util_1.default.hide(this.itemList);
+ Util_1.default.hide(this.clearButton);
+ }
+ /**
+ * Adds the initial values to the list.
+ */
+ initValues() {
+ Array.from(this.itemList.children).forEach((el) => {
+ this.items.add(el.dataset.value);
+ el.querySelector(".jsDeleteItem").addEventListener("click", (ev) => this.deleteItem(ev));
+ });
+ }
+ /**
+ * Inserts the given item to the list.
+ */
+ insertItem(item) {
+ this.items.add(item);
+ const itemElement = document.createElement("li");
+ itemElement.dataset.value = item;
+ const deleteButton = document.createElement("span");
+ deleteButton.classList.add("icon", "icon16", "fa-times", "jsDeleteItem", "jsTooltip", "pointer");
+ deleteButton.title = Language.get("wcf.global.button.delete");
+ deleteButton.addEventListener("click", (ev) => this.deleteItem(ev));
+ itemElement.append(deleteButton);
+ itemElement.append(document.createTextNode(" "));
+ const label = document.createElement("span");
+ label.innerText = item;
+ itemElement.append(label);
+ const nextElement = Array.from(this.itemList.children).find((el) => el.dataset.value > item);
+ if (nextElement) {
+ this.itemList.insertBefore(itemElement, nextElement);
+ }
+ else {
+ this.itemList.append(itemElement);
+ }
+ this.showList();
+ }
+ /**
+ * Adds an item to the list when pressing "Enter" in the input field.
+ */
+ keydown(event) {
+ if (event.key === "Enter") {
+ this.addItem(event);
+ }
+ }
+ /**
+ * Adds multiple items at one to the list when pasting multiple lines of text into the input
+ * field.
+ */
+ paste(event) {
+ const items = event.clipboardData.getData("text/plain").split("\n");
+ if (items.length > 1) {
+ event.preventDefault();
+ items.forEach((item) => this.insertItem(item));
+ this.resetInput();
+ }
+ }
+ /**
+ * Resets the input field.
+ */
+ resetInput() {
+ Util_1.default.innerError(this.itemInput.parentElement, "");
+ this.itemInput.value = "";
+ }
+ /**
+ * Shows the item list and clear button.
+ */
+ showList() {
+ Util_1.default.show(this.itemList);
+ Util_1.default.show(this.clearButton);
+ }
+ /**
+ * Adds a hidden input field with the data to the form before it is submitted.
+ */
+ submit() {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = this.options.submitFieldName;
+ input.value = Array.from(this.items).join("\n");
+ this.itemList.parentElement.append(input);
+ }
+ }
+ exports.UiItemListLineBreakSeparatedText = UiItemListLineBreakSeparatedText;
+ exports.default = UiItemListLineBreakSeparatedText;
+});
--- /dev/null
+<?php
+
+namespace wcf\system\option;
+
+use wcf\data\option\Option;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Option type implementation for separate items that are stored as line break-separated text.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Option
+ * @since 5.4
+ */
+class LineBreakSeparatedTextOptionType extends TextareaOptionType
+{
+ /**
+ * @inheritDoc
+ */
+ public function getFormElement(Option $option, $value)
+ {
+ $values = ArrayUtil::trim(\explode("\n", StringUtil::unifyNewlines($value)));
+ \uasort($values, 'strnatcmp');
+
+ return WCF::getTPL()->fetch('lineBreakSeparatedTextOptionType', 'wcf', [
+ 'option' => $option,
+ 'values' => $values,
+ ]);
+ }
+}
<item name="wcf.acp.option.module_smiley"><![CDATA[Smileys]]></item>
<item name="wcf.acp.option.category.message.censorship"><![CDATA[Zensur-Funktion]]></item>
<item name="wcf.acp.option.censored_words"><![CDATA[Zu zensierende Wörter]]></item>
- <item name="wcf.acp.option.censored_words.description"><![CDATA[Ein Wort pro Zeile. Sollte bei der Erstellung einer Nachricht eines dieser Wörter verwendet werden, so wird die Erstellung verweigert.<br>
+ <item name="wcf.acp.option.censored_words.description"><![CDATA[Sollte bei der Erstellung einer Nachricht eines dieser Wörter verwendet werden, so wird die Erstellung verweigert.<br>
<em>{if LANGUAGE_USE_INFORMAL_VARIANT}Verwende{else}Verwenden Sie{/if} „*“, um Wortteile zu finden: „wolt*“ findet auch „woltlab“</em><br>
<em>{if LANGUAGE_USE_INFORMAL_VARIANT}Verwende{else}Verwenden Sie{/if} „~“, um Worttrennungen zu finden: „wolt~“ findet auch „wolt-lab“</em>]]></item>
<item name="wcf.acp.option.enable_censorship"><![CDATA[Zensur aktivieren]]></item>
<item name="wcf.acp.option.password_min_score.1"><![CDATA[1: Sehr leicht zu erraten (Eine Million Versuche)]]></item>
<item name="wcf.acp.option.password_min_score.2"><![CDATA[2: Leicht zu erraten (100 Millionen Versuche)]]></item>
<item name="wcf.acp.option.register_forbidden_usernames"><![CDATA[Reservierte Namen]]></item>
- <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[Namen, die nicht als Benutzername verwendet werden dürfen. Ein Name pro Zeile]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[Namen, die nicht als Benutzername verwendet werden dürfen.]]></item>
<item name="wcf.acp.option.register_forbidden_emails"><![CDATA[Reservierte E-Mail-Adressen]]></item>
- <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[E-Mail-Adressen, die nicht bei der Registrierung verwendet werden dürfen. Eine Adresse pro Zeile]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[E-Mail-Adressen, die nicht bei der Registrierung verwendet werden dürfen.]]></item>
<item name="wcf.acp.option.register_allowed_emails"><![CDATA[Erlaubte E-Mail-Adressen]]></item>
- <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[E-Mail-Adressen, die ausschließlich bei der Registrierung verwendet werden dürfen. Eine Adresse pro Zeile]]></item>
+ <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[E-Mail-Adressen, die ausschließlich bei der Registrierung verwendet werden dürfen.]]></item>
<item name="wcf.acp.option.register_username_min_length"><![CDATA[Minimale Benutzernamenlänge]]></item>
<item name="wcf.acp.option.register_username_max_length"><![CDATA[Maximale Benutzernamenlänge]]></item>
<item name="wcf.acp.option.register_username_force_ascii"><![CDATA[Benutzernamen auf ASCII-Zeichen beschränken]]></item>
<item name="wcf.acp.option.sitemap_index_time_frame.description"><![CDATA[Maximales Alter der Objekte um in die Sitemap aufgenommen zu werden [0 um das Zeitfenster zu deaktivieren].]]></item>
<item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximale Länge des Benutzertitels]]></item>
<item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reservierte Benutzertitel]]></item>
- <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[Benutzertitel, die nicht verwendet werden dürfen. Ein Titel pro Zeile]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[Benutzertitel, die nicht verwendet werden dürfen.]]></item>
<item name="wcf.acp.option.profile_show_old_username"><![CDATA[Alten Namen anzeigen]]></item>
<item name="wcf.acp.option.profile_show_old_username.description"><![CDATA[Zeitraum in dem bei Änderungen des Benutzernamens zusätzlich der alte Benutzername im Profil angezeigt wird.]]></item>
<item name="wcf.acp.option.members_list_users_per_page"><![CDATA[Mitglieder pro Seite]]></item>
<item name="wcf.acp.option.footer_code.description"><![CDATA[Der hier angegebene Code wird im Fußbereich jeder Seite ausgegeben. Der Footer-Code eignet sich z.B. sehr gut für die Einbindung von Diensten wie „Google Analytics“ oder „Matomo“.]]></item>
<item name="wcf.acp.option.profile_enable_visitors"><![CDATA[Profil-Besucher anzeigen]]></item>
<item name="wcf.acp.option.url_title_component_replacement"><![CDATA[URL-Ersetzungen]]></item>
- <item name="wcf.acp.option.url_title_component_replacement.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} den in URLs enthaltenen Titel durch eigene Ersetzungen verändern. Dies kann z.B. zum Ersetzen von Umlauten oder zum Expandieren von Abkürzungen genutzt werden. (Eine Ersetzung pro Zeile)<br>
+ <item name="wcf.acp.option.url_title_component_replacement.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} den in URLs enthaltenen Titel durch eigene Ersetzungen verändern. Dies kann z.B. zum Ersetzen von Umlauten oder zum Expandieren von Abkürzungen genutzt werden.<br>
Beispiele:<br>
WBB=WoltLab Burning Board<br>
GmbH=Gesellschaft mit beschränkter Haftung]]></item>
<item name="wcf.acp.option.image_proxy_insecure_only"><![CDATA[Nur Bilder aus unverschlüsselten Quellen zwischenspeichern]]></item>
<item name="wcf.acp.option.image_proxy_enable_prune"><![CDATA[Zwischengespeicherte Bilder regelmäßig löschen]]></item>
<item name="wcf.acp.option.image_proxy_host_whitelist"><![CDATA[Ausnahmen von der Zwischenspeicherung]]></item>
- <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[Die aufgeführten Domains werden von der Zwischenspeicherung ausgenommen, die eigene Domain ist implizit enthalten. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.<br>Bitte nur eine Domain pro Zeile eingeben.]]></item>
+ <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[Die aufgeführten Domains werden von der Zwischenspeicherung ausgenommen, die eigene Domain ist implizit enthalten. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.]]></item>
<item name="wcf.acp.option.share_buttons_providers"><![CDATA[Anbieter zum Teilen von Inhalten]]></item>
<item name="wcf.acp.option.show_style_changer"><![CDATA[Stil-Auswahl anzeigen]]></item>
<item name="wcf.acp.option.language_use_informal_variant"><![CDATA[Informelle Anrede verwenden]]></item>
<item name="wcf.acp.option.page_logo_link_to_app_default.description"><![CDATA[Deaktiviere{if !LANGUAGE_USE_INFORMAL_VARIANT}n Sie{/if} diese Option, damit das Logo stets auf die globale Startseite verlinkt. Die Deaktivierung entspricht dem Verhalten in früheren Versionen.]]></item>
<item name="wcf.acp.option.image_allow_external_source"><![CDATA[Bilder von externen Seiten erlauben]]></item>
<item name="wcf.acp.option.image_external_source_whitelist"><![CDATA[Erlaubte Bilder von externen Seiten]]></item>
- <item name="wcf.acp.option.image_external_source_whitelist.description"><![CDATA[Die aufgeführten Domains sind von der Blockade ausgenommen. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.<br>Bitte nur eine Domain pro Zeile eingeben.]]></item>
+ <item name="wcf.acp.option.image_external_source_whitelist.description"><![CDATA[Die aufgeführten Domains sind von der Blockade ausgenommen. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.]]></item>
<item name="wcf.acp.option.message_enable_toc"><![CDATA[Inhaltsverzeichnisse aktivieren]]></item>
<item name="wcf.acp.option.search_enable_articles"><![CDATA[Artikel sind durchsuchbar]]></item>
<item name="wcf.acp.option.search_enable_pages"><![CDATA[Seiten sind durchsuchbar]]></item>
<item name="wcf.acp.option.modification_log_expiration"><![CDATA[Speicherzeit für Änderungen]]></item>
<item name="wcf.acp.option.modification_log_expiration.description"><![CDATA[Zeitraum, nachdem alte Änderungen aus dem Änderungsprotokoll entfernt werden [0, um die Entfernung gänzlich zu deaktivieren]]]></item>
<item name="wcf.acp.option.internal_hostnames"><![CDATA[Zusätzliche interne Domains]]></item>
- <item name="wcf.acp.option.internal_hostnames.description"><![CDATA[Die aufgeführten Domains werden, neben den Domains der installierten Apps, als interne Domains angesehen. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>.<br>Bitte nur eine Domain pro Zeile eingeben.]]></item>
+ <item name="wcf.acp.option.internal_hostnames.description"><![CDATA[Die aufgeführten Domains werden, neben den Domains der installierten Apps, als interne Domains angesehen. Der Abgleich erfolgt auf Basis der strikten Übereinstimmung, optional können Subdomains mit einem Platzhalter berücksichtigt werden: <kbd>*.example.com</kbd> umfasst sowohl <kbd>example.com</kbd> als auch Subdomains wie <kbd>foo.example.com</kbd> oder <kbd>www.example.com</kbd>]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.placeholder"><![CDATA[Neuen Eintrag hinzufügen]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.error.duplicate"><![CDATA[Der Eintrag <strong>{$item}</strong> existiert bereits.]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} alle Einträge wirklich löschen?]]></item>
</category>
<category name="wcf.acp.customOption">
<item name="wcf.acp.customOption.list"><![CDATA[Eingabefelder]]></item>
<item name="wcf.acp.option.module_smiley"><![CDATA[Smilies]]></item>
<item name="wcf.acp.option.category.message.censorship"><![CDATA[Censorship]]></item>
<item name="wcf.acp.option.censored_words"><![CDATA[Censored Words]]></item>
- <item name="wcf.acp.option.censored_words.description"><![CDATA[You can specify which words will be censored. Using at least one of these words within a message causes an immediate rejection. Enter one word per line. Examples:
+ <item name="wcf.acp.option.censored_words.description"><![CDATA[You can specify which words will be censored. Using at least one of these words within a message causes an immediate rejection. Examples:
<ul class="nativeList">
<li>“*” to match parts: “wolt*” matches “woltlab”</li>
<li>“~” to find splitted parts: “wolt~” matches “wolt-lab”</li>
<item name="wcf.acp.option.password_min_score.1"><![CDATA[1: Very guessable (1 million attempts)]]></item>
<item name="wcf.acp.option.password_min_score.2"><![CDATA[2: Somewhat guessable (100 million attempts)]]></item>
<item name="wcf.acp.option.register_forbidden_usernames"><![CDATA[Reserved Usernames]]></item>
- <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[You can specify which usernames are unavailable for registration. Enter one username per line.]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[You can specify which usernames are unavailable for registration.]]></item>
<item name="wcf.acp.option.register_forbidden_emails"><![CDATA[Reserved Email Addresses]]></item>
- <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[You can specify which email addresses are unavailable for registration. Enter one email address per line.]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[You can specify which email addresses are unavailable for registration.]]></item>
<item name="wcf.acp.option.register_allowed_emails"><![CDATA[Allowed Email Addresses]]></item>
- <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[You can specify which email addresses are allowed for registration. Enter one email address per line.]]></item>
+ <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[You can specify which email addresses are allowed for registration.]]></item>
<item name="wcf.acp.option.register_username_min_length"><![CDATA[Minimum Username Length]]></item>
<item name="wcf.acp.option.register_username_max_length"><![CDATA[Maximum Username Length]]></item>
<item name="wcf.acp.option.register_username_force_ascii"><![CDATA[Require ASCII characters for usernames]]></item>
<item name="wcf.acp.option.sitemap_index_time_frame.description"><![CDATA[Maximum age of the objects to be included in the sitemap. Use 0 to disable the time frame.]]></item>
<item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximum User Title Length]]></item>
<item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reserved User Titles]]></item>
- <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[You can specify which user titles are unavailable for users. Enter one user title per line.]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[You can specify which user titles are unavailable for users.]]></item>
<item name="wcf.acp.option.profile_show_old_username"><![CDATA[Display Previous Username]]></item>
<item name="wcf.acp.option.profile_show_old_username.description"><![CDATA[Previous usernames will be displayed for the following days.]]></item>
<item name="wcf.acp.option.members_list_users_per_page"><![CDATA[Members per Page]]></item>
<item name="wcf.acp.option.footer_code.description"><![CDATA[The entered code will be appended to the page footer of your site. You can use it to embed services like “Google Analytics” or “Matomo”.]]></item>
<item name="wcf.acp.option.profile_enable_visitors"><![CDATA[Display user’s profile visitors in profile sidebar]]></item>
<item name="wcf.acp.option.url_title_component_replacement"><![CDATA[URL Replacements]]></item>
- <item name="wcf.acp.option.url_title_component_replacement.description"><![CDATA[You can replace parts of the title within the URL. You could use it to replace special characters or to expand abbreviations. Enter one replacement per line. Examples:
+ <item name="wcf.acp.option.url_title_component_replacement.description"><![CDATA[You can replace parts of the title within the URL. You could use it to replace special characters or to expand abbreviations. Examples:
<ul class="nativeList">
<li>“WBB=WoltLab Burning Board”</li>
<li>“GmbH=Gesellschaft mit beschränkter Haftung”</li>
<item name="wcf.acp.option.image_proxy_insecure_only"><![CDATA[Store images from insecure sources only]]></item>
<item name="wcf.acp.option.image_proxy_enable_prune"><![CDATA[Remove Cached Images Regularly]]></item>
<item name="wcf.acp.option.image_proxy_host_whitelist"><![CDATA[Image proxy whitelist]]></item>
- <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[The listed domains will not be handled by the image proxy, the current domain is implicitly added. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.<br>Enter one domain per line only.]]></item>
+ <item name="wcf.acp.option.image_proxy_host_whitelist.description"><![CDATA[The listed domains will not be handled by the image proxy, the current domain is implicitly added. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.]]></item>
<item name="wcf.acp.option.share_buttons_providers"><![CDATA[Share Button Providers]]></item>
<item name="wcf.acp.option.show_style_changer"><![CDATA[Enable style changer]]></item>
<item name="wcf.acp.option.language_use_informal_variant"><![CDATA[Use informal language variant]]></item>
<item name="wcf.acp.option.page_logo_link_to_app_default.description"><![CDATA[Disabling this option will cause the link to always point to the global landing page instead. This option enforces the behavior known from previous versions when disabled.]]></item>
<item name="wcf.acp.option.image_allow_external_source"><![CDATA[Allow images from external sites]]></item>
<item name="wcf.acp.option.image_external_source_whitelist"><![CDATA[Allowed images from external sites]]></item>
- <item name="wcf.acp.option.image_external_source_whitelist.description"><![CDATA[The listed domains will be exempt from blocking. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.<br>Enter one domain per line only.]]></item>
+ <item name="wcf.acp.option.image_external_source_whitelist.description"><![CDATA[The listed domains will be exempt from blocking. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.]]></item>
<item name="wcf.acp.option.message_enable_toc"><![CDATA[Enable the table of contents]]></item>
<item name="wcf.acp.option.search_enable_articles"><![CDATA[Articles are searchable]]></item>
<item name="wcf.acp.option.search_enable_pages"><![CDATA[Pages are searchable]]></item>
<item name="wcf.acp.option.modification_log_expiration"><![CDATA[Storage Time for Modification Logs]]></item>
<item name="wcf.acp.option.modification_log_expiration.description"><![CDATA[Modification logs will be deleted after the entered amount of days. To disable deleting old modification logs, enter “0”.]]></item>
<item name="wcf.acp.option.internal_hostnames"><![CDATA[Additional Internal Domains]]></item>
- <item name="wcf.acp.option.internal_hostnames.description"><![CDATA[The listed domains will be considered as internal domains in addition to the domains in use by installed apps. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.<br>Enter one domain per line only.]]></item>
+ <item name="wcf.acp.option.internal_hostnames.description"><![CDATA[The listed domains will be considered as internal domains in addition to the domains in use by installed apps. Hostnames are exact matches only, a leading wildcard can be used to exclude an entire domain: <kbd>*.example.com</kbd> matches <kbd>example.com</kbd> and subdomains such as <kbd>foo.example.com</kbd> or <kbd>www.example.com</kbd>.]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.placeholder"><![CDATA[Add new entry]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.error.duplicate"><![CDATA[The entry <strong>{$item}</strong> already exists.]]></item>
+ <item name="wcf.acp.option.type.lineBreakSeparatedText.clearList.confirmMessage"><![CDATA[Do you really want to delete all entries?]]></item>
</category>
<category name="wcf.acp.customOption">
<item name="wcf.acp.customOption.list"><![CDATA[Option Fields]]></item>