diff --git a/libs/pell.js b/libs/pell.js
new file mode 100644
index 00000000..9274f50d
--- /dev/null
+++ b/libs/pell.js
@@ -0,0 +1,163 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Pell = factory());
+}(this, (function () { 'use strict';
+
+ const defaultParagraphSeparatorString = 'defaultParagraphSeparator'
+ const formatBlock = 'formatBlock'
+ const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener)
+ const appendChild = (parent, child) => parent.appendChild(child)
+ const createElement = tag => document.createElement(tag)
+ const queryCommandState = command => document.queryCommandState(command)
+ const queryCommandValue = command => document.queryCommandValue(command)
+ const exec = (command, value = null) => document.execCommand(command, false, value)
+
+ const defaultActions = {
+ bold: {
+ icon: 'B',
+ title: 'Bold',
+ state: () => queryCommandState('bold'),
+ result: () => exec('bold')
+ },
+ italic: {
+ icon: 'I',
+ title: 'Italic',
+ state: () => queryCommandState('italic'),
+ result: () => exec('italic')
+ },
+ underline: {
+ icon: 'U',
+ title: 'Underline',
+ state: () => queryCommandState('underline'),
+ result: () => exec('underline')
+ },
+ strikethrough: {
+ icon: 'S',
+ title: 'Strike-through',
+ state: () => queryCommandState('strikeThrough'),
+ result: () => exec('strikeThrough')
+ },
+ heading1: {
+ icon: 'H1',
+ title: 'Heading 1',
+ result: () => exec(formatBlock, '
') + }, + quote: { + icon: '“ ”', + title: 'Quote', + result: () => exec(formatBlock, '
')
+ },
+ olist: {
+ icon: '#',
+ title: 'Ordered List',
+ result: () => exec('insertOrderedList')
+ },
+ ulist: {
+ icon: '•',
+ title: 'Unordered List',
+ result: () => exec('insertUnorderedList')
+ },
+ code: {
+ icon: '</>',
+ title: 'Code',
+ result: () => exec(formatBlock, '')
+ },
+ line: {
+ icon: '―',
+ title: 'Horizontal Line',
+ result: () => exec('insertHorizontalRule')
+ },
+ link: {
+ icon: '🔗',
+ title: 'Link',
+ result: () => navigator.clipboard.readText().then(url => exec('createLink', url))
+ },
+ image: {
+ icon: '📷',
+ title: 'Image',
+ result: () => {
+ navigator.clipboard.readText().then(url => exec('insertImage', url))
+ exec('enableObjectResizing')
+ }
+ }
+ }
+
+ const defaultClasses = {
+ actionbar: 'pell-actionbar',
+ button: 'pell-button',
+ content: 'pell-content',
+ selected: 'pell-button-selected'
+ }
+
+ const init = settings => {
+ const actions = settings.actions
+ ? (
+ settings.actions.map(action => {
+ if (typeof action === 'string') return defaultActions[action]
+ else if (defaultActions[action.name]) return { ...defaultActions[action.name], ...action }
+ return action
+ })
+ )
+ : Object.keys(defaultActions).map(action => defaultActions[action])
+
+ const classes = { ...defaultClasses, ...settings.classes }
+
+ const defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || 'div'
+
+ const actionbar = createElement('div')
+ actionbar.className = classes.actionbar
+ appendChild(settings.element, actionbar)
+
+ const content = settings.element.content = createElement('div')
+ content.contentEditable = true
+ content.className = classes.content
+ content.oninput = ({ target: { firstChild } }) => {
+ if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`)
+ else if (content.innerHTML === '
') content.innerHTML = ''
+ settings.onChange(content.innerHTML)
+ }
+ content.onkeydown = event => {
+ if (event.key === 'Enter' && queryCommandValue(formatBlock) === 'blockquote') {
+ setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0)
+ }
+ }
+ appendChild(settings.element, content)
+
+ actions.forEach(action => {
+ const button = createElement('button')
+ button.className = classes.button
+ button.innerHTML = action.icon
+ button.title = action.title
+ button.setAttribute('type', 'button')
+ button.onclick = () => action.result() && content.focus()
+
+ if (action.state) {
+ const handler = () => button.classList[action.state() ? 'add' : 'remove'](classes.selected)
+ addEventListener(content, 'keyup', handler)
+ addEventListener(content, 'mouseup', handler)
+ addEventListener(button, 'click', handler)
+ }
+
+ appendChild(actionbar, button)
+ })
+
+ if (settings.styleWithCSS) exec('styleWithCSS')
+ exec(defaultParagraphSeparatorString, defaultParagraphSeparator)
+
+ return settings.element
+ }
+
+ return {exec, init}
+
+})));
\ No newline at end of file
diff --git a/main.js b/main.js
index 37d89739..6ca7317b 100644
--- a/main.js
+++ b/main.js
@@ -637,6 +637,7 @@ function calculateVoronoi(graph, points) {
function markFeatures() {
TIME && console.time("markFeatures");
Math.random = aleaPRNG(seed); // restart Math.random() to get the same result on heightmap edit in Erase mode
+
const cells = grid.cells, heights = grid.cells.h;
cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land coast; -1 = water near coast;
diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js
index d0e37161..bf9d2620 100644
--- a/modules/cultures-generator.js
+++ b/modules/cultures-generator.js
@@ -74,6 +74,7 @@
ERROR && console.error("Name base is empty, default nameBases will be applied");
nameBases = Names.getNameBases();
}
+
cultures.forEach(c => c.base = c.base % nameBases.length);
function getRandomCultures(c) {
@@ -380,6 +381,7 @@
}
});
}
+
TIME && console.timeEnd('expandCultures');
}
diff --git a/modules/ui/general.js b/modules/ui/general.js
index 99c51095..5e6d671f 100644
--- a/modules/ui/general.js
+++ b/modules/ui/general.js
@@ -100,6 +100,7 @@ function showMapTooltip(point, e, i, g) {
tip(e.target.parentNode.dataset.name + ". Click to edit");
return;
}
+
if (group === "emblems" && e.target.tagName === "use") {
const parent = e.target.parentNode;
const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] :
@@ -115,6 +116,7 @@ function showMapTooltip(point, e, i, g) {
tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`);
return;
}
+
if (group === "rivers") {
const river = +e.target.id.slice(5);
const r = pack.rivers.find(r => r.i === river);
diff --git a/modules/ui/layers.js b/modules/ui/layers.js
index 8cf4a2c4..ef60400e 100644
--- a/modules/ui/layers.js
+++ b/modules/ui/layers.js
@@ -954,7 +954,6 @@ function getProvincesVertices() {
chain.push([start, province, land]); // add starting vertex to sequence to close the path
return chain;
}
-
}
function toggleGrid(event) {