pWiki/v2/index.html
Alex A. Naanou 11ce24e145 cleanup:separating versions...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2023-09-20 16:31:25 +03:00

610 lines
15 KiB
HTML
Executable File

<!DOCTYPE html>
<html>
<!--html manifest="pwiki.appcache"-->
<head>
<title>pWiki</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="manifest" href="manifest.json">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Bold.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-BoldItalic.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-ExtraBold.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-ExtraBoldItalic.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Italic.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Light.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-LightItalic.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Regular.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-Semibold.ttf">
<link rel="prefetch" href="css/fonts/Open_Sans/OpenSans-SemiboldItalic.ttf">
<link rel="stylesheet" href="css/fonts.css">
<!-- NativeMarkdown -->
<script src="ext-lib/showdown.min.js"></script>
<script>
var MarkdownPage = {
}
</script>
<!-- NativeMarkdown -->
<!-- MediumEditor -->
<!--link rel="stylesheet" href="experiments/medium-editor/css/medium-editor.css">
<link rel="stylesheet" href="experiments/medium-editor/css/themes/default.css">
<script src="ext-lib/showdown.min.js"></script>
<script src="experiments/medium-editor/js/medium-editor.js"></script>
<script src="experiments/medium-editor/js/me-markdown.standalone.js"></script>
<script>
var setupMediumEditor = async function () {
var editorelem = document.querySelector('.medium-editor')
if(editorelem){
console.log('MediumEditor: setup...')
var page = pwiki.get('..')
// load the initial state...
var converter = new showdown.Converter()
editorelem.innerHTML = converter.makeHtml(await page.raw)
var elem = document.querySelector('.medium-markdown')
editor = new MediumEditor(editorelem, {
extensions: {
markdown: new MeMarkdown(function(code) {
saveLiveContent(page.path, code)
// XXX DEBUG...
elem
&& (elem.textContent = code) }) } }) } }
</script-->
<!-- MediumEditor -->
<!-- ToastUIEditor -->
<!-- ToastUIEditor -->
</head>
<style>
body {
font-size: 1.1em;
}
h1:empty {
display: none;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.show-on-hover {
opacity: 0;
}
:hover>.show-on-hover {
opacity: 0.4;
}
.show-on-hover:hover {
opacity: 0.8;
}
/* Spinner... */
.spinner {
position: fixed;
display: flex;
text-align: center;
left: 50%;
top: 50%;
width: 100px;
height: 100px;
margin-top: -50px;
margin-left: -50px;
white-space: nowrap;
background: transparent;
pointer-events: none;
animation: fadein 2s 1;
}
.spinner span {
position: relative;
display: inline-block;
font-size: 2em;
animation: loading 2s infinite ;
animation-delay: calc(0.2s * var(--i));
}
body:not(.loading) .page.spinner {
display: none;
}
body.loading .page.spinner {
opacity: 0.9;
animation: none;
}
body.loading .page.spinner span {
font-size: 4em;
/*
animation: loading 2s infinite ;
animation-delay: calc(0.2s * var(--i));
*/
transform: rotateY(90deg);
animation: loading-ninty 2s infinite ;
animation-delay: calc(0.2s * var(--i));
}
@keyframes fadein {
from {
opacity: 0;
}
50% {
opacity: 0.8;
}
to {
opacity: 1;
}
}
@keyframes loading {
0%, 60% {
transform: rotateY(360deg);
}
}
@keyframes loading-ninty {
0% {
transform: rotateY(90deg);
}
98% {
transform: rotateY(360deg);
}
}
.placeholder {
opacity: 0.4;
}
.new-page-indicator {
position: absolute;
font-size: small;
font-style: italic;
opacity: 0.5;
}
/* TOC */
toc {
--toc-level-offset: 2em;
}
toc a {
display: block;
}
toc .h1 {
margin-left: 0em;
}
toc .h2 {
margin-left: calc(var(--toc-level-offset) * 1);
}
toc .h3 {
margin-left: calc(var(--toc-level-offset) * 2);
}
toc .h4 {
margin-left: calc(var(--toc-level-offset) * 3);
}
toc .h5 {
margin-left: calc(var(--toc-level-offset) * 4);
}
toc .h5 {
margin-left: calc(var(--toc-level-offset) * 5);
}
.error .msg {
font-weight: bold;
color: red;
margin-bottom: 1em;
}
.error {
background: lightyellow;
padding: 1em;
box-shadow: inset 3px 5px 15px 5px rgba(0,0,0,0.03);
border: dashed red 1px;
}
textarea {
font-size: 1.2em;
border: none;
resize: none;
}
[contenteditable] {
outline: 0px solid transparent;
}
textarea:empty:after,
[contenteditable]:empty:after {
display: block;
content: 'Empty';
opacity: 0.3;
}
.tree-page-title:empty:after {
content: "/";
}
</style>
<!--
Do not edit here...
This is loaded with the style defined by the system
-->
<style id="style"></style>
<!-- XXX do we need this??? -->
<script src="bootstrap.js"></script>
<!--script data-main="pwiki2" src="ext-lib/require.js"></script-->
<script src="ext-lib/require.js"></script>
<script>
//---------------------------------------------------------------------
var DEBUG = true
//---------------------------------------------------------------------
// Module loading...
var makeFallbacks =
function(paths, search=['lib']){
return Object.entries(paths)
.map(function([key, path]){
// package...
if(path.endsWith('/')){
return [key, path] }
return [
key,
[
path,
...search
.map(function(base){
return base +'/'+ path.split(/[\\\/]+/g).pop() }),
]
] })
.reduce(function(res, [key, value]){
res[key] = value
return res }, {}) }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
require.config({
paths: {
...makeFallbacks({
'ig-doc': 'node_modules/ig-doc/doc',
'ig-stoppable': 'node_modules/ig-stoppable/stoppable',
'object-run': 'node_modules/object-run/run',
'ig-object': 'node_modules/ig-object/object',
}),
// packages...
'ig-types': [
'node_modules/ig-types',
'lib/types',
],
// external stuff...
...makeFallbacks({
'jszip': 'node_modules/jszip/dist/jszip',
'pouchdb': 'node_modules/pouchdb/dist/pouchdb',
'showdown': 'node_modules/showdown/dist/showdown',
'idb-keyval': 'node_modules/idb-keyval/dist/umd',
'flexsearch': 'node_modules/flexsearch/dist/flexsearch.bundle',
'any-date-parser': 'node_modules/any-date-parser/dist/browser-bundle',
}, ['ext-lib']),
},
packages: [
'ig-types',
]
})
//---------------------------------------------------------------------
// Editor -- save changes...
/* XXX GLOBAL_STYLE
// XXX might be a good idea to make this a method of pwiki???
var STYLE_UPDATED = false
var updateStyle = async function(){
document.querySelector('#style').innerHTML =
await pwiki.get('/.config/Style/_text').text }
//*/
// XXX might be a good idea to make this a method of pwiki???
// XXX the page seems to be broken...
var CONFIG_UPDATED = false
var updateConfig = async function(){
// XXX need to set this to something...
// XXX
// XXX need to use a parser that supports comments and stuff...
return JSON.parse(await pwiki.get('/.config/Config/_text').text) }
// XXX versioning??
var SAVE_LIVE_TIMEOUT = 5000
var SAVE_LIVE_QUEUE = {}
var saveLiveContent =
function(path, text){
SAVE_LIVE_QUEUE[path] = text
// clear editor page cache...
pwiki.cache = null }
var SAVE_QUEUE = {}
var saveContent =
function(path, text){
SAVE_QUEUE[path] = text }
var saveAll =
function(){
saveNow()
var queue = Object.entries(SAVE_QUEUE)
SAVE_QUEUE = {}
queue
.forEach(function([path, text]){
console.log('saving changes to:', path)
pwiki.get(path).raw = text }) }
var saveNow =
function(){
var queue = Object.entries(SAVE_LIVE_QUEUE)
SAVE_LIVE_QUEUE = {}
NEW_TITLE = undefined
queue
.forEach(function([path, text]){
console.log('saving changes to:', path)
pwiki.get(path).raw = text }) }
setInterval(saveNow, SAVE_LIVE_TIMEOUT)
//---------------------------------------------------------------------
// Spinner/loading...
// loading spinner...
window.startSpinner = function(){
document.body.classList.add('loading') }
window.stopSpinner = function(){
document.body.classList.remove('loading') }
//---------------------------------------------------------------------
// General setup...
document.pwikiloaded = new Event('pwikiloaded')
var logTime = async function(promise, msg=''){
var t = Date.now()
var res = await promise
t = Date.now() - t
DEBUG
&& console.log(`## ${
typeof(msg) == 'function' ?
msg(res)
: msg
} (${t}ms)`)
return res }
REFRESH_DELAY = 20
var refresh = async function(){
pwiki.__prev_path = pwiki.path
startSpinner()
setTimeout(function(){
logTime(
pwiki.refresh(),
pwiki.location) }, REFRESH_DELAY) }
history.scrollRestoration = 'manual'
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// start loading pWiki...
require(['./browser'], function(browser){
var pwiki = window.pwiki = browser.pwiki
//var pwpath = window.path = browser.pwpath
pwiki.dom = document.querySelector('#pWiki')
// handle location.hash/history (both directions)
window.addEventListener('hashchange', function(evt){
evt.preventDefault()
var [path, hash] = decodeURI(location.hash).slice(1).split('#')
path = path.trim() == '' ?
pwiki.location
//'/'
: path
// treat links as absolute unless explicitly relative...
path = /^\.\.?([\\\/].*)?$/.test(path) ?
path
: '/'+path
startSpinner()
// NOTE: setTimeout(..) to allow the spinner to start...
// NOTE: this seems not to work if the REFRESH_DELAY is too small...
setTimeout(function(){
pwiki.location = [path, hash] }, REFRESH_DELAY) })
// scroll...
// NOTE: we restore scroll position only on history navigation...
var save_scroll = async function(){
history.replaceState({
path: pwiki.location,
// XXX HACK this will work only on full page...
scroll: document.scrollingElement.scrollTop,
}, '', window.location.hash) }
// save scroll position just after scroll is done...
var _scrolling
window.addEventListener('scroll', function(evt){
_scrolling
&& clearTimeout(_scrolling)
_scrolling = setTimeout(save_scroll, 200) })
// get scroll position from history state...
window.addEventListener('popstate', function(evt){
pwiki.__scroll = (evt.state ?? {}).scroll })
pwiki
.onBeforeNavigate(function(){
this.__prev_path = this.path
saveAll() })
.navigate(async function(){
// NOTE: we do not need to directly update location.hash here as
// that will push an extra history item...
history.replaceState(
{ path: this.location },
'',
'#'+this.location
+(this.hash ?
'#'+this.hash
: ''))
/* XXX GLOBAL_STYLE
// style...
if(STYLE_UPDATED){
STYLE_UPDATED = false
await updateStyle() }
// config...
if(CONFIG_UPDATED){
CONFIG_UPDATED = false
await updateConfig() }
//*/
// NOTE: we are intentionally not awaiting for this -- this
// separates the navigate and load events...
logTime(
this.refresh(),
this.location) })
.onLoad(function(evt){
var that = this
// stop spinner...
stopSpinner()
// handle title...
// NOTE: we set the global title to either the last <title>
// tag value or the attr .title
var titles = [...document.querySelectorAll('title')]
titles[0].innerHTML =
`${titles.length > 1 ?
titles.at(-1).innerText
: this.title} &mdash; pWiki`
// scroll...
this.hash ?
// to anchor element...
this.dom
.querySelector('#'+ this.hash)
.scrollIntoView()
// restore history position...
// NOTE: only on navigate to new page...
// XXX HACK this will work only on full page pWiki and
// not on a element/nested pWiki...
: (this.__prev_path != this.path
&& (document.scrollingElement.scrollTop = this.__scroll ?? 0))
// handle refresh...
// NOTE: we need to do this as hashchange is only triggered
// when the hash is actually changed...
for(var lnk of this.dom.querySelectorAll(`a[href="${location.hash}"]`)){
lnk.addEventListener('click', refresh) } })
.delete(refresh)
// handle special file updates...
// NOTE: the actual updates are done .navigate(..)
pwiki.store
.update(function(_, path){
// XXX GLOBAL_STYLE
//if(path == '/.config/Style'){
// STYLE_UPDATED = true }
if(path == '/.config/Config'){
CONFIG_UPDATED = true } })
// wait for stuff to finish...
browser.setup.then(async function(){
// index...
await logTime(
pwiki.store.index(),
'Indexing')
// setup global stuff...
// XXX GLOBAL_STYLE
//updateStyle()
//updateConfig()
// show current page...
pwiki.location = decodeURI(location.hash).slice(1) }) })
//---------------------------------------------------------------------
// Export/Import...
// XXX
var importData = function(evt){
var files = event.target.files
var reader = new FileReader()
reader.addEventListener('load', function(evt){
var json = JSON.parse(evt.target.result)
console.log('LOADING JSON:', json)
pwiki.store
.load(json)
.then(function(){
location.reload() }) })
reader.readAsText(files[0]) }
// XXX
var exportData = async function(options={}){
var filename
if(typeof(options) == 'string'){
filename = options
options = arguments[1] ?? {} }
var blob = new Blob(
[await pwiki.store.json({stringify: true, space: 4, ...options})],
{type: "text/plain;charset=utf-8"});
var a = document.createElement('a')
var blobURL = a.href = URL.createObjectURL(blob)
a.download = filename
?? options.filename
?? (Date.timeStamp() +'.pWiki-export.pwiki')
//document.body.appendChild(a)
a.dispatchEvent(new MouseEvent("click"))
//document.body.removeChild(a)
//URL.revokeObjectURL(blobURL)
}
//---------------------------------------------------------------------
</script>
<body>
<!-- XXX need to add something passive but animated here... -->
<div id="pWiki">
<div class="spinner">
<span style="--i:0">p</span>
<span style="--i:1">W</span>
<span style="--i:2">i</span>
<span style="--i:3">k</span>
<span style="--i:4">i</span>
</div>
</div>
<div class="page spinner">
<span style="--i:0">.</span>
<span style="--i:1">.</span>
<span style="--i:2">.</span>
</div>
</body>
</html>
<!-- vim:set sw=4 ts=4 : -->