You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1303 lines
194 KiB

3 years ago
/*
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugins github repository
*/
'use strict';
var obsidian = require('obsidian');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
/*
* Filename: multi-column-markdown/src/regionSettings.ts
* Created Date: Tuesday, February 1st 2022, 12:23:53 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
var BorderOption;
(function (BorderOption) {
BorderOption[BorderOption["enabled"] = 0] = "enabled";
BorderOption[BorderOption["on"] = 1] = "on";
BorderOption[BorderOption["true"] = 2] = "true";
BorderOption[BorderOption["disabled"] = 3] = "disabled";
BorderOption[BorderOption["off"] = 4] = "off";
BorderOption[BorderOption["false"] = 5] = "false";
})(BorderOption || (BorderOption = {}));
var ShadowOption;
(function (ShadowOption) {
ShadowOption[ShadowOption["enabled"] = 0] = "enabled";
ShadowOption[ShadowOption["on"] = 1] = "on";
ShadowOption[ShadowOption["true"] = 2] = "true";
ShadowOption[ShadowOption["disabled"] = 3] = "disabled";
ShadowOption[ShadowOption["off"] = 4] = "off";
ShadowOption[ShadowOption["false"] = 5] = "false";
})(ShadowOption || (ShadowOption = {}));
var ColumnLayout;
(function (ColumnLayout) {
ColumnLayout[ColumnLayout["standard"] = 0] = "standard";
ColumnLayout[ColumnLayout["left"] = 1] = "left";
ColumnLayout[ColumnLayout["first"] = 2] = "first";
ColumnLayout[ColumnLayout["center"] = 3] = "center";
ColumnLayout[ColumnLayout["middle"] = 4] = "middle";
ColumnLayout[ColumnLayout["second"] = 5] = "second";
ColumnLayout[ColumnLayout["right"] = 6] = "right";
ColumnLayout[ColumnLayout["third"] = 7] = "third";
ColumnLayout[ColumnLayout["last"] = 8] = "last";
})(ColumnLayout || (ColumnLayout = {}));
/*
* File: multi-column-markdown/src/MultiColumnParser.ts
* Created Date: Saturday, January 22nd 2022, 6:02:46 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
const START_REGEX_STRS = ["=== *start-multi-column",
"=== *multi-column-start"];
const START_REGEX_ARR = [];
for (let i = 0; i < START_REGEX_STRS.length; i++) {
START_REGEX_ARR.push(new RegExp(START_REGEX_STRS[i]));
}
function findStartTag(text) {
let found = false;
let startPosition = -1;
for (let i = 0; i < START_REGEX_ARR.length; i++) {
if (START_REGEX_ARR[i].test(text)) {
found = true;
startPosition = text.search(START_REGEX_STRS[i]);
break;
}
}
return { found, startPosition };
}
function containsStartTag(text) {
return findStartTag(text).found;
}
function isStartTagWithID(text) {
let startTagData = findStartTag(text);
if (startTagData.found === true) {
let key = getStartTagKey(text);
if (key === null || key === "") {
return { isStartTag: true, hasKey: false };
}
return { isStartTag: true, hasKey: true };
}
return { isStartTag: false, hasKey: false };
}
const END_REGEX_STRS = ["=== *end-multi-column",
"=== *multi-column-end"];
const END_REGEX_ARR = [];
for (let i = 0; i < END_REGEX_STRS.length; i++) {
END_REGEX_ARR.push(new RegExp(END_REGEX_STRS[i]));
}
function findEndTag(text) {
let found = false;
let startPosition = -1;
for (let i = 0; i < END_REGEX_ARR.length; i++) {
if (END_REGEX_ARR[i].test(text)) {
found = true;
startPosition = text.search(END_REGEX_STRS[i]);
break;
}
}
return { found, startPosition };
}
function containsEndTag(text) {
return findEndTag(text).found;
}
const COL_REGEX_STRS = ["=== *column-end *===",
"=== *end-column *===",
"=== *column-break *===",
"=== *break-column *==="];
const COL_REGEX_ARR = [];
for (let i = 0; i < COL_REGEX_STRS.length; i++) {
COL_REGEX_ARR.push(new RegExp(COL_REGEX_STRS[i]));
}
function containsColEndTag(text) {
let found = false;
for (let i = 0; i < COL_REGEX_ARR.length; i++) {
if (COL_REGEX_ARR[i].test(text)) {
found = true;
break;
}
}
return found;
}
const COL_SETTINGS_REGEX_STRS = ["```settings",
"```column-settings",
"```multi-column-settings"];
const COL_SETTINGS_REGEX_ARR = [];
for (let i = 0; i < COL_SETTINGS_REGEX_STRS.length; i++) {
COL_SETTINGS_REGEX_ARR.push(new RegExp(COL_SETTINGS_REGEX_STRS[i]));
}
function containsColSettingsTag(text) {
let found = false;
for (let i = 0; i < COL_SETTINGS_REGEX_ARR.length; i++) {
if (COL_SETTINGS_REGEX_ARR[i].test(text)) {
found = true;
break;
}
}
return found;
}
function parseColumnSettings(settingsStr) {
// Set the minimum number of columnds to 2.
let numberOfColumns = 2;
let columnLayout = ColumnLayout.standard;
let borderDrawn = true;
let shadowDrawn = true;
let settingsLines = settingsStr.split("\n");
for (let i = 0; i < settingsLines.length; i++) {
if (settingsLines[i].toLowerCase().replace(/\s/g, "").contains("numberofcolumns:")) {
let userDefNumberOfCols = parseInt(settingsLines[i].split(":")[1]);
if (Number.isNaN(userDefNumberOfCols) === false) {
if (userDefNumberOfCols === 3) {
numberOfColumns = 3;
}
else if (userDefNumberOfCols === 2) {
numberOfColumns = 2;
}
}
break;
}
}
for (let i = 0; i < settingsLines.length; i++) {
if (settingsLines[i].toLowerCase().replace(/\s/g, "").contains("largestcolumn:")) {
let setting = settingsLines[i].split(":")[1].trimStart().trimEnd().toLowerCase();
let userDefLayout = ColumnLayout[setting];
if (userDefLayout !== undefined) {
columnLayout = userDefLayout;
}
}
}
for (let i = 0; i < settingsLines.length; i++) {
if (settingsLines[i].toLowerCase().replace(/\s/g, "").contains("border:")) {
let setting = settingsLines[i].split(":")[1].trimStart().trimEnd().toLowerCase();
let isBorderDrawn = BorderOption[setting];
if (isBorderDrawn !== undefined) {
switch (isBorderDrawn) {
case (BorderOption.disabled):
case (BorderOption.off):
case (BorderOption.false):
borderDrawn = false;
break;
}
}
}
}
for (let i = 0; i < settingsLines.length; i++) {
if (settingsLines[i].toLowerCase().replace(/\s/g, "").contains("shadow:")) {
let setting = settingsLines[i].split(":")[1].trimStart().trimEnd().toLowerCase();
let isShadowDrawn = ShadowOption[setting];
if (isShadowDrawn !== undefined) {
switch (isShadowDrawn) {
case (ShadowOption.disabled):
case (ShadowOption.off):
case (ShadowOption.false):
shadowDrawn = false;
break;
}
}
}
}
let settings = { numberOfColumns, columnLayout, drawBorder: borderDrawn, drawShadow: shadowDrawn };
return settings;
}
function countStartTags(text) {
let keys = [];
let startTagData = findStartTag(text);
while (startTagData.found) {
// Slice off everything before the tag
text = text.slice(startTagData.startPosition);
/**
* Get just the start tag line and then set text to everything just
* after the start tag.
*/
let tag = text.split("\n")[0];
text = text.slice(1); // This moves the text 1 character so we dont match the same tag.
// Parse out the key and append to the list.
let key = getStartTagKey(tag);
if (key === null) {
key = "";
}
keys.push(key);
// Search again for another tag before looping.
startTagData = findStartTag(text);
}
return { numberOfTags: keys.length, keys };
}
/**
* This function will filter a set of strings, returning all items starting
* from the closest open start tag through the last item in the set.
*
* The function filters out all end tags to make sure that the start tag we
* find is the proper start tag for the list sent.
* @param linesAboveArray
* @returns
*/
function getStartBlockAboveLine(linesAboveArray) {
// Reduce the array down into a single string so that we can
// easily RegEx over the string and find the indicies we're looking for.
let linesAboveStr = linesAboveArray.reduce((prev, current) => {
return prev + "\n" + current;
}, "");
/*
* First thing we need to do is check if there are any end tags in the
* set of strings (which logically would close start tags and therefore
* the start tag it closes is not what we want). If there are we want to
* slowly narrow down our set of strings until the last end tag is
* removed. This makes it easier to find the closest open start tag
* in the data.
*/
let endTagSerachData = findEndTag(linesAboveStr);
while (endTagSerachData.found === true) {
// Get the index of where the first regex match in the
// string is. then we slice from 0 to index off of the string
// split it by newline, cut off the first line (which actually
// contains the regex) then reduce back down to a single string.
//
// TODO: This could be simplified if we just slice the text after
// the end tag instead of the begining.
let indexOfRegex = endTagSerachData.startPosition;
linesAboveArray = linesAboveStr.slice(indexOfRegex).split("\n").splice(1);
linesAboveStr = linesAboveArray.reduce((prev, current) => {
return prev + "\n" + current;
}, "");
endTagSerachData = findEndTag(linesAboveStr);
}
/**
* Now we have the set of lines after all other end tags. We now
* need to check if there is still a start tag left in the data. If
* there is no start tag then we want to return an empty array and empty
* key.
*/
let startBlockKey = "";
let startTagSearchData = findStartTag(linesAboveStr);
if (startTagSearchData.found === false) {
return null;
}
else {
/**
* Now we know there is at least 1 start key left, however there
* may be multiple start keys if the user is not closing their
* blocks. We currently dont allow recusive splitting so we
* want to get the last key in our remaining set. Same idea as
* above.
*/
while (startTagSearchData.found === true) {
// Get the index of where the first regex match in the
// string is. then we slice from 0 to index off of the string
// split it by newline, cut off the first line (which actually
// contains the regex) then reduce back down to a single string.
//
// TODO: This could be simplified if we just slice the text after
// the end tag instead of the begining.
let startIndex = startTagSearchData.startPosition;
linesAboveArray = linesAboveStr.slice(startIndex).split("\n");
let startTag = linesAboveArray[0];
let key = getStartTagKey(startTag);
if (key !== null) {
startBlockKey = key;
}
linesAboveArray = linesAboveArray.splice(1);
linesAboveStr = linesAboveArray.reduce((prev, current) => {
return prev + "\n" + current;
}, "");
startTagSearchData = findStartTag(linesAboveStr);
}
}
return { startBlockKey, linesAboveArray };
}
3 years ago
function getEndBlockBelow(linesBelow) {
// Reduce the array down into a single string so that we can
// easily RegEx over the string and find the indicies we're looking for.
let linesBelowStr = linesBelow.reduce((prev, current) => {
return prev + "\n" + current;
}, "");
let endTagSerachData = findEndTag(linesBelowStr);
let startTagSearchData = findStartTag(linesBelowStr);
let sliceEndIndex = -1; // If neither start or end found we return the entire array.
if (endTagSerachData.found === true && startTagSearchData.found === false) {
sliceEndIndex = endTagSerachData.startPosition;
}
else if (endTagSerachData.found === false && startTagSearchData.found === true) {
sliceEndIndex = startTagSearchData.startPosition;
}
else if (endTagSerachData.found === true && startTagSearchData.found === true) {
sliceEndIndex = endTagSerachData.startPosition;
if (startTagSearchData.startPosition < endTagSerachData.startPosition) {
/**
* If we found a start tag before an end tag we want to use the start tag
* our current block is not properly ended and we use the next start tag
* as our limit
*/
sliceEndIndex = startTagSearchData.startPosition;
}
}
return linesBelow.slice(0, sliceEndIndex);
}
3 years ago
function getStartTagKey(startTag) {
let keySplit = startTag.split(":");
if (keySplit.length > 1) {
return keySplit[1].replace(" ", "");
}
return null;
}
/*
* Filename: multi-column-markdown/src/utilities/utils.ts
* Created Date: Tuesday, January 30th 2022, 4:02:19 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
function getUID(length = 10) {
if (length > 10) {
length = 10;
}
let UID = Math.random().toString(36).substring(2);
UID = UID.slice(0, length);
return UID;
}
3 years ago
var ElementRenderType;
(function (ElementRenderType) {
ElementRenderType[ElementRenderType["undefined"] = 0] = "undefined";
ElementRenderType[ElementRenderType["normalRender"] = 1] = "normalRender";
ElementRenderType[ElementRenderType["specialRender"] = 2] = "specialRender";
})(ElementRenderType || (ElementRenderType = {}));
function getElementRenderType(element) {
/**
* Look for specific kinds of elements by their CSS class names here. These
* are going to be brittle links as they rely on other plugin definitions but
* as this is only adding in extra compatability to the plugins defined here
* it should be ok.
*
* These may be classes on one of the simple elements (such as a paragraph)
* that we search for below so need to look for these first.
*/
if (hasDiceRoller(element) === true) {
return ElementRenderType.specialRender;
}
if (hasAdmonition(element) === true) {
return ElementRenderType.normalRender;
}
/**
* If we didnt find a special element we want to check for simple elements
* such as paragraphs or lists. In the current implementation we only set up
* the special case for "specialRender" elements so this *should* be saving
* some rendering time by setting these tags properly.
*/
if (hasParagraph(element) ||
hasHeader(element) ||
hasList(element)) {
return ElementRenderType.normalRender;
}
// If still nothing found we return other as the default response if nothing else found.
return ElementRenderType.specialRender;
}
function hasParagraph(element) {
return element.innerHTML.startsWith("<p");
}
function hasHeader(element) {
if (element.innerHTML.startsWith("<h1") ||
element.innerHTML.startsWith("<h2") ||
element.innerHTML.startsWith("<h3") ||
element.innerHTML.startsWith("<h4") ||
element.innerHTML.startsWith("<h5")) {
return true;
}
return false;
}
function hasList(element) {
if (element.innerHTML.startsWith("<ul") ||
element.innerHTML.startsWith("<ol")) {
return true;
}
return false;
}
function hasDiceRoller(element) {
return element.getElementsByClassName("dice-roller").length !== 0;
}
function hasAdmonition(element) {
return element.getElementsByClassName("admonition").length !== 0;
}
3 years ago
/*
* Filename: multi-column-markdown/src/domObject.ts
* Created Date: Tuesday, February 1st 2022, 12:04:00 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
var DOMObjectTag;
(function (DOMObjectTag) {
DOMObjectTag[DOMObjectTag["none"] = 0] = "none";
DOMObjectTag[DOMObjectTag["startRegion"] = 1] = "startRegion";
DOMObjectTag[DOMObjectTag["regionSettings"] = 2] = "regionSettings";
DOMObjectTag[DOMObjectTag["columnBreak"] = 3] = "columnBreak";
DOMObjectTag[DOMObjectTag["endRegion"] = 4] = "endRegion";
})(DOMObjectTag || (DOMObjectTag = {}));
class DOMObject {
3 years ago
constructor(element, randomID = getUID(), tag = DOMObjectTag.none) {
this.elementType = ElementRenderType.undefined;
this.elementContainer = null;
this.nodeKey = element.innerText.trim();
3 years ago
this.element = element;
this.UID = randomID;
this.tag = tag;
3 years ago
this.usingOriginalElement = false;
}
setMainDOMElement(domElement) {
this.element = domElement;
this.usingOriginalElement = true;
3 years ago
}
}
class DOMRegionSettingsObject extends DOMObject {
constructor(baseDOMObject, regionSettings) {
3 years ago
super(baseDOMObject.element, baseDOMObject.UID, DOMObjectTag.regionSettings);
3 years ago
this.regionSettings = regionSettings;
}
}
3 years ago
/*
* File: multi-column-markdown/src/utilities/cssDefinitions.ts
* Created Date: Wednesday, February 16th 2022, 11:09:06 am
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
var MultiColumnLayoutCSS;
(function (MultiColumnLayoutCSS) {
MultiColumnLayoutCSS["RegionRootContainerDiv"] = "multiColumnContainer";
MultiColumnLayoutCSS["RegionErrorContainerDiv"] = "multiColumnErrorContainer";
MultiColumnLayoutCSS["RegionContentContainerDiv"] = "RenderColRegion";
MultiColumnLayoutCSS["RegionColumnContainerDiv"] = "multiColumnParent";
MultiColumnLayoutCSS["RegionColumnContent"] = "columnContent";
MultiColumnLayoutCSS["ColumnDualElementContainer"] = "MultiColumn_ElementContainer";
MultiColumnLayoutCSS["OriginalElementType"] = "MultiColumn_OriginalElement";
MultiColumnLayoutCSS["ClonedElementType"] = "MultiColumn_ClonedElement";
})(MultiColumnLayoutCSS || (MultiColumnLayoutCSS = {}));
var MultiColumnStyleCSS;
(function (MultiColumnStyleCSS) {
MultiColumnStyleCSS["RegionErrorMessage"] = "multiColumnErrorMessage";
MultiColumnStyleCSS["RegionSettings"] = "multiColumnSettings";
MultiColumnStyleCSS["RegionContent"] = "multiColumnContent";
MultiColumnStyleCSS["RegionEndTag"] = "multiColumnRegionEndTag";
MultiColumnStyleCSS["ColumnEndTag"] = "multiColumnBreak";
MultiColumnStyleCSS["RegionShadow"] = "multiColumnParentShadow";
MultiColumnStyleCSS["ColumnShadow"] = "columnShadow";
MultiColumnStyleCSS["ColumnBorder"] = "columnBorder";
})(MultiColumnStyleCSS || (MultiColumnStyleCSS = {}));
3 years ago
/*
* File: multi-column-markdown/src/domManager.ts
* Created Date: Saturday, January 30th 2022, 3:16:32 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
class GlobalDOMManager {
constructor() {
this.managers = new Map();
}
removeFileManagerCallback(key) {
if (this.managers.has(key) === true) {
this.managers.delete(key);
}
}
getFileManager(key) {
let fileManager = null;
if (this.managers.has(key) === true) {
fileManager = this.managers.get(key);
}
else {
fileManager = createFileDOMManager(this, key);
this.managers.set(key, fileManager);
}
return fileManager;
}
3 years ago
getAllFileManagers() {
return Array.from(this.managers.values());
}
3 years ago
}
function createFileDOMManager(parentManager, fileKey) {
let regionMap = new Map();
let hasStartTag = false;
function removeRegion(regionKey) {
let regionManager = regionMap.get(regionKey);
if (regionManager) {
regionManager.displayOriginalElements();
}
regionMap.delete(regionKey);
if (regionMap.size === 0) {
parentManager.removeFileManagerCallback(fileKey);
}
}
3 years ago
function createRegionalManager(regionKey, rootElement, errorElement, renderRegionElement) {
3 years ago
//TODO: Use the error element whenever there is an error.
3 years ago
let regonalManager = createRegionalDomManager(this, regionKey, rootElement, renderRegionElement);
3 years ago
regionMap.set(regionKey, regonalManager);
return regonalManager;
}
function getRegionalManager(regionKey) {
let regonalManager = null;
if (regionMap.has(regionKey) === true) {
regonalManager = regionMap.get(regionKey);
}
return regonalManager;
}
3 years ago
function getAllRegionalManagers() {
return Array.from(regionMap.values());
}
3 years ago
function setHasStartTag() {
hasStartTag = true;
}
function getHasStartTag() {
return hasStartTag;
}
function getNumberOfRegions() {
return regionMap.size;
}
function checkKeyExists(checkKey) {
return regionMap.has(checkKey);
}
return { regionMap: regionMap,
hasStartTag: hasStartTag,
createRegionalManager: createRegionalManager,
getRegionalManager: getRegionalManager,
3 years ago
getAllRegionalManagers: getAllRegionalManagers,
3 years ago
removeRegion: removeRegion,
setHasStartTag: setHasStartTag,
getHasStartTag: getHasStartTag,
getNumberOfRegions: getNumberOfRegions,
checkKeyExists: checkKeyExists
};
}
3 years ago
function createRegionalDomManager(fileManager, regionKey, rootElement, renderRegionElement) {
3 years ago
/**
* We use a list and a map to help keep track of the objects. Requires
* more memory but makes processing things a little cleaner and presumably
* faster.
*
* Use the map to look up object by key and the list is used to track objects
* in the order they are in the document.
*/
let domList = [];
let domObjectMap = new Map();
3 years ago
let regionParent = renderRegionElement;
3 years ago
let regionSettings = [];
3 years ago
function addObject(siblingsAbove, siblingsBelow, obj) {
let prevObj = siblingsAbove.children[siblingsAbove.children.length - 1];
let nextObj = siblingsBelow.children[0];
3 years ago
let addAtIndex = siblingsAbove.children.length;
3 years ago
if (prevObj !== undefined) {
for (let i = domList.length - 1; i >= 0; i--) {
if (domList[i].nodeKey === prevObj.innerText) {
addAtIndex = i + 1;
break;
}
}
}
let nextElIndex = addAtIndex;
if (nextObj !== undefined) {
for (let i = addAtIndex; i < domList.length; i++) {
if (domList[i].nodeKey === nextObj.innerText.trim()) {
nextElIndex = i;
break;
}
}
}
// console.log(" Prev: ", siblingsAbove.children[siblingsAbove.children.length - 1], "Adding: ", obj.element, " Next: ", siblingsBelow.children[0], "Overwriting:", domList.slice(addAtIndex, nextElIndex));
domList.splice(addAtIndex, nextElIndex - addAtIndex, obj);
3 years ago
domObjectMap.set(obj.UID, obj);
// /**
// * Make a copy of the list to log, only because
// * console log updates its references with updates in memory.
// */
// let x = domList.slice(0);
// console.log(x);
return addAtIndex;
}
function removeObject(objectUID) {
// /**
// * Make a copy of the list to log
// */
// let x = domList.slice(0);
// console.log(x);
// Get the object by key, remove it from the map and then
// from the list.
let obj = domObjectMap.get(objectUID);
domObjectMap.delete(objectUID);
3 years ago
if (obj === undefined) {
return;
}
3 years ago
if (domList.contains(obj)) {
domList.remove(obj);
}
// If the object is a settings object we need to remove from the
// settings list.
if (obj.tag === DOMObjectTag.regionSettings) {
let settingsObj = obj;
if (regionSettings.contains(settingsObj)) {
regionSettings.remove(settingsObj);
}
}
if (domList.length === 0) {
fileManager.removeRegion(regionKey);
}
// x = domList.slice(0);
// console.log(x);
}
function updateElementTag(objectUID, newTag) {
let obj = domObjectMap.get(objectUID);
let index = domList.indexOf(obj);
if (index !== -1) {
domList[index].tag = newTag;
}
}
function setElementToSettingsBlock(objectUID, settingsText) {
let obj = domObjectMap.get(objectUID);
let index = domList.indexOf(obj);
if (index !== -1) {
let settings = parseColumnSettings(settingsText);
let regionSettingsObj = new DOMRegionSettingsObject(domList[index], settings);
domObjectMap.set(regionSettingsObj.UID, regionSettingsObj);
domList[index] = regionSettingsObj;
regionSettings.push(regionSettingsObj);
}
}
/**
* Creates an object containing all necessary information for the region
* to be rendered to the preview pane.
*
* @returns a MultiColumnRenderData object with the root DOM element, settings object, and
* all child objects in the order they should be rendered.
*/
function getRegionRenderData() {
// Set defaults before attempting to get settings.
let settings = { numberOfColumns: 2, columnLayout: ColumnLayout.standard, drawBorder: true, drawShadow: true };
if (regionSettings.length > 0) {
/**
* Since we append settings onto the end of the array we want the last
* item in the array as that would be the most recent settings we parsed.
*/
settings = regionSettings[regionSettings.length - 1].regionSettings;
}
return {
parentRenderElement: regionParent,
parentRenderSettings: settings,
domObjects: domList
};
}
/**
* This fuction is called when a start tag is removed from view meaning
* our parent element storing the multi-column region is removed. It
* removes the CSS class from all of the elements so they will be
* re-rendered in the preview window.
*/
function displayOriginalElements() {
for (let i = 0; i < domList.length; i++) {
3 years ago
if (domList[i].element) {
domList[i].element.removeClasses([MultiColumnStyleCSS.RegionEndTag,
MultiColumnStyleCSS.ColumnEndTag,
MultiColumnStyleCSS.RegionSettings,
MultiColumnStyleCSS.RegionContent]);
if (domList[i].element.parentElement) {
domList[i].element.parentElement.removeChild(domList[i].element);
}
}
3 years ago
}
for (let i = 0; i < regionSettings.length; i++) {
3 years ago
if (regionSettings[i].element) {
regionSettings[i].element.removeClasses([MultiColumnStyleCSS.RegionEndTag,
MultiColumnStyleCSS.ColumnEndTag,
MultiColumnStyleCSS.RegionSettings,
MultiColumnStyleCSS.RegionContent]);
if (regionSettings[i].element.parentElement) {
regionSettings[i].element.parentElement.removeChild(regionSettings[i].element);
}
}
3 years ago
}
}
3 years ago
function getRootRegionElement() {
return rootElement;
}
3 years ago
return { addObject: addObject,
removeObject: removeObject,
updateElementTag: updateElementTag,
setElementToSettingsBlock: setElementToSettingsBlock,
getRegionRenderData: getRegionRenderData,
3 years ago
displayOriginalElements: displayOriginalElements,
getRootRegionElement: getRootRegionElement
3 years ago
};
}
/*
* File: multi-column-markdown/src/main.ts
* Created Date: Tuesday, October 5th 2021, 1:09 pm
* Author: Cameron Robinson
*
* Copyright (c) 2022 Cameron Robinson
*/
class MultiColumnMarkdown extends obsidian.Plugin {
constructor() {
// settings: SplitColumnMarkdownSettings;
super(...arguments);
this.globalManager = new GlobalDOMManager();
}
onload() {
return __awaiter(this, void 0, void 0, function* () {
console.log("Loading multi-column markdown");
this.setupMarkdownPostProcessor();
//TODO: Set up this as a modal to set settings automatically
this.addCommand({
id: `insert-multi-column-region`,
name: `Insert Multi-Column Region`,
editorCallback: (editor, view) => {
try {
editor.getDoc().replaceSelection(`
=== multi-column-start: ID_${getUID(4)}
\`\`\`column-settings
Number of Columns: 2
Largest Column: standard
\`\`\`
=== end-column ===
=== multi-column-end
${editor.getDoc().getSelection()}`);
}
catch (e) {
new obsidian.Notice("Encountered an error inserting a multi-column region. Please try again later.");
}
}
});
this.addCommand({
id: `add-IDs-To-multi-column-region`,
name: `Fix Missing IDs for Multi-Column Regions`,
editorCallback: (editor, view) => {
try {
/**
* Not sure if there is an easier way to do this.
*
* Get all of the lines of the document split by newlines.
*/
let lines = editor.getRange({ line: 0, ch: 0 }, { line: editor.getDoc().lineCount(), ch: 0 }).split("\n");
/**
* Loop through all of the lines checking if the line is a
* start tag and if so is it missing an ID.
*/
let linesWithoutIDs = [];
let textWithoutIDs = [];
for (let i = 0; i < lines.length; i++) {
let data = isStartTagWithID(lines[i]);
if (data.isStartTag === true && data.hasKey === false) {
linesWithoutIDs.push(i);
textWithoutIDs.push(lines[i]);
}
}
if (linesWithoutIDs.length === 0) {
new obsidian.Notice("Found 0 missing IDs in the current document.");
return;
}
/**
* Now loop through each line that is missing an ID and
* generate a random ID and replace the original text.
*/
for (let i = 0; i < linesWithoutIDs.length; i++) {
let originalText = textWithoutIDs[i];
let text = originalText;
text = text.trimEnd();
if (text.charAt(text.length - 1) === ":") {
text = text.slice(0, text.length - 1);
}
text = `${text}: ID_${getUID(4)}`;
editor.replaceRange(text, { line: linesWithoutIDs[i], ch: 0 }, { line: linesWithoutIDs[i], ch: originalText.length });
}
new obsidian.Notice(`Replaced ${linesWithoutIDs.length} missing ID(s) in the current document.`);
}
catch (e) {
new obsidian.Notice("Encountered an error addign IDs to multi-column regions. Please try again later.");
}
}
});
3 years ago
this.registerInterval(window.setInterval(() => {
this.UpdateOpenFilePreviews();
}, 2000));
});
}
UpdateOpenFilePreviews() {
let fileManagers = this.globalManager.getAllFileManagers();
fileManagers.forEach(element => {
let regionalManagers = element.getAllRegionalManagers();
regionalManagers.forEach(regionManager => {
let parentElementData = regionManager.getRegionRenderData();
this.updateRenderedMarkdown(parentElementData.domObjects);
});
3 years ago
});
}
setupMarkdownPostProcessor() {
this.registerMarkdownPostProcessor((el, ctx) => __awaiter(this, void 0, void 0, function* () {
// Get the info for our current context and then check
// if the entire text contains a start tag. If there is
// no start tag in the document we can just return and
// ignore the rest of the parsing.
let info = ctx.getSectionInfo(el);
/**
* We need the context info to properly parse so returning here
* info is null. TODO: Set error in view if this occurs.
*/
if (!info) {
return;
}
const sourcePath = ctx.sourcePath;
let fileDOMManager = this.globalManager.getFileManager(sourcePath);
if (fileDOMManager === null) {
console.log("Found null DOM manager. Could not process multi-column markdown.");
return;
}
/**
* If we encounter a start tag on the document we set the flag to start
* parsing the rest of the document.
*/
if (containsStartTag(el.textContent)) {
fileDOMManager.setHasStartTag();
}
/**
* If the document does not contain any start tags we ignore the
* rest of the parsing. This is only set to true once the first
* start tag element is parsed above.
*/
if (fileDOMManager.getHasStartTag() === false) {
return;
}
/**
* Take the info provided and generate the required variables from
* the line start and end values.
*/
3 years ago
let docLines = info.text.split("\n");
let linesAboveArray = docLines.slice(0, info.lineStart);
let linesOfElement = docLines.slice(info.lineStart, info.lineEnd + 1);
let linesBelowArray = docLines.slice(info.lineEnd + 1);
3 years ago
/**
* If the current line is a start tag we want to set up the
* region manager. The regional manager takes care
* of all items between it's start and end tags while the
* file manager we got above above takes care of all regional
* managers in each file.
*/
3 years ago
let elementTextSpaced = linesOfElement.reduce((prev, curr) => {
return prev + "\n" + curr;
});
3 years ago
if (containsStartTag(el.textContent)) {
/**
* Set up the current element to act as the parent for the
* multi-column region.
*/
el.children[0].detach();
3 years ago
el.classList.add(MultiColumnLayoutCSS.RegionRootContainerDiv);
3 years ago
let renderErrorRegion = el.createDiv({
3 years ago
cls: `${MultiColumnLayoutCSS.RegionErrorContainerDiv}, ${MultiColumnStyleCSS.RegionErrorMessage}`,
3 years ago
});
let renderColumnRegion = el.createDiv({
3 years ago
cls: MultiColumnLayoutCSS.RegionContentContainerDiv
3 years ago
});
let startBlockData = getStartBlockAboveLine(linesOfElement);
let regionKey = startBlockData.startBlockKey;
if (fileDOMManager.checkKeyExists(regionKey) === true) {
let { numberOfTags, keys } = countStartTags(info.text);
3 years ago
let numMatches = 0;
for (let i = 0; i < numberOfTags; i++) {
3 years ago
// Because we checked if key exists one of these has to match.
3 years ago
if (keys[i] === regionKey) {
numMatches++;
3 years ago
}
}
3 years ago
// We only want to display an error if there are more than 2 of the same id across
// the whole document. This prevents erros when obsidian reloads the whole document
// and there are two of the same key in the map.
if (numMatches >= 2) {
if (regionKey === "") {
renderErrorRegion.innerText = "Found multiple regions with empty IDs. Please set a unique ID after each start tag.\nEG: '=== multi-column-start: randomID'\nOr use 'Fix Missing IDs' in the command palette and reload the document.";
}
else {
renderErrorRegion.innerText = "Region ID already exists in document, please set a unique ID.\nEG: '=== multi-column-start: randomID'";
}
return;
3 years ago
}
}
3 years ago
el.id = `MultiColumnID:${regionKey}`;
3 years ago
let elementMarkdownRenderer = new obsidian.MarkdownRenderChild(el);
3 years ago
fileDOMManager.createRegionalManager(regionKey, el, renderErrorRegion, renderColumnRegion);
3 years ago
elementMarkdownRenderer.onunload = () => {
if (fileDOMManager) {
fileDOMManager.removeRegion(startBlockData.startBlockKey);
}
};
ctx.addChild(elementMarkdownRenderer);
/**
* Now we have created our regional manager and defined what elements
* need to be rendered into. So we can return without any more processing.
*/
return;
}
/**
3 years ago
* Check if any of the lines above us contain a start block, and if
* so get the lines from our current element to the start block.
3 years ago
*/
let startBockAbove = getStartBlockAboveLine(linesAboveArray);
3 years ago
if (startBockAbove === null) {
3 years ago
return;
}
/**
3 years ago
* We now know we're within a multi-column region, so we update our
* list of lines above to just be the items within this region.
3 years ago
*/
linesAboveArray = startBockAbove.linesAboveArray;
/**
3 years ago
* We use the start block's key to get our regional manager. If this
* lookup fails we can not continue processing this element.
3 years ago
*/
3 years ago
let regionalManager = fileDOMManager.getRegionalManager(startBockAbove.startBlockKey);
3 years ago
if (regionalManager === null) {
return;
}
3 years ago
/**
* To make sure we're placing the item in the right location (and
* overwrite elements that are now gone) we now want all of the
* lines after this element up to the end tag.
*/
linesBelowArray = getEndBlockBelow(linesBelowArray);
3 years ago
/**
* Now we take the lines above our current element up until the
* start region tag and render that into an HTML element. We will
3 years ago
* use these elements to determine where to place our current element.
3 years ago
*/
3 years ago
let siblingsAbove = renderMarkdownFromLines(linesAboveArray, sourcePath);
let siblingsBelow = renderMarkdownFromLines(linesBelowArray, sourcePath);
3 years ago
/**
* Set up our dom object to be added to the manager.
*/
3 years ago
let currentObject = new DOMObject(el);
el.id = currentObject.UID;
3 years ago
/**
* Now we add the object to the manager and then setup the
* callback for when the object is removed from view that will remove
* the item from the manager.
*/
3 years ago
regionalManager.addObject(siblingsAbove, siblingsBelow, currentObject);
3 years ago
let elementMarkdownRenderer = new obsidian.MarkdownRenderChild(el);
elementMarkdownRenderer.onunload = () => {
if (regionalManager) {
// We can attempt to update the view here after the item is removed
// but need to get the item's parent element before removing object from manager.
let regionRenderData = regionalManager.getRegionRenderData();
regionalManager.removeObject(currentObject.UID);
/**
* Need to check here if element is null as this closure will be called
* repeatedly on file change.
*/
if (regionRenderData.parentRenderElement === null) {
return;
}
this.renderColumnMarkdown(regionRenderData.parentRenderElement, regionRenderData.domObjects, regionRenderData.parentRenderSettings);
}
};
ctx.addChild(elementMarkdownRenderer);
/**
* Now we check if our current element is a special flag so we can
* properly set the element tag within the regional manager.
*/
if (containsEndTag(el.textContent) === true) {
3 years ago
el.addClass(MultiColumnStyleCSS.RegionEndTag);
3 years ago
regionalManager.updateElementTag(currentObject.UID, DOMObjectTag.endRegion);
}
else if (containsColEndTag(elementTextSpaced) === true) {
3 years ago
el.addClass(MultiColumnStyleCSS.ColumnEndTag);
3 years ago
regionalManager.updateElementTag(currentObject.UID, DOMObjectTag.columnBreak);
}
else if (containsColSettingsTag(elementTextSpaced) === true) {
3 years ago
el.addClass(MultiColumnStyleCSS.RegionSettings);
3 years ago
regionalManager.setElementToSettingsBlock(currentObject.UID, elementTextSpaced);
}
3 years ago
else {
el.addClass(MultiColumnStyleCSS.RegionContent);
}
3 years ago
/**
* Use our regional manager to get everything needed to render the region.
*/
let parentElementData = regionalManager.getRegionRenderData();
this.renderColumnMarkdown(parentElementData.parentRenderElement, parentElementData.domObjects, parentElementData.parentRenderSettings);
return;
}));
}
/**
* This function takes in the data for the multi-column region and sets up the
* user defined number of children with the proper css classes to be rendered properly.
*
* @param parentElement The element that the multi-column region will be rendered under.
* @param regionElements The list of DOM objects that will be coppied under the parent object
* @param settings The settings the user has defined for the region.
*/
renderColumnMarkdown(parentElement, regionElements, settings) {
let multiColumnParent = createDiv({
3 years ago
cls: MultiColumnLayoutCSS.RegionColumnContainerDiv,
3 years ago
});
if (settings.drawShadow === true) {
3 years ago
multiColumnParent.addClass(MultiColumnStyleCSS.RegionShadow);
3 years ago
}
/**
* Pass our parent div and settings to parser to create the required
* column divs as children of the parent.
*/
let columnContentDivs = getColumnContentDivs(settings, multiColumnParent);
for (let i = 0; i < columnContentDivs.length; i++) {
if (settings.drawBorder === true) {
3 years ago
columnContentDivs[i].addClass(MultiColumnStyleCSS.ColumnBorder);
3 years ago
}
if (settings.drawShadow === true) {
3 years ago
columnContentDivs[i].addClass(MultiColumnStyleCSS.ColumnShadow);
3 years ago
}
}
// Create markdown renderer to parse the passed markdown
// between the tags.
let markdownRenderChild = new obsidian.MarkdownRenderChild(multiColumnParent);
// Remove every other child from the parent so
// we dont end up with multiple sets of data. This should
// really only need to loop once for i = 0 but loop just
// in case.
for (let i = parentElement.children.length - 1; i >= 0; i--) {
parentElement.children[i].detach();
}
parentElement.appendChild(markdownRenderChild.containerEl);
let columnIndex = 0;
for (let i = 0; i < regionElements.length; i++) {
3 years ago
if (regionElements[i].tag !== DOMObjectTag.startRegion ||
regionElements[i].tag !== DOMObjectTag.regionSettings ||
regionElements[i].tag !== DOMObjectTag.endRegion ||
regionElements[i].tag !== DOMObjectTag.columnBreak) {
// We store the elements in a wrapper container until we determine
let element = createDiv({
cls: MultiColumnLayoutCSS.ColumnDualElementContainer,
});
regionElements[i].elementContainer = element;
// Otherwise we just make a copy of the original element to display.
element.appendChild(regionElements[i].element.cloneNode(true));
if (element !== null) {
columnContentDivs[columnIndex].appendChild(element);
}
3 years ago
/**
3 years ago
* If the tag is a column break we update the column index after
* appending the item to the column div. This keeps the main DOM
* cleaner by removing other items and placing them all within
* a region container.
3 years ago
*/
3 years ago
if (regionElements[i].tag === DOMObjectTag.columnBreak &&
(columnIndex + 1) < settings.numberOfColumns) {
columnIndex++;
}
}
}
}
setUpDualRender(domElement) {
/**
* If our element is of "specialRender" type it *may* need to be rendered
* using the original element rather than a copy. For example, an element
* may have an onClick event that would not get coppied to the clone.
*
* If we just moved these elements into the region it would get
* moved back out into the original location in the DOM by obsidian
* when scrolling or when the file is updated. On the next refresh it
* would be moved back but that can lead to a region jumping
* around as the item is moved in and out.
*
* Here we set up the div to contain the element and create
* a visual only clone of it. The clone will only be visible
* when the original is not in the multi-column region so it
* saves us from the visual noise of the region jumping around.
*/
// Remove the old elements before we set up the dual rendered elements.
let containerElement = domElement.elementContainer;
let renderElement = domElement.element;
for (let i = containerElement.children.length - 1; i >= 0; i--) {
containerElement.children[i].detach();
}
containerElement.appendChild(renderElement);
renderElement.addClass(MultiColumnLayoutCSS.OriginalElementType);
let clonedNode = renderElement.cloneNode(true);
clonedNode.addClass(MultiColumnLayoutCSS.ClonedElementType);
clonedNode.removeClasses([MultiColumnStyleCSS.RegionContent, MultiColumnLayoutCSS.OriginalElementType]);
containerElement.appendChild(clonedNode);
}
updateRenderedMarkdown(regionElements) {
/**
* Go through every node of the region looking for the "specialRender" type
* which are the elements that may need to be rendered using the original
* element rather than a copy.
*/
for (let i = 0; i < regionElements.length; i++) {
/**
* Here we check every item again to see if they need to be updated.
* This could be made slightly more efficient if we can truly determine
* wether an item is a normal render item, however it seems like it
* may take a bit of extra time in order for the classes we check for
* to be added to the elements.
*/
let elementType = regionElements[i].elementType;
// If the element is not currently a special render element we check again
// as the original element may have been updated.
if (elementType !== ElementRenderType.specialRender) {
// If the new result returns as a special renderer we update so
// this wont run again for this item.
elementType = getElementRenderType(regionElements[i].element);
}
if (elementType === ElementRenderType.specialRender) {
regionElements[i].elementType = elementType;
this.setUpDualRender(regionElements[i]);
3 years ago
}
}
}
}
/**
* Sets up the CSS classes and the number of columns based on the passed settings.
* @param settings The user defined settings that determine what CSS is set here.
* @param multiColumnParent The parent object that the column divs will be created under.
* @returns The list of column divs created under the passed parent element.
*/
function getColumnContentDivs(settings, multiColumnParent) {
let columnContentDivs = [];
if (settings.numberOfColumns === 2) {
switch (settings.columnLayout) {
case (ColumnLayout.standard):
case (ColumnLayout.middle):
case (ColumnLayout.center):
case (ColumnLayout.third):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoEqualColumns_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoEqualColumns_Right`
}));
break;
case (ColumnLayout.left):
case (ColumnLayout.first):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoColumnsHeavyLeft_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoColumnsHeavyLeft_Right`
}));
break;
case (ColumnLayout.right):
case (ColumnLayout.second):
case (ColumnLayout.last):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoColumnsHeavyRight_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent twoColumnsHeavyRight_Right`
}));
break;
}
}
else if (settings.numberOfColumns === 3) {
switch (settings.columnLayout) {
case (ColumnLayout.standard):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threeEqualColumns_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threeEqualColumns_Middle`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threeEqualColumns_Right`
}));
break;
case (ColumnLayout.left):
case (ColumnLayout.first):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyLeft_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyLeft_Middle`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyLeft_Right`
}));
break;
case (ColumnLayout.middle):
case (ColumnLayout.center):
case (ColumnLayout.second):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyMiddle_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyMiddle_Middle`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyMiddle_Right`
}));
break;
case (ColumnLayout.right):
case (ColumnLayout.third):
case (ColumnLayout.last):
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyRight_Left`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyRight_Middle`
}));
columnContentDivs.push(multiColumnParent.createDiv({
cls: `columnContent threColumnsHeavyRight_Right`
}));
break;
}
}
return columnContentDivs;
}
3 years ago
function renderMarkdownFromLines(mdLines, sourcePath) {
3 years ago
/**
* We re-render all of the items above our element, until the start tag,
* so we can determine where to place the new item in the manager.
*
* TODO: Can reduce the amount needing to be rendered by only rendering to
* the start tag or a column-break whichever is closer.
*/
3 years ago
let siblings = createDiv();
let markdownRenderChild = new obsidian.MarkdownRenderChild(siblings);
obsidian.MarkdownRenderer.renderMarkdown(mdLines.reduce((prev, current) => {
3 years ago
return prev + "\n" + current;
3 years ago
}, ""), siblings, sourcePath, markdownRenderChild);
return siblings;
3 years ago
}
module.exports = MultiColumnMarkdown;
3 years ago
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9yZWdpb25TZXR0aW5ncy50cyIsInNyYy91dGlsaXRpZXMvdGV4dFBhcnNlci50cyIsInNyYy91dGlsaXRpZXMvdXRpbHMudHMiLCJzcmMvdXRpbGl0aWVzL2VsZW1lbnRSZW5kZXJUeXBlUGFyc2VyLnRzIiwic3JjL2RvbV9tYW5hZ2VyL2RvbU9iamVjdC50cyIsInNyYy91dGlsaXRpZXMvY3NzRGVmaW5pdGlvbnMudHMiLCJzcmMvZG9tX21hbmFnZXIvZG9tTWFuYWdlci50cyIsInNyYy9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qISAqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9