Skip to content

Commit

Permalink
Merge branch 'master' into require-changelog-label
Browse files Browse the repository at this point in the history
  • Loading branch information
krisstern authored Feb 15, 2025
2 parents e8619cb + c2f6c00 commit 27ebedc
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 124 deletions.
7 changes: 4 additions & 3 deletions core/src/main/java/hudson/util/AtomicFileWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.AccessDeniedException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
Expand Down Expand Up @@ -246,11 +247,11 @@ private static void move(Path source, Path destination) throws IOException {
// Both files are on the same filesystem, so this should not happen.
LOGGER.log(Level.WARNING, e, () -> "Atomic move " + source + " → " + destination + " not supported. Falling back to non-atomic move.");
atomicMoveSupported = false;
} catch (AccessDeniedException e) {
LOGGER.log(Level.INFO, e, () -> "Move " + source + " → " + destination + " failed, perhaps due to a temporary file lock. Falling back to non-atomic move.");
}
}
if (!atomicMoveSupported) {
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
}
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
}

private static final class CleanupChecker implements Runnable {
Expand Down
253 changes: 132 additions & 121 deletions src/main/js/add-item.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import $ from "jquery";
import { createElementFromHtml } from "@/util/dom";

var getItems = function () {
var d = $.Deferred();
$.get("itemCategories?depth=3&iconStyle=icon-xlg").done(function (data) {
d.resolve(data);
});
return d.promise();
const nameInput = document.querySelector(`#createItem input[name="name"]`);
const copyFromInput = document.querySelector(`#createItem input[name="from"]`);
const copyRadio = document.querySelector(`#createItem input[value="copy"]`);

const getItems = function () {
return fetch("itemCategories?depth=3&iconStyle=icon-xlg").then((response) =>
response.json(),
);
};

var jRoot = $("head").attr("data-rooturl");
const jRoot = document.querySelector("head").getAttribute("data-rooturl");

$.when(getItems()).done(function (data) {
$(function () {
document.addEventListener("DOMContentLoaded", () => {
getItems().then((data) => {
//////////////////////////
// helper functions...

function parseResponseFromCheckJobName(data) {
var html = $.parseHTML(data);
var element = html[0];
if (element !== undefined) {
return $(element).text();
var parser = new DOMParser();
var html = parser.parseFromString(data, "text/html");
var element = html.body.firstChild;
if (element) {
return element.textContent;
}
return undefined;
}
Expand All @@ -38,50 +41,48 @@ $.when(getItems()).done(function (data) {
}

function getCopyFromValue() {
return $('input[type="text"][name="from"]', "#createItem").val();
return copyFromInput.value;
}

function isItemNameEmpty() {
var itemName = $('input[name="name"]', "#createItem").val();
return itemName === "" ? true : false;
var itemName = nameInput.value;
return itemName.trim() === "";
}

function getFieldValidationStatus(fieldId) {
return $("#" + fieldId).data("valid");
return document.querySelector("#" + fieldId)?.dataset.valid === "true";
}

function setFieldValidationStatus(fieldId, status) {
$("#" + fieldId).data("valid", status);
const element = document.querySelector("#" + fieldId);
if (element) {
element.dataset.valid = status;
}
}

function activateValidationMessage(messageId, context, message) {
if (message !== undefined && message !== "") {
$(messageId, context).text("» " + message);
document.querySelector(context + " " + messageId).textContent =
"» " + message;
}
cleanValidationMessages(context);
$(messageId).removeClass("input-message-disabled");
enableSubmit(false);
document
.querySelector(messageId)
.classList.remove("input-message-disabled");
refreshSubmitButtonState();
}

function cleanValidationMessages(context) {
$(context)
.find(".input-validation-message")
.addClass("input-message-disabled");
document
.querySelectorAll(context + " .input-validation-message")
.forEach((element) => element.classList.add("input-message-disabled"));
}

function enableSubmit(status) {
var btn = $(".bottom-sticker-inner button[type=submit]");
if (status === true) {
if (btn.hasClass("disabled")) {
btn.removeClass("disabled");
btn.prop("disabled", false);
}
} else {
if (!btn.hasClass("disabled")) {
btn.addClass("disabled");
btn.prop("disabled", true);
}
}
function refreshSubmitButtonState() {
const submitButton = document.querySelector(
".bottom-sticker-inner button[type=submit]",
);
submitButton.disabled = !getFormValidationStatus();
}

function getFormValidationStatus() {
Expand All @@ -95,36 +96,42 @@ $.when(getItems()).done(function (data) {
}

function cleanItemSelection() {
$(".categories").find('li[role="radio"]').attr("aria-checked", "false");
$("#createItem")
.find('input[type="radio"][name="mode"]')
.removeAttr("checked");
$(".categories").find(".active").removeClass("active");
document
.querySelector('.categories li[role="radio"]')
.setAttribute("aria-checked", "false");
document
.querySelector('#createItem input[type="radio"][name="mode"]')
.removeAttribute("checked");
document.querySelectorAll(".categories .active").forEach((item) => {
item.classList.remove("active");
});
setFieldValidationStatus("items", false);
}

function cleanCopyFromOption() {
$("#createItem")
.find('input[type="radio"][value="copy"]')
.removeAttr("checked");
$('input[type="text"][name="from"]', "#createItem").val("");
copyRadio?.removeAttribute("checked");
if (copyFromInput) {
copyFromInput.value = "";
}
setFieldValidationStatus("from", false);
}

//////////////////////////////////
// Draw functions

function drawCategory(category) {
var $category = $("<div/>")
.addClass("category")
.attr("id", "j-add-item-type-" + cleanClassName(category.id));
var $items = $("<ul/>").addClass("j-item-options");
var $catHeader = $('<div class="header" />');
var $category = createElementFromHtml("<div class='category' />");
$category.setAttribute(
"id",
"j-add-item-type-" + cleanClassName(category.id),
);
var $items = createElementFromHtml(`<ul class="j-item-options" />`);
var $catHeader = createElementFromHtml(`<div class="header" />`);
var title = "<h2>" + category.name + "</h2>";
var description = "<p>" + category.description + "</p>";

// Add items
$.each(category.items, function (i, elem) {
category.items.forEach((elem) => {
$items.append(drawItem(elem));
});

Expand Down Expand Up @@ -169,17 +176,13 @@ $.when(getItems()).done(function (data) {
cleanCopyFromOption();
cleanItemSelection();

$(this).attr("aria-checked", "true");
$(this).find('input[type="radio"][name="mode"]').prop("checked", true);
$(this).addClass("active");
item.setAttribute("aria-checked", "true");
radio.checked = true;
item.classList.add("active");

setFieldValidationStatus("items", true);
if (!getFieldValidationStatus("name")) {
$('input[name="name"][type="text"]', "#createItem").focus();
} else {
if (getFormValidationStatus()) {
enableSubmit(true);
}
if (getFieldValidationStatus("name")) {
refreshSubmitButtonState();
}
}

Expand Down Expand Up @@ -253,98 +256,106 @@ $.when(getItems()).done(function (data) {
}

// The main panel content is hidden by default via an inline style. We're ready to remove that now.
$("#add-item-panel").removeAttr("style");
document.querySelector("#add-item-panel").removeAttribute("style");

// Render all categories
var $categories = $("div.categories");
$.each(data.categories, function (i, elem) {
drawCategory(elem).appendTo($categories);
var $categories = document.querySelector("div.categories");
data.categories.forEach((elem) => {
$categories.append(drawCategory(elem));
});

// Focus
$("#add-item-panel").find("#name").focus();
document.querySelector("#add-item-panel #name").focus();

// Init NameField
$('input[name="name"]', "#createItem").on("blur input", function () {
function nameFieldEvent() {
if (!isItemNameEmpty()) {
var itemName = $('input[name="name"]', "#createItem").val();
$.get("checkJobName", { value: itemName }).done(function (data) {
var message = parseResponseFromCheckJobName(data);
if (message !== "") {
activateValidationMessage(
"#itemname-invalid",
".add-item-name",
message,
);
} else {
cleanValidationMessages(".add-item-name");
setFieldValidationStatus("name", true);
if (getFormValidationStatus()) {
enableSubmit(true);
}
}
});
var itemName = nameInput.value;

fetch(`checkJobName?value=${encodeURIComponent(itemName)}`).then(
(response) => {
response.text().then((data) => {
var message = parseResponseFromCheckJobName(data);
if (message !== "") {
activateValidationMessage(
"#itemname-invalid",
".add-item-name",
message,
);
} else {
cleanValidationMessages(".add-item-name");
setFieldValidationStatus("name", true);
refreshSubmitButtonState();
}
});
},
);
} else {
enableSubmit(false);
setFieldValidationStatus("name", false);
cleanValidationMessages(".add-item-name");
activateValidationMessage("#itemname-required", ".add-item-name");
refreshSubmitButtonState();
}
});
}

nameInput.addEventListener("blur", nameFieldEvent);
nameInput.addEventListener("input", nameFieldEvent);

// Init CopyFromField
$('input[name="from"]', "#createItem").on("blur input", function () {
function copyFromFieldEvent() {
if (getCopyFromValue() === "") {
$("#createItem")
.find('input[type="radio"][value="copy"]')
.removeAttr("checked");
copyRadio.removeAttribute("checked");
} else {
cleanItemSelection();
$("#createItem")
.find('input[type="radio"][value="copy"]')
.prop("checked", true);
copyRadio.setAttribute("checked", true);
setFieldValidationStatus("from", true);
if (!getFieldValidationStatus("name")) {
activateValidationMessage("#itemname-required", ".add-item-name");
setTimeout(function () {
var parentName = $('input[name="from"]', "#createItem").val();
$.get("job/" + parentName + "/api/json?tree=name").done(
function (data) {
if (data.name === parentName) {
//if "name" is invalid, but "from" is a valid job, then switch focus to "name"
$('input[name="name"][type="text"]', "#createItem").focus();
}
var parentName = copyFromInput.value;

fetch("job/" + parentName + "/api/json?tree=name").then(
(response) => {
response.json().then((data) => {
if (data.name === parentName) {
//if "name" is invalid, but "from" is a valid job, then switch focus to "name"
nameInput.focus();
}
});
},
);
}, 400);
} else {
if (getFormValidationStatus()) {
enableSubmit(true);
}
refreshSubmitButtonState();
}
}
});
}

copyFromInput?.addEventListener("blur", copyFromFieldEvent);
copyFromInput?.addEventListener("input", copyFromFieldEvent);

// Client-side validation
$("#createItem").submit(function (event) {
if (!getFormValidationStatus()) {
event.preventDefault();
if (!getFieldValidationStatus("name")) {
activateValidationMessage("#itemname-required", ".add-item-name");
$('input[name="name"][type="text"]', "#createItem").focus();
} else {
if (
!getFieldValidationStatus("items") &&
!getFieldValidationStatus("from")
) {
activateValidationMessage("#itemtype-required", ".add-item-name");
$('input[name="name"][type="text"]', "#createItem").focus();
document
.querySelector("#createItem")
.addEventListener("submit", function (event) {
if (!getFormValidationStatus()) {
event.preventDefault();
if (!getFieldValidationStatus("name")) {
activateValidationMessage("#itemname-required", ".add-item-name");
nameInput.focus();
} else {
if (
!getFieldValidationStatus("items") &&
!getFieldValidationStatus("from")
) {
activateValidationMessage("#itemtype-required", ".add-item-name");
nameInput.focus();
}
}
}
}
});
});

// Disable the submit button
enableSubmit(false);
refreshSubmitButtonState();
});
});

0 comments on commit 27ebedc

Please sign in to comment.