@kenjura/ursa 0.10.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +162 -0
- package/README.md +182 -19
- package/bin/ursa.js +208 -0
- package/lib/index.js +7 -2
- package/meta/character-sheet-template.html +2 -0
- package/meta/default-template.html +29 -5
- package/meta/default.css +451 -115
- package/meta/menu.js +387 -0
- package/meta/search.js +208 -0
- package/meta/sectionify.js +36 -0
- package/meta/sticky.js +73 -0
- package/meta/toc-generator.js +124 -0
- package/meta/toc.js +93 -0
- package/package.json +25 -4
- package/src/helper/WikiImage.js +138 -0
- package/src/helper/automenu.js +215 -55
- package/src/helper/contentHash.js +71 -0
- package/src/helper/findStyleCss.js +26 -0
- package/src/helper/linkValidator.js +246 -0
- package/src/helper/metadataExtractor.js +19 -8
- package/src/helper/whitelistFilter.js +66 -0
- package/src/helper/wikitextHelper.js +6 -3
- package/src/jobs/generate.js +353 -112
- package/src/serve.js +138 -37
- package/.nvmrc +0 -1
- package/.vscode/launch.json +0 -20
- package/TODO.md +0 -16
- package/nodemon.json +0 -16
package/meta/sticky.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
+
const article = document.querySelector('article#main-content');
|
|
3
|
+
if (!article) return;
|
|
4
|
+
|
|
5
|
+
const headings = article.querySelectorAll('h1, h2, h3');
|
|
6
|
+
|
|
7
|
+
function updateStuckState() {
|
|
8
|
+
let currentStuckHeading = null;
|
|
9
|
+
|
|
10
|
+
headings.forEach(el => {
|
|
11
|
+
const wasStuck = el.classList.contains('stuck');
|
|
12
|
+
const style = window.getComputedStyle(el);
|
|
13
|
+
if (style.position === 'sticky' && style.top !== 'auto') {
|
|
14
|
+
const rect = el.getBoundingClientRect();
|
|
15
|
+
const top = parseInt(style.top, 10) || 0;
|
|
16
|
+
if (rect.top <= top && rect.bottom > top) {
|
|
17
|
+
el.classList.add('stuck');
|
|
18
|
+
currentStuckHeading = el;
|
|
19
|
+
} else {
|
|
20
|
+
el.classList.remove('stuck');
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
el.classList.remove('stuck');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Dispatch event if stuck state changed
|
|
27
|
+
if (wasStuck !== el.classList.contains('stuck')) {
|
|
28
|
+
const event = new CustomEvent('headingStuckStateChanged', {
|
|
29
|
+
detail: {
|
|
30
|
+
heading: el,
|
|
31
|
+
stuck: el.classList.contains('stuck'),
|
|
32
|
+
currentStuckHeading: currentStuckHeading
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
document.dispatchEvent(event);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Handle text updates for H1 elements
|
|
39
|
+
if (el.tagName === 'H1') {
|
|
40
|
+
// Store original text if not already stored
|
|
41
|
+
if (!el.dataset.originalText) {
|
|
42
|
+
el.dataset.originalText = el.textContent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Only update text if this H1 is stuck
|
|
46
|
+
if (el.classList.contains('stuck')) {
|
|
47
|
+
// Find the last stuck h2 and h3 (i.e., the "current" ones)
|
|
48
|
+
const stuckH2s = Array.from(headings).filter(h => h.tagName === 'H2' && h.classList.contains('stuck'));
|
|
49
|
+
const stuckH3s = Array.from(headings).filter(h => h.tagName === 'H3' && h.classList.contains('stuck'));
|
|
50
|
+
const stuckH2 = stuckH2s.length ? stuckH2s[stuckH2s.length - 1] : null;
|
|
51
|
+
const stuckH3 = stuckH3s.length ? stuckH3s[stuckH3s.length - 1] : null;
|
|
52
|
+
|
|
53
|
+
let newText = el.dataset.originalText;
|
|
54
|
+
|
|
55
|
+
if (stuckH3 && stuckH2) {
|
|
56
|
+
newText += ' > ' + (stuckH2.dataset.originalText || stuckH2.textContent) + ' > ' + (stuckH3.dataset.originalText || stuckH3.textContent);
|
|
57
|
+
} else if (stuckH2) {
|
|
58
|
+
newText += ' > ' + (stuckH2.dataset.originalText || stuckH2.textContent);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
el.textContent = newText;
|
|
62
|
+
} else {
|
|
63
|
+
// Restore original text if not stuck
|
|
64
|
+
el.textContent = el.dataset.originalText;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
updateStuckState();
|
|
71
|
+
window.addEventListener('scroll', updateStuckState, { passive: true });
|
|
72
|
+
window.addEventListener('resize', updateStuckState);
|
|
73
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Table of Contents Generator
|
|
2
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
3
|
+
const tocNav = document.getElementById('nav-toc');
|
|
4
|
+
const article = document.querySelector('article#main-content');
|
|
5
|
+
|
|
6
|
+
if (!tocNav || !article) return;
|
|
7
|
+
|
|
8
|
+
// Find all headings in the article
|
|
9
|
+
const headings = article.querySelectorAll('h1, h2, h3');
|
|
10
|
+
|
|
11
|
+
if (headings.length === 0) {
|
|
12
|
+
tocNav.style.display = 'none';
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Generate TOC HTML
|
|
17
|
+
const tocList = document.createElement('ul');
|
|
18
|
+
|
|
19
|
+
headings.forEach((heading, index) => {
|
|
20
|
+
// Create unique ID for the heading if it doesn't have one
|
|
21
|
+
if (!heading.id) {
|
|
22
|
+
const text = heading.textContent.trim()
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^\w\s-]/g, '') // Remove special characters
|
|
25
|
+
.replace(/\s+/g, '-'); // Replace spaces with hyphens
|
|
26
|
+
heading.id = `heading-${index}-${text}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create TOC item
|
|
30
|
+
const listItem = document.createElement('li');
|
|
31
|
+
listItem.className = `toc-${heading.tagName.toLowerCase()}`;
|
|
32
|
+
|
|
33
|
+
const link = document.createElement('a');
|
|
34
|
+
link.href = `#${heading.id}`;
|
|
35
|
+
link.textContent = heading.textContent;
|
|
36
|
+
link.addEventListener('click', handleTocClick);
|
|
37
|
+
|
|
38
|
+
listItem.appendChild(link);
|
|
39
|
+
tocList.appendChild(listItem);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
tocNav.appendChild(tocList);
|
|
43
|
+
|
|
44
|
+
// Handle TOC link clicks for smooth scrolling
|
|
45
|
+
function handleTocClick(e) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
const targetId = e.target.getAttribute('href').substring(1);
|
|
48
|
+
const targetElement = document.getElementById(targetId);
|
|
49
|
+
|
|
50
|
+
if (targetElement) {
|
|
51
|
+
// Calculate offset to account for sticky header
|
|
52
|
+
const globalNavHeight = getComputedStyle(document.documentElement)
|
|
53
|
+
.getPropertyValue('--global-nav-height') || '48px';
|
|
54
|
+
const offset = parseInt(globalNavHeight) + 40; // Adjusted offset (was +20, now -29 for 49px less)
|
|
55
|
+
|
|
56
|
+
const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - offset;
|
|
57
|
+
|
|
58
|
+
window.scrollTo({
|
|
59
|
+
top: targetPosition,
|
|
60
|
+
behavior: 'smooth'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Update active TOC item based on scroll position
|
|
66
|
+
function updateActiveTocItem() {
|
|
67
|
+
const scrollPosition = window.pageYOffset;
|
|
68
|
+
const globalNavHeight = getComputedStyle(document.documentElement)
|
|
69
|
+
.getPropertyValue('--global-nav-height') || '48px';
|
|
70
|
+
const offset = parseInt(globalNavHeight) + 100;
|
|
71
|
+
|
|
72
|
+
let activeHeading = null;
|
|
73
|
+
|
|
74
|
+
// Find the current heading based on scroll position
|
|
75
|
+
headings.forEach(heading => {
|
|
76
|
+
const headingTop = heading.getBoundingClientRect().top + window.pageYOffset;
|
|
77
|
+
if (headingTop <= scrollPosition + offset) {
|
|
78
|
+
activeHeading = heading;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
updateTocActiveState(activeHeading);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update TOC active state
|
|
86
|
+
function updateTocActiveState(activeHeading) {
|
|
87
|
+
const tocLinks = tocNav.querySelectorAll('a');
|
|
88
|
+
tocLinks.forEach(link => {
|
|
89
|
+
link.classList.remove('active');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (activeHeading) {
|
|
93
|
+
const activeLink = tocNav.querySelector(`a[href="#${activeHeading.id}"]`);
|
|
94
|
+
if (activeLink) {
|
|
95
|
+
activeLink.classList.add('active');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Listen for heading stuck state changes from sticky.js
|
|
101
|
+
document.addEventListener('headingStuckStateChanged', (event) => {
|
|
102
|
+
if (event.detail.currentStuckHeading) {
|
|
103
|
+
updateTocActiveState(event.detail.currentStuckHeading);
|
|
104
|
+
} else {
|
|
105
|
+
// If no heading is stuck, fall back to scroll-based detection
|
|
106
|
+
updateActiveTocItem();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Listen for scroll events
|
|
111
|
+
let ticking = false;
|
|
112
|
+
window.addEventListener('scroll', () => {
|
|
113
|
+
if (!ticking) {
|
|
114
|
+
requestAnimationFrame(() => {
|
|
115
|
+
updateActiveTocItem();
|
|
116
|
+
ticking = false;
|
|
117
|
+
});
|
|
118
|
+
ticking = true;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Initial update
|
|
123
|
+
updateActiveTocItem();
|
|
124
|
+
});
|
package/meta/toc.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
const toc = document.getElementById('toc');
|
|
3
|
+
if (!toc) return;
|
|
4
|
+
|
|
5
|
+
const links = Array.from(toc.querySelectorAll('a[href^="#"]'));
|
|
6
|
+
const heads = links
|
|
7
|
+
.map(a => document.getElementById(decodeURIComponent(a.hash.slice(1))))
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
|
|
10
|
+
const linkById = new Map(links.map(a => [decodeURIComponent(a.hash.slice(1)), a]));
|
|
11
|
+
|
|
12
|
+
// === sticky top detector ===
|
|
13
|
+
let STICKY_TOP = 48; // fallback
|
|
14
|
+
const nav = document.getElementById('nav-global');
|
|
15
|
+
|
|
16
|
+
function readStickyTop() {
|
|
17
|
+
const b = nav?.getBoundingClientRect();
|
|
18
|
+
// if nav is fixed at top, its bottom is the sticky line
|
|
19
|
+
STICKY_TOP = b ? Math.max(0, Math.round(b.bottom)) : 48;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
readStickyTop();
|
|
23
|
+
window.addEventListener('resize', readStickyTop, { passive: true });
|
|
24
|
+
if (nav && 'ResizeObserver' in window) {
|
|
25
|
+
new ResizeObserver(readStickyTop).observe(nav);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// === build 1px sentinels just above each heading ===
|
|
29
|
+
function ensureSentinels() {
|
|
30
|
+
heads.forEach(h => {
|
|
31
|
+
if (h.previousElementSibling?.classList.contains('toc-sentinel')) return;
|
|
32
|
+
const s = document.createElement('div');
|
|
33
|
+
s.className = 'toc-sentinel';
|
|
34
|
+
s.style.position = 'relative';
|
|
35
|
+
s.style.height = '1px';
|
|
36
|
+
s.style.marginTop = `-${STICKY_TOP}px`; // place sentinel STICKY_TOP above h
|
|
37
|
+
s.style.pointerEvents = 'none';
|
|
38
|
+
s.dataset.forId = h.id;
|
|
39
|
+
h.before(s);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// === observer factory tied to current STICKY_TOP ===
|
|
44
|
+
let observer = null;
|
|
45
|
+
function (re)buildObserver() {
|
|
46
|
+
if (observer) observer.disconnect();
|
|
47
|
+
// ignore intersections near the bottom; we only care about the top line
|
|
48
|
+
const bottomRM = -(window.innerHeight - 1) + 'px';
|
|
49
|
+
observer = new IntersectionObserver(updateActiveFromSentinels, {
|
|
50
|
+
root: null,
|
|
51
|
+
rootMargin: `-${STICKY_TOP}px 0px ${bottomRM} 0px`,
|
|
52
|
+
threshold: 0
|
|
53
|
+
});
|
|
54
|
+
document.querySelectorAll('.toc-sentinel').forEach(s => observer.observe(s));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function updateActiveFromSentinels() {
|
|
58
|
+
// Pick the last sentinel whose top is <= 0 relative to the adjusted rootMargin,
|
|
59
|
+
// i.e. the heading whose sticky line has been crossed.
|
|
60
|
+
const sentinels = Array.from(document.querySelectorAll('.toc-sentinel'));
|
|
61
|
+
let candidate = null;
|
|
62
|
+
for (const s of sentinels) {
|
|
63
|
+
const top = s.getBoundingClientRect().top - STICKY_TOP; // compare to sticky line
|
|
64
|
+
if (top <= 0) candidate = s; else break;
|
|
65
|
+
}
|
|
66
|
+
const activeId = candidate ? candidate.dataset.forId : heads[0]?.id;
|
|
67
|
+
links.forEach(a => a.classList.toggle('active', decodeURIComponent(a.hash.slice(1)) === activeId));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// keep anchor jumps clear of the sticky bar
|
|
71
|
+
heads.forEach(h => h.style.scrollMarginTop = (STICKY_TOP + 8) + 'px');
|
|
72
|
+
|
|
73
|
+
// initial setup
|
|
74
|
+
ensureSentinels();
|
|
75
|
+
(re)buildObserver();
|
|
76
|
+
updateActiveFromSentinels();
|
|
77
|
+
|
|
78
|
+
// react when sticky top changes
|
|
79
|
+
let resizeTick = false;
|
|
80
|
+
window.addEventListener('resize', () => {
|
|
81
|
+
if (resizeTick) return;
|
|
82
|
+
resizeTick = true;
|
|
83
|
+
requestAnimationFrame(() => {
|
|
84
|
+
resizeTick = false;
|
|
85
|
+
readStickyTop();
|
|
86
|
+
// update sentinel offsets
|
|
87
|
+
document.querySelectorAll('.toc-sentinel').forEach(s => s.style.marginTop = `-${STICKY_TOP}px`);
|
|
88
|
+
heads.forEach(h => h.style.scrollMarginTop = (STICKY_TOP + 8) + 'px');
|
|
89
|
+
(re)buildObserver();
|
|
90
|
+
updateActiveFromSentinels();
|
|
91
|
+
});
|
|
92
|
+
}, { passive: true });
|
|
93
|
+
})();
|
package/package.json
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
"name": "@kenjura/ursa",
|
|
3
3
|
"author": "Andrew London <andrew@kenjura.com>",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.33.0",
|
|
6
6
|
"description": "static site generator from MD/wikitext/YML",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ursa": "bin/ursa.js"
|
|
10
|
+
},
|
|
8
11
|
"scripts": {
|
|
9
12
|
"serve": "nodemon --config nodemon.json src/serve.js",
|
|
10
13
|
"serve:debug": "nodemon --config nodemon.json --inspect-brk src/serve.js",
|
|
14
|
+
"cli:debug": "node --inspect bin/ursa.js",
|
|
15
|
+
"cli:debug-brk": "node --inspect-brk bin/ursa.js",
|
|
11
16
|
"start": "node src/index.js",
|
|
12
17
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
13
18
|
},
|
|
@@ -27,7 +32,8 @@
|
|
|
27
32
|
"markdown-it-sup": "^1.0.0",
|
|
28
33
|
"node-watch": "^0.7.3",
|
|
29
34
|
"object-to-xml": "^2.0.0",
|
|
30
|
-
"yaml": "^2.1.3"
|
|
35
|
+
"yaml": "^2.1.3",
|
|
36
|
+
"yargs": "^17.7.2"
|
|
31
37
|
},
|
|
32
38
|
"devDependencies": {
|
|
33
39
|
"jest": "^29.6.2",
|
|
@@ -43,6 +49,21 @@
|
|
|
43
49
|
},
|
|
44
50
|
"keywords": [
|
|
45
51
|
"static-site",
|
|
46
|
-
"markdown"
|
|
47
|
-
|
|
52
|
+
"markdown",
|
|
53
|
+
"static-site-generator",
|
|
54
|
+
"wikitext",
|
|
55
|
+
"cli"
|
|
56
|
+
],
|
|
57
|
+
"files": [
|
|
58
|
+
"lib/",
|
|
59
|
+
"src/",
|
|
60
|
+
"bin/",
|
|
61
|
+
"meta/",
|
|
62
|
+
"README.md",
|
|
63
|
+
"CHANGELOG.md"
|
|
64
|
+
],
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public",
|
|
67
|
+
"registry": "https://registry.npmjs.org/"
|
|
68
|
+
}
|
|
48
69
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export function getImageTag(args) {
|
|
2
|
+
if (!args) args = {};
|
|
3
|
+
if (!args.name) { console.error('WikiImage.getImage > cannot get image with no name.'); return ''; }
|
|
4
|
+
// if (!args.article) { console.error('WikiImage.getImage > article not supplied.'); return ''; }
|
|
5
|
+
|
|
6
|
+
// var images = args.article.images;
|
|
7
|
+
// var image = null;
|
|
8
|
+
// if (images) {
|
|
9
|
+
// for (var i = 0; i < images.length; i++) {
|
|
10
|
+
// if ( images[i].name == args.name ) {
|
|
11
|
+
// image = images[i];
|
|
12
|
+
// break;
|
|
13
|
+
// }
|
|
14
|
+
// }
|
|
15
|
+
// }
|
|
16
|
+
// if (!image) {
|
|
17
|
+
// // image not yet uploaded
|
|
18
|
+
// return '<div class="noImage" ng-click="activateImage(\''+args.name+'\')">?</div>';
|
|
19
|
+
// } else {
|
|
20
|
+
// path
|
|
21
|
+
// var imgUrl = WikiImage.imageRoot + image.path;
|
|
22
|
+
var imgUrl = args.imgUrl;
|
|
23
|
+
// style
|
|
24
|
+
var width = 'auto', height = 'auto', classes = '', caption = '', fillMode = null, float = '';
|
|
25
|
+
if (args.args&&args.args.length) {
|
|
26
|
+
for (var i = 0; i < args.args.length; i++) {
|
|
27
|
+
var arg = args.args[i];
|
|
28
|
+
// numbers = width/height. for now, let's just do width
|
|
29
|
+
if (!isNaN(arg)) {
|
|
30
|
+
if (width=='auto') width = parseFloat(arg) + 'px';
|
|
31
|
+
else height = parseFloat(arg) + 'px';
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg.substr(-1)=='%') {
|
|
35
|
+
if (width=='auto') width = arg;
|
|
36
|
+
else height = arg;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg.substr(-2)=='px') {
|
|
40
|
+
if (width=='auto') width = arg;
|
|
41
|
+
else height = arg;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// string values might mean something...
|
|
45
|
+
if (arg=='right') { float = 'float: right; clear: right;'; continue; }
|
|
46
|
+
if (arg=='fit'||arg=='box') { classes += arg + ' '; continue; }
|
|
47
|
+
if (arg=='center') { classes += 'center '; continue; }
|
|
48
|
+
if (arg=='cover'||arg=='contain') { fillMode = arg; continue; }
|
|
49
|
+
// else, assume it's the caption
|
|
50
|
+
caption = arg;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
var style = 'width: '+width+'; height: '+height+';' + float;
|
|
54
|
+
|
|
55
|
+
// events
|
|
56
|
+
// var events = 'onclick="_scope.activateImage(\''+args.name+'\',\''+imgUrl+'\')"';
|
|
57
|
+
var events = 'ng-click="activateImage(\''+args.name+'\',\''+imgUrl+'\')"';
|
|
58
|
+
|
|
59
|
+
//return '<img class="wikiImage" src="'+imgUrl+'" style="'+style+'" '+events+' />';
|
|
60
|
+
|
|
61
|
+
var template = ''+
|
|
62
|
+
'<a href="{src}">'+
|
|
63
|
+
'<div class="wikiImage {class}" style="{style}" title="{caption}">'+
|
|
64
|
+
'<img src="{src}" {events} />'+
|
|
65
|
+
'<div class="wikiImage_caption">{caption}</div>'+
|
|
66
|
+
'</div>'+
|
|
67
|
+
'</a>';
|
|
68
|
+
|
|
69
|
+
// var template2 = ''+
|
|
70
|
+
// '<a href="{src}">'+
|
|
71
|
+
// '<div class="wikiImage {class}" style="background: url(\'{src}\'); background-size: {fillMode}; {style}" {events}>'+
|
|
72
|
+
// '<div class="wikiImage_caption">{caption}</div>'+
|
|
73
|
+
// '</div>'+
|
|
74
|
+
// '</a>';
|
|
75
|
+
|
|
76
|
+
const template2 = `<span class="wiki-image-container"><img src="${imgUrl}" style="${style}" class="wikiImage wiki-image" onClick="evt => imageZoom(evt)" />`;
|
|
77
|
+
return template2;
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
var html = template;
|
|
81
|
+
if (fillMode) html = template2;
|
|
82
|
+
|
|
83
|
+
html = html.replace( /\{src\}/g , imgUrl );
|
|
84
|
+
html = html.replace( '{class}' , classes );
|
|
85
|
+
html = html.replace( '{style}' , style );
|
|
86
|
+
html = html.replace( '{events}' , events );
|
|
87
|
+
html = html.replace( /\{caption\}/g , caption );
|
|
88
|
+
if (fillMode)
|
|
89
|
+
html = html.replace( '{fillMode}' , fillMode );
|
|
90
|
+
|
|
91
|
+
return html;
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
var imgCache = localStorage.getItem('imageCache');
|
|
96
|
+
if (!imgCache) imgCache = JSON.stringify({});
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
imgCache = JSON.parse(imgCache);
|
|
100
|
+
var imgUrl = imgCache[args.name];
|
|
101
|
+
if (!imgUrl) {
|
|
102
|
+
//var img = new WikiImage(args);
|
|
103
|
+
var img = new WikiImage(args);
|
|
104
|
+
var tag = img.render();
|
|
105
|
+
imgCache[args.name] = img.url;
|
|
106
|
+
} else {
|
|
107
|
+
args.url = imgUrl;
|
|
108
|
+
var img = new WikiImage(args);
|
|
109
|
+
var tag = img.render();
|
|
110
|
+
}
|
|
111
|
+
localStorage.setItem('imageCache',JSON.stringify(imgCache));
|
|
112
|
+
return tag;
|
|
113
|
+
} catch(e) {
|
|
114
|
+
error('WikiImage.getImage > could not retrieve or parse the image cache. error to follow.');
|
|
115
|
+
error(e);
|
|
116
|
+
return new WikiImage(args);
|
|
117
|
+
}
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
/* how images should work:
|
|
127
|
+
* when you load an article, you also get a list of all images used in that article, and their URLs
|
|
128
|
+
* when rendering an image, check that list first. if the image name isn't in that list, don't bother trying to load the image
|
|
129
|
+
** if it is in the list, load it, relying on browser cache to reduce load
|
|
130
|
+
** important: do not use the server method to find each image's url; get them all from the loadArticle call
|
|
131
|
+
** the FS db option should store image (and link) data in a special section at the end of the article body. it also doesn't allow images to have a different name than their url
|
|
132
|
+
* when an image is not yet uploaded, show a gray box with a question mark
|
|
133
|
+
* when the grey box or the image is clicked, pop up the image upload modal
|
|
134
|
+
* the image upload modal uses the appropriate service endpoint to upload, then returns the new URL
|
|
135
|
+
** the new URL is then cached
|
|
136
|
+
** the image is loaded from the URL
|
|
137
|
+
** the image is associated with the article, which is immediately saved with the new image association
|
|
138
|
+
*/
|