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.

746 lines
58 KiB

3 years ago
/*
THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugin's 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());
});
}
var codemirror = CodeMirror;
function createCommonjsModule(fn) {
var module = { exports: {} };
return fn(module, module.exports), module.exports;
}
createCommonjsModule(function (module, exports) {
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
mod(codemirror);
})(function(CodeMirror) {
CodeMirror.defineMode("cook", function() {
return {
token: function(stream, state) {
var sol = stream.sol() || state.afterSection;
var eol = stream.eol();
state.afterSection = false;
if (sol) {
if (state.nextMultiline) {
state.inMultiline = true;
state.nextMultiline = false;
} else {
state.position = null;
}
}
if (eol && ! state.nextMultiline) {
state.inMultiline = false;
state.position = null;
}
if (sol) {
while(stream.eatSpace()) {}
}
var ch = stream.next();
if (sol && ch === ">") {
if (stream.eat(">")) {
state.position = "metadata-key";
return "metadata"
}
}
if(state.position === "metadata");
else if(state.position === "metadata-key") {
if(ch === ':') state.position = "metadata";
}
else {
if (ch === "-") {
if (stream.eat("-")) {
stream.skipToEnd();
return "comment";
}
}
if (stream.match(/\[-.+?-\]/))
return "comment";
if(stream.match(/^@([^@#~]+?(?={))/))
return "ingredient";
else if(stream.match(/^@(.+?\b)/))
return "ingredient";
if(stream.match(/^#([^@#~]+?(?={))/))
return "cookware";
else if(stream.match(/^#(.+?\b)/))
return "cookware";
if(ch === '~'){
state.position = "timer";
return "formatting";
}
if(ch === '{'){
if(state.position != "timer") state.position = "measurement";
return "formatting";
}
if(ch === '}'){
state.position = null;
return "formatting";
}
if(ch === '%' && (state.position === "measurement" || state.position === "timer")){
state.position = "unit";
return "formatting";
}
}
return state.position;
},
startState: function() {
return {
formatting : false,
nextMultiline : false, // Is the next line multiline value
inMultiline : false, // Is the current line a multiline value
afterSection : false // Did we just open a section
};
}
};
});
CodeMirror.defineMIME("text/x-cook", "cook");
CodeMirror.defineMIME("text/x-cooklang", "cook");
});
});
// utility class for parsing cooklang files
class CookLang {
static parse(source) {
const recipe = new Recipe();
source.split('\n').forEach(line => {
let match;
// clear comments
line = line.replace(/(--.*)|(\[-.+?-\])/, '');
// skip blank lines
if (line.trim().length === 0)
return;
// metadata lines
else if (match = Metadata.regex.exec(line)) {
recipe.metadata.push(new Metadata(match[0]));
}
// method lines
else {
// ingredients on a line
while (match = Ingredient.regex.exec(line)) {
const ingredient = new Ingredient(match[0]);
recipe.ingredients.push(ingredient);
line = line.replace(match[0], ingredient.methodOutput());
}
// cookware on a line
while (match = Cookware.regex.exec(line)) {
const c = new Cookware(match[0]);
recipe.cookware.push(c);
line = line.replace(match[0], c.methodOutput());
}
// timers on a line
while (match = Timer.regex.exec(line)) {
const t = new Timer(match[0]);
recipe.timers.push(t);
line = line.replace(match[0], t.methodOutput());
}
// add in the method line
recipe.method.push(line.trim());
}
});
return recipe;
}
}
// a class representing a recipe
class Recipe {
constructor() {
this.metadata = [];
this.ingredients = [];
this.cookware = [];
this.timers = [];
this.method = [];
this.methodImages = new Map();
}
calculateTotalTime() {
let time = 0;
this.timers.forEach(timer => {
let amount = 0;
if (parseFloat(timer.amount) + '' == timer.amount)
amount = parseFloat(timer.amount);
else if (timer.amount.contains('/')) {
const split = timer.amount.split('/');
if (split.length == 2) {
const num = parseFloat(split[0]);
const den = parseFloat(split[1]);
if (num && den) {
amount = num / den;
}
}
}
if (amount > 0) {
if (timer.unit.toLowerCase().startsWith('s')) {
time += amount;
}
else if (timer.unit.toLowerCase().startsWith('m')) {
time += amount * 60;
}
else if (timer.unit.toLowerCase().startsWith('h')) {
time += amount * 60 * 60;
}
}
});
return time;
}
}
// a class representing an ingredient
class Ingredient {
constructor(s) {
var _a;
this.originalString = null;
this.name = null;
this.amount = null;
this.unit = null;
this.methodOutput = () => {
let s = `<span class='ingredient'>`;
if (this.amount !== null) {
s += `<span class='amount'>${this.amount} </span>`;
}
if (this.unit !== null) {
s += `<span class='unit'>${this.unit} </span>`;
}
s += `${this.name}</span>`;
return s;
};
this.listOutput = () => {
let s = ``;
if (this.amount !== null) {
s += `<span class='amount'>${this.amount}</span> `;
}
if (this.unit !== null) {
s += `<span class='unit'>${this.unit}</span> `;
}
s += this.name;
return s;
};
this.originalString = s;
const match = Ingredient.regex.exec(s);
this.name = match[1] || match[3];
const attrs = (_a = match[2]) === null || _a === void 0 ? void 0 : _a.split('%');
this.amount = attrs && attrs.length > 0 ? attrs[0] : null;
this.unit = attrs && attrs.length > 1 ? attrs[1] : null;
}
}
// starts with an @, ends at a word boundary or {}
// (also capture what's inside the {})
Ingredient.regex = /@(?:([^@#~]+?)(?:{(.*?)}|{}))|@(.+?\b)/;
// a class representing an item of cookware
class Cookware {
constructor(s) {
this.originalString = null;
this.name = null;
this.methodOutput = () => {
return `<span class='cookware'>${this.name}</span>`;
};
this.listOutput = () => {
return this.name;
};
this.originalString = s;
const match = Cookware.regex.exec(s);
this.name = match[1] || match[2];
}
}
// starts with a #, ends at a word boundary or {}
Cookware.regex = /#(?:([^@#~]+?)(?:{}))|#(.+?\b)/;
// a class representing a timer
class Timer {
constructor(s) {
this.originalString = null;
this.amount = null;
this.unit = null;
this.methodOutput = () => {
return `<span class='time'><span class='time-amount'>${this.amount}</span> <span class='time-unit'>${this.unit}</span></span>`;
};
this.listOutput = () => {
return `<span class='time-amount'>${this.amount}</span> <span class='time-unit'>${this.unit}</span>`;
};
const match = Timer.regex.exec(s);
this.amount = match[1];
this.unit = match[2];
}
}
// contained within ~{}
Timer.regex = /~{([0-9]+)%(.+?)}/;
// a class representing metadata item
class Metadata {
constructor(s) {
this.originalString = null;
this.key = null;
this.value = null;
this.methodOutput = () => {
return `<span class='metadata metadata-key'>${this.key}</span> <span class='metadata metadata-value'>${this.value}</span>`;
};
this.listOutput = () => {
return `<span class='metadata-key'>${this.key}</span> <span class='metadata-value'>${this.value}</span>`;
};
const match = Metadata.regex.exec(s);
this.key = match[1].trim();
this.value = match[2].trim();
}
}
// starts with >>
Metadata.regex = />>\s*(.*?):\s*(.*)/;
// This is the custom view
class CookView extends obsidian.TextFileView {
constructor(leaf, settings) {
super(leaf);
this.settings = settings;
// Add Preview Mode Container
this.previewEl = this.contentEl.createDiv({ cls: 'cook-preview-view', attr: { 'style': 'display: none' } });
// Add Source Mode Container
this.sourceEl = this.contentEl.createDiv({ cls: 'cook-source-view', attr: { 'style': 'display: block' } });
// Add container for CodeMirror editor
this.editorEl = this.sourceEl.createEl('textarea', { cls: 'cook-cm-editor' });
// Create CodeMirror Editor with specific config
this.editor = CodeMirror.fromTextArea(this.editorEl, {
lineNumbers: false,
lineWrapping: true,
scrollbarStyle: null,
keyMap: "default",
theme: "obsidian"
});
}
onload() {
// Save file on change
this.editor.on('change', () => {
this.requestSave();
});
// add the action to switch between source and preview mode
this.changeModeButton = this.addAction('lines-of-text', 'Preview (Ctrl+Click to open in new pane)', (evt) => this.switchMode(evt), 17);
// undocumented: Get the current default view mode to switch to
let defaultViewMode = this.app.vault.getConfig('defaultViewMode');
this.setState(Object.assign(Object.assign({}, this.getState()), { mode: defaultViewMode }), {});
}
getState() {
return super.getState();
}
setState(state, result) {
return super.setState(state, result).then(() => {
if (state.mode)
this.switchMode(state.mode);
});
}
// function to switch between source and preview mode
switchMode(arg) {
let mode = arg;
// if force mode not provided, switch to opposite of current mode
if (!mode || mode instanceof MouseEvent)
mode = this.currentView === 'source' ? 'preview' : 'source';
if (arg instanceof MouseEvent) {
if (obsidian.Keymap.isModEvent(arg)) {
this.app.workspace.duplicateLeaf(this.leaf).then(() => {
var _a;
const cookLeaf = (_a = this.app.workspace.activeLeaf) === null || _a === void 0 ? void 0 : _a.view;
if (cookLeaf) {
cookLeaf.setState(Object.assign(Object.assign({}, cookLeaf.getState()), { mode: mode }), {});
}
});
}
else {
this.setState(Object.assign(Object.assign({}, this.getState()), { mode: mode }), {});
}
}
else {
// switch to preview mode
if (mode === 'preview') {
this.currentView = 'preview';
obsidian.setIcon(this.changeModeButton, 'pencil');
this.changeModeButton.setAttribute('aria-label', 'Edit (Ctrl+Click to edit in new pane)');
this.renderPreview(this.recipe);
this.previewEl.style.setProperty('display', 'block');
this.sourceEl.style.setProperty('display', 'none');
}
// switch to source mode
else {
this.currentView = 'source';
obsidian.setIcon(this.changeModeButton, 'lines-of-text');
this.changeModeButton.setAttribute('aria-label', 'Preview (Ctrl+Click to open in new pane)');
this.previewEl.style.setProperty('display', 'none');
this.sourceEl.style.setProperty('display', 'block');
this.editor.refresh();
}
}
}
// get the data for save
getViewData() {
this.data = this.editor.getValue();
// may as well parse the recipe while we're here.
this.recipe = CookLang.parse(this.data);
return this.data;
}
// load the data into the view
setViewData(data, clear) {
return __awaiter(this, void 0, void 0, function* () {
this.data = data;
if (clear) {
this.editor.swapDoc(CodeMirror.Doc(data, "text/x-cook"));
this.editor.clearHistory();
}
this.editor.setValue(data);
this.recipe = CookLang.parse(data);
// if we're in preview view, also render that
if (this.currentView === 'preview')
this.renderPreview(this.recipe);
});
}
// clear the editor, etc
clear() {
this.previewEl.empty();
this.editor.setValue('');
this.editor.clearHistory();
this.recipe = new Recipe();
this.data = null;
}
getDisplayText() {
if (this.file)
return this.file.basename;
else
return "Cooklang (no file)";
}
canAcceptExtension(extension) {
return extension == 'cook';
}
getViewType() {
return "cook";
}
// when the view is resized, refresh CodeMirror (thanks Licat!)
onResize() {
this.editor.refresh();
}
// icon for the view
getIcon() {
return "document-cook";
}
// render the preview view
renderPreview(recipe) {
// clear the preview before adding the rest
this.previewEl.empty();
// we can't render what we don't have...
if (!recipe)
return;
if (this.settings.showImages) {
// add any files following the cooklang conventions to the recipe object
// https://cooklang.org/docs/spec/#adding-pictures
const otherFiles = this.file.parent.children.filter(f => (f instanceof obsidian.TFile) && (f.basename == this.file.basename || f.basename.startsWith(this.file.basename + '.')) && f.name != this.file.name);
otherFiles.forEach(f => {
// convention specifies JPEGs and PNGs. Added GIFs as well. Why not?
if (f.extension == "jpg" || f.extension == "jpeg" || f.extension == "png" || f.extension == "gif") {
// main recipe image
if (f.basename == this.file.basename)
recipe.image = f;
else {
const split = f.basename.split('.');
// individual step images
if (split.length == 2 && parseInt(split[1])) {
recipe.methodImages.set(parseInt(split[1]), f);
}
}
}
});
// if there is a main image, put it as a banner image at the top
if (recipe.image) {
const img = this.previewEl.createEl('img', { cls: 'main-image' });
img.src = this.app.vault.getResourcePath(recipe.image);
}
}
if (this.settings.showIngredientList) {
// Add the Ingredients header
this.previewEl.createEl('h2', { cls: 'ingredients-header', text: 'Ingredients' });
// Add the ingredients list
const ul = this.previewEl.createEl('ul', { cls: 'ingredients' });
recipe.ingredients.forEach(ingredient => {
const li = ul.createEl('li');
if (ingredient.amount !== null) {
li.createEl('span', { cls: 'amount', text: ingredient.amount });
li.appendText(' ');
}
if (ingredient.unit !== null) {
li.createEl('span', { cls: 'unit', text: ingredient.unit });
li.appendText(' ');
}
li.appendText(ingredient.name);
});
}
if (this.settings.showCookwareList) {
// Add the Cookware header
this.previewEl.createEl('h2', { cls: 'cookware-header', text: 'Cookware' });
// Add the Cookware list
const ul = this.previewEl.createEl('ul', { cls: 'cookware' });
recipe.cookware.forEach(item => {
ul.createEl('li', { text: item.name });
});
}
if (this.settings.showTotalTime) {
let time = recipe.calculateTotalTime();
if (time > 0) {
// Add the Timers header
this.previewEl.createEl('h2', { cls: 'time-header', text: 'Total Time' });
this.previewEl.createEl('p', { cls: 'time', text: this.formatTime(time) });
}
}
// add the method header
this.previewEl.createEl('h2', { cls: 'method-header', text: 'Method' });
// add the method list
const mol = this.previewEl.createEl('ol', { cls: 'method' });
let i = 1;
recipe.method.forEach(line => {
var _a, _b;
const mli = mol.createEl('li');
mli.innerHTML = line;
if (!this.settings.showQuantitiesInline) {
(_a = mli.querySelectorAll('.amount')) === null || _a === void 0 ? void 0 : _a.forEach(el => el.remove());
(_b = mli.querySelectorAll('.unit')) === null || _b === void 0 ? void 0 : _b.forEach(el => el.remove());
}
if (this.settings.showImages && recipe.methodImages.has(i)) {
const img = mli.createEl('img', { cls: 'method-image' });
img.src = this.app.vault.getResourcePath(recipe.methodImages.get(i));
}
i++;
});
}
formatTime(time) {
let minutes = Math.floor(time / 60);
let hours = Math.floor(minutes / 60);
minutes = minutes % 60;
let result = "";
if (hours > 0)
result += hours + " hours ";
if (minutes > 0)
result += minutes + " minutes ";
return result;
}
}
class CookLangSettings {
constructor() {
this.showImages = true;
this.showIngredientList = true;
this.showCookwareList = true;
this.showTotalTime = true;
this.showQuantitiesInline = false;
}
}
class CookSettingsTab extends obsidian.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
let { containerEl } = this;
containerEl.empty();
new obsidian.Setting(containerEl)
.setName('Preview Options')
.setHeading();
new obsidian.Setting(containerEl)
.setName('Show images')
.setDesc('Show images in the recipe (see https://cooklang.org/docs/spec/#adding-pictures)')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showImages)
.onChange((value) => {
this.plugin.settings.showImages = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadCookViews();
}));
new obsidian.Setting(containerEl)
.setName('Show ingredient list')
.setDesc('Show the list of ingredients at the top of the recipe')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showIngredientList)
.onChange((value) => {
this.plugin.settings.showIngredientList = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadCookViews();
}));
new obsidian.Setting(containerEl)
.setName('Show cookware list')
.setDesc('Show the list of cookware at the top of the recipe')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showCookwareList)
.onChange((value) => {
this.plugin.settings.showCookwareList = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadCookViews();
}));
new obsidian.Setting(containerEl)
.setName('Show total time')
.setDesc('Show the total of all timers at the top of the recipe')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showTotalTime)
.onChange((value) => {
this.plugin.settings.showTotalTime = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadCookViews();
}));
new obsidian.Setting(containerEl)
.setName('Show quantities inline')
.setDesc('Show the ingredient quantities inline in the recipe method')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showQuantitiesInline)
.onChange((value) => {
this.plugin.settings.showQuantitiesInline = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadCookViews();
}));
}
}
class CookPlugin extends obsidian.Plugin {
constructor() {
super(...arguments);
this.cookFileCreator = () => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
let newFileFolderPath = null;
const newFileLocation = this.app.vault.getConfig('newFileLocation');
if (!newFileLocation || newFileLocation === "root") {
newFileFolderPath = '/';
}
else if (newFileLocation === "current") {
newFileFolderPath = (_b = (_a = this.app.workspace.getActiveFile()) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.path;
}
else {
newFileFolderPath = this.app.vault.getConfig('newFileFolderPath');
}
if (!newFileFolderPath)
newFileFolderPath = '/';
else if (!newFileFolderPath.endsWith('/'))
newFileFolderPath += '/';
const originalPath = newFileFolderPath;
newFileFolderPath = newFileFolderPath + 'Untitled.cook';
let i = 0;
while (this.app.vault.getAbstractFileByPath(newFileFolderPath)) {
newFileFolderPath = `${originalPath}Untitled ${++i}.cook`;
}
const newFile = yield this.app.vault.create(newFileFolderPath, '');
return newFile;
});
// function to create the view
this.cookViewCreator = (leaf) => {
return new CookView(leaf, this.settings);
};
// this function provides the icon for the document
// I added a modification of the CookLang icon with no colours or shadows
this.addDocumentIcon = (extension) => {
obsidian.addIcon(`document-${extension}`, `
<svg viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30 48C39.9411 48 48 39.9411 48 30H12C12 39.9411 20.0589 48 30 48Z" fill="currentColor"/>
<circle cx="18" cy="18" r="4" fill="currentColor"/>
<circle cx="42" cy="18" r="4" fill="currentColor"/>
<circle cx="30" cy="16" r="4" fill="currentColor"/>
</svg>
`);
};
}
onload() {
const _super = Object.create(null, {
onload: { get: () => super.onload }
});
return __awaiter(this, void 0, void 0, function* () {
_super.onload.call(this);
this.settings = Object.assign(new CookLangSettings(), yield this.loadData());
// register a custom icon
this.addDocumentIcon("cook");
// register the view and extensions
this.registerView("cook", this.cookViewCreator);
this.registerExtensions(["cook"], "cook");
this.addSettingTab(new CookSettingsTab(this.app, this));
// commands:
// - Create new recipe
// - Create recipe in new pane
// - Convert markdown file to `.cook`
this.addCommand({
id: "create-cook",
name: "Create new recipe",
callback: () => __awaiter(this, void 0, void 0, function* () {
const newFile = yield this.cookFileCreator();
this.app.workspace.getLeaf().openFile(newFile);
})
});
this.addCommand({
id: "create-cook-new-pane",
name: "Create recipe in new pane",
callback: () => __awaiter(this, void 0, void 0, function* () {
const newFile = yield this.cookFileCreator();
yield this.app.workspace.getLeaf(true).openFile(newFile);
})
});
// register the convert to cook command
this.addCommand({
id: "convert-to-cook",
name: "Convert markdown file to `.cook`",
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
const isMd = file.extension === "md";
if (checking) {
return isMd;
}
else if (isMd) {
// replace last instance of .md with .cook
this.app.vault.rename(file, file.path.replace(/\.md$/, ".cook")).then(() => {
this.app.workspace.activeLeaf.openFile(file);
});
}
}
});
});
}
reloadCookViews() {
this.app.workspace.getLeavesOfType('cook').forEach(leaf => {
if (leaf.view instanceof CookView) {
leaf.view.settings = this.settings;
if (leaf.view.recipe)
leaf.view.renderPreview(leaf.view.recipe);
}
});
}
}
module.exports = CookPlugin;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9saWIvY29kZW1pcnJvci5qcyIsInNyYy9tb2RlL2Nvb2svY29vay5qcyIsInNyYy9jb29rbGFuZy50cyIsInNyYy9jb29rVmlldy50cyIsInNyYy9zZXR0aW5ncy50cyIsInNyYy9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpudWxsLCJuYW1lcyI6WyJyZXF1aXJlJCQwIiwiVGV4dEZpbGVWaWV3IiwiS2V5bWFwIiwic2V0SWNvbiIsIlRGaWxlIiwiUGx1Z2luU2V0dGluZ1RhYiIsIlNldHRpbmciLCJQbHVnaW4iLCJhZGRJY29uIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBdURBO0FBQ08sU0FBUyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFO0FBQzdELElBQUksU0FBUyxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsT0FBTyxLQUFLLFlBQVksQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxVQUFVLE9BQU8sRUFBRSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFO0FBQ2hILElBQUksT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsVUFBVSxPQUFPLEVBQUUsTUFBTSxFQUFFO0FBQy9ELFFBQVEsU0FBUyxTQUFTLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtBQUNuRyxRQUFRLFNB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