mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-28 09:30:07 +00:00
610 lines
15 KiB
HTML
Executable File
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} — 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 : -->
|