dnd5-scripts/timeline-widget.js
2026-06-17 22:32:48 +03:00

320 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

await dv.view("scripts/timeline");
await dv.view("scripts/locations");
// Уровень видимости "глобальный" для этого файла
let locationCounter = 0; // счётчик, если нужно что-то уникальное
async function updateTimeline(dv) {
// Удаляем предыдущую диаграмму
const oldContainer = dv.container.querySelector(".mermaid-timeline");
if (oldContainer) {
oldContainer.remove();
}
const locations = Array.from(dv.container.querySelectorAll(".location")).map(
(input) => input.value
);
const characters = Array.from(
dv.container.querySelectorAll(".character")
).map((input) => input.value);
const currentDate = dv.container.querySelector(".currentDate").value;
const startDate = dv.container.querySelector(".startDate").value;
const endDate = dv.container.querySelector(".endDate").value;
const pcsParticipationOnly = dv.container.querySelector(
".pcsParticipationOnly"
).checked;
const includeRegionsParticipants = dv.container.querySelector(
".includeRegionsParticipants"
).checked;
// Вызываем рендеринг таймлайна (важно, чтобы dv был виден в этой функции)
dv.timeline.showTimeline(
dv,
locations,
characters,
currentDate,
startDate,
endDate,
pcsParticipationOnly,
includeRegionsParticipants
);
}
/**
* Заполняем общий <locationsDataList> всеми заметками.
*/
function setupAutocomplete(dv, locationsDataList, tag) {
// Получаем список всех заметок
const notes = dv.pages(tag).file.name; // это массив названий файлов
// Очищаем текущие опции (на случай повторных вызовов)
locationsDataList.innerHTML = "";
notes.forEach((note) => {
const option = document.createElement("option");
option.value = note;
locationsDataList.appendChild(option);
});
}
/**
* Добавляет новый блок с <input> для локации
*/
function addListFormField(container, inputName, dataListName) {
// locationsDataList
const div = document.createElement("div");
div.className = "locationContainer";
// Обратите внимание, указываем list="locationsDataList", а <datalist id="locationsDataList">
// будет один на всю форму
div.innerHTML = `
<input type="text" class="${inputName}"
placeholder="Введите название" list="${dataListName}" />
<button type="button" class="removeDiv">Удалить</button>
`;
container.appendChild(div);
// При удалении блока убираем сам <div>
div.querySelector(".removeDiv").addEventListener("click", () => {
div.remove();
});
}
async function run_show_timeline_widget(...args) {
let dv = args[0].arg1; // Чтобы не путаться, явно назовём dvParam
let defaultLocations = args[0].arg2 || [];
let defaultCurrentDate = args[0].arg3 || "";
let defaultStartDate = args[0].arg4 || "";
let defaultEndDate = args[0].arg5 || "";
let defaultPcsParticipationOnly = args[0].arg6 || false;
let defaultIncludeRegionsParticipants = args[0].arg7 || false;
let fullWidth = args[0].arg8 || false;
// Добавляем стили для полной ширины (чтобы диаграмму можно было растянуть максимально), если fullWidth = true
const markdownPreviewSizer = dv.container.closest(
".markdown-preview-view .markdown-preview-sizer"
);
if (markdownPreviewSizer) {
if (fullWidth) {
markdownPreviewSizer.style.maxWidth = "none";
markdownPreviewSizer.style.margin = "0 1em";
} else {
markdownPreviewSizer.style.maxWidth = "";
markdownPreviewSizer.style.margin = "";
}
}
// Сохраним глобально dv, если нужно использовать вне этой функции
window.dv = dv;
// Формируем HTML формы
dv.container.innerHTML = `
<style>
/* Главный контейнер формы */
.form-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 0.5rem;
max-width: 800px;
margin: 0 auto;
}
/* Контейнер для полей ввода */
.field-container {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
/* Общий стиль для кнопок */
button {
padding: 0.3rem 0.8rem;
font-size: 0.9rem;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f9f9f9;
cursor: pointer;
}
button:hover {
background-color: #e0e0e0;
}
/* Группы полей */
.form-group {
display: flex;
flex-direction: column;
gap: 0.55rem;
padding: 0.5rem;
border: 1px solid #eee;
border-radius: 4px;
}
.form-group-2 {
display: flex;
flex-direction: column;
gap: 0.3em;
}
/* Поля ввода */
.form-input {
padding: 0.3rem;
font-size: 0.9rem;
border: 1px solid #ccc;
border-radius: 4px;
}
/* Чекбоксы */
.form-checkbox {
margin-left: 0.3rem;
}
/* Кнопки добавления */
.add-button {
align-self: flex-start;
margin-top: 0.3rem;
}
/* Добавьте контейнер для кнопки обновления */
.update-button-container {
grid-column: 1 / -1;
display: flex;
justify-content: center;
margin: 0.3rem 0 0.8rem 0;
}
.update-button {
width: 200px;
}
/* Метки полей */
label {
font-size: 0.9rem;
margin-bottom: 0.2rem;
}
.update-button-container {
grid-column: 1 / -1;
display: flex;
justify-content: center;
gap: 2em;
margin: 0.3rem 0 0.8rem 0;
}
</style>
<form id="timelineForm" class="form-container">
<div class="form-group">
<div class="form-group-2">
<label>Локации:</label>
<div class="locationsContainer field-container"></div>
<button type="button" class="addLocation add-button">Добавить локацию</button>
</div>
</div>
<div class="form-group">
<div class="form-group-2">
<label>Участвующие персонажи:</label>
<div class="characters field-container"></div>
<button type="button" class="addCharacter add-button">Добавить персонажа</button>
</div>
<div class="form-group-2">
<label for="pcsParticipationOnly">Только с участием ИП:</label>
<input type="checkbox" name="pcsParticipationOnly"
${
defaultPcsParticipationOnly ? "checked" : ""
} class="pcsParticipationOnly form-checkbox" />
</div>
<div class="form-group-2">
<label for="includeRegionsParticipants">Включить regions и participants:</label>
<input type="checkbox" name="includeRegionsParticipants"
${
defaultIncludeRegionsParticipants ? "checked" : ""
} class="includeRegionsParticipants form-checkbox" />
</div>
</div>
<div class="form-group">
<div class="form-group-2">
<label for="currentDate">Текущая дата:</label>
<input type="text" name="currentDate" value="${defaultCurrentDate}" class="currentDate form-input" />
</div>
<div class="form-group-2">
<label for="startDate">Дата начала:</label>
<input type="text" name="startDate" value="${defaultStartDate}" class="startDate form-input" />
</div>
<div class="form-group-2">
<label for="endDate">Дата окончания:</label>
<input type="text" name="endDate" value="${defaultEndDate}" class="endDate form-input" />
</div>
</div>
<datalist id="locationsDataList"></datalist>
<datalist id="charactersDataList"></datalist>
<div class="update-button-container">
<button type="button" class="updateButton update-button">Обновить график</button>
<button type="button" class="copyMermaidButton update-button">Копировать mermaid</button>
</div>
</form>
<div class="timelineOutput"></div>
`;
const locationsContainer = dv.container.querySelector(".locationsContainer");
const addLocationButton = dv.container.querySelector(".addLocation");
const locationsDataList = dv.container.querySelector("#locationsDataList"); // общий datalist
const charactersContainer = dv.container.querySelector(".characters");
const addCharacterButton = dv.container.querySelector(".addCharacter");
const charactersDataList = dv.container.querySelector("#charactersDataList"); // общий datalist
// Заполним общий datalist всеми заметками (автозаполнение)
setupAutocomplete(dv, locationsDataList, "#location");
setupAutocomplete(dv, charactersDataList, "#npc");
// Инициализируем поля по умолчанию
defaultLocations.forEach((location) => {
addListFormField(locationsContainer, "location", "locationsDataList");
locationsContainer.lastChild.querySelector(".location").value = location;
});
// Добавляем новое поле по кнопке
addLocationButton.addEventListener("click", () => {
addListFormField(locationsContainer, "location", "locationsDataList");
});
// Добавляем новое поле по кнопке
addCharacterButton.addEventListener("click", () => {
addListFormField(charactersContainer, "character", "charactersDataList");
});
// Обновляем график при нажатии на кнопку
dv.container
.querySelector(".updateButton")
.addEventListener("click", () => updateTimeline(dv));
dv.container
.querySelector(".copyMermaidButton")
.addEventListener("click", () => {
if (window.lastGeneratedMermaidCode) {
navigator.clipboard
.writeText(window.lastGeneratedMermaidCode)
.then(() => {
// Можно добавить визуальную обратную связь
const button = dv.container.querySelector(".copyMermaidButton");
const originalText = button.textContent;
button.textContent = "Скопировано!";
setTimeout(() => {
button.textContent = originalText;
}, 1000);
})
.catch((err) => {
console.error("Ошибка при копировании: ", err);
});
}
});
updateTimeline(dv);
}
await run_show_timeline_widget(input);