Skip to content

Commit c19381c

Browse files
committed
Merge branch 'refs/heads/6.2' into 6.2-option-handler
# Conflicts: # wcfsetup/install/files/acp/templates/userGroupAdd.tpl
2 parents fcf08ed + 4eaa3d7 commit c19381c

File tree

39 files changed

+1060
-628
lines changed

39 files changed

+1060
-628
lines changed

.github/workflows/javascript.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ jobs:
7171
- name: "Check '@fancyapps/ui'"
7272
run: |
7373
diff -wu wcfsetup/install/files/js/3rdParty/fancybox/fancybox.umd.js node_modules/@fancyapps/ui/dist/fancybox/fancybox.umd.js
74+
- name: "Check 'sortablejs'"
75+
run: |
76+
diff -wu wcfsetup/install/files/js/3rdParty/Sortable.min.js node_modules/sortablejs/Sortable.min.js

com.woltlab.wcf/fileDelete.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<file>acp/images/wcfLogoWhite.svg</file>
2727
<file>acp/install.php</file>
2828
<file>acp/js/WCF.ACP.User.js</file>
29+
<file>acp/js/WCF.ACP.Language.js</file>
30+
<file>acp/js/WCF.ACP.Language.min.js</file>
2931
<file>acp/js/wcombined.min.js</file>
3032
<file>acp/post_install.php</file>
3133
<file>acp/pre_update_com.woltlab.wcf_2.1.php</file>

com.woltlab.wcf/templates/flexibleCategoryList.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
{/foreach}
3333
</ol>
3434
<script data-relocate="true">
35-
$(function() {
36-
new WCF.Category.FlexibleCategoryList('{$flexibleCategoryListID}');
35+
require(['WoltLabSuite/Core/Component/Category/Flexible'], ({ FlexibleCategoryList }) => {
36+
new FlexibleCategoryList('{$flexibleCategoryListID}');
3737
});
3838
</script>

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@types/jquery": "^3.5.32",
2323
"@types/pica": "5.1.3",
2424
"@types/prismjs": "^1.26.5",
25+
"@types/sortablejs": "^1.15.8",
2526
"@types/supercluster": "^7.1.3",
2627
"@types/twitter-for-web": "0.0.6",
2728
"@woltlab/editor": "git+https://github.com/WoltLab/editor.git#54d7052a834e3b41e5fdacd8a17a4a4ea1b6e76c",
@@ -33,6 +34,7 @@
3334
"perfect-scrollbar": "^1.5.6",
3435
"qr-creator": "^1.0.0",
3536
"reflect-metadata": "^0.2.2",
37+
"sortablejs": "^1.15.4",
3638
"tabbable": "^6.2.0",
3739
"tslib": "^2.8.1",
3840
"webpack-cli": "^5.1.4"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Handles the dialog to set tags as synonyms.
3+
*
4+
* @author Olaf Braun
5+
* @copyright 2001-2024 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
*/
8+
9+
import { add as addEvent } from "WoltLabSuite/Core/Event/Handler";
10+
import { ClipboardActionData } from "WoltLabSuite/Core/Controller/Clipboard/Data";
11+
import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog";
12+
import { getPhrase } from "WoltLabSuite/Core/Language";
13+
import DomUtil from "WoltLabSuite/Core/Dom/Util";
14+
import { dboAction } from "WoltLabSuite/Core/Ajax";
15+
16+
export function init() {
17+
addEvent("com.woltlab.wcf.clipboard", "com.woltlab.wcf.tag", (actionData: { data: ClipboardActionData }) => {
18+
if (actionData.data.actionName === "com.woltlab.wcf.tag.setAsSynonyms") {
19+
openDialog(actionData.data.parameters.objectIDs, actionData.data.parameters.template);
20+
}
21+
});
22+
}
23+
24+
function openDialog(objectIDs: number[], template: string) {
25+
const dialog = dialogFactory().fromHtml(template).asConfirmation();
26+
dialog.addEventListener("validate", (event) => {
27+
const checked = dialog.querySelectorAll("input[type=radio]:checked").length > 0;
28+
event.detail.push(Promise.resolve(checked));
29+
30+
DomUtil.innerError(
31+
dialog.querySelector(".containerBoxList")!,
32+
checked ? undefined : getPhrase("wcf.global.form.error.empty"),
33+
);
34+
});
35+
dialog.addEventListener("primary", () => {
36+
void dboAction("setAsSynonyms", "wcf\\data\\tag\\TagAction")
37+
.objectIds(objectIDs)
38+
.payload({
39+
tagID: dialog.querySelector<HTMLInputElement>('input[name="tagID"]:checked')!.value,
40+
})
41+
.dispatch()
42+
.then(() => {
43+
window.location.reload();
44+
});
45+
});
46+
47+
dialog.show(getPhrase("wcf.acp.tag.setAsSynonyms"));
48+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Handles the dialog to copy a user group.
3+
*
4+
* @author Olaf Braun
5+
* @copyright 2001-2024 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
*/
8+
9+
import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog";
10+
11+
interface CopyResponse {
12+
groupID: number;
13+
redirectURL: string;
14+
}
15+
16+
export function init() {
17+
const button = document.querySelector<HTMLElement>(".jsButtonUserGroupCopy");
18+
button?.addEventListener("click", () => {
19+
void dialogFactory()
20+
.usingFormBuilder()
21+
.fromEndpoint<CopyResponse>(button.dataset.endpoint!)
22+
.then((result) => {
23+
if (result.ok) {
24+
window.location.href = result.result.redirectURL;
25+
}
26+
});
27+
});
28+
}

ts/WoltLabSuite/Core/Acp/Form/Builder/Field/Devtools/Project/Instructions.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,7 @@ class Instructions {
294294
new UiSortableList({
295295
containerId: instructionListContainer.id,
296296
isSimpleSorting: true,
297-
options: {
298-
toleranceElement: "> div",
299-
},
297+
toleranceElement: "> div",
300298
});
301299

302300
if (instructionsData.type === "update") {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Handles language item list.
3+
*
4+
* @author Olaf Braun
5+
* @copyright 2001-2024 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
*/
8+
9+
import { dboAction } from "WoltLabSuite/Core/Ajax";
10+
import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog";
11+
import { getPhrase } from "WoltLabSuite/Core/Language";
12+
import { confirmationFactory } from "WoltLabSuite/Core/Component/Confirmation";
13+
import { show as showNotification } from "WoltLabSuite/Core/Ui/Notification";
14+
15+
interface BeginEditResponse {
16+
languageItem: string;
17+
isCustomLanguageItem: boolean;
18+
template: string;
19+
}
20+
21+
export function init() {
22+
document.querySelectorAll<HTMLElement>(".jsLanguageItem").forEach((button) => {
23+
button.addEventListener("click", () => {
24+
void beginEdit(parseInt(button.dataset.languageItemId!, 10));
25+
});
26+
});
27+
}
28+
29+
async function beginEdit(languageItemID: number) {
30+
const result = (await dboAction("prepareEdit", "wcf\\data\\language\\item\\LanguageItemAction")
31+
.objectIds([languageItemID])
32+
.dispatch()) as BeginEditResponse;
33+
34+
const dialog = dialogFactory()
35+
.fromHtml(result.template)
36+
.asPrompt(
37+
result.isCustomLanguageItem
38+
? {
39+
extra: getPhrase("wcf.global.button.delete"),
40+
}
41+
: undefined,
42+
);
43+
44+
dialog.addEventListener("extra", () => {
45+
void confirmationFactory()
46+
.custom(getPhrase("wcf.global.confirmation.title"))
47+
.message(getPhrase("wcf.acp.language.item.delete.confirmMessage"))
48+
.then((result) => {
49+
if (result) {
50+
void dboAction("deleteCustomLanguageItems", "wcf\\data\\language\\item\\LanguageItemAction")
51+
.objectIds([languageItemID])
52+
.dispatch();
53+
54+
dialog.close();
55+
56+
window.location.reload();
57+
}
58+
});
59+
});
60+
61+
dialog.addEventListener("primary", () => {
62+
const languageItemValue = dialog.querySelector<HTMLInputElement>('[name="languageItemValue"]')?.value;
63+
const languageCustomItemValue = dialog.querySelector<HTMLInputElement>('[name="languageCustomItemValue"]')?.value;
64+
const languageUseCustomValue = dialog.querySelector<HTMLInputElement>('[name="languageUseCustomValue"]')?.checked;
65+
66+
void dboAction("edit", "wcf\\data\\language\\item\\LanguageItemAction")
67+
.objectIds([languageItemID])
68+
.payload({
69+
languageItemValue: languageItemValue ?? null,
70+
languageCustomItemValue: languageCustomItemValue ?? null,
71+
languageUseCustomValue: languageUseCustomValue ?? null,
72+
})
73+
.dispatch()
74+
.then(() => {
75+
showNotification();
76+
});
77+
});
78+
79+
dialog.show(result.languageItem);
80+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Handles selection of categories.
3+
*
4+
* @author Olaf Braun
5+
* @copyright 2001-2024 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.2
8+
*/
9+
import { triggerEvent } from "../../Core";
10+
11+
export class FlexibleCategoryList {
12+
readonly #list: HTMLElement;
13+
readonly #categories = new Map<HTMLInputElement, HTMLInputElement[]>();
14+
readonly #parentCategories = new Map<HTMLInputElement, HTMLInputElement>();
15+
16+
constructor(elementID: string) {
17+
this.#list = document.getElementById(elementID)!;
18+
19+
// No nested categories
20+
if (!this.#list.querySelector("li li")) {
21+
this.#list.classList.add("flexibleCategoryListDisabled");
22+
return;
23+
}
24+
25+
this.#buildStructure();
26+
27+
this.#list.querySelectorAll("input:checked").forEach((input: HTMLInputElement) => {
28+
triggerEvent(input, "change");
29+
});
30+
}
31+
32+
#buildStructure() {
33+
this.#list.querySelectorAll(".jsCategory").forEach((category: HTMLInputElement) => {
34+
category.addEventListener("change", () => {
35+
this.#updateSelection(category);
36+
});
37+
this.#categories.set(category, []);
38+
39+
category
40+
.closest("li")!
41+
.querySelectorAll<HTMLInputElement>(".jsChildCategory")
42+
.forEach((childCategory) => {
43+
this.#categories.get(category)!.push(childCategory);
44+
this.#categories.set(childCategory, []);
45+
this.#parentCategories.set(childCategory, category);
46+
47+
childCategory.addEventListener("change", () => {
48+
this.#updateSelection(childCategory);
49+
});
50+
51+
childCategory
52+
.closest("li")!
53+
.querySelectorAll<HTMLInputElement>(".jsSubChildCategory")
54+
.forEach((subChildCategory) => {
55+
this.#categories.get(childCategory)!.push(subChildCategory);
56+
this.#parentCategories.set(subChildCategory, childCategory);
57+
58+
subChildCategory.addEventListener("change", () => {
59+
this.#updateSelection(subChildCategory);
60+
});
61+
});
62+
});
63+
});
64+
}
65+
66+
#updateSelection(category: HTMLInputElement) {
67+
const parentCategory = this.#parentCategories.get(category);
68+
69+
if (category.checked) {
70+
if (parentCategory) {
71+
parentCategory.checked = true;
72+
73+
this.#updateSelection(parentCategory);
74+
}
75+
} else {
76+
// uncheck child categories
77+
this.#categories.get(category)?.forEach((childCategory) => {
78+
childCategory.checked = false;
79+
80+
this.#updateSelection(childCategory);
81+
});
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)