From 7ce3e6f8bcc821aaf2a2fa145cda921a2faf4b69 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Thu, 4 Aug 2022 14:29:25 +0300 Subject: [PATCH] working on browser... Signed-off-by: Alex A. Naanou --- Makefile | 8 +- browser.js | 35 +- ext-lib/README | 1 + ext-lib/pouchdb.find.js | 6798 ++++++++ ext-lib/pouchdb.find.min.js | 8 + ext-lib/pouchdb.indexeddb.js | 5152 ++++++ ext-lib/pouchdb.indexeddb.min.js | 2 + ext-lib/pouchdb.js | 13146 +++++++++++++++ ext-lib/pouchdb.localstorage.js | 21765 +++++++++++++++++++++++++ ext-lib/pouchdb.localstorage.min.js | 45 + ext-lib/pouchdb.memory.js | 22008 ++++++++++++++++++++++++++ ext-lib/pouchdb.memory.min.js | 45 + ext-lib/pouchdb.min.js | 10 +- filters/base.js | 46 + filters/markdown.js | 31 + page.js | 115 +- pwiki2.html | 6 +- pwiki2.js | 121 +- store/localstorage.js | 7 + store/pouchdb.js | 19 +- 20 files changed, 69228 insertions(+), 140 deletions(-) create mode 100755 ext-lib/README create mode 100755 ext-lib/pouchdb.find.js create mode 100755 ext-lib/pouchdb.find.min.js create mode 100755 ext-lib/pouchdb.indexeddb.js create mode 100755 ext-lib/pouchdb.indexeddb.min.js create mode 100755 ext-lib/pouchdb.js create mode 100755 ext-lib/pouchdb.localstorage.js create mode 100755 ext-lib/pouchdb.localstorage.min.js create mode 100755 ext-lib/pouchdb.memory.js create mode 100755 ext-lib/pouchdb.memory.min.js create mode 100755 filters/base.js create mode 100755 filters/markdown.js diff --git a/Makefile b/Makefile index 9e13f71..fe95b3d 100755 --- a/Makefile +++ b/Makefile @@ -13,8 +13,14 @@ LOCAL_MODULES := \ node_modules/ig-actions/actions.js \ node_modules/ig-features/features.js +POUCH_DB := \ + $(wildcard node_modules/pouchdb/dist/*) + +ext-lib/pouchdb.js: node_modules $(POUCH_DB) + cp $(POUCH_DB) ext-lib/ + bootstrap.js: scripts/bootstrap.js $(BOOTSTRAP_FILES) node $< @@ -28,7 +34,7 @@ node_modules: npm install -dev: node_modules $(LOCAL_MODULES) bootstrap +dev: node_modules ext-lib/pouchdb.js $(LOCAL_MODULES) bootstrap cp $(LOCAL_MODULES) lib/ diff --git a/browser.js b/browser.js index 0101def..a02a30f 100755 --- a/browser.js +++ b/browser.js @@ -11,17 +11,48 @@ var object = require('ig-object') var types = require('ig-types') var pwpath = require('./lib/path') +var page = require('./page') var basestore = require('./store/base') var localstoragestore = require('./store/localstorage') -// XXX for some reason this does not run quietly in browser -//var pouchdbstore = require('./store/pouchdb') +var pouchdbstore = require('./store/pouchdb') // XXX this fails silently in browser... //var bootstrap = require('./bootstrap') +//--------------------------------------------------------------------- + +var store = +module.store = + { __proto__: basestore.BaseStore } + .nest({ __proto__: basestore.MetaStore }) + +store.update('System', + Object.create(basestore.BaseStore).load(page.System)) + +var pwiki = +module.pwiki = + page.Page('/', '/', store) + + +pwiki.store.update('@local', { + __proto__: localstoragestore.localStorageStore, + data: localStorage, +}) + +pwiki.store.update('@session', { + __proto__: localstoragestore.localStorageStore, + data: sessionStorage, +}) + +pwiki.store.update('@pouch', { + __proto__: pouchdbstore.PouchDBStore, +}) + + + /********************************************************************** * vim:set ts=4 sw=4 : */ return module }) diff --git a/ext-lib/README b/ext-lib/README new file mode 100755 index 0000000..12f8dc8 --- /dev/null +++ b/ext-lib/README @@ -0,0 +1 @@ +This directory contains external modules used for in-browser rendering. diff --git a/ext-lib/pouchdb.find.js b/ext-lib/pouchdb.find.js new file mode 100755 index 0000000..26d3764 --- /dev/null +++ b/ext-lib/pouchdb.find.js @@ -0,0 +1,6798 @@ +// pouchdb-find plugin 7.3.0 +// Based on Mango: https://github.com/cloudant/mango +// +// (c) 2012-2022 Dale Harvey and the PouchDB team +// PouchDB may be freely distributed under the Apache license, version 2.0. +// For all details and documentation: +// http://pouchdb.com +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1) + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Unhandled "error" event. (' + er + ')'); + err.context = er; + throw err; + } + return false; + } + + handler = events[type]; + + if (!handler) + return false; + + var isFn = typeof handler === 'function'; + len = arguments.length; + switch (len) { + // fast cases + case 1: + emitNone(handler, isFn, this); + break; + case 2: + emitOne(handler, isFn, this, arguments[1]); + break; + case 3: + emitTwo(handler, isFn, this, arguments[1], arguments[2]); + break; + case 4: + emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); + break; + // slower + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + emitMany(handler, isFn, this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = target._events; + if (!events) { + events = target._events = objectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (!existing) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + } else { + // If we've already got an array, just append. + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + } + + // Check for listener leak + if (!existing.warned) { + m = $getMaxListeners(target); + if (m && m > 0 && existing.length > m) { + existing.warned = true; + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' "' + String(type) + '" listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit.'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + if (typeof console === 'object' && console.warn) { + console.warn('%s: %s', w.name, w.message); + } + } + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + switch (arguments.length) { + case 0: + return this.listener.call(this.target); + case 1: + return this.listener.call(this.target, arguments[0]); + case 2: + return this.listener.call(this.target, arguments[0], arguments[1]); + case 3: + return this.listener.call(this.target, arguments[0], arguments[1], + arguments[2]); + default: + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) + args[i] = arguments[i]; + this.listener.apply(this.target, args); + } + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = bind.call(onceWrapper, state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = this._events; + if (!events) + return this; + + list = events[type]; + if (!list) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else + spliceOne(list, position); + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (!events) + return this; + + // not listening for removeListener, no need to emit + if (!events.removeListener) { + if (arguments.length === 0) { + this._events = objectCreate(null); + this._eventsCount = 0; + } else if (events[type]) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = objectKeys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = objectCreate(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (!events) + return []; + + var evlistener = events[type]; + if (!evlistener) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; + +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); +} + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function objectCreatePolyfill(proto) { + var F = function() {}; + F.prototype = proto; + return new F; +} +function objectKeysPolyfill(obj) { + var keys = []; + for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { + keys.push(k); + } + return k; +} +function functionBindPolyfill(context) { + var fn = this; + return function () { + return fn.apply(context, arguments); + }; +} + +},{}],4:[function(_dereq_,module,exports){ +'use strict'; +var types = [ + _dereq_(2), + _dereq_(7), + _dereq_(6), + _dereq_(5), + _dereq_(8), + _dereq_(9) +]; +var draining; +var currentQueue; +var queueIndex = -1; +var queue = []; +var scheduled = false; +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + nextTick(); + } +} + +//named nextTick for less confusing stack traces +function nextTick() { + if (draining) { + return; + } + scheduled = false; + draining = true; + var len = queue.length; + var timeout = setTimeout(cleanUpNextTick); + while (len) { + currentQueue = queue; + queue = []; + while (currentQueue && ++queueIndex < len) { + currentQueue[queueIndex].run(); + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + queueIndex = -1; + draining = false; + clearTimeout(timeout); +} +var scheduleDrain; +var i = -1; +var len = types.length; +while (++i < len) { + if (types[i] && types[i].test && types[i].test()) { + scheduleDrain = types[i].install(nextTick); + break; + } +} +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + var fun = this.fun; + var array = this.array; + switch (array.length) { + case 0: + return fun(); + case 1: + return fun(array[0]); + case 2: + return fun(array[0], array[1]); + case 3: + return fun(array[0], array[1], array[2]); + default: + return fun.apply(null, array); + } + +}; +module.exports = immediate; +function immediate(task) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(task, args)); + if (!scheduled && !draining) { + scheduled = true; + scheduleDrain(); + } +} + +},{"2":2,"5":5,"6":6,"7":7,"8":8,"9":9}],5:[function(_dereq_,module,exports){ +(function (global){(function (){ +'use strict'; + +exports.test = function () { + if (global.setImmediate) { + // we can only get here in IE10 + // which doesn't handel postMessage well + return false; + } + return typeof global.MessageChannel !== 'undefined'; +}; + +exports.install = function (func) { + var channel = new global.MessageChannel(); + channel.port1.onmessage = func; + return function () { + channel.port2.postMessage(0); + }; +}; +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],6:[function(_dereq_,module,exports){ +(function (global){(function (){ +'use strict'; +//based off rsvp https://github.com/tildeio/rsvp.js +//license https://github.com/tildeio/rsvp.js/blob/master/LICENSE +//https://github.com/tildeio/rsvp.js/blob/master/lib/rsvp/asap.js + +var Mutation = global.MutationObserver || global.WebKitMutationObserver; + +exports.test = function () { + return Mutation; +}; + +exports.install = function (handle) { + var called = 0; + var observer = new Mutation(handle); + var element = global.document.createTextNode(''); + observer.observe(element, { + characterData: true + }); + return function () { + element.data = (called = ++called % 2); + }; +}; +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],7:[function(_dereq_,module,exports){ +(function (global){(function (){ +'use strict'; +exports.test = function () { + return typeof global.queueMicrotask === 'function'; +}; + +exports.install = function (func) { + return function () { + global.queueMicrotask(func); + }; +}; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],8:[function(_dereq_,module,exports){ +(function (global){(function (){ +'use strict'; + +exports.test = function () { + return 'document' in global && 'onreadystatechange' in global.document.createElement('script'); +}; + +exports.install = function (handle) { + return function () { + + // Create a + diff --git a/pwiki2.js b/pwiki2.js index 9523200..41ead38 100755 --- a/pwiki2.js +++ b/pwiki2.js @@ -1,22 +1,52 @@ /********************************************************************** * * +* XXX ROADMAP: +* - run in browser +* - basics, loading -- DONE +* - test localStorage / sessionStorage -- DONE +* - test pouch -- DONE +* - render page +* - navigation +* - hash/anchor +* - service worker +* - migrate bootstrap +* - test pwa +* - archive old code +* - update docs +* - refactor and cleanup +* - pack as electron app (???) +* +* +* +* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +* * Architecture: * store * page * renderer * +* Modules: +* page - base pages and page APIs (XXX should this be in lib???) +* parser - pWiki macro parser (XXX should this be in lib???) +* store - stores +* base - memory store and store utils +* file - file storage +* localstorage - localStorage / sessionStorage stores +* pouchdb - +* ... +* filter - page filters +* base - base filters incl. wikiword +* markdown - markdown renderer +* ... +* pwiki2 - main cli / node entry point +* browser - browser entry point +* pwiki2-test - testing and experimenting (XXX move to test.js) * -* XXX ROADMAP: -* - run in browser -* - test localStorage / sessionStorage -* - test pouch -* - move the bootstrap over -* - test pwa -* - archive old code -* - update docs -* - refactor and cleanup -* - pack as electron app (???) +* +* Q: can we make this a single module with +/- some plugins?? +* ...this would make things quite a bit simpler but will negate the +* use of high level libs like types... * * * XXX weaknesses to review: @@ -26,8 +56,6 @@ * ...need to be independent of the number of pages if at * all possible -- otherwise this will hinder long-term use... * - -* -* * TODO: * - .then() -- resolve when all pending write operations done ??? * - an async REPL??? @@ -114,80 +142,13 @@ module.store = .nest({ __proto__: basestore.MetaStore }) -var System = -module.System = { - // base templates... - // - _text: { - text: '@source(.)' }, - NotFound: { - text: page.PAGE_NOT_FOUND - .replace('$PATH', '@source(./path)') }, - - // XXX tests... - test_list: function(){ - return 'abcdef'.split('') }, - - // metadata... - // - path: function(){ - return this.get('..').path }, - location: function(){ - return this.get('..').path }, - dir: function(){ - return this.get('..').dir }, - name: function(){ - return this.get('..').name }, - ctime: function(){ - return this.get('..').data.ctime }, - mtime: function(){ - return this.get('..').data.mtime }, - - // XXX this can be a list for pattern paths... - resolved: function(){ - return this.get('..').resolve() }, - - title: function(){ - var p = this.get('..') - return p.title - ?? p.name }, - - - // utils... - // - // XXX System/subpaths - // XXX - links: function(){ - // XXX - return '' }, - // XXX links to pages... - to: function(){ - return (this.get('..').data || {}).to ?? [] }, - // XXX pages linking to us... - 'from': function(){ - return (this.get('..').data || {})['from'] ?? [] }, - - - // actions... - // - delete: function(){ - this.location = '..' - this.delete() - return this.text }, - // XXX System/back - // XXX System/forward - // XXX System/sort - // XXX System/reverse -} - - // XXX note sure how to organize the system actions -- there can be two // options: // - a root ram store with all the static stuff and nest the rest // - a nested store (as is the case here) // XXX nested system store... store.update('System', - Object.create(basestore.BaseStore).load(System)) + Object.create(basestore.BaseStore).load(page.System)) // NOTE: in general the root wiki api is simply a page instance. diff --git a/store/localstorage.js b/store/localstorage.js index b61d9ab..65f06c6 100755 --- a/store/localstorage.js +++ b/store/localstorage.js @@ -50,11 +50,18 @@ module.localStorageStore = { JSON.stringify(data) }, __delete__: function(path){ delete this.data[(this.__prefix__ ?? '')+ path] }, + + clear: function(){ + for(var e in this.data){ + if(e.startsWith(this.__prefix__)){ + delete this.data[e] } } + return this }, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// XXX var localStorageNestedStore = module.localStorageNestedStore = { __proto__: base.BaseStore, diff --git a/store/pouchdb.js b/store/pouchdb.js index a06da26..4cae63d 100755 --- a/store/pouchdb.js +++ b/store/pouchdb.js @@ -11,15 +11,18 @@ var object = require('ig-object') var types = require('ig-types') var pwpath = require('../lib/path') - var base = require('../store/base') +// XXX HACK: trick requirejs to delay module loading... +var req = require +module.PouchDB = + typeof(PouchDB) != 'undefined' ? + PouchDB + : req('pouchdb') + //--------------------------------------------------------------------- -// XXX -module.PouchDB = undefined - var PouchDBStore = module.PouchDBStore = { __proto__: base.BaseStore, @@ -31,12 +34,8 @@ module.PouchDBStore = { __data: undefined, get data(){ - if(!this.__data){ - var PouchDB = - module.PouchDB = - require('pouchdb') - return (this.__data = new PouchDB(this.__path__)) } - return this.__data }, + return this.__data + ?? (this.__data = new module.PouchDB(this.__path__)) }, set data(value){ this.__data = value },