mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
v1.198.4
This commit is contained in:
@@ -136,7 +136,15 @@ class BaseParserFeature extends BaseParser {
|
||||
}
|
||||
handleStack();
|
||||
|
||||
if (pres.length) state.entity.prerequisite = pres;
|
||||
if (!pres.length) return;
|
||||
|
||||
const presDeduped = [];
|
||||
pres.forEach(pre => {
|
||||
if (presDeduped.some(it => CollectionUtil.deepEquals(pre, it))) return;
|
||||
presDeduped.push(pre);
|
||||
});
|
||||
|
||||
state.entity.prerequisite = presDeduped;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,8 @@ class SpellParser extends BaseParser {
|
||||
unit = unit.toLowerCase().trim();
|
||||
switch (unit) {
|
||||
case "days":
|
||||
case "weeks":
|
||||
case "months":
|
||||
case "years":
|
||||
case "hours":
|
||||
case "minutes":
|
||||
@@ -344,6 +346,8 @@ class SpellParser extends BaseParser {
|
||||
case "rounds": return unit.slice(0, -1);
|
||||
|
||||
case "day":
|
||||
case "week":
|
||||
case "month":
|
||||
case "year":
|
||||
case "hour":
|
||||
case "minute":
|
||||
@@ -441,7 +445,7 @@ class SpellParser extends BaseParser {
|
||||
if (dur.toLowerCase() === "special") return stats.duration = [{type: "special"}];
|
||||
if (dur.toLowerCase() === "permanent") return stats.duration = [{type: "permanent"}];
|
||||
|
||||
const mConcOrUpTo = /^(concentration, )?up to (\d+|an?) (hour|minute|turn|round|week|day|year)(?:s)?$/i.exec(dur);
|
||||
const mConcOrUpTo = /^(concentration, )?up to (\d+|an?) (hour|minute|turn|round|week|month|day|year)(?:s)?$/i.exec(dur);
|
||||
if (mConcOrUpTo) {
|
||||
const amount = mConcOrUpTo[2].toLowerCase().startsWith("a") ? 1 : Number(mConcOrUpTo[2]);
|
||||
const out = {type: "timed", duration: {type: this._getCleanTimeUnit(mConcOrUpTo[3], true, options), amount}, concentration: true};
|
||||
@@ -450,7 +454,7 @@ class SpellParser extends BaseParser {
|
||||
return stats.duration = [out];
|
||||
}
|
||||
|
||||
const mTimed = /^(\d+) (hour|minute|turn|round|week|day|year)(?:s)?$/i.exec(dur);
|
||||
const mTimed = /^(\d+) (hour|minute|turn|round|week|month|day|year)(?:s)?$/i.exec(dur);
|
||||
if (mTimed) return stats.duration = [{type: "timed", duration: {type: this._getCleanTimeUnit(mTimed[2], true, options), amount: Number(mTimed[1])}}];
|
||||
|
||||
const mDispelledTriggered = /^until dispelled( or triggered)?$/i.exec(dur);
|
||||
|
||||
260
js/converter.js
260
js/converter.js
@@ -801,7 +801,7 @@ class BackgroundConverter extends BaseConverter {
|
||||
}
|
||||
}
|
||||
// region sample
|
||||
BackgroundConverter._SAMPLE_TEXT = `Giant Foundling
|
||||
BackgroundConverter._SAMPLE_TEXT = `Giant Foundling
|
||||
Skill Proficiencies: Intimidation, Survival
|
||||
Languages: Giant and one other language of your choice
|
||||
Equipment: A backpack, a set of traveler’s clothes, a small stone or sprig that reminds you of home, and a pouch containing 10 gp
|
||||
@@ -996,117 +996,139 @@ class ConverterUi extends BaseComponent {
|
||||
JqueryUtil.doToast({type: "warning", content: "Enabled editing. Note that edits will be overwritten as you parse new stat blocks."});
|
||||
});
|
||||
|
||||
const $btnSaveLocal = $(`#save_local`).click(async () => {
|
||||
const output = this._outText;
|
||||
$(`#preview`)
|
||||
.on("click", async evt => {
|
||||
const metaCurr = this._getCurrentEntities();
|
||||
|
||||
if (!(output || "").trim()) {
|
||||
if (!metaCurr?.entities?.length) return JqueryUtil.doToast({content: "Nothing to preview!", type: "warning"});
|
||||
if (metaCurr.error) return JqueryUtil.doToast({content: `Current output was not valid JSON!`, type: "danger"});
|
||||
|
||||
const entries = !this.activeConverter.prop
|
||||
? metaCurr.entities.flat()
|
||||
: metaCurr.entities
|
||||
.map(ent => {
|
||||
// Handle nameless/sourceless entities (e.g. tables)
|
||||
if (!ent.name) ent.name = "(Unnamed)";
|
||||
if (!ent.source) ent.source = VeCt.STR_GENERIC;
|
||||
|
||||
return {
|
||||
type: "statblockInline",
|
||||
dataType: this.activeConverter.prop,
|
||||
data: ent,
|
||||
};
|
||||
});
|
||||
|
||||
Renderer.hover.getShowWindow(
|
||||
Renderer.hover.$getHoverContent_generic({
|
||||
type: "entries",
|
||||
entries,
|
||||
}),
|
||||
Renderer.hover.getWindowPositionFromEvent(evt),
|
||||
{
|
||||
title: "Preview",
|
||||
isPermanent: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const $btnSaveLocal = $(`#save_local`).click(async () => {
|
||||
const metaCurr = this._getCurrentEntities();
|
||||
|
||||
if (!metaCurr?.entities?.length) return JqueryUtil.doToast({content: "Nothing to save!", type: "warning"});
|
||||
if (metaCurr.error) return JqueryUtil.doToast({content: `Current output was not valid JSON!`, type: "danger"});
|
||||
|
||||
const prop = this.activeConverter.prop;
|
||||
|
||||
const invalidSources = metaCurr.entities.map(it => !it.source || !BrewUtil2.hasSourceJson(it.source) ? (it.name || it.caption || "(Unnamed)").trim() : false).filter(Boolean);
|
||||
if (invalidSources.length) {
|
||||
JqueryUtil.doToast({
|
||||
content: `One or more entries have missing or unknown sources: ${invalidSources.join(", ")}`,
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const brewDocEditable = await BrewUtil2.pGetEditableBrewDoc();
|
||||
const uneditableSources = metaCurr.entities
|
||||
.filter(ent => !(brewDocEditable?.body?._meta?.sources || []).some(src => src.json === ent.source))
|
||||
.map(ent => ent.source);
|
||||
if (uneditableSources.length) {
|
||||
JqueryUtil.doToast({
|
||||
content: `One or more entries have sources which belong to non-editable homebrew: ${uneditableSources.join(", ")}`,
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore duplicates
|
||||
const _dupes = {};
|
||||
const dupes = [];
|
||||
const dedupedEntries = metaCurr.entities
|
||||
.map(it => {
|
||||
const lSource = it.source.toLowerCase();
|
||||
const lName = it.name.toLowerCase();
|
||||
_dupes[lSource] = _dupes[lSource] || {};
|
||||
if (_dupes[lSource][lName]) {
|
||||
dupes.push(it.name);
|
||||
return null;
|
||||
} else {
|
||||
_dupes[lSource][lName] = true;
|
||||
return it;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (dupes.length) {
|
||||
JqueryUtil.doToast({
|
||||
type: "warning",
|
||||
content: `Ignored ${dupes.length} duplicate entr${dupes.length === 1 ? "y" : "ies"}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!dedupedEntries.length) {
|
||||
return JqueryUtil.doToast({
|
||||
content: "Nothing to save!",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const prop = this.activeConverter.prop;
|
||||
const entries = JSON.parse(`[${output}]`);
|
||||
// handle overwrites
|
||||
const brewDoc = await BrewUtil2.pGetOrCreateEditableBrewDoc();
|
||||
const overwriteMeta = dedupedEntries
|
||||
.map(it => {
|
||||
if (!brewDoc?.body?.[prop]) return {entry: it, isOverwrite: false};
|
||||
|
||||
const invalidSources = entries.map(it => !it.source || !BrewUtil2.hasSourceJson(it.source) ? (it.name || it.caption || "(Unnamed)").trim() : false).filter(Boolean);
|
||||
if (invalidSources.length) {
|
||||
JqueryUtil.doToast({
|
||||
content: `One or more entries have missing or unknown sources: ${invalidSources.join(", ")}`,
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ix = brewDoc.body[prop].findIndex(bru => bru.name.toLowerCase() === it.name.toLowerCase() && bru.source.toLowerCase() === it.source.toLowerCase());
|
||||
if (!~ix) return {entry: it, isOverwrite: false};
|
||||
|
||||
const brewDocEditable = await BrewUtil2.pGetEditableBrewDoc();
|
||||
const uneditableSources = entries
|
||||
.filter(ent => !(brewDocEditable?.body?._meta?.sources || []).some(src => src.json === ent.source))
|
||||
.map(ent => ent.source);
|
||||
if (uneditableSources.length) {
|
||||
JqueryUtil.doToast({
|
||||
content: `One or more entries have sources which belong to non-editable homebrew: ${uneditableSources.join(", ")}`,
|
||||
type: "danger",
|
||||
});
|
||||
return;
|
||||
}
|
||||
return {
|
||||
isOverwrite: true,
|
||||
ix,
|
||||
entry: it,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
// ignore duplicates
|
||||
const _dupes = {};
|
||||
const dupes = [];
|
||||
const dedupedEntries = entries
|
||||
.map(it => {
|
||||
const lSource = it.source.toLowerCase();
|
||||
const lName = it.name.toLowerCase();
|
||||
_dupes[lSource] = _dupes[lSource] || {};
|
||||
if (_dupes[lSource][lName]) {
|
||||
dupes.push(it.name);
|
||||
return null;
|
||||
} else {
|
||||
_dupes[lSource][lName] = true;
|
||||
return it;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (dupes.length) {
|
||||
JqueryUtil.doToast({
|
||||
type: "warning",
|
||||
content: `Ignored ${dupes.length} duplicate entr${dupes.length === 1 ? "y" : "ies"}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!dedupedEntries.length) {
|
||||
return JqueryUtil.doToast({
|
||||
content: "Nothing to save!",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
|
||||
// handle overwrites
|
||||
const brewDoc = await BrewUtil2.pGetOrCreateEditableBrewDoc();
|
||||
const overwriteMeta = dedupedEntries
|
||||
.map(it => {
|
||||
if (!brewDoc?.body?.[prop]) return {entry: it, isOverwrite: false};
|
||||
|
||||
const ix = brewDoc.body[prop].findIndex(bru => bru.name.toLowerCase() === it.name.toLowerCase() && bru.source.toLowerCase() === it.source.toLowerCase());
|
||||
if (!~ix) return {entry: it, isOverwrite: false};
|
||||
|
||||
return {
|
||||
isOverwrite: true,
|
||||
ix,
|
||||
entry: it,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const willOverwrite = overwriteMeta.map(it => it.isOverwrite).filter(Boolean);
|
||||
if (
|
||||
willOverwrite.length
|
||||
&& !await InputUiUtil.pGetUserBoolean({title: "Overwrite Entries", htmlDescription: `This will overwrite ${willOverwrite.length} entr${willOverwrite.length === 1 ? "y" : "ies"}. Are you sure?`, textYes: "Yes", textNo: "Cancel"})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cpyBrewDoc = MiscUtil.copy(brewDoc);
|
||||
overwriteMeta.forEach(meta => {
|
||||
if (meta.isOverwrite) return cpyBrewDoc.body[prop][meta.ix] = MiscUtil.copy(meta.entry);
|
||||
(cpyBrewDoc.body[prop] = cpyBrewDoc.body[prop] || []).push(MiscUtil.copy(meta.entry));
|
||||
});
|
||||
|
||||
await BrewUtil2.pSetEditableBrewDoc(cpyBrewDoc);
|
||||
|
||||
JqueryUtil.doToast({
|
||||
type: "success",
|
||||
content: `Saved!`,
|
||||
});
|
||||
} catch (e) {
|
||||
JqueryUtil.doToast({
|
||||
content: `Current output was not valid JSON!`,
|
||||
type: "danger",
|
||||
});
|
||||
setTimeout(() => { throw e; });
|
||||
const willOverwrite = overwriteMeta.map(it => it.isOverwrite).filter(Boolean);
|
||||
if (
|
||||
willOverwrite.length
|
||||
&& !await InputUiUtil.pGetUserBoolean({title: "Overwrite Entries", htmlDescription: `This will overwrite ${willOverwrite.length} entr${willOverwrite.length === 1 ? "y" : "ies"}. Are you sure?`, textYes: "Yes", textNo: "Cancel"})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cpyBrewDoc = MiscUtil.copy(brewDoc);
|
||||
overwriteMeta.forEach(meta => {
|
||||
if (meta.isOverwrite) return cpyBrewDoc.body[prop][meta.ix] = MiscUtil.copy(meta.entry);
|
||||
(cpyBrewDoc.body[prop] = cpyBrewDoc.body[prop] || []).push(MiscUtil.copy(meta.entry));
|
||||
});
|
||||
|
||||
await BrewUtil2.pSetEditableBrewDoc(cpyBrewDoc);
|
||||
|
||||
JqueryUtil.doToast({
|
||||
type: "success",
|
||||
content: `Saved!`,
|
||||
});
|
||||
});
|
||||
const hkConverter = () => {
|
||||
$btnSaveLocal.toggleClass("hidden", !this.activeConverter.canSaveLocal);
|
||||
@@ -1115,26 +1137,20 @@ class ConverterUi extends BaseComponent {
|
||||
hkConverter();
|
||||
|
||||
$(`#btn-output-download`).click(() => {
|
||||
const output = this._outText;
|
||||
if (!output || !output.trim()) {
|
||||
return JqueryUtil.doToast({
|
||||
content: "Nothing to download!",
|
||||
type: "danger",
|
||||
});
|
||||
}
|
||||
const metaCurr = this._getCurrentEntities();
|
||||
|
||||
try {
|
||||
const prop = this.activeConverter.prop;
|
||||
const out = {[prop]: JSON.parse(`[${output}]`)};
|
||||
DataUtil.userDownload(`converter-output`, out);
|
||||
} catch (e) {
|
||||
if (!metaCurr?.entities?.length) return JqueryUtil.doToast({content: "Nothing to download!", type: "warning"});
|
||||
if (metaCurr.error) {
|
||||
JqueryUtil.doToast({
|
||||
content: `Current output was not valid JSON. Downloading as <span class="code">.txt</span> instead.`,
|
||||
type: "warning",
|
||||
});
|
||||
DataUtil.userDownloadText(`converter-output.txt`, output);
|
||||
setTimeout(() => { throw e; });
|
||||
DataUtil.userDownloadText(`converter-output.txt`, metaCurr.text);
|
||||
return;
|
||||
}
|
||||
|
||||
const out = {[this.activeConverter.prop]: metaCurr.entities};
|
||||
DataUtil.userDownload(`converter-output`, out);
|
||||
});
|
||||
|
||||
$(`#btn-output-copy`).click(async evt => {
|
||||
@@ -1204,6 +1220,18 @@ class ConverterUi extends BaseComponent {
|
||||
window.dispatchEvent(new Event("toolsLoaded"));
|
||||
}
|
||||
|
||||
_getCurrentEntities () {
|
||||
const output = this._outText;
|
||||
|
||||
if (!(output || "").trim()) return null;
|
||||
|
||||
try {
|
||||
return {entities: JSON.parse(`[${output}]`)};
|
||||
} catch (e) {
|
||||
return {error: e.message, text: output.trim()};
|
||||
}
|
||||
}
|
||||
|
||||
initSideMenu () {
|
||||
const $mnu = $(`.sidemenu`);
|
||||
|
||||
@@ -1267,7 +1295,7 @@ class ConverterUi extends BaseComponent {
|
||||
if (append) {
|
||||
const strs = [asCleanString, this._outText];
|
||||
if (this._state.appendPrependMode === "prepend") strs.reverse();
|
||||
this._outText = strs.join(",\n");
|
||||
this._outText = strs.map(it => it.trimEnd()).join(",\n");
|
||||
this._state.hasAppended = true;
|
||||
} else {
|
||||
this._outText = asCleanString;
|
||||
|
||||
@@ -1368,6 +1368,7 @@ class SpellcastingTraitConvert {
|
||||
{re: /\/rest/i, prop: "rest"},
|
||||
{re: /\/day/i, prop: "daily"},
|
||||
{re: /\/week/i, prop: "weekly"},
|
||||
{re: /\/month/i, prop: "monthly"},
|
||||
{re: /\/yeark/i, prop: "yearly"},
|
||||
];
|
||||
|
||||
|
||||
@@ -191,8 +191,9 @@ class PageFilterSpells extends PageFilter {
|
||||
return "24+ Hours";
|
||||
}
|
||||
|
||||
case "week":
|
||||
case "day":
|
||||
case "week":
|
||||
case "month":
|
||||
case "year": return "24+ Hours";
|
||||
default: return "Special";
|
||||
}
|
||||
|
||||
@@ -2352,6 +2352,11 @@ class CreatureBuilder extends Builder {
|
||||
type: "weekly",
|
||||
mode: "frequency",
|
||||
},
|
||||
{
|
||||
display: "\uD835\uDC65/month (/each) spells",
|
||||
type: "monthly",
|
||||
mode: "frequency",
|
||||
},
|
||||
{
|
||||
display: "\uD835\uDC65/year (/each) spells",
|
||||
type: "yearly",
|
||||
@@ -2439,6 +2444,7 @@ class CreatureBuilder extends Builder {
|
||||
if (trait.daily) handleFrequency("daily");
|
||||
if (trait.rest) handleFrequency("rest");
|
||||
if (trait.weekly) handleFrequency("weekly");
|
||||
if (trait.monthly) handleFrequency("monthly");
|
||||
if (trait.yearly) handleFrequency("yearly");
|
||||
if (trait.spells) {
|
||||
Object.entries(trait.spells).forEach(([k, v]) => {
|
||||
@@ -2539,6 +2545,7 @@ class CreatureBuilder extends Builder {
|
||||
case "daily": return "/Day";
|
||||
case "rest": return "/Rest";
|
||||
case "weekly": return "/Week";
|
||||
case "monthly": return "/Month";
|
||||
case "yearly": return "/Year";
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -667,8 +667,8 @@ Parser.sourceJsonToStylePart = function (source) {
|
||||
Parser.sourceJsonToMarkerHtml = function (source, {isList = true, additionalStyles = ""} = {}) {
|
||||
source = Parser._getSourceStringFromSource(source);
|
||||
// TODO(Future) consider enabling this
|
||||
// if (SourceUtil.isPartneredSourceWotc(source)) return `<span class="help-subtle ve-source-marker ${isList ? `ve-source-marker--list` : ""} ve-source-marker--partnered ${additionalStyles}" title="D&D Partnered Source">✦</span>`;
|
||||
if (SourceUtil.isLegacySourceWotc(source)) return `<span class="help-subtle ve-source-marker ${isList ? `ve-source-marker--list` : ""} ve-source-marker--legacy ${additionalStyles}" title="Legacy Source">ʟ</span>`;
|
||||
// if (SourceUtil.isPartneredSourceWotc(source)) return `<span class="help-subtle ve-source-marker ${isList ? `ve-source-marker--list` : ""} ve-source-marker--partnered ${additionalStyles}" title="D&D Partnered Source">${isList ? "" : "["}✦${isList ? "" : "]"}</span>`;
|
||||
if (SourceUtil.isLegacySourceWotc(source)) return `<span class="help-subtle ve-source-marker ${isList ? `ve-source-marker--list` : ""} ve-source-marker--legacy ${additionalStyles}" title="Legacy Source">${isList ? "" : "["}ʟ${isList ? "" : "]"}</span>`;
|
||||
return "";
|
||||
};
|
||||
|
||||
@@ -1345,6 +1345,7 @@ Parser.DURATION_AMOUNT_TYPES = [
|
||||
"hour",
|
||||
"day",
|
||||
"week",
|
||||
"month",
|
||||
"year",
|
||||
];
|
||||
|
||||
|
||||
11
js/render.js
11
js/render.js
@@ -1035,7 +1035,7 @@ globalThis.Renderer = function () {
|
||||
const hidden = new Set(entry.hidden || []);
|
||||
const toRender = [{type: "entries", name: entry.name, entries: entry.headerEntries ? MiscUtil.copyFast(entry.headerEntries) : []}];
|
||||
|
||||
if (entry.constant || entry.will || entry.recharge || entry.charges || entry.rest || entry.daily || entry.weekly || entry.yearly || entry.ritual) {
|
||||
if (entry.constant || entry.will || entry.recharge || entry.charges || entry.rest || entry.daily || entry.weekly || entry.monthly || entry.yearly || entry.ritual) {
|
||||
const tempList = {type: "list", style: "list-hang-notitle", items: [], data: {isSpellList: true}};
|
||||
if (entry.constant && !hidden.has("constant")) tempList.items.push({type: "itemSpell", name: `Constant:`, entry: this._renderSpellcasting_getRenderableList(entry.constant).join(", ")});
|
||||
if (entry.will && !hidden.has("will")) tempList.items.push({type: "itemSpell", name: `At will:`, entry: this._renderSpellcasting_getRenderableList(entry.will).join(", ")});
|
||||
@@ -1045,6 +1045,7 @@ globalThis.Renderer = function () {
|
||||
this._renderSpellcasting_getEntries_procPerDuration({entry, tempList, hidden, prop: "rest", durationText: "/rest"});
|
||||
this._renderSpellcasting_getEntries_procPerDuration({entry, tempList, hidden, prop: "daily", durationText: "/day"});
|
||||
this._renderSpellcasting_getEntries_procPerDuration({entry, tempList, hidden, prop: "weekly", durationText: "/week"});
|
||||
this._renderSpellcasting_getEntries_procPerDuration({entry, tempList, hidden, prop: "monthly", durationText: "/month"});
|
||||
this._renderSpellcasting_getEntries_procPerDuration({entry, tempList, hidden, prop: "yearly", durationText: "/year"});
|
||||
|
||||
if (entry.ritual && !hidden.has("ritual")) tempList.items.push({type: "itemSpell", name: `Rituals:`, entry: this._renderSpellcasting_getRenderableList(entry.ritual).join(", ")});
|
||||
@@ -1465,8 +1466,7 @@ globalThis.Renderer = function () {
|
||||
};
|
||||
|
||||
this._renderHomebrew = function (entry, textStack, meta, options) {
|
||||
this._renderPrefix(entry, textStack, meta, options);
|
||||
textStack[0] += `<div class="homebrew-section"><div class="homebrew-float"><span class="homebrew-notice"></span>`;
|
||||
textStack[0] += `<div class="rd-homebrew__b"><div class="rd-homebrew__wrp-notice"><span class="rd-homebrew__disp-notice"></span>`;
|
||||
|
||||
if (entry.oldEntries) {
|
||||
const hoverMeta = Renderer.hover.getInlineHover({type: "entries", name: "Homebrew", entries: entry.oldEntries});
|
||||
@@ -1478,7 +1478,7 @@ globalThis.Renderer = function () {
|
||||
} else {
|
||||
markerText = "(See removed content)";
|
||||
}
|
||||
textStack[0] += `<span class="homebrew-old-content" href="#${window.location.hash}" ${hoverMeta.html}>${markerText}</span>`;
|
||||
textStack[0] += `<span class="rd-homebrew__disp-old-content" href="#${window.location.hash}" ${hoverMeta.html}>${markerText}</span>`;
|
||||
}
|
||||
|
||||
textStack[0] += `</div>`;
|
||||
@@ -1493,7 +1493,6 @@ globalThis.Renderer = function () {
|
||||
}
|
||||
|
||||
textStack[0] += `</div>`;
|
||||
this._renderSuffix(entry, textStack, meta, options);
|
||||
};
|
||||
|
||||
this._renderCode = function (entry, textStack, meta, options) {
|
||||
@@ -1849,7 +1848,7 @@ globalThis.Renderer = function () {
|
||||
name: "Homebrew Modifications",
|
||||
entries: tooltipEntries,
|
||||
});
|
||||
textStack[0] += `<span class="homebrew-inline" ${hoverMeta.html}>`;
|
||||
textStack[0] += `<span class="rd-homebrew__disp-inline" ${hoverMeta.html}>`;
|
||||
this._recursiveRender(newText || "[...]", textStack, meta);
|
||||
textStack[0] += `</span>`;
|
||||
|
||||
|
||||
284
js/utils-brew.js
284
js/utils-brew.js
@@ -274,6 +274,7 @@ class _BrewUtil2Base {
|
||||
DISPLAY_NAME_PLURAL;
|
||||
DEFAULT_AUTHOR;
|
||||
STYLE_BTN;
|
||||
IS_PREFER_DATE_ADDED;
|
||||
|
||||
_LOCK = new VeLock({name: this.constructor.name});
|
||||
|
||||
@@ -290,6 +291,15 @@ class _BrewUtil2Base {
|
||||
|
||||
_storage = StorageUtil;
|
||||
|
||||
_parent = null;
|
||||
|
||||
/**
|
||||
* @param {?_BrewUtil2Base} parent
|
||||
*/
|
||||
constructor ({parent = null} = {}) {
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_pActiveInit = null;
|
||||
@@ -572,8 +582,12 @@ class _BrewUtil2Base {
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
getCacheIteration () { return this._cache_iteration; }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async pSetBrew (val, {lockToken} = {}) {
|
||||
try {
|
||||
await this._LOCK.pLock({token: lockToken});
|
||||
@@ -602,6 +616,8 @@ class _BrewUtil2Base {
|
||||
this._setBrewMetas(val.map(brew => this._getBrewDocReduced(brew)));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_getBrewId (brew) {
|
||||
if (brew.head.url) return brew.head.url;
|
||||
if (brew.body._meta?.sources?.length) return brew.body._meta.sources.map(src => (src.json || "").toLowerCase()).sort(SortUtil.ascSortLower).join(" :: ");
|
||||
@@ -618,6 +634,24 @@ class _BrewUtil2Base {
|
||||
return [...brews, ...brewsToAdd];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _pLoadParentDependencies ({unavailableSources}) {
|
||||
if (!unavailableSources?.length) return false;
|
||||
if (!this._parent) return false;
|
||||
|
||||
await Promise.allSettled(unavailableSources.map(async source => {
|
||||
const url = await this._parent.pGetSourceUrl(source);
|
||||
if (!url) return;
|
||||
await this._parent.pAddBrewFromUrl(url, {isLazy: true});
|
||||
}));
|
||||
await this._parent.pAddBrewsLazyFinalize();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _pGetBrewDependencies ({brewDocs, brewsRaw = null, brewsRawLocal = null, lockToken}) {
|
||||
try {
|
||||
lockToken = await this._LOCK.pLock({token: lockToken});
|
||||
@@ -629,11 +663,13 @@ class _BrewUtil2Base {
|
||||
|
||||
async _pGetBrewDependencies_ ({brewDocs, brewsRaw = null, brewsRawLocal = null, lockToken}) {
|
||||
const urlRoot = await this.pGetCustomUrl();
|
||||
const brewIndex = await this._pGetSourceIndex(urlRoot);
|
||||
const brewIndex = await this.pGetSourceIndex(urlRoot);
|
||||
|
||||
const toLoadSources = [];
|
||||
const loadedSources = new Set();
|
||||
const out = [];
|
||||
|
||||
const unavailableSources = new Set();
|
||||
const brewDocsDependencies = [];
|
||||
|
||||
brewsRaw = brewsRaw || await this._pGetBrewRaw({lockToken});
|
||||
brewsRawLocal = brewsRawLocal || await this._pGetBrew_pGetLocalBrew({lockToken});
|
||||
@@ -644,7 +680,11 @@ class _BrewUtil2Base {
|
||||
brewsRaw.forEach(brew => trackLoaded(brew));
|
||||
brewsRawLocal.forEach(brew => trackLoaded(brew));
|
||||
|
||||
brewDocs.forEach(brewDoc => toLoadSources.push(...this._getBrewDependencySources({brewDoc, brewIndex})));
|
||||
brewDocs.forEach(brewDoc => {
|
||||
const {available, unavailable} = this._getBrewDependencySources({brewDoc, brewIndex});
|
||||
toLoadSources.push(...available);
|
||||
unavailable.forEach(src => unavailableSources.add(src));
|
||||
});
|
||||
|
||||
while (toLoadSources.length) {
|
||||
const src = toLoadSources.pop();
|
||||
@@ -653,18 +693,23 @@ class _BrewUtil2Base {
|
||||
|
||||
const url = this.getFileUrl(brewIndex[src], urlRoot);
|
||||
const brewDocDep = await this._pGetBrewDocFromUrl({url});
|
||||
out.push(brewDocDep);
|
||||
brewDocsDependencies.push(brewDocDep);
|
||||
trackLoaded(brewDocDep);
|
||||
|
||||
toLoadSources.push(...this._getBrewDependencySources({brewDoc: brewDocDep, brewIndex}));
|
||||
const {available, unavailable} = this._getBrewDependencySources({brewDoc: brewDocDep, brewIndex});
|
||||
toLoadSources.push(...available);
|
||||
unavailable.forEach(src => unavailableSources.add(src));
|
||||
}
|
||||
|
||||
return out;
|
||||
return {
|
||||
brewDocsDependencies,
|
||||
unavailableSources: [...unavailableSources].sort(SortUtil.ascSortLower),
|
||||
};
|
||||
}
|
||||
|
||||
async pGetSourceUrl (source) {
|
||||
const urlRoot = await this.pGetCustomUrl();
|
||||
const brewIndex = await this._pGetSourceIndex(urlRoot);
|
||||
const brewIndex = await this.pGetSourceIndex(urlRoot);
|
||||
|
||||
if (brewIndex[source]) return this.getFileUrl(brewIndex[source], urlRoot);
|
||||
|
||||
@@ -677,7 +722,7 @@ class _BrewUtil2Base {
|
||||
}
|
||||
|
||||
/** @abstract */
|
||||
async _pGetSourceIndex (urlRoot) { throw new Error("Unimplemented!"); }
|
||||
async pGetSourceIndex (urlRoot) { throw new Error("Unimplemented!"); }
|
||||
/** @abstract */
|
||||
getFileUrl (path, urlRoot) { throw new Error("Unimplemented!"); }
|
||||
/** @abstract */
|
||||
@@ -691,15 +736,14 @@ class _BrewUtil2Base {
|
||||
_PROPS_DEPS_DEEP = ["otherSources"];
|
||||
|
||||
_getBrewDependencySources ({brewDoc, brewIndex}) {
|
||||
const out = new Set();
|
||||
const sources = new Set();
|
||||
|
||||
this._PROPS_DEPS.forEach(prop => {
|
||||
const obj = brewDoc.body._meta?.[prop];
|
||||
if (!obj || !Object.keys(obj).length) return;
|
||||
Object.values(obj)
|
||||
.flat()
|
||||
.filter(src => brewIndex[src])
|
||||
.forEach(src => out.add(src));
|
||||
.forEach(src => sources.add(src));
|
||||
});
|
||||
|
||||
this._PROPS_DEPS_DEEP.forEach(prop => {
|
||||
@@ -708,21 +752,28 @@ class _BrewUtil2Base {
|
||||
return Object.values(obj)
|
||||
.map(objSub => Object.keys(objSub))
|
||||
.flat()
|
||||
.filter(src => brewIndex[src])
|
||||
.forEach(src => out.add(src));
|
||||
.forEach(src => sources.add(src));
|
||||
});
|
||||
|
||||
return out;
|
||||
const [available, unavailable] = [...sources]
|
||||
.segregate(src => brewIndex[src]);
|
||||
|
||||
return {available, unavailable};
|
||||
}
|
||||
|
||||
async pAddBrewFromUrl (url, {lockToken, isLazy} = {}) {
|
||||
async pAddBrewFromUrl (url, {isLazy} = {}) {
|
||||
let brewDocs = []; let unavailableSources = [];
|
||||
|
||||
try {
|
||||
return (await this._pAddBrewFromUrl({url, lockToken, isLazy}));
|
||||
({brewDocs, unavailableSources} = await this._pAddBrewFromUrl({url, isLazy}));
|
||||
} catch (e) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Failed to load ${this.DISPLAY_NAME} from URL "${url}"! ${VeCt.STR_SEE_CONSOLE}`});
|
||||
setTimeout(() => { throw e; });
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
|
||||
await this._pLoadParentDependencies({unavailableSources});
|
||||
return brewDocs;
|
||||
}
|
||||
|
||||
async _pGetBrewDocFromUrl ({url}) {
|
||||
@@ -741,16 +792,17 @@ class _BrewUtil2Base {
|
||||
this._LOCK.unlock();
|
||||
}
|
||||
|
||||
return [brewDoc];
|
||||
return {brewDocs: [brewDoc], unavailableSources: []};
|
||||
}
|
||||
|
||||
const brewDocs = [brewDoc];
|
||||
const brewDocs = [brewDoc]; const unavailableSources = [];
|
||||
try {
|
||||
lockToken = await this._LOCK.pLock({token: lockToken});
|
||||
const brews = MiscUtil.copyFast(await this._pGetBrewRaw({lockToken}));
|
||||
|
||||
const brewDocsDependencies = await this._pGetBrewDependencies({brewDocs, brewsRaw: brews, lockToken});
|
||||
const {brewDocsDependencies, unavailableSources: unavailableSources_} = await this._pGetBrewDependencies({brewDocs, brewsRaw: brews, lockToken});
|
||||
brewDocs.push(...brewDocsDependencies);
|
||||
unavailableSources.push(...unavailableSources_);
|
||||
|
||||
const brewsNxt = this._getNextBrews(brews, brewDocs);
|
||||
await this.pSetBrew(brewsNxt, {lockToken});
|
||||
@@ -758,20 +810,25 @@ class _BrewUtil2Base {
|
||||
this._LOCK.unlock();
|
||||
}
|
||||
|
||||
return brewDocs;
|
||||
return {brewDocs, unavailableSources};
|
||||
}
|
||||
|
||||
async pAddBrewsFromFiles (files) {
|
||||
let brewDocs = []; let unavailableSources = [];
|
||||
|
||||
try {
|
||||
const lockToken = await this._LOCK.pLock();
|
||||
return (await this._pAddBrewsFromFiles({files, lockToken}));
|
||||
({brewDocs, unavailableSources} = await this._pAddBrewsFromFiles({files, lockToken}));
|
||||
} catch (e) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Failed to load ${this.DISPLAY_NAME} from file(s)! ${VeCt.STR_SEE_CONSOLE}`});
|
||||
setTimeout(() => { throw e; });
|
||||
return [];
|
||||
} finally {
|
||||
this._LOCK.unlock();
|
||||
}
|
||||
return [];
|
||||
|
||||
await this._pLoadParentDependencies({unavailableSources});
|
||||
return brewDocs;
|
||||
}
|
||||
|
||||
async _pAddBrewsFromFiles ({files, lockToken}) {
|
||||
@@ -779,32 +836,41 @@ class _BrewUtil2Base {
|
||||
|
||||
const brews = MiscUtil.copyFast(await this._pGetBrewRaw({lockToken}));
|
||||
|
||||
const brewDocsDependencies = await this._pGetBrewDependencies({brewDocs, brewsRaw: brews, lockToken});
|
||||
const {brewDocsDependencies, unavailableSources} = await this._pGetBrewDependencies({brewDocs, brewsRaw: brews, lockToken});
|
||||
brewDocs.push(...brewDocsDependencies);
|
||||
|
||||
const brewsNxt = this._getNextBrews(brews, brewDocs);
|
||||
await this.pSetBrew(brewsNxt, {lockToken});
|
||||
|
||||
return brewDocs;
|
||||
return {brewDocs, unavailableSources};
|
||||
}
|
||||
|
||||
async pAddBrewsLazyFinalize ({lockToken} = {}) {
|
||||
async pAddBrewsLazyFinalize () {
|
||||
let brewDocs = []; let unavailableSources = [];
|
||||
|
||||
try {
|
||||
lockToken = await this._LOCK.pLock({token: lockToken});
|
||||
return (await this._pAddBrewsLazyFinalize_({lockToken}));
|
||||
const lockToken = await this._LOCK.pLock();
|
||||
({brewDocs, unavailableSources} = await this._pAddBrewsLazyFinalize_({lockToken}));
|
||||
} catch (e) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Failed to finalize ${this.DISPLAY_NAME_PLURAL}! ${VeCt.STR_SEE_CONSOLE}`});
|
||||
setTimeout(() => { throw e; });
|
||||
return [];
|
||||
} finally {
|
||||
this._LOCK.unlock();
|
||||
}
|
||||
|
||||
await this._pLoadParentDependencies({unavailableSources});
|
||||
return brewDocs;
|
||||
}
|
||||
|
||||
async _pAddBrewsLazyFinalize_ ({lockToken}) {
|
||||
const brewsRaw = await this._pGetBrewRaw({lockToken});
|
||||
const brewDeps = await this._pGetBrewDependencies({brewDocs: this._addLazy_brewsTemp, brewsRaw, lockToken});
|
||||
const out = MiscUtil.copyFast(brewDeps);
|
||||
const brewsNxt = this._getNextBrews(MiscUtil.copyFast(brewsRaw), [...this._addLazy_brewsTemp, ...brewDeps]);
|
||||
const {brewDocsDependencies, unavailableSources} = await this._pGetBrewDependencies({brewDocs: this._addLazy_brewsTemp, brewsRaw, lockToken});
|
||||
const brewDocs = MiscUtil.copyFast(brewDocsDependencies);
|
||||
const brewsNxt = this._getNextBrews(MiscUtil.copyFast(brewsRaw), [...this._addLazy_brewsTemp, ...brewDocsDependencies]);
|
||||
await this.pSetBrew(brewsNxt, {lockToken});
|
||||
this._addLazy_brewsTemp = [];
|
||||
return out;
|
||||
return {brewDocs, unavailableSources};
|
||||
}
|
||||
|
||||
async pPullAllBrews ({brews} = {}) {
|
||||
@@ -1268,6 +1334,66 @@ class _BrewUtil2Base {
|
||||
// endregion
|
||||
}
|
||||
|
||||
class _PrereleaseUtil extends _BrewUtil2Base {
|
||||
_STORAGE_KEY_LEGACY = null;
|
||||
_STORAGE_KEY_LEGACY_META = null;
|
||||
|
||||
_STORAGE_KEY = "PRERELEASE_STORAGE";
|
||||
_STORAGE_KEY_META = "PRERELEASE_META_STORAGE";
|
||||
|
||||
_STORAGE_KEY_CUSTOM_URL = "PRERELEASE_CUSTOM_REPO_URL";
|
||||
_STORAGE_KEY_MIGRATION_VERSION = "PRERELEASE_STORAGE_MIGRATION";
|
||||
|
||||
_PATH_LOCAL_DIR = "prerelease";
|
||||
_PATH_LOCAL_INDEX = VeCt.JSON_PRERELEASE_INDEX;
|
||||
|
||||
_VERSION = 1;
|
||||
|
||||
IS_EDITABLE = false;
|
||||
PAGE_MANAGE = UrlUtil.PG_MANAGE_PRERELEASE;
|
||||
URL_REPO_DEFAULT = VeCt.URL_PRERELEASE;
|
||||
DISPLAY_NAME = "prerelease content";
|
||||
DISPLAY_NAME_PLURAL = "prereleases";
|
||||
DEFAULT_AUTHOR = "Wizards of the Coast";
|
||||
STYLE_BTN = "btn-primary";
|
||||
IS_PREFER_DATE_ADDED = false;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_pInit_doBindDragDrop () { /* No-op */ }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async pGetSourceIndex (urlRoot) { return DataUtil.prerelease.pLoadSourceIndex(urlRoot); }
|
||||
|
||||
getFileUrl (path, urlRoot) { return DataUtil.prerelease.getFileUrl(path, urlRoot); }
|
||||
|
||||
pLoadTimestamps (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadTimestamps(urlRoot); }
|
||||
|
||||
pLoadPropIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadPropIndex(urlRoot); }
|
||||
|
||||
pLoadMetaIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadMetaIndex(urlRoot); }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// region Editable
|
||||
|
||||
pGetEditableBrewDoc (brew) { return super.pGetEditableBrewDoc(brew); }
|
||||
pGetOrCreateEditableBrewDoc () { return super.pGetOrCreateEditableBrewDoc(); }
|
||||
pSetEditableBrewDoc () { return super.pSetEditableBrewDoc(); }
|
||||
pGetEditableBrewEntity (prop, uniqueId, {isDuplicate = false} = {}) { return super.pGetEditableBrewEntity(prop, uniqueId, {isDuplicate}); }
|
||||
pPersistEditableBrewEntity (prop, ent) { return super.pPersistEditableBrewEntity(prop, ent); }
|
||||
pRemoveEditableBrewEntity (prop, uniqueId) { return super.pRemoveEditableBrewEntity(prop, uniqueId); }
|
||||
pAddSource (sourceObj) { return super.pAddSource(sourceObj); }
|
||||
pEditSource (sourceObj) { return super.pEditSource(sourceObj); }
|
||||
pIsEditableSourceJson (sourceJson) { return super.pIsEditableSourceJson(sourceJson); }
|
||||
pMoveOrCopyToEditableBySourceJson (sourceJson) { return super.pMoveOrCopyToEditableBySourceJson(sourceJson); }
|
||||
pMoveToEditable ({brews}) { return super.pMoveToEditable({brews}); }
|
||||
pCopyToEditable ({brews}) { return super.pCopyToEditable({brews}); }
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
class _BrewUtil2 extends _BrewUtil2Base {
|
||||
_STORAGE_KEY_LEGACY = "HOMEBREW_STORAGE";
|
||||
_STORAGE_KEY_LEGACY_META = "HOMEBREW_META_STORAGE";
|
||||
@@ -1291,6 +1417,7 @@ class _BrewUtil2 extends _BrewUtil2Base {
|
||||
DISPLAY_NAME_PLURAL = "homebrews";
|
||||
DEFAULT_AUTHOR = "";
|
||||
STYLE_BTN = "btn-info";
|
||||
IS_PREFER_DATE_ADDED = true;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
@@ -1344,7 +1471,7 @@ class _BrewUtil2 extends _BrewUtil2Base {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _pGetSourceIndex (urlRoot) { return DataUtil.brew.pLoadSourceIndex(urlRoot); }
|
||||
async pGetSourceIndex (urlRoot) { return DataUtil.brew.pLoadSourceIndex(urlRoot); }
|
||||
|
||||
getFileUrl (path, urlRoot) { return DataUtil.brew.getFileUrl(path, urlRoot); }
|
||||
|
||||
@@ -1518,67 +1645,8 @@ class _BrewUtil2 extends _BrewUtil2Base {
|
||||
// endregion
|
||||
}
|
||||
|
||||
class _PrereleaseUtil extends _BrewUtil2Base {
|
||||
_STORAGE_KEY_LEGACY = null;
|
||||
_STORAGE_KEY_LEGACY_META = null;
|
||||
|
||||
_STORAGE_KEY = "PRERELEASE_STORAGE";
|
||||
_STORAGE_KEY_META = "PRERELEASE_META_STORAGE";
|
||||
|
||||
_STORAGE_KEY_CUSTOM_URL = "PRERELEASE_CUSTOM_REPO_URL";
|
||||
_STORAGE_KEY_MIGRATION_VERSION = "PRERELEASE_STORAGE_MIGRATION";
|
||||
|
||||
_PATH_LOCAL_DIR = "prerelease";
|
||||
_PATH_LOCAL_INDEX = VeCt.JSON_PRERELEASE_INDEX;
|
||||
|
||||
_VERSION = 1;
|
||||
|
||||
IS_EDITABLE = false;
|
||||
PAGE_MANAGE = UrlUtil.PG_MANAGE_PRERELEASE;
|
||||
URL_REPO_DEFAULT = VeCt.URL_PRERELEASE;
|
||||
DISPLAY_NAME = "prerelease content";
|
||||
DISPLAY_NAME_PLURAL = "prereleases";
|
||||
DEFAULT_AUTHOR = "Wizards of the Coast";
|
||||
STYLE_BTN = "btn-primary";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_pInit_doBindDragDrop () { /* No-op */ }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _pGetSourceIndex (urlRoot) { return DataUtil.prerelease.pLoadSourceIndex(urlRoot); }
|
||||
|
||||
getFileUrl (path, urlRoot) { return DataUtil.prerelease.getFileUrl(path, urlRoot); }
|
||||
|
||||
pLoadTimestamps (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadTimestamps(urlRoot); }
|
||||
|
||||
pLoadPropIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadPropIndex(urlRoot); }
|
||||
|
||||
pLoadMetaIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadMetaIndex(urlRoot); }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// region Editable
|
||||
|
||||
pGetEditableBrewDoc (brew) { return super.pGetEditableBrewDoc(brew); }
|
||||
pGetOrCreateEditableBrewDoc () { return super.pGetOrCreateEditableBrewDoc(); }
|
||||
pSetEditableBrewDoc () { return super.pSetEditableBrewDoc(); }
|
||||
pGetEditableBrewEntity (prop, uniqueId, {isDuplicate = false} = {}) { return super.pGetEditableBrewEntity(prop, uniqueId, {isDuplicate}); }
|
||||
pPersistEditableBrewEntity (prop, ent) { return super.pPersistEditableBrewEntity(prop, ent); }
|
||||
pRemoveEditableBrewEntity (prop, uniqueId) { return super.pRemoveEditableBrewEntity(prop, uniqueId); }
|
||||
pAddSource (sourceObj) { return super.pAddSource(sourceObj); }
|
||||
pEditSource (sourceObj) { return super.pEditSource(sourceObj); }
|
||||
pIsEditableSourceJson (sourceJson) { return super.pIsEditableSourceJson(sourceJson); }
|
||||
pMoveOrCopyToEditableBySourceJson (sourceJson) { return super.pMoveOrCopyToEditableBySourceJson(sourceJson); }
|
||||
pMoveToEditable ({brews}) { return super.pMoveToEditable({brews}); }
|
||||
pCopyToEditable ({brews}) { return super.pCopyToEditable({brews}); }
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
globalThis.BrewUtil2 = new _BrewUtil2();
|
||||
globalThis.PrereleaseUtil = new _PrereleaseUtil();
|
||||
globalThis.BrewUtil2 = new _BrewUtil2({parent: globalThis.PrereleaseUtil}); // Homebrew can depend on prerelease, but not the other way around
|
||||
|
||||
class ManageBrewUi {
|
||||
static _RenderState = class {
|
||||
@@ -2496,13 +2564,16 @@ class GetBrewUi {
|
||||
}
|
||||
|
||||
static mutateForFilters (brewInfo) {
|
||||
if (brewInfo._brewAuthor && brewInfo._brewAuthor.toLowerCase().startsWith("sample -")) brewInfo._fMisc = ["Sample"];
|
||||
brewInfo._fMisc = [];
|
||||
if (brewInfo._brewAuthor && brewInfo._brewAuthor.toLowerCase().startsWith("sample -")) brewInfo._fMisc.push("Sample");
|
||||
if (brewInfo.sources?.some(ab => ab.startsWith(Parser.SRC_UA_ONE_PREFIX))) brewInfo._fMisc.push("One D&D");
|
||||
}
|
||||
|
||||
addToFilters (it, isExcluded) {
|
||||
if (isExcluded) return;
|
||||
|
||||
this._typeFilter.addItem(it.props);
|
||||
this._miscFilter.addItem(it._fMisc);
|
||||
}
|
||||
|
||||
async _pPopulateBoxOptions (opts) {
|
||||
@@ -2555,6 +2626,7 @@ class GetBrewUi {
|
||||
case "category": return this.constructor._sortUrlList_orFallback(a, b, SortUtil.ascSortLower, "_brewPropDisplayName");
|
||||
case "added": return this.constructor._sortUrlList_orFallback(a, b, SortUtil.ascSort, "_brewAdded");
|
||||
case "modified": return this.constructor._sortUrlList_orFallback(a, b, SortUtil.ascSort, "_brewModified");
|
||||
case "published": return this.constructor._sortUrlList_orFallback(a, b, SortUtil.ascSort, "_brewPublished");
|
||||
default: throw new Error(`No sort order defined for property "${o.sortBy}"`);
|
||||
}
|
||||
}
|
||||
@@ -2573,10 +2645,11 @@ class GetBrewUi {
|
||||
|
||||
async pInit () {
|
||||
const urlRoot = await this._brewUtil.pGetCustomUrl();
|
||||
const [timestamps, propIndex, metaIndex] = await Promise.all([
|
||||
const [timestamps, propIndex, metaIndex, sourceIndex] = await Promise.all([
|
||||
this._brewUtil.pLoadTimestamps(urlRoot),
|
||||
this._brewUtil.pLoadPropIndex(urlRoot),
|
||||
this._brewUtil.pLoadMetaIndex(urlRoot),
|
||||
this._brewUtil.pGetSourceIndex(urlRoot),
|
||||
]);
|
||||
|
||||
const pathToMeta = {};
|
||||
@@ -2589,6 +2662,12 @@ class GetBrewUi {
|
||||
});
|
||||
});
|
||||
|
||||
Object.entries(sourceIndex)
|
||||
.forEach(([src, path]) => {
|
||||
if (!pathToMeta[path]) return;
|
||||
(pathToMeta[path].sources ||= []).push(src);
|
||||
});
|
||||
|
||||
this._dataList = Object.entries(pathToMeta)
|
||||
.map(([path, meta]) => {
|
||||
const out = {
|
||||
@@ -2597,6 +2676,7 @@ class GetBrewUi {
|
||||
name: UrlUtil.getFilename(path),
|
||||
dirProp: this._brewUtil.getDirProp(meta.dir),
|
||||
props: meta.props,
|
||||
sources: meta.sources,
|
||||
};
|
||||
|
||||
const spl = out.name.trim().replace(/\.json$/, "").split(";").map(it => it.trim());
|
||||
@@ -2610,6 +2690,7 @@ class GetBrewUi {
|
||||
|
||||
out._brewAdded = timestamps[out.path]?.a ?? 0;
|
||||
out._brewModified = timestamps[out.path]?.m ?? 0;
|
||||
out._brewPublished = timestamps[out.path]?.p ?? 0;
|
||||
out._brewInternalSources = metaIndex[out.name]?.n || [];
|
||||
out._brewStatus = metaIndex[out.name]?.s || "ready";
|
||||
out._brewPropDisplayName = this._brewUtil.getPropDisplayName(out.dirProp);
|
||||
@@ -2662,13 +2743,17 @@ class GetBrewUi {
|
||||
|
||||
const $wrpMiniPills = $(`<div class="fltr__mini-view btn-group"></div>`);
|
||||
|
||||
const btnSortAddedPublished = this._brewUtil.IS_PREFER_DATE_ADDED
|
||||
? `<button class="col-1-4 sort btn btn-default btn-xs" data-sort="added">Added</button>`
|
||||
: `<button class="col-1-4 sort btn btn-default btn-xs" data-sort="published">Published</button>`;
|
||||
|
||||
const $wrpSort = $$`<div class="filtertools manbrew__filtertools btn-group input-group input-group--bottom ve-flex no-shrink">
|
||||
<label class="col-0-5 pr-0 btn btn-default btn-xs ve-flex-vh-center">${rdState.cbAll}</label>
|
||||
<button class="col-3-5 sort btn btn-default btn-xs" data-sort="name">Name</button>
|
||||
<button class="col-3 sort btn btn-default btn-xs" data-sort="author">Author</button>
|
||||
<button class="col-1-2 sort btn btn-default btn-xs" data-sort="category">Category</button>
|
||||
<button class="col-1-4 sort btn btn-default btn-xs" data-sort="modified">Modified</button>
|
||||
<button class="col-1-4 sort btn btn-default btn-xs" data-sort="added">Added</button>
|
||||
${btnSortAddedPublished}
|
||||
<button class="sort btn btn-default btn-xs ve-grow" disabled>Source</button>
|
||||
</div>`;
|
||||
|
||||
@@ -2747,8 +2832,9 @@ class GetBrewUi {
|
||||
}
|
||||
|
||||
_pRender_getUrlRowMeta (rdState, brewInfo, ix) {
|
||||
const timestampAdded = brewInfo._brewAdded
|
||||
? DatetimeUtil.getDateStr({date: new Date(brewInfo._brewAdded * 1000), isShort: true, isPad: true})
|
||||
const epochAddedPublished = this._brewUtil.IS_PREFER_DATE_ADDED ? brewInfo._brewAdded : brewInfo._brewPublished;
|
||||
const timestampAddedPublished = epochAddedPublished
|
||||
? DatetimeUtil.getDateStr({date: new Date(epochAddedPublished * 1000), isShort: true, isPad: true})
|
||||
: "";
|
||||
const timestampModified = brewInfo._brewModified
|
||||
? DatetimeUtil.getDateStr({date: new Date(brewInfo._brewModified * 1000), isShort: true, isPad: true})
|
||||
@@ -2784,7 +2870,7 @@ class GetBrewUi {
|
||||
e_({tag: "span", clazz: "col-3", text: brewInfo._brewAuthor}),
|
||||
e_({tag: "span", clazz: "col-1-2 ve-text-center mobile__text-clip-ellipsis", text: brewInfo._brewPropDisplayName, title: brewInfo._brewPropDisplayName}),
|
||||
e_({tag: "span", clazz: "col-1-4 ve-text-center code", text: timestampModified}),
|
||||
e_({tag: "span", clazz: "col-1-4 ve-text-center code", text: timestampAdded}),
|
||||
e_({tag: "span", clazz: "col-1-4 ve-text-center code", text: timestampAddedPublished}),
|
||||
e_({
|
||||
tag: "span",
|
||||
clazz: "col-1 manbrew__source ve-text-center pr-0",
|
||||
|
||||
@@ -177,6 +177,7 @@ PropOrder._MONSTER = [
|
||||
"rest",
|
||||
"daily",
|
||||
"weekly",
|
||||
"monthly",
|
||||
"yearly",
|
||||
"recharge",
|
||||
"charges",
|
||||
|
||||
28
js/utils.js
28
js/utils.js
@@ -2,7 +2,7 @@
|
||||
|
||||
// in deployment, `IS_DEPLOYED = "<version number>";` should be set below.
|
||||
globalThis.IS_DEPLOYED = undefined;
|
||||
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.197.3"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.197.4"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.DEPLOYED_IMG_ROOT = undefined;
|
||||
// for the roll20 script to set
|
||||
globalThis.IS_VTT = false;
|
||||
@@ -3108,17 +3108,6 @@ if (!IS_DEPLOYED && !IS_VTT && typeof window !== "undefined") {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(img) remove this in future
|
||||
window.addEventListener("load", () => {
|
||||
if (window.location?.host === "5etools-mirror-1.github.io") {
|
||||
JqueryUtil.doToast({
|
||||
type: "warning",
|
||||
isAutoHide: false,
|
||||
content: $(`<div>This mirror is no longer being updated/maintained, and will be shut down on March 1st 2024.<br>Please use <a href="https://5etools-mirror-2.github.io/" rel="noopener noreferrer">5etools-mirror-2.github.io</a> instead, and <a href="https://gist.github.com/5etools-mirror-2/40d6d80f40205882d3fa5006fae963a4" rel="noopener noreferrer">migrate your data</a>.</div>`),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// SORTING =============================================================================================================
|
||||
@@ -4359,7 +4348,7 @@ globalThis.DataUtil = {
|
||||
modInfo[prop].forEach(sp => (spellcasting[prop] = spellcasting[prop] || []).push(sp));
|
||||
});
|
||||
|
||||
["recharge", "charges", "rest", "daily", "weekly", "yearly"].forEach(prop => {
|
||||
["recharge", "charges", "rest", "daily", "weekly", "monthly", "yearly"].forEach(prop => {
|
||||
if (!modInfo[prop]) return;
|
||||
|
||||
for (let i = 1; i <= 9; ++i) {
|
||||
@@ -4442,7 +4431,7 @@ globalThis.DataUtil = {
|
||||
spellcasting[prop].filter(it => !modInfo[prop].includes(it));
|
||||
});
|
||||
|
||||
["recharge", "charges", "rest", "daily", "weekly", "yearly"].forEach(prop => {
|
||||
["recharge", "charges", "rest", "daily", "weekly", "monthly", "yearly"].forEach(prop => {
|
||||
if (!modInfo[prop]) return;
|
||||
|
||||
for (let i = 1; i <= 9; ++i) {
|
||||
@@ -7567,4 +7556,15 @@ if (!IS_VTT && typeof window !== "undefined") {
|
||||
// $(`.cancer__sidebar-rhs-inner--top`).append(`<div class="TEST_RHS_TOP"></div>`)
|
||||
// $(`.cancer__sidebar-rhs-inner--bottom`).append(`<div class="TEST_RHS_BOTTOM"></div>`)
|
||||
// });
|
||||
|
||||
// TODO(img) remove this in future
|
||||
window.addEventListener("load", () => {
|
||||
if (window.location?.host !== "5etools-mirror-1.github.io") return;
|
||||
|
||||
JqueryUtil.doToast({
|
||||
type: "warning",
|
||||
isAutoHide: false,
|
||||
content: $(`<div>This mirror is no longer being updated/maintained, and will be shut down on March 1st 2024.<br>Please use <a href="https://5etools-mirror-2.github.io/" rel="noopener noreferrer">5etools-mirror-2.github.io</a> instead, and <a href="https://gist.github.com/5etools-mirror-2/40d6d80f40205882d3fa5006fae963a4" rel="noopener noreferrer">migrate your data</a>.</div>`),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user