Add wiki draft approval UI, MediaWiki client, HitCounters extension, and blog parser
- Add MediaWiki API client (src/mediawiki.py) for draft management and authentication - Add wiki draft approval endpoints (GET /wiki/drafts, POST /wiki/approve, GET /wiki/auth) - Add Wiki Drafts panel to web UI with approval workflow and confirmation modal - Install HitCounters extension on wiki server (106M+ historical page views restored) - Fix HitCounters deprecation warning (Language::convert -> LanguageConverterFactory) - Add blog parser for static blog content processing - Update .gitignore to exclude large data/blog_static directories - Increase Ollama timeout to 5 min for large content generation - Initialize backlog task tracking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
88bb049a24
commit
7633ea6781
|
|
@ -11,14 +11,15 @@ dist/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
# Data files (too large for git)
|
# Data files (too large for git)
|
||||||
data/articles.json
|
data/
|
||||||
data/chroma/
|
|
||||||
data/review_queue/
|
|
||||||
xmldump/
|
xmldump/
|
||||||
xmldump-2014.tar.gz
|
xmldump-2014.tar.gz
|
||||||
articles/
|
articles/
|
||||||
articles.tar.gz
|
articles.tar.gz
|
||||||
|
|
||||||
|
# Blog static export (too large for git)
|
||||||
|
blog_static/
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
project_name: "P2P Wiki Content"
|
||||||
|
default_status: "To Do"
|
||||||
|
statuses: ["To Do", "In Progress", "Done"]
|
||||||
|
labels: []
|
||||||
|
milestones: []
|
||||||
|
date_format: yyyy-mm-dd
|
||||||
|
max_column_width: 20
|
||||||
|
default_editor: "nvim"
|
||||||
|
auto_open_browser: true
|
||||||
|
default_port: 6420
|
||||||
|
remote_operations: true
|
||||||
|
auto_commit: false
|
||||||
|
bypass_git_hooks: false
|
||||||
|
check_active_branches: true
|
||||||
|
active_branch_days: 30
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
id: task-1
|
||||||
|
title: 'P2P Foundation Website, Blog & Wiki Updates'
|
||||||
|
status: Done
|
||||||
|
assignee: []
|
||||||
|
created_date: '2026-01-30 18:32'
|
||||||
|
updated_date: '2026-02-02 16:59'
|
||||||
|
labels: []
|
||||||
|
dependencies: []
|
||||||
|
priority: high
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Comprehensive updates to P2P Foundation web properties including:
|
||||||
|
|
||||||
|
## p2pfoundation.net (Main Site)
|
||||||
|
- Created Knowledge Commons page with responsive grid layout
|
||||||
|
- Added Knowledge Commons to navigation menu (position 3)
|
||||||
|
- Menu reordered: Home, About, Knowledge Commons, Wiki, Websites, Infrastructure, Contact
|
||||||
|
|
||||||
|
## blog.p2pfoundation.net
|
||||||
|
- Replaced header logo with p2pf.jpg (50x50px square)
|
||||||
|
- Fixed critical performance issues (load time reduced from 10-15s to ~0.5s)
|
||||||
|
- Implemented advanced page caching system
|
||||||
|
- Fixed HTTPS/SSL detection for wp-login.php
|
||||||
|
- Fixed mixed content issues (http → https URLs)
|
||||||
|
- Added contact page redirect to main site
|
||||||
|
- Installed multiple mu-plugins for performance and SSL fixes
|
||||||
|
|
||||||
|
## wiki.p2pfoundation.net
|
||||||
|
- Fixed Main Page formatting (left alignment, YouTube on new line)
|
||||||
|
- Centered P2pCommons image at 800px
|
||||||
|
- Removed Diagram.png from main page
|
||||||
|
- Logo already updated to square p2pf.jpg
|
||||||
|
|
||||||
|
## wikifr.p2pfoundation.net
|
||||||
|
- Updated logo to match main wiki (p2pf-logo-160.jpg)
|
||||||
|
- Added cache-busting parameter for logo refresh
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
## Session 2026-02-02: Draft Approval Workflow
|
||||||
|
|
||||||
|
### wiki.p2pfoundation.net
|
||||||
|
- Configured Draft namespace (NS_DRAFT = 118)
|
||||||
|
- Created Draft Approval Gadget in MediaWiki:Common.js
|
||||||
|
- Green "Approve & Publish" button
|
||||||
|
- Red "Delete Draft" button
|
||||||
|
- Authorized users: Mbauwens, JeffEmmett
|
||||||
|
- Fixed deprecated JavaScript variables (wgArticleId, wgServer, etc.)
|
||||||
|
- Added favicon (Logo-final-box-128.png)
|
||||||
|
- Uploaded 42 draft articles for review
|
||||||
|
- Created Jeff Emmett article about Commons Stack
|
||||||
|
|
||||||
|
### wikifr.p2pfoundation.net
|
||||||
|
- Configured Draft namespace (NS_DRAFT = 118)
|
||||||
|
- Created French Draft Approval Gadget
|
||||||
|
- "Approuver & Publier" / "Supprimer Brouillon" buttons
|
||||||
|
- Authorized users: JavierRgz, MaiaDereva, Mbauwens, JeffEmmett
|
||||||
|
- Added short URL support (.htaccess, $wgArticlePath)
|
||||||
|
- Created admin accounts: Mbauwens, JeffEmmett (WikiAdmin2026)
|
||||||
|
- Created bot account: Mbauwens_bot (WikiBot2026)
|
||||||
|
- Added favicon
|
||||||
|
- Created test draft article
|
||||||
|
|
||||||
|
### Server Changes (Netcup)
|
||||||
|
- Removed redirect from p2pfoundation.net → wiki.p2pfoundation.net
|
||||||
|
- p2pfoundation.net now available for its own website
|
||||||
|
- Updated French wiki docker-compose.yml with .htaccess mounts
|
||||||
|
|
||||||
|
### Local Commits
|
||||||
|
- Committed wiki_scripts/ with both gadget versions
|
||||||
|
- Committed drafts/ with 42 draft articles
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
@ -0,0 +1,781 @@
|
||||||
|
<!doctype html><html
|
||||||
|
lang=en-US class=is-header-fixed><head><meta
|
||||||
|
charset=UTF-8><meta
|
||||||
|
name=viewport content="width=device-width, initial-scale=1"><title>10 blockchain projects to keep an eye on | P2P Foundation</title><meta
|
||||||
|
name=description content="The blockchain space is fast-moving and constantly brimming with new projects that could make the sharing economy increasingly accessible to all."><meta
|
||||||
|
name=robots content="noindex, follow"><meta
|
||||||
|
property=og:locale content=en_US><meta
|
||||||
|
property=og:type content=article><meta
|
||||||
|
property=og:title content="10 blockchain projects to keep an eye on | P2P Foundation"><meta
|
||||||
|
property=og:description content="The blockchain space is fast-moving and constantly brimming with new projects that could make the sharing economy increasingly accessible to all."><meta
|
||||||
|
property=og:url content=https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/ ><meta
|
||||||
|
property=og:site_name content="P2P Foundation"><meta
|
||||||
|
property=article:publisher content=https://www.facebook.com/P2PFoundation><meta
|
||||||
|
property=article:published_time content=2018-09-23T10:00:00+00:00><meta
|
||||||
|
property=article:modified_time content=2021-05-13T21:26:44+00:00><meta
|
||||||
|
property=og:image content=https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg><meta
|
||||||
|
property=og:image:width content=750><meta
|
||||||
|
property=og:image:height content=450><meta
|
||||||
|
name=twitter:card content=summary_large_image><meta
|
||||||
|
name=twitter:creator content=@p2p_foundation><meta
|
||||||
|
name=twitter:site content=@p2p_foundation> <script type=application/ld+json class=yoast-schema-graph>{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://blog.p2pfoundation.net/#website","url":"https://blog.p2pfoundation.net/","name":"P2P Foundation","description":"Researching, documenting and promoting peer to peer practices","potentialAction":[{"@type":"SearchAction","target":"https://blog.p2pfoundation.net/?s={search_term_string}","query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"ImageObject","@id":"https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/#primaryimage","inLanguage":"en-US","url":"https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg","width":750,"height":450},{"@type":"WebPage","@id":"https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/#webpage","url":"https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/","name":"10 blockchain projects to keep an eye on | P2P Foundation","isPartOf":{"@id":"https://blog.p2pfoundation.net/#website"},"primaryImageOfPage":{"@id":"https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/#primaryimage"},"datePublished":"2018-09-23T10:00:00+00:00","dateModified":"2021-05-13T21:26:44+00:00","author":{"@id":"https://blog.p2pfoundation.net/#/schema/person/c247fb5537a67a00e9384d69579b682d"},"description":"The blockchain space is fast-moving and constantly brimming with new projects that could make the sharing economy increasingly accessible to all.","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/"]}]},{"@type":"Person","@id":"https://blog.p2pfoundation.net/#/schema/person/c247fb5537a67a00e9384d69579b682d","name":"Shareable","image":{"@type":"ImageObject","@id":"https://blog.p2pfoundation.net/#personlogo","inLanguage":"en-US","url":"https://secure.gravatar.com/avatar/d80dd2f9d1b33a5a2dc91071c8df87c2?s=96&d=blank&r=pg","caption":"Shareable"},"description":"Shareable is an award-winning nonprofit news, action and connection hub for the sharing transformation. What\u2019s the sharing transformation? It\u2019s a movement of movements emerging from the grassroots up to solve today\u2019s biggest challenges, which old, top-down institutions are failing to address. Behind these failing industrial-age institutions are outmoded beliefs about how the world works \u2013 that ordinary people can\u2019t govern themselves directly; that nonstop economic growth leads to widespread prosperity; and that more stuff leads to more happiness. Amid crisis, a new way forward is emerging \u2013 the sharing transformation. The sharing transformation is big, global, and impacts every part of society. Visit Shareable.net for more."}]}</script> <link
|
||||||
|
rel=dns-prefetch href=//hcaptcha.com><link
|
||||||
|
rel=dns-prefetch href=//secure.gravatar.com><link
|
||||||
|
rel=dns-prefetch href=//fonts.googleapis.com><link
|
||||||
|
rel=dns-prefetch href=//s.w.org><link
|
||||||
|
rel=dns-prefetch href=//v0.wordpress.com><link
|
||||||
|
rel=alternate type=application/rss+xml title="P2P Foundation » Feed" href=https://blog.p2pfoundation.net/feed/ ><link
|
||||||
|
rel=alternate type=application/rss+xml title="P2P Foundation » Comments Feed" href=https://blog.p2pfoundation.net/comments/feed/ ><link
|
||||||
|
rel=alternate type=application/rss+xml title="P2P Foundation » 10 blockchain projects to keep an eye on Comments Feed" href=https://blog.p2pfoundation.net/10-blockchain-projects-to-keep-an-eye-on/feed/ >
|
||||||
|
<script data-cfasync=false>var mi_version = '7.12.2';
|
||||||
|
var mi_track_user = true;
|
||||||
|
var mi_no_track_reason = '';
|
||||||
|
|
||||||
|
var disableStr = 'ga-disable-UA-184755-2';
|
||||||
|
|
||||||
|
/* Function to detect opted out users */
|
||||||
|
function __gaTrackerIsOptedOut() {
|
||||||
|
return document.cookie.indexOf(disableStr + '=true') > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable tracking if the opt-out cookie exists. */
|
||||||
|
if ( __gaTrackerIsOptedOut() ) {
|
||||||
|
window[disableStr] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Opt-out function */
|
||||||
|
function __gaTrackerOptout() {
|
||||||
|
document.cookie = disableStr + '=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/';
|
||||||
|
window[disableStr] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'undefined' === typeof gaOptout ) {
|
||||||
|
function gaOptout() {
|
||||||
|
__gaTrackerOptout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mi_track_user ) {
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','//www.google-analytics.com/analytics.js','__gaTracker');
|
||||||
|
|
||||||
|
__gaTracker('create', 'UA-184755-2', 'auto');
|
||||||
|
__gaTracker('set', 'forceSSL', true);
|
||||||
|
__gaTracker('send','pageview');
|
||||||
|
} else {
|
||||||
|
console.log( "" );
|
||||||
|
(function() {
|
||||||
|
/* https://developers.google.com/analytics/devguides/collection/analyticsjs/ */
|
||||||
|
var noopfn = function() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
var noopnullfn = function() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
var Tracker = function() {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
var p = Tracker.prototype;
|
||||||
|
p.get = noopfn;
|
||||||
|
p.set = noopfn;
|
||||||
|
p.send = noopfn;
|
||||||
|
var __gaTracker = function() {
|
||||||
|
var len = arguments.length;
|
||||||
|
if ( len === 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var f = arguments[len-1];
|
||||||
|
if ( typeof f !== 'object' || f === null || typeof f.hitCallback !== 'function' ) {
|
||||||
|
console.log( 'Not running function __gaTracker(' + arguments[0] + " ....) because you are not being tracked. " + mi_no_track_reason );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
f.hitCallback();
|
||||||
|
} catch (ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
__gaTracker.create = function() {
|
||||||
|
return new Tracker();
|
||||||
|
};
|
||||||
|
__gaTracker.getByName = noopnullfn;
|
||||||
|
__gaTracker.getAll = function() {
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
__gaTracker.remove = noopfn;
|
||||||
|
window['__gaTracker'] = __gaTracker;
|
||||||
|
})();
|
||||||
|
}</script> <script>window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/13.0.0\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/13.0.0\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/blog.p2pfoundation.net\/wp-includes\/js\/wp-emoji-release.min.js?ver=5.5.17"}};
|
||||||
|
!function(e,a,t){var n,r,o,i=a.createElement("canvas"),p=i.getContext&&i.getContext("2d");function s(e,t){var a=String.fromCharCode;p.clearRect(0,0,i.width,i.height),p.fillText(a.apply(this,e),0,0);e=i.toDataURL();return p.clearRect(0,0,i.width,i.height),p.fillText(a.apply(this,t),0,0),e===i.toDataURL()}function c(e){var t=a.createElement("script");t.src=e,t.defer=t.type="text/javascript",a.getElementsByTagName("head")[0].appendChild(t)}for(o=Array("flag","emoji"),t.supports={everything:!0,everythingExceptFlag:!0},r=0;r<o.length;r++)t.supports[o[r]]=function(e){if(!p||!p.fillText)return!1;switch(p.textBaseline="top",p.font="600 32px Arial",e){case"flag":return s([127987,65039,8205,9895,65039],[127987,65039,8203,9895,65039])?!1:!s([55356,56826,55356,56819],[55356,56826,8203,55356,56819])&&!s([55356,57332,56128,56423,56128,56418,56128,56421,56128,56430,56128,56423,56128,56447],[55356,57332,8203,56128,56423,8203,56128,56418,8203,56128,56421,8203,56128,56430,8203,56128,56423,8203,56128,56447]);case"emoji":return!s([55357,56424,8205,55356,57212],[55357,56424,8203,55356,57212])}return!1}(o[r]),t.supports.everything=t.supports.everything&&t.supports[o[r]],"flag"!==o[r]&&(t.supports.everythingExceptFlag=t.supports.everythingExceptFlag&&t.supports[o[r]]);t.supports.everythingExceptFlag=t.supports.everythingExceptFlag&&!t.supports.flag,t.DOMReady=!1,t.readyCallback=function(){t.DOMReady=!0},t.supports.everything||(n=function(){t.readyCallback()},a.addEventListener?(a.addEventListener("DOMContentLoaded",n,!1),e.addEventListener("load",n,!1)):(e.attachEvent("onload",n),a.attachEvent("onreadystatechange",function(){"complete"===a.readyState&&t.readyCallback()})),(n=t.source||{}).concatemoji?c(n.concatemoji):n.wpemoji&&n.twemoji&&(c(n.twemoji),c(n.wpemoji)))}(window,document,window._wpemojiSettings);</script> <style>img.wp-smiley,
|
||||||
|
img.emoji {
|
||||||
|
display: inline !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
height: 1em !important;
|
||||||
|
width: 1em !important;
|
||||||
|
margin: 0 .07em !important;
|
||||||
|
vertical-align: -0.1em !important;
|
||||||
|
background: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}</style><link
|
||||||
|
rel=stylesheet href=https://blog.p2pfoundation.net/wp-content/cache/minify/a5ff7.css media=all><style id=wp-block-library-inline-css>.has-text-align-justify{text-align:justify;}</style><link
|
||||||
|
rel=stylesheet href=https://blog.p2pfoundation.net/wp-content/cache/minify/8a106.css media=all><link
|
||||||
|
rel=stylesheet id=noticia-text-css href='//fonts.googleapis.com/css?family=Noticia+Text:400,400italic,700,700italic&subset=latin' type=text/css media=all><link
|
||||||
|
rel=stylesheet href=https://blog.p2pfoundation.net/wp-content/cache/minify/a2679.css media=all> <script id=monsterinsights-frontend-script-js-extra>var monsterinsights_frontend = {"js_events_tracking":"true","download_extensions":"doc,pdf,ppt,zip,xls,docx,pptx,xlsx","inbound_paths":"[]","home_url":"https:\/\/blog.p2pfoundation.net","hash_tracking":"false"};</script> <script src=https://blog.p2pfoundation.net/wp-content/cache/minify/0ee0f.js></script> <link
|
||||||
|
rel=https://api.w.org/ href=https://blog.p2pfoundation.net/wp-json/ ><link
|
||||||
|
rel=alternate type=application/json href=https://blog.p2pfoundation.net/wp-json/wp/v2/posts/72709><link
|
||||||
|
rel=EditURI type=application/rsd+xml title=RSD href=https://blog.p2pfoundation.net/xmlrpc.php?rsd><link
|
||||||
|
rel=wlwmanifest type=application/wlwmanifest+xml href=https://blog.p2pfoundation.net/wp-includes/wlwmanifest.xml><meta
|
||||||
|
name=generator content="WordPress 5.5.17"><link
|
||||||
|
rel=shortlink href=https://wp.me/p4csWb-iUJ><link
|
||||||
|
rel=alternate type=application/json+oembed href="https://blog.p2pfoundation.net/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fblog.p2pfoundation.net%2F10-blockchain-projects-to-keep-an-eye-on%2F"><link
|
||||||
|
rel=alternate type=text/xml+oembed href="https://blog.p2pfoundation.net/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fblog.p2pfoundation.net%2F10-blockchain-projects-to-keep-an-eye-on%2F&format=xml"><style>#category-posts-2-internal .cat-post-thumbnail .cat-post-crop img {object-fit: cover;max-width:100%;}
|
||||||
|
#category-posts-2-internal .cat-post-thumbnail .cat-post-crop-not-supported img {width:100%;}
|
||||||
|
#category-posts-2-internal .cat-post-thumbnail {max-width:100%;}
|
||||||
|
#category-posts-2-internal .cat-post-item img {margin: initial;}
|
||||||
|
#category-posts-2-internal .cat-post-thumbnail {float:left;}</style><link
|
||||||
|
rel=stylesheet href=https://blog.p2pfoundation.net/wp-content/cache/minify/5106b.css media=all> <script>var jspq_options = new Array("1", "1", "right", "1", "1", "div", "pullquote", "pullquote pqRight");</script> <script src=https://blog.p2pfoundation.net/wp-content/cache/minify/b362a.js></script> <!--[if lt IE 9]> <script src=https://blog.p2pfoundation.net/wp-content/themes/readme/js/ie.js></script> <![endif]--><style>.author-info a:not(.button),
|
||||||
|
.category-description a:not(.button),
|
||||||
|
.entry-content a:not(.button) {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info a:hover:not(.button),
|
||||||
|
.category-description a:hover:not(.button),
|
||||||
|
.entry-content a:hover:not(.button) {
|
||||||
|
border-bottom: 2px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info a:active:not(.button),
|
||||||
|
.category-description a:active:not(.button),
|
||||||
|
.entry-content a:active:not(.button) {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 521px) and (max-width: 768px) {
|
||||||
|
h4.author-name {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.with-sidebar {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
.share.sharer-0 {
|
||||||
|
display: block !important;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
}</style><style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style></head><body
|
||||||
|
class="post-template-default single single-post postid-72709 single-format-standard cookies-not-set"><div
|
||||||
|
id=page class="hfeed site"><header
|
||||||
|
id=masthead class=site-header role=banner><h1 class="site-title">
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/ rel=home>
|
||||||
|
<img
|
||||||
|
alt="P2P Foundation" src=//blog.p2pfoundation.net/wp-content/uploads/p2pf-logo-title.png>
|
||||||
|
</a></h1><nav
|
||||||
|
id=primary-navigation class="site-navigation primary-navigation" role=navigation>
|
||||||
|
<a
|
||||||
|
class="menu-toggle toggle-link"></a><div
|
||||||
|
class=nav-menu><ul
|
||||||
|
id=nav class="menu-custom vs-nav"><li
|
||||||
|
id=menu-item-67465 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-67465"><a
|
||||||
|
href=https://p2pfoundation.net/ >ABOUT</a></li><li
|
||||||
|
id=menu-item-60905 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-60905"><a
|
||||||
|
href=#>CONTENT</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-57090 class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-57090"><a
|
||||||
|
href=https://blog.p2pfoundation.net/category/p2pfoundation/p2pf_original_content/ >P2PF Articles</a></li><li
|
||||||
|
id=menu-item-57092 class="menu-item menu-item-type-taxonomy menu-item-object-post_format menu-item-57092"><a
|
||||||
|
title="Featured Videos" href=https://blog.p2pfoundation.net/type/video/ >Video</a></li><li
|
||||||
|
id=menu-item-60078 class="menu-item menu-item-type-taxonomy menu-item-object-post_format menu-item-60078"><a
|
||||||
|
title="Featured Audio" href=https://blog.p2pfoundation.net/type/audio/ >Audio</a></li><li
|
||||||
|
id=menu-item-53968 class="menu-item menu-item-type-post_type menu-item-object-page menu-item-53968"><a
|
||||||
|
href=https://blog.p2pfoundation.net/archives/ >Archives</a></li><li
|
||||||
|
id=menu-item-53925 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-53925"><a
|
||||||
|
href=http://p2pfoundation.net/the-p2p-foundation/about-the-p2p-foundation#section2>STREAMS</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-53949 class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-53949"><a
|
||||||
|
href=https://blog.p2pfoundation.net/category/channels-streams/open-coops-sustainable-livelihoods/ >Open Coops & Sustainable Livelihoods</a></li><li
|
||||||
|
id=menu-item-53951 class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-53951"><a
|
||||||
|
href=https://blog.p2pfoundation.net/category/channels-streams/p2p-cultures-and-politics/ >P2P Cultures and Politics</a></li><li
|
||||||
|
id=menu-item-53948 class="menu-item menu-item-type-taxonomy menu-item-object-category current-post-ancestor current-menu-parent current-post-parent menu-item-53948"><a
|
||||||
|
href=https://blog.p2pfoundation.net/category/channels-streams/building-the-osce/ >Building the Open Source Circular Economy</a></li></ul></li></ul></li><li
|
||||||
|
id=menu-item-53926 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-53926"><a
|
||||||
|
href=https://p2pfoundation.net/knowledge-commons>OUR WEBSITES</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-69072 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-69072"><a
|
||||||
|
href=https://p2pfoundation.net/ >P2P Foundation Homepage</a></li><li
|
||||||
|
id=menu-item-53935 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53935"><a
|
||||||
|
href=https://wiki.p2pfoundation.net/Main_Page>P2P Foundation Wiki</a></li><li
|
||||||
|
id=menu-item-69071 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-69071"><a>Commons Transition</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-69070 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-69070"><a
|
||||||
|
href=https://primer.commonstransition.org/ >The Commons Transition Primer</a></li><li
|
||||||
|
id=menu-item-53936 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53936"><a
|
||||||
|
href=http://commonstransition.org/ >Commons Transition</a></li><li
|
||||||
|
id=menu-item-53937 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53937"><a
|
||||||
|
href=http://commonstransition.org/stories/ >Commons Transition Stories</a></li><li
|
||||||
|
id=menu-item-53938 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53938"><a
|
||||||
|
href=http://wiki.commonstransition.org/wiki/Main_Page>Commons Transition Wiki</a></li></ul></li><li
|
||||||
|
id=menu-item-53939 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53939"><a
|
||||||
|
href=http://www.p2plab.gr/en/ >P2P Lab</a></li><li
|
||||||
|
id=menu-item-54198 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-54198"><a>PROJECTS</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-69450 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-69450"><a
|
||||||
|
href=http://www.p2plab.gr/en/archives/1119>Phygital</a></li><li
|
||||||
|
id=menu-item-69449 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-69449"><a
|
||||||
|
href=http://odmplatform.eu/ >Open Design & Manufacturing</a></li><li
|
||||||
|
id=menu-item-54199 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-54199"><a
|
||||||
|
href=http://p2pvalue.eu/ >P2Pvalue</a></li></ul></li></ul></li><li
|
||||||
|
id=menu-item-53927 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-53927"><a
|
||||||
|
href=#>PARTICIPATE</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-53928 class="menu-item menu-item-type-post_type menu-item-object-page menu-item-53928"><a
|
||||||
|
href=https://blog.p2pfoundation.net/join-the-blog-team/ >Blog</a></li><li
|
||||||
|
id=menu-item-68858 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-68858"><a
|
||||||
|
href=https://p2pfoundation.net/infrastructure/donate>Donate</a></li><li
|
||||||
|
id=menu-item-53930 class="menu-item menu-item-type-post_type menu-item-object-page menu-item-53930"><a
|
||||||
|
href=https://blog.p2pfoundation.net/mailing-list/ >Mailing List</a></li><li
|
||||||
|
id=menu-item-53931 class="menu-item menu-item-type-post_type menu-item-object-page menu-item-53931"><a
|
||||||
|
href=https://blog.p2pfoundation.net/contact/ >Contact</a></li></ul></li><li
|
||||||
|
id=menu-item-53932 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-53932"><a
|
||||||
|
href=#>ENG</a><ul
|
||||||
|
class=sub-menu><li
|
||||||
|
id=menu-item-53933 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53933"><a
|
||||||
|
href=http://blognl.p2pfoundation.net/ >DUTCH</a></li><li
|
||||||
|
id=menu-item-54782 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-54782"><a
|
||||||
|
href=http://blogfr.p2pfoundation.net/ >FRENCH</a></li><li
|
||||||
|
id=menu-item-53934 class="menu-item menu-item-type-custom menu-item-object-custom menu-item-53934"><a
|
||||||
|
href=http://bloggr.p2pfoundation.net/ >GREEK</a></li></ul></li></ul></div></nav><div
|
||||||
|
class="search-container easing">
|
||||||
|
<a
|
||||||
|
class="search-toggle toggle-link"></a><div
|
||||||
|
class=search-box><form
|
||||||
|
class=search-form role=search method=get action=https://blog.p2pfoundation.net/ >
|
||||||
|
<label>
|
||||||
|
<span
|
||||||
|
class=screen-reader-text>Search for:</span>
|
||||||
|
<input
|
||||||
|
type=search name=s id=search-field placeholder="type and hit enter …">
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type=submit class=search-submit value=Search></form></div></div><div
|
||||||
|
class=social-container>
|
||||||
|
<a
|
||||||
|
class="social-toggle toggle-link"></a><div
|
||||||
|
class=textwidget><ul
|
||||||
|
class=social><li><a
|
||||||
|
target=_blank class=facebook href=https://www.facebook.com/P2PFoundation/ rel="noopener noreferrer"></a></li><li><a
|
||||||
|
target=_blank class=twitter href=https://twitter.com/P2P_Foundation rel="noopener noreferrer"></a></li><li><a
|
||||||
|
target=_blank class=google-plus href=https://plus.google.com/117327906914230312044/posts rel="noopener noreferrer"></a></li><li><a
|
||||||
|
target=_blank class=youtube href=https://www.youtube.com/user/P2PFoundation rel="noopener noreferrer"></a></li><li><a
|
||||||
|
target=_blank class=soundcloud href=https://soundcloud.com/p2p_foundation rel="noopener noreferrer"></a></li><li><a
|
||||||
|
target=_blank class=rss href=http://blog.p2pfoundation.net/feed/ rel="noopener noreferrer"></a></li></ul></div></div></header><div
|
||||||
|
id=main class=site-main><div
|
||||||
|
class=post-thumbnail style="background-image: url( https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg );"><div
|
||||||
|
class=f-image>
|
||||||
|
<img
|
||||||
|
src=https://blog.p2pfoundation.net/wp-content/plugins/lazy-load/images/1x1.trans.gif data-lazy-src=https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg width=750 height=450 class="attachment-pixelwars_theme_image_size_1920 size-pixelwars_theme_image_size_1920 wp-post-image" alt="10 blockchain projects to keep an eye on" loading=lazy title srcset="https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg 750w, https://blog.p2pfoundation.net/wp-content/uploads/blockchain-300x180.jpg 300w, https://blog.p2pfoundation.net/wp-content/uploads/blockchain-600x360.jpg 600w" sizes="(max-width: 750px) 100vw, 750px"><noscript><img
|
||||||
|
width=750 height=450 src=https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg class="attachment-pixelwars_theme_image_size_1920 size-pixelwars_theme_image_size_1920 wp-post-image" alt="10 blockchain projects to keep an eye on" loading=lazy title srcset="https://blog.p2pfoundation.net/wp-content/uploads/blockchain.jpg 750w, https://blog.p2pfoundation.net/wp-content/uploads/blockchain-300x180.jpg 300w, https://blog.p2pfoundation.net/wp-content/uploads/blockchain-600x360.jpg 600w" sizes="(max-width: 750px) 100vw, 750px"></noscript></div><header
|
||||||
|
class=entry-header><div
|
||||||
|
class=layout-fixed><div
|
||||||
|
class=entry-meta>
|
||||||
|
<span
|
||||||
|
class=cat-links>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/category/technology-2/blockchain/ rel="category tag">Blockchain</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/channels-streams/building-the-osce/ rel="category tag">Building the Open Source Circular Economy</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/culture-ideas/collective-intelligence/ rel="category tag">Collective Intelligence</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/commons-transition/ rel="category tag">Commons Transition</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/economy-and-business/ethical-economy/ rel="category tag">Ethical Economy</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/featured-content/featured-project/ rel="category tag">Featured Project</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/networks-2/ rel="category tag">Networks</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/economy-and-business/open-innovation/ rel="category tag">Open Innovation</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/economy-and-business/p2p-development/ rel="category tag">P2P Development</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/technology-2/p2p-infrastructures/ rel="category tag">P2P Infrastructures</a> </span></div><h1 class="entry-title" >10 blockchain projects to keep an eye on</h1><div
|
||||||
|
class=entry-meta>
|
||||||
|
<span
|
||||||
|
class=entry-date>
|
||||||
|
<i
|
||||||
|
class=pw-icon-clock></i>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/10-blockchain-projects-to-keep-an-eye-on/ rel=bookmark>
|
||||||
|
<time
|
||||||
|
class=entry-date datetime=2012-02-13T04:34:10+00:00>September 23, 2018</time>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class=comment-link>
|
||||||
|
<i
|
||||||
|
class=pw-icon-comment></i>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/10-blockchain-projects-to-keep-an-eye-on/#respond>No Comment</a> </span>
|
||||||
|
<span
|
||||||
|
class=byline>
|
||||||
|
<span
|
||||||
|
class="author vcard">
|
||||||
|
<i
|
||||||
|
class=pw-icon-user-outline></i>
|
||||||
|
<a
|
||||||
|
class="url fn n" href=https://blog.p2pfoundation.net/author/shareable/ rel=author>Shareable</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class=read-time><i
|
||||||
|
class=pw-icon-bookmark-empty-1></i><span
|
||||||
|
class=eta></span> read</span></div><div
|
||||||
|
class=share></div></div></header></div><div
|
||||||
|
id=primary class="content-area with-sidebar"><div
|
||||||
|
id=content class=site-content role=main><div
|
||||||
|
class=layout-fixed><article
|
||||||
|
id=post-72709 class="post-72709 post type-post status-publish format-standard has-post-thumbnail hentry category-blockchain category-building-the-osce category-collective-intelligence category-commons-transition category-ethical-economy category-featured-project category-networks-2 category-open-innovation category-p2p-development category-p2p-infrastructures tag-aaron-fernando tag-beenest tag-blockchain tag-chamapesa tag-digital-town tag-hellbiz tag-holochain tag-open-bazaar tag-p2p tag-possible tag-right-mesh tag-sarafu-credit tag-sharering"><div
|
||||||
|
class=entry-content><p
|
||||||
|
dir=ltr><em>Cross-posted from <a
|
||||||
|
href=https://www.shareable.net/10-blockchain-projects-to-keep-an-eye-on/ >Shareable</a>.</em></p><p
|
||||||
|
dir=ltr><a
|
||||||
|
class=username title="View user profile." xml:lang href=https://www.shareable.net/users/aaron-fernando>Aaron Fernando</a>: We recently explored how <a
|
||||||
|
href=https://www.shareable.net/blog/blockchain-as-a-force-for-good-how-this-technology-could-transform-the-sharing-economy target=_blank rel="noopener noreferrer">blockchain is being used as a force for good</a> and conducted a handful of interviews with practitioners in the industry. Yet the blockchain space is fast-moving and constantly brimming with new projects that could make the sharing economy increasingly accessible to all. This is a list of some other projects to look out for in this space:</p><h2 dir="ltr"><strong>1. <a
|
||||||
|
href=https://www.helbiz.com/ target=_blank rel="noopener noreferrer">Helbiz</a></strong></h2><p
|
||||||
|
dir=ltr>Helbiz is a startup creating a marketplace that allows people to share not just cars, but any vehicle — bikes, boats, and other modes of transportation. Using both hardware and software, the idea is to be able to turn entire vehicles into Internet-of-Things (IoT) devices that it can be unlocked with a smartphone, tracked with GPS, and monitored for problems and maintenance. The Helbiz app will allow users to unlock vehicles and pay for vehicle usage in cryptocurrency. Though innovative, there are other organizations developing similar capabilities in the shared mobility space including <a
|
||||||
|
href=https://www.hirego.io/ target=_blank rel="noopener noreferrer">HireGo</a> and <a
|
||||||
|
href=http://www.xain.io/ target=_blank rel="noopener noreferrer">Xain</a>.</p><h2 dir="ltr"><strong>2. <a
|
||||||
|
href=https://www.openbazaar.org/ target=_blank rel="noopener noreferrer">Open Bazaar</a></strong></h2><p
|
||||||
|
dir=ltr>Though there are other decentralized marketplaces that use blockchain technology, OpenBazaar was the first of its kind and is still going strong. Users are able to pay for goods in over fifty cryptocurrencies. Since there is no company or entity running it, there are no fees or limits to what can be bought or sold. Though significant technical differences exist, for the casual user, up-and-coming challengers such as <a
|
||||||
|
href=https://swarm.city/ target=_blank rel="noopener noreferrer">Swarm City</a>, <a
|
||||||
|
href=https://publicmarket.io/ target=_blank rel="noopener noreferrer">Public Market</a>, and <a
|
||||||
|
href=https://blockmarket.com/ target=_blank rel="noopener noreferrer">Blockmarket</a> will offer similar services with different features.</p><h2><strong>3. <a
|
||||||
|
href=https://www.grassrootseconomics.org/ target=_blank rel="noopener noreferrer">Sarafu-Credit</a></strong></h2><p
|
||||||
|
dir=ltr>As a country, Kenya has been one of the fastest countries in the world to wholeheartedly embrace mobile money and mobile payments, with <a
|
||||||
|
href=https://www.gsma.com/mobilefordevelopment/wp-content/uploads/2018/05/GSMA_2017_State_of_the_Industry_Report_on_Mobile_Money_Full_Report.pdf target=_blank rel="noopener noreferrer">two-thirds of the population using it on a daily basis</a>. So, not surprisingly, it is also among the first countries where blockchain-enabled community currencies are being used by merchants who might otherwise be too cash-strapped to transact with each other. The organization Grassroots Economics has been operating multiple community currencies in Kenya and and South Africa for the past few years to increase the buying and selling power of various communities. Recently, in partnership with blockchain-startup <a
|
||||||
|
href=https://about.bancor.network/ target=_blank rel="noopener noreferrer">Bancor</a>, Kenyan vendors have been <a
|
||||||
|
href=https://blog.bancor.network/trading-the-first-tomatoes-on-blockchain-92372dd105bc target=_blank rel="noopener noreferrer">signing up</a> to accept cryptocurrency versions of these community currencies which they already accept in paper form.</p><h2 dir="ltr"><strong>4. <a
|
||||||
|
href=https://chamapesa.com/ target=_blank rel="noopener noreferrer">Chamapesa</a></strong></h2><p
|
||||||
|
dir=ltr>Another Kenya-based project making use of Kenyans’ high rates of adoption in mobile banking is Chamapesa, which is using blockchain technology to facilitate lending circles with smartphones. The organization has pointed out that it is not trying to change any core behavior, but rather is using blockchain to facilitate this type of community finance scheme — in one <a
|
||||||
|
href=https://twitter.com/Chamapesa/status/1025330522036338689 target=_blank rel="noopener noreferrer">tweet</a>, it said “We’re not changing Chamas behaviour except that instead of using paper books were going to be using smart phones.”</p><h2><strong>5. <a
|
||||||
|
href=https://holochain.org/ target=_blank rel="noopener noreferrer">Holochain</a></strong></h2><p>Although the proponents of Holochain proudly stand by the fact that it is not a blockchain, for the layperson Holochain has very many similar use-cases. One main difference is that it comes without the prerequisite of using the enormous amounts of energy required by “proof-of-work” blockchains like Bitcoin and Ethereum, yet it is still possible to run a blockchain exactly like Bitcoin on Holochain. Instead of having only one global agreement about what data is valid (as with a regular blockchain), individual users create their own intermeshed ledgers of valid data and personal histories.</p><p
|
||||||
|
dir=ltr>Additionally, whenever we access a website on the Internet, we are really accessing information hosted on servers operated by some third party — usually in a location we don’t care about, owned by a separate private company. What Holochain makes possible is a blockchain-like method and reward structure for storing and accessing data and applications between users themselves, without having to rely on these third parties. This makes it possible to create and run applications of any sort between users, allowing the operation of a truly peer-to-peer internet.</p><h2><strong>6. <a
|
||||||
|
href=https://www.rightmesh.io/ target=_blank rel="noopener noreferrer">Right Mesh</a></strong></h2><p>Only about <a
|
||||||
|
href=https://www.statista.com/topics/1145/internet-usage-worldwide/ target=_blank rel="noopener noreferrer">half of the world’s population has regular Internet access</a>, which means billions still do not have the ability to take advantage of web-based peer-to-peer technologies. Right Mesh is working to change that by allowing people can create their own networks with limited access to internet using mesh networking.</p><p>In a mesh network, information leaps between phones, computers, and other devices like frogs on lily pads until it gets to its destination. It does this by using these devices’ ability to connect directly with each other via Bluetooth or Wifi without being linked to the Internet itself. By encrypting the information — whether it’s a message, image, or payment — the network can ensure that only the desired recipient can understand and make use of the message, which opens up a host of mesh applications that can run on peer-to-peer devices in the absence of internet. Other mesh networks and mesh software already exist, but by using blockchain, this network will allow users to get paid for providing content to peers and existing as an infrastructure of the network itself, offsetting hardware and battery costs of doing so.</p><h2 dir="ltr"><strong>7. <a
|
||||||
|
href=https://beenest.com/ target=_blank rel="noopener noreferrer">Beenest</a></strong></h2><p>Just like AirBnB, the platform Beenest connects hosts with potential guests and leverages blockchain technology to keep costs down. If using its own cryptocurrency — Bee Token — no fees or commissions are taken by the platform on booking and listing properties and charges lower fees than AirBnb when using other currencies or cryptocurrencies.</p><h2><strong>8.</strong> <strong><a
|
||||||
|
href=http://www.possible.today/#english target=_blank rel="noopener noreferrer">Possible</a></strong></h2><p
|
||||||
|
dir=ltr>Possible is a Netherlands-based project is intended to increase social capital by giving people incentives to do the work that might not normally earn money, yet is crucial for healthy societies. Possible is a time bank, meaning that it enables individuals to offer up and use each other’s services denominated in hours of time rather than in some other unit of currency. Time banks have been around for well over a century, but they are often volunteer run and administering them using a centrally-managed database can, at times, prove difficult. By using blockchain technology to and decentralize who gets to update the database, projects like Possible could make it easier to operate time banks without having to rely on volunteers.</p><h2 dir="ltr"><strong>9. <a
|
||||||
|
href=https://sharering.network/en target=_blank rel="noopener noreferrer">ShareRing</a></strong></h2><p
|
||||||
|
dir=ltr>ShareRing is developing an app that uses blockchain technology that will allow users to find and search for nearby services and actually share anything with each other. Like a truly decentralized library of things — both physically and digitally — ShareRing could maximize the usage people get out of physical objects by enabling people to share them easily and fairly. The idea is to have a truly global network, so that the ShareRing app can be used to find and pay for similar services no matter where in the world a person may be. The app will use two of its own cryptocurrencies: one that allows merchants to access the blockchain, and the other as a currency for paying for services on the platform.</p><h2 dir="ltr"><strong>10. <a
|
||||||
|
href=https://digitaltown.com/ target=_blank rel="noopener noreferrer">Digital Town</a></strong></h2><p
|
||||||
|
dir=ltr>Platforms like Amazon, Uber, and AirBnB offer useful services but continuously extract wealth from those who generate it though high fees, which are paid to these companies and their shareholders. Digital Town [one of Shareable’s sponsor] aims to change this business model by linking users in specific geographic localities with the information, resources, and services they need, but instead of extracting high fees, but instead of extracting high fees will ensure more of the profits generated locally, stay local.</p><p
|
||||||
|
dir=ltr>With DigitalTown’s blockchain-based solution, merchants and consumers are rewarded for engagement. Merchants receive a free storefront with low commission rates and everyone receives a free SmartWallet, which supports traditional and cryptocurrencies. Businesses pay just a 1% payment processing fee and everyone enjoys free peer-to-peer transfers.</p><p>DigitalTown’s tools make it easy for communities to share content, discussions, events, and projects. Users of the platform are rewarded with CommunityPoints. Merchants can use CommunityPoints for marketing campaigns on DigitalTown and consumers can use CommunityPoints with participating merchants.</p><p><em>Header image by <a
|
||||||
|
href="https://unsplash.com/photos/yFbyvpEGHFQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target=_blank rel="noopener noreferrer">John Schnobrich</a> on <a
|
||||||
|
href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target=_blank rel="noopener noreferrer">Unsplash</a>.</em></p></div><footer
|
||||||
|
class="entry-meta post-tags"><h3>TAGS</h3>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/aaron-fernando/ rel=tag>Aaron Fernando</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/beenest/ rel=tag>Beenest</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/blockchain/ rel=tag>blockchain</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/chamapesa/ rel=tag>Chamapesa</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/digital-town/ rel=tag>Digital Town</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/hellbiz/ rel=tag>Hellbiz</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/holochain/ rel=tag>holochain</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/open-bazaar/ rel=tag>Open Bazaar</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/p2p/ rel=tag>p2p</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/possible/ rel=tag>Possible</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/right-mesh/ rel=tag>Right Mesh</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/sarafu-credit/ rel=tag>Sarafu-Credit</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/tag/sharering/ rel=tag>ShareRing</a></footer></article><aside
|
||||||
|
class=read-next><div
|
||||||
|
class="post-thumbnail " style="background-image: url( https://blog.p2pfoundation.net/wp-content/uploads/2018_05_111213_CIB_©Momo-C.Gumz_b_b_MG_6525.jpeg );"><header
|
||||||
|
class=entry-header><div
|
||||||
|
class=layout-fixed><h3>NEXT READING</h3><div
|
||||||
|
class=entry-meta>
|
||||||
|
<span
|
||||||
|
class=cat-links>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/category/politics/activism/ rel="category tag">Activism</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/culture-ideas/collective-intelligence/ rel="category tag">Collective Intelligence</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/culture-ideas/ rel="category tag">Culture & Ideas</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/events/ rel="category tag">Events</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/featured-content/featured-project/ rel="category tag">Featured Project</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/open-access/ rel="category tag">Open Access</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/politics/open-government/ rel="category tag">Open Government</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/politics/p2p-carework/ rel="category tag">P2P Carework</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/commons/p2p-collaboration/ rel="category tag">P2P Collaboration</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/channels-streams/p2p-cultures-and-politics/ rel="category tag">P2P Cultures and Politics</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/economy-and-business/p2p-development/ rel="category tag">P2P Development</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/culture-ideas/p2p-education/ rel="category tag">P2P Education</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/politics/p2p-governance/ rel="category tag">P2P Governance</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/economy-and-business/p2p-localization/ rel="category tag">P2P Localization</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/culture-ideas/p2p-solidarity/ rel="category tag">P2P Solidarity</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/politics/ rel="category tag">Politics</a> <a
|
||||||
|
href=https://blog.p2pfoundation.net/category/commons/sharing/ rel="category tag">Sharing</a> </span></div><h1 class="entry-title">
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/collaboration-incubators-for-practicing-democracy/ >Collaboration Incubators for Practicing Democracy</a></h1><div
|
||||||
|
class=entry-meta>
|
||||||
|
<span
|
||||||
|
class=entry-date>
|
||||||
|
<i
|
||||||
|
class=pw-icon-clock></i>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/collaboration-incubators-for-practicing-democracy/ rel=bookmark>
|
||||||
|
<time
|
||||||
|
class=entry-date datetime=2012-02-13T04:34:10+00:00>September 22, 2018</time>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class=comment-link>
|
||||||
|
<i
|
||||||
|
class=pw-icon-comment></i>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/collaboration-incubators-for-practicing-democracy/ >0 Comment</a>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class=byline>
|
||||||
|
<span
|
||||||
|
class="author vcard">
|
||||||
|
<i
|
||||||
|
class=pw-icon-user-outline></i>
|
||||||
|
<a
|
||||||
|
class="url fn n" href=https://blog.p2pfoundation.net/author/ann-marie-utratel/ rel=author>Ann Marie Utratel</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class=read-time><i
|
||||||
|
class=pw-icon-bookmark-empty-1></i><span
|
||||||
|
class=eta></span> read</span></div></div></header></div></aside><nav
|
||||||
|
class="nav-single row"><div
|
||||||
|
class="nav-previous col-sm-6"><h4>PREVIOUS POST</h4><a
|
||||||
|
href=https://blog.p2pfoundation.net/collaboration-incubators-for-practicing-democracy/ rel=prev><span
|
||||||
|
class=meta-nav>←</span> Collaboration Incubators for Practicing Democracy</a></div><div
|
||||||
|
class="nav-next col-sm-6"><h4>NEXT POST</h4><a
|
||||||
|
href=https://blog.p2pfoundation.net/girona-spain-cooperative-breaks-the-mould-to-provide-renewable-energy/ rel=next>Girona, Spain: Cooperative breaks the mould to provide renewable energy <span
|
||||||
|
class=meta-nav>→</span></a></div></nav><div
|
||||||
|
id=comments class=comments-area><div
|
||||||
|
id=respond class=comment-respond><h3 id="reply-title" class="comment-reply-title">Leave A Comment <small><a
|
||||||
|
rel=nofollow id=cancel-comment-reply-link href=/10-blockchain-projects-to-keep-an-eye-on/#respond style=display:none;>Cancel reply</a></small></h3><form
|
||||||
|
action=https://blog.p2pfoundation.net/wp-comments-post.php method=post id=commentform class=comment-form><p
|
||||||
|
class=comment-notes><span
|
||||||
|
id=email-notes>Your email address will not be published.</span> Required fields are marked <span
|
||||||
|
class=required>*</span></p><p
|
||||||
|
class=comment-form-comment><label
|
||||||
|
for=comment>Comment</label><textarea id=comment name=comment cols=45 rows=8 maxlength=65525 required=required></textarea></p><p
|
||||||
|
class=comment-form-author><label
|
||||||
|
for=author>Name <span
|
||||||
|
class=required>*</span></label> <input
|
||||||
|
id=author name=author type=text value size=30 maxlength=245 required=required></p><p
|
||||||
|
class=comment-form-email><label
|
||||||
|
for=email>Email <span
|
||||||
|
class=required>*</span></label> <input
|
||||||
|
id=email name=email type=text value size=30 maxlength=100 aria-describedby=email-notes required=required></p><p
|
||||||
|
class=comment-form-url><label
|
||||||
|
for=url>Website</label> <input
|
||||||
|
id=url name=url type=text value size=30 maxlength=200></p><div
|
||||||
|
class=h-captcha
|
||||||
|
data-sitekey=c3d868f8-ce1f-47a9-9624-bb38139966cd
|
||||||
|
data-theme=light
|
||||||
|
data-size=normal></div>
|
||||||
|
<input
|
||||||
|
type=hidden id=hcaptcha_comment_form_nonce name=hcaptcha_comment_form_nonce value=7945e59e48><input
|
||||||
|
type=hidden name=_wp_http_referer value=/10-blockchain-projects-to-keep-an-eye-on/ ><p
|
||||||
|
class=comment-subscription-form><input
|
||||||
|
type=checkbox name=subscribe_comments id=subscribe_comments value=subscribe style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"> <label
|
||||||
|
class=subscribe-label id=subscribe-label for=subscribe_comments>Notify me of follow-up comments by email.</label></p><p
|
||||||
|
class=comment-subscription-form><input
|
||||||
|
type=checkbox name=subscribe_blog id=subscribe_blog value=subscribe style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"> <label
|
||||||
|
class=subscribe-label id=subscribe-blog-label for=subscribe_blog>Notify me of new posts by email.</label></p><p
|
||||||
|
class=form-submit><input
|
||||||
|
name=submit type=submit id=submit class=submit value="Post Comment"> <input
|
||||||
|
type=hidden name=comment_post_ID value=72709 id=comment_post_ID>
|
||||||
|
<input
|
||||||
|
type=hidden name=comment_parent id=comment_parent value=0></p><p
|
||||||
|
style="display: none;"><input
|
||||||
|
type=hidden id=akismet_comment_nonce name=akismet_comment_nonce value=c50b0fe6b4></p><p
|
||||||
|
style="display: none;"><input
|
||||||
|
type=hidden id=ak_js name=ak_js value=50></p></form></div><p
|
||||||
|
class=akismet_comment_form_privacy_notice>This site uses Akismet to reduce spam. <a
|
||||||
|
href=https://akismet.com/privacy/ target=_blank rel="nofollow noopener">Learn how your comment data is processed</a>.</p></div></div></div></div><div
|
||||||
|
id=secondary class="widget-area sidebar" role=complementary><aside
|
||||||
|
id=readmetenderauthorwidget-2 class="widget widget_readmetenderauthorwidget"><h3 class="widget-title">WRITTEN BY</h3><div
|
||||||
|
class=author-bio><div
|
||||||
|
class=author-img>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/author/shareable/ >
|
||||||
|
<img
|
||||||
|
src=https://blog.p2pfoundation.net/wp-content/plugins/lazy-load/images/1x1.trans.gif data-lazy-src="https://secure.gravatar.com/avatar/d80dd2f9d1b33a5a2dc91071c8df87c2?s=192&d=left&r=pg" width=192 height=192 alt=Shareable class="avatar avatar-192 wp-user-avatar wp-user-avatar-192 photo avatar-default"><noscript><img
|
||||||
|
src="https://secure.gravatar.com/avatar/d80dd2f9d1b33a5a2dc91071c8df87c2?s=192&d=left&r=pg" width=192 height=192 alt=Shareable class="avatar avatar-192 wp-user-avatar wp-user-avatar-192 photo avatar-default"></noscript> </a></div><div
|
||||||
|
class=author-info><h4 class="author-name">Shareable</h4><p>
|
||||||
|
Shareable is an award-winning nonprofit news, action and connection hub for the sharing transformation. What’s the sharing transformation? It’s a movement of movements emerging from the grassroots up to solve today’s biggest challenges, which old, top-down institutions are failing to address. Behind these failing industrial-age institutions are outmoded beliefs about how the world works – that ordinary people can’t govern themselves directly; that nonstop economic growth leads to widespread prosperity; and that more stuff leads to more happiness. Amid crisis, a new way forward is emerging – the sharing transformation. The sharing transformation is big, global, and impacts every part of society. Visit <a
|
||||||
|
href=http://www.shareable.net/ >Shareable.net</a> for more.</p></div></div></aside><aside
|
||||||
|
id=search-3 class="widget widget_search"><h3 class="widget-title">Search</h3><form
|
||||||
|
role=search id=searchform class=searchform method=get action=https://blog.p2pfoundation.net/ ><div>
|
||||||
|
<label
|
||||||
|
class=screen-reader-text for=s>Search for:</label>
|
||||||
|
<input
|
||||||
|
type=text id=s name=s required=required placeholder="type and hit enter ..." value>
|
||||||
|
<input
|
||||||
|
type=submit id=searchsubmit style="display: none;" value=Search></div></form></aside><aside
|
||||||
|
id=categories-3 class="widget widget_categories"><h3 class="widget-title">Categories</h3><form
|
||||||
|
action=https://blog.p2pfoundation.net method=get><label
|
||||||
|
class=screen-reader-text for=cat>Categories</label><select
|
||||||
|
name=cat id=cat class=postform ><option
|
||||||
|
value=-1>Select Category</option><option
|
||||||
|
class=level-0 value=634>Campaigns</option><option
|
||||||
|
class=level-0 value=1996>Channels (Streams)</option><option
|
||||||
|
class=level-1 value=1997> Building the Open Source Circular Economy</option><option
|
||||||
|
class=level-1 value=1998> Open Coops & Sustainable Livelihoods</option><option
|
||||||
|
class=level-1 value=1999> P2P Cultures and Politics</option><option
|
||||||
|
class=level-0 value=544>Commons</option><option
|
||||||
|
class=level-1 value=25> P2P Collaboration</option><option
|
||||||
|
class=level-1 value=18> Peer Property</option><option
|
||||||
|
class=level-1 value=215> Sharing</option><option
|
||||||
|
class=level-0 value=1049>Commons Transition</option><option
|
||||||
|
class=level-0 value=916>Cooperatives</option><option
|
||||||
|
class=level-0 value=494>Culture & Ideas</option><option
|
||||||
|
class=level-1 value=45> Collective Intelligence</option><option
|
||||||
|
class=level-1 value=176> Copyright/IP</option><option
|
||||||
|
class=level-1 value=33> Open Content</option><option
|
||||||
|
class=level-1 value=47> Open Models</option><option
|
||||||
|
class=level-1 value=35> P2P Art and Culture</option><option
|
||||||
|
class=level-1 value=13> P2P Bibliography</option><option
|
||||||
|
class=level-1 value=36> P2P Books</option><option
|
||||||
|
class=level-1 value=19> P2P Education</option><option
|
||||||
|
class=level-1 value=44> P2P Lifestyles</option><option
|
||||||
|
class=level-1 value=32> P2P Music</option><option
|
||||||
|
class=level-1 value=3261> P2P Solidarity</option><option
|
||||||
|
class=level-1 value=2> P2P Spirituality</option><option
|
||||||
|
class=level-1 value=26> P2P Subjectivity</option><option
|
||||||
|
class=level-1 value=132> Urban Commons</option><option
|
||||||
|
class=level-0 value=1>Default</option><option
|
||||||
|
class=level-0 value=492>Economy and Business</option><option
|
||||||
|
class=level-1 value=37> Cognitive Capitalism</option><option
|
||||||
|
class=level-1 value=52> Crowdfunding</option><option
|
||||||
|
class=level-1 value=46> Crowdsourcing</option><option
|
||||||
|
class=level-1 value=306> Ethical Economy</option><option
|
||||||
|
class=level-1 value=187> Food and Agriculture</option><option
|
||||||
|
class=level-1 value=38> Gift Economies</option><option
|
||||||
|
class=level-1 value=130> Open Innovation</option><option
|
||||||
|
class=level-1 value=40> P2P Business Models</option><option
|
||||||
|
class=level-1 value=39> P2P Company Watch</option><option
|
||||||
|
class=level-1 value=10> P2P Development</option><option
|
||||||
|
class=level-1 value=126> P2P Energy</option><option
|
||||||
|
class=level-1 value=3373> P2P Finance</option><option
|
||||||
|
class=level-1 value=185> P2P Labor</option><option
|
||||||
|
class=level-1 value=93> P2P Localization</option><option
|
||||||
|
class=level-1 value=131> P2P Money</option><option
|
||||||
|
class=level-0 value=495>Events</option><option
|
||||||
|
class=level-1 value=257> Conferences</option><option
|
||||||
|
class=level-1 value=393> Open Calls</option><option
|
||||||
|
class=level-0 value=493>Featured Content</option><option
|
||||||
|
class=level-1 value=483> Featured Book</option><option
|
||||||
|
class=level-1 value=485> Featured Essay</option><option
|
||||||
|
class=level-1 value=499> Featured Graphic</option><option
|
||||||
|
class=level-1 value=4161> Featured Interview</option><option
|
||||||
|
class=level-1 value=487> Featured Movement</option><option
|
||||||
|
class=level-1 value=486> Featured Person</option><option
|
||||||
|
class=level-1 value=489> Featured Podcast</option><option
|
||||||
|
class=level-1 value=484> Featured Project</option><option
|
||||||
|
class=level-1 value=488> Featured Tool</option><option
|
||||||
|
class=level-1 value=491> Featured Trend</option><option
|
||||||
|
class=level-1 value=490> Featured Video</option><option
|
||||||
|
class=level-0 value=497>Media</option><option
|
||||||
|
class=level-1 value=27> Podcasts</option><option
|
||||||
|
class=level-1 value=6> Social Media</option><option
|
||||||
|
class=level-1 value=24> Videos</option><option
|
||||||
|
class=level-1 value=408> Visualisations</option><option
|
||||||
|
class=level-0 value=628>Networks</option><option
|
||||||
|
class=level-0 value=655>Open Access</option><option
|
||||||
|
class=level-0 value=640>P2P Foundation</option><option
|
||||||
|
class=level-1 value=3824> Commons Transition Primer</option><option
|
||||||
|
class=level-1 value=2754> P2P Lab</option><option
|
||||||
|
class=level-1 value=641> P2PF Articles</option><option
|
||||||
|
class=level-1 value=642> P2PF network articles</option><option
|
||||||
|
class=level-0 value=397>Politics</option><option
|
||||||
|
class=level-1 value=384> Activism</option><option
|
||||||
|
class=level-1 value=21> Anti-P2P</option><option
|
||||||
|
class=level-1 value=34> Empire</option><option
|
||||||
|
class=level-1 value=134> Open Government</option><option
|
||||||
|
class=level-1 value=14> P2P Action Items</option><option
|
||||||
|
class=level-1 value=4253> P2P Carework</option><option
|
||||||
|
class=level-1 value=23> P2P Ecology</option><option
|
||||||
|
class=level-1 value=16> P2P Gender Issues</option><option
|
||||||
|
class=level-1 value=22> P2P Governance</option><option
|
||||||
|
class=level-1 value=127> P2P Healthcare</option><option
|
||||||
|
class=level-1 value=49> P2P Legal Dev.</option><option
|
||||||
|
class=level-1 value=112> P2P Movements</option><option
|
||||||
|
class=level-1 value=28> P2P Public Policy</option><option
|
||||||
|
class=level-1 value=250> P2P Rights</option><option
|
||||||
|
class=level-1 value=30> P2P Warfare</option><option
|
||||||
|
class=level-1 value=9253> Surveillance</option><option
|
||||||
|
class=level-0 value=496>Technology</option><option
|
||||||
|
class=level-1 value=6479> Appropriate Technology</option><option
|
||||||
|
class=level-1 value=1877> Blockchain</option><option
|
||||||
|
class=level-1 value=42> Free Software</option><option
|
||||||
|
class=level-1 value=43> Mobile Developments</option><option
|
||||||
|
class=level-1 value=48> Open Hardware and Design</option><option
|
||||||
|
class=level-1 value=51> Open Standards</option><option
|
||||||
|
class=level-1 value=440> P2P Infrastructures</option><option
|
||||||
|
class=level-1 value=41> P2P Manufacturing</option><option
|
||||||
|
class=level-1 value=145> P2P Mapping</option><option
|
||||||
|
class=level-1 value=211> P2P Research</option><option
|
||||||
|
class=level-1 value=7> P2P Science</option><option
|
||||||
|
class=level-1 value=207> P2P Software</option><option
|
||||||
|
class=level-1 value=8> P2P Technology</option><option
|
||||||
|
class=level-0 value=2324>The P2P Foundation Library</option><option
|
||||||
|
class=level-1 value=3545> Patterns of Commoning</option><option
|
||||||
|
class=level-1 value=2325> Techno-Utopianism Counterfeit and Real</option><option
|
||||||
|
class=level-1 value=3175> The Communard Manifesto</option><option
|
||||||
|
class=level-1 value=2319> The Desktop Regulatory State</option><option
|
||||||
|
class=level-0 value=405>Theory</option><option
|
||||||
|
class=level-1 value=31> Integral Theory</option><option
|
||||||
|
class=level-1 value=17> P2P Epistemology</option><option
|
||||||
|
class=level-1 value=20> P2P Hierarchy Theory</option><option
|
||||||
|
class=level-1 value=3> P2P Theory</option><option
|
||||||
|
class=level-1 value=15> Peer Production</option>
|
||||||
|
</select></form> <script>(function() {
|
||||||
|
var dropdown = document.getElementById( "cat" );
|
||||||
|
function onCatChange() {
|
||||||
|
if ( dropdown.options[ dropdown.selectedIndex ].value > 0 ) {
|
||||||
|
dropdown.parentNode.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dropdown.onchange = onCatChange;
|
||||||
|
})();</script> </aside><aside
|
||||||
|
id=category-posts-2 class="widget cat-post-widget"><h3 class="widget-title"><a
|
||||||
|
href=https://blog.p2pfoundation.net/category/events/open-calls/ >Open Calls</a></h3><ul
|
||||||
|
id=category-posts-2-internal class=category-posts-internal><li
|
||||||
|
class=cat-post-item><div><a
|
||||||
|
class=cat-post-title href=https://blog.p2pfoundation.net/dlt4eu-call-for-applicants-opens-april-14/ rel=bookmark>DLT4EU: Call for Applicants opens April 14</a></div><div><a
|
||||||
|
class=cat-post-thumbnail href=https://blog.p2pfoundation.net/dlt4eu-call-for-applicants-opens-april-14/ title="DLT4EU: Call for Applicants opens April 14"><span
|
||||||
|
class="cat-post-crop cat-post-format cat-post-format-standard"><img
|
||||||
|
width=150 height=150 src=https://blog.p2pfoundation.net/wp-content/uploads/3251147920_b73b9e2f67_b_Networks-150x150.jpg class="attachment-150x150 size-150x150 wp-post-image" alt loading=lazy></span></a></div></li><li
|
||||||
|
class=cat-post-item><div><a
|
||||||
|
class=cat-post-title href=https://blog.p2pfoundation.net/futures-of-production-through-cosmo-local-and-commons-based-design/ rel=bookmark>Futures of Production Through Cosmo-Local and Commons-Based Design</a></div><div><a
|
||||||
|
class=cat-post-thumbnail href=https://blog.p2pfoundation.net/futures-of-production-through-cosmo-local-and-commons-based-design/ title="Futures of Production Through Cosmo-Local and Commons-Based Design"><span
|
||||||
|
class="cat-post-crop cat-post-format cat-post-format-standard"><img
|
||||||
|
width=150 height=150 src=https://blog.p2pfoundation.net/wp-content/uploads/5380331031_56b075474a_b_Fablab-150x150.jpg class="attachment-150x150 size-150x150 wp-post-image" alt loading=lazy></span></a></div></li><li
|
||||||
|
class=cat-post-item><div><a
|
||||||
|
class=cat-post-title href=https://blog.p2pfoundation.net/global-jam-dictionary-of-cosmolocalism/ rel=bookmark>Global Jam – Dictionary of Cosmolocalism</a></div><div><a
|
||||||
|
class=cat-post-thumbnail href=https://blog.p2pfoundation.net/global-jam-dictionary-of-cosmolocalism/ title="Global Jam – Dictionary of Cosmolocalism"><span
|
||||||
|
class="cat-post-crop cat-post-format cat-post-format-standard"><img
|
||||||
|
width=150 height=150 src=https://blog.p2pfoundation.net/wp-content/uploads/1460378282_ed497405c2_b_Ecovillage-1-150x150.jpg class="attachment-150x150 size-150x150 wp-post-image" alt loading=lazy></span></a></div></li></ul></aside><aside
|
||||||
|
id=recent-comments-3 class="widget widget_recent_comments"><h3 class="widget-title">Recent Comments</h3><ul
|
||||||
|
id=recentcomments><li
|
||||||
|
class=recentcomments><span
|
||||||
|
class=comment-author-link><a
|
||||||
|
href=http://www.integralpermaculture.wordpress.com rel='external nofollow ugc' class=url>David MacLeod</a></span> on <a
|
||||||
|
href=https://blog.p2pfoundation.net/history-modes-exchange-points-towards-emergence-p2p-mode-exchange/comment-page-1/#comment-1741699>The history of modes of exchange points towards the emergence of a P2P mode of exchange</a></li><li
|
||||||
|
class=recentcomments><span
|
||||||
|
class=comment-author-link><a
|
||||||
|
href=http://www.diapraxis.com rel='external nofollow ugc' class=url>Rosa</a></span> on <a
|
||||||
|
href=https://blog.p2pfoundation.net/democracy-series-vorarlberg-civic-councils/comment-page-1/#comment-1741695>Democracy Series: Vorarlberg Civic Councils</a></li><li
|
||||||
|
class=recentcomments><span
|
||||||
|
class=comment-author-link>John Cobey</span> on <a
|
||||||
|
href=https://blog.p2pfoundation.net/free-the-vaccine-for-covid-19/comment-page-1/#comment-1741649>Free the Vaccine for Covid-19</a></li><li
|
||||||
|
class=recentcomments><span
|
||||||
|
class=comment-author-link>Wojciech Dragan</span> on <a
|
||||||
|
href=https://blog.p2pfoundation.net/the-financialization-of-life/comment-page-1/#comment-1741648>The Financialization of Life</a></li><li
|
||||||
|
class=recentcomments><span
|
||||||
|
class=comment-author-link>Lotta Hedström</span> on <a
|
||||||
|
href=https://blog.p2pfoundation.net/ecofeminism-to-escape-collapse/comment-page-1/#comment-1741632>Ecofeminism to Escape Collapse</a></li></ul></aside><aside
|
||||||
|
id=blog_subscription-2 class="widget widget_blog_subscription jetpack_subscription_widget"><h3 class="widget-title">Subscribe to the P2PF Blog</h3><form
|
||||||
|
action=# method=post accept-charset=utf-8 id=subscribe-blog-blog_subscription-2><div
|
||||||
|
id=subscribe-text><p>Enter your email address to subscribe and receive our daily content</p></div><div
|
||||||
|
class=jetpack-subscribe-count><p>
|
||||||
|
Join 31,379 other subscribers</p></div><p
|
||||||
|
id=subscribe-email>
|
||||||
|
<label
|
||||||
|
id=jetpack-subscribe-label
|
||||||
|
class=screen-reader-text
|
||||||
|
for=subscribe-field-blog_subscription-2>
|
||||||
|
Email Address </label>
|
||||||
|
<input
|
||||||
|
type=email name=email required=required
|
||||||
|
value
|
||||||
|
id=subscribe-field-blog_subscription-2
|
||||||
|
placeholder="Email Address"></p><p
|
||||||
|
id=subscribe-submit>
|
||||||
|
<input
|
||||||
|
type=hidden name=action value=subscribe>
|
||||||
|
<input
|
||||||
|
type=hidden name=source value=https://blog.p2pfoundation.net/10-blockchain-projects-to-keep-an-eye-on/ >
|
||||||
|
<input
|
||||||
|
type=hidden name=sub-type value=widget>
|
||||||
|
<input
|
||||||
|
type=hidden name=redirect_fragment value=blog_subscription-2>
|
||||||
|
<button
|
||||||
|
type=submit
|
||||||
|
name=jetpack_subscriptions_widget
|
||||||
|
>
|
||||||
|
Subscribe </button></p></form></aside><aside
|
||||||
|
id=recent-posts-2 class="widget widget_recent_entries"><h3 class="widget-title">Recent Posts</h3><ul><li>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/the-pandemic-as-a-catalyst-for-institutional-innovation/ >The Pandemic as a Catalyst for Institutional Innovation</a></li><li>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/awakening-to-an-ecology-of-the-commons/ >Awakening to an Ecology of the Commons</a></li><li>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/how-contact-tracing-apps-can-foil-both-covid-19-and-big-brother/ >How Contact Tracing Apps Can Foil Both COVID-19 and Big Brother</a></li><li>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/we-are-not-the-virus-we-are-the-kamikazes/ >We Are Not the Virus. We Are the Kamikazes.</a></li><li>
|
||||||
|
<a
|
||||||
|
href=https://blog.p2pfoundation.net/double-edge-theatre-art-commoning/ >Double Edge Theatre: Art & Commoning</a></li></ul></aside><aside
|
||||||
|
id=media_image-3 class="widget widget_media_image"><div
|
||||||
|
style="width: 308px" class="wp-caption alignnone"><a
|
||||||
|
href=https://blog.p2pfoundation.net/exploring-offline-online-communities-procomuns-barcelona/2016/05/11 target=_blank rel="noopener noreferrer"><img
|
||||||
|
width=298 height=133 src=https://blog.p2pfoundation.net/wp-content/uploads/Golden-Nica-P2PF2.jpg class="image wp-image-56638 aligncenter attachment-full size-full" alt="P2P Foundation: 2016 Prix Ars Golden Nica award for Digital Communities" loading=lazy style="max-width: 100%; height: auto;"></a><p
|
||||||
|
class=wp-caption-text><strong>P2P Foundation: 2016 Prix Ars Golden Nica award for Digital Communities</strong></p></div></aside></div></div><footer
|
||||||
|
id=colophon class=site-footer role=contentinfo><div
|
||||||
|
id=footer-sidebar class="footer-sidebar widget-area layout-full" role=complementary><aside
|
||||||
|
id=text-4 class="widget widget_text"><h3 class="widget-title">Admin</h3><div
|
||||||
|
class=textwidget><a
|
||||||
|
href=https://blog.p2pfoundation.net/wp-login.php>Log in</a></div></aside><aside
|
||||||
|
id=text-5 class="widget widget_text"><h3 class="widget-title">License</h3><div
|
||||||
|
class=textwidget><span
|
||||||
|
style="color: #808080;">If no other source is specified, the contents of this blog are under a Creative Commons Attribution - Share Alike 3.0 Unported License.</span> <a
|
||||||
|
href=http://p2pfoundation.net/P2P_Foundation:Copyright>Read the details...</a></div></aside><aside
|
||||||
|
id=text-6 class="widget widget_text"><h3 class="widget-title">Privacy Policy</h3><div
|
||||||
|
class=textwidget><span
|
||||||
|
style="color: #808080;">Click<a
|
||||||
|
href=http://blog.p2pfoundation.net/privacy-policy> here</a> for our Privacy Policy</span></div></aside></div><div
|
||||||
|
class=site-info><p></p></div></footer></div><div
|
||||||
|
style=display:none><div
|
||||||
|
class=grofile-hash-map-d80dd2f9d1b33a5a2dc91071c8df87c2></div></div> <script id=cookie-notice-front-js-extra>var cnArgs = {"ajaxUrl":"https:\/\/blog.p2pfoundation.net\/wp-admin\/admin-ajax.php","nonce":"214312d89d","hideEffect":"slide","position":"bottom","onScroll":"0","onScrollOffset":"100","onClick":"0","cookieName":"cookie_notice_accepted","cookieTime":"604800","cookieTimeRejected":"2592000","cookiePath":"\/","cookieDomain":"blog.p2pfoundation.net","redirection":"1","cache":"1","refuse":"1","revokeCookies":"0","revokeCookiesOpt":"manual","secure":"1","coronabarActive":"0"};</script> <script src=https://blog.p2pfoundation.net/wp-content/cache/minify/545b0.js></script> <script src='//hcaptcha.com/1/api.js?hl&ver=1.6.1' id=hcaptcha-script-js></script> <script src='https://secure.gravatar.com/js/gprofiles.js?ver=202501' id=grofiles-cards-js></script> <script id=wpgroho-js-extra>var WPGroHo = {"my_hash":""};</script> <script src=https://blog.p2pfoundation.net/wp-content/cache/minify/56619.js></script> <script>/**
|
||||||
|
* Category Posts Widget
|
||||||
|
* https://github.com/tiptoppress/category-posts-widget
|
||||||
|
*
|
||||||
|
* Adds a widget that shows the most recent posts from a single category.
|
||||||
|
*
|
||||||
|
* Released under the GPLv2 license or later - http://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof jQuery !== 'undefined') {
|
||||||
|
jQuery( document ).ready(function () {
|
||||||
|
if ('objectFit' in document.documentElement.style === false) {
|
||||||
|
jQuery('.cat-post-item span').removeClass('cat-post-crop');
|
||||||
|
jQuery('.cat-post-item span').addClass('cat-post-crop-not-supported');
|
||||||
|
}
|
||||||
|
if (document.documentMode || /Edge/.test(navigator.userAgent)) {
|
||||||
|
jQuery('.cat-post-item span img').height('+=1');
|
||||||
|
window.setTimeout(function(){
|
||||||
|
jQuery('.cat-post-item span img').height('-=1');
|
||||||
|
},0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}</script><script src=https://stats.wp.com/e-202501.js async=async defer=defer></script> <script>_stq = window._stq || [];
|
||||||
|
_stq.push([ 'view', {v:'ext',j:'1:8.9',blog:'62076519',post:'72709',tz:'1',srv:'blog.p2pfoundation.net'} ]);
|
||||||
|
_stq.push([ 'clickTrackerInit', '62076519', '72709' ]);</script> <div
|
||||||
|
id=cookie-notice role=banner class="cookie-notice-hidden cookie-revoke-hidden cn-position-bottom" aria-label="Cookie Notice" style="background-color: rgba(0,0,0,1);"><div
|
||||||
|
class=cookie-notice-container style="color: #fff;"><span
|
||||||
|
id=cn-notice-text class=cn-text-container>We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.</span><span
|
||||||
|
id=cn-notice-buttons class=cn-buttons-container><a
|
||||||
|
href=# id=cn-accept-cookie data-cookie-set=accept class="cn-set-cookie cn-button bootstrap button" aria-label=Ok>Ok</a><a
|
||||||
|
href=# id=cn-refuse-cookie data-cookie-set=refuse class="cn-set-cookie cn-button bootstrap button" aria-label=No>No</a><a
|
||||||
|
href=https://blog.p2pfoundation.net/privacy-policy/ target=_blank id=cn-more-info class="cn-more-info cn-button bootstrap button" aria-label="Read more">Read more</a></span><a
|
||||||
|
href=javascript:void(0); id=cn-close-notice data-cookie-set=accept class=cn-close-icon aria-label=Ok></a></div></div></body></html>
|
||||||
|
|
@ -5318,7 +5318,6 @@ VP8
|
||||||
BarCamps
|
BarCamps
|
||||||
Guerilla_Gardening
|
Guerilla_Gardening
|
||||||
VODO
|
VODO
|
||||||
http://blog.p2pfoundation.net/
|
|
||||||
Taylor,_Mark
|
Taylor,_Mark
|
||||||
Kevin_Kelly_on_the_Past,_Present,_and_Future_of_Publishing_and_Collaboration
|
Kevin_Kelly_on_the_Past,_Present,_and_Future_of_Publishing_and_Collaboration
|
||||||
Ralph_Nader_at_Occupy_Washington
|
Ralph_Nader_at_Occupy_Washington
|
||||||
|
|
|
||||||
66
src/api.py
66
src/api.py
|
|
@ -15,6 +15,7 @@ from .config import settings
|
||||||
from .embeddings import WikiVectorStore
|
from .embeddings import WikiVectorStore
|
||||||
from .rag import WikiRAG, RAGResponse
|
from .rag import WikiRAG, RAGResponse
|
||||||
from .ingress import IngressPipeline, get_review_queue, approve_item, reject_item
|
from .ingress import IngressPipeline, get_review_queue, approve_item, reject_item
|
||||||
|
from .mediawiki import wiki_client
|
||||||
|
|
||||||
# Global instances
|
# Global instances
|
||||||
vector_store: Optional[WikiVectorStore] = None
|
vector_store: Optional[WikiVectorStore] = None
|
||||||
|
|
@ -107,6 +108,12 @@ class ReviewActionRequest(BaseModel):
|
||||||
action: str # "approve" or "reject"
|
action: str # "approve" or "reject"
|
||||||
|
|
||||||
|
|
||||||
|
class DraftApproveRequest(BaseModel):
|
||||||
|
"""Request to approve a draft article."""
|
||||||
|
|
||||||
|
title: str # e.g., "Draft:Article_Name" or just "Article_Name"
|
||||||
|
|
||||||
|
|
||||||
# --- API Endpoints ---
|
# --- API Endpoints ---
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -295,6 +302,65 @@ async def list_articles(limit: int = 100, offset: int = 0):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Wiki Draft Management Endpoints ---
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wiki/auth")
|
||||||
|
async def wiki_auth_status():
|
||||||
|
"""Check wiki authentication status."""
|
||||||
|
try:
|
||||||
|
auth_info = await wiki_client.check_auth()
|
||||||
|
return auth_info
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"authenticated": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/wiki/drafts")
|
||||||
|
async def list_wiki_drafts():
|
||||||
|
"""List draft articles pending review from the wiki."""
|
||||||
|
try:
|
||||||
|
drafts = await wiki_client.list_draft_articles()
|
||||||
|
return {
|
||||||
|
"count": len(drafts),
|
||||||
|
"drafts": drafts
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/wiki/approve")
|
||||||
|
async def approve_wiki_draft(request: DraftApproveRequest):
|
||||||
|
"""
|
||||||
|
Approve a draft article - moves it from Draft: namespace to main namespace.
|
||||||
|
|
||||||
|
Requires authentication with 'move' permission via wiki cookies.
|
||||||
|
"""
|
||||||
|
# Check authentication first
|
||||||
|
auth_info = await wiki_client.check_auth()
|
||||||
|
if not auth_info.get("authenticated"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail="Not authenticated. Please ensure wiki cookies are set up."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not auth_info.get("can_move"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=f"Move permission required. Current user: {auth_info.get('username')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Approve the draft
|
||||||
|
result = await wiki_client.approve_draft(request.title)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
raise HTTPException(status_code=400, detail=result["error"])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# --- Static Files (Web UI) ---
|
# --- Static Files (Web UI) ---
|
||||||
|
|
||||||
web_dir = Path(__file__).parent.parent / "web"
|
web_dir = Path(__file__).parent.parent / "web"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
"""Blog HTML parser - extracts blog posts from cached WordPress HTML files."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import zipfile
|
||||||
|
from dataclasses import dataclass, field, asdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterator, Optional
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from rich.progress import Progress
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BlogPost:
|
||||||
|
"""Represents a parsed blog post."""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
content: str # Raw HTML content
|
||||||
|
plain_text: str # Cleaned plain text for embedding
|
||||||
|
categories: list[str] = field(default_factory=list)
|
||||||
|
links: list[str] = field(default_factory=list) # Internal links
|
||||||
|
external_links: list[str] = field(default_factory=list)
|
||||||
|
timestamp: str = ""
|
||||||
|
contributor: str = ""
|
||||||
|
url: str = ""
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
def clean_html_text(soup_element) -> str:
|
||||||
|
"""Extract clean text from HTML element."""
|
||||||
|
if not soup_element:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Get text with space separators
|
||||||
|
text = soup_element.get_text(separator="\n", strip=True)
|
||||||
|
|
||||||
|
# Clean up whitespace
|
||||||
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
||||||
|
text = re.sub(r" {2,}", " ", text)
|
||||||
|
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_links(soup_element) -> tuple[list[str], list[str]]:
|
||||||
|
"""Extract internal and external links from HTML."""
|
||||||
|
internal = []
|
||||||
|
external = []
|
||||||
|
|
||||||
|
if not soup_element:
|
||||||
|
return internal, external
|
||||||
|
|
||||||
|
for a in soup_element.find_all("a", href=True):
|
||||||
|
href = a["href"]
|
||||||
|
if "blog.p2pfoundation.net" in href or "wiki.p2pfoundation.net" in href:
|
||||||
|
internal.append(href)
|
||||||
|
elif href.startswith("http"):
|
||||||
|
external.append(href)
|
||||||
|
|
||||||
|
return list(set(internal)), list(set(external))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_blog_html(html_content: str, post_id: int, slug: str) -> Optional[BlogPost]:
|
||||||
|
"""Parse a single blog post HTML file."""
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
|
||||||
|
# Extract title
|
||||||
|
title_tag = soup.find("title")
|
||||||
|
title = title_tag.text.replace(" | P2P Foundation", "").strip() if title_tag else slug.replace("-", " ").title()
|
||||||
|
|
||||||
|
# Extract description
|
||||||
|
desc_meta = soup.find("meta", attrs={"name": "description"})
|
||||||
|
description = desc_meta["content"] if desc_meta and desc_meta.get("content") else ""
|
||||||
|
|
||||||
|
# Extract published time
|
||||||
|
pub_meta = soup.find("meta", attrs={"property": "article:published_time"})
|
||||||
|
timestamp = pub_meta["content"] if pub_meta and pub_meta.get("content") else ""
|
||||||
|
|
||||||
|
# Extract author
|
||||||
|
author = ""
|
||||||
|
author_meta = soup.find("meta", attrs={"name": "author"})
|
||||||
|
if author_meta and author_meta.get("content"):
|
||||||
|
author = author_meta["content"]
|
||||||
|
else:
|
||||||
|
# Try to find in schema.org data
|
||||||
|
schema = soup.find("script", class_="yoast-schema-graph")
|
||||||
|
if schema:
|
||||||
|
try:
|
||||||
|
schema_data = json.loads(schema.string)
|
||||||
|
for item in schema_data.get("@graph", []):
|
||||||
|
if item.get("@type") == "Person":
|
||||||
|
author = item.get("name", "")
|
||||||
|
break
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Extract article content
|
||||||
|
article = soup.find("article")
|
||||||
|
if not article:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get HTML content
|
||||||
|
content = str(article)
|
||||||
|
|
||||||
|
# Get plain text
|
||||||
|
plain_text = clean_html_text(article)
|
||||||
|
|
||||||
|
# Skip if too short (likely not a real post)
|
||||||
|
if len(plain_text) < 100:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract links
|
||||||
|
internal_links, external_links = extract_links(article)
|
||||||
|
|
||||||
|
# Extract categories from tags/categories section
|
||||||
|
categories = []
|
||||||
|
cat_links = soup.find_all("a", rel="category tag")
|
||||||
|
for cat in cat_links:
|
||||||
|
categories.append(cat.text.strip())
|
||||||
|
|
||||||
|
tag_links = soup.find_all("a", rel="tag")
|
||||||
|
for tag in tag_links:
|
||||||
|
if tag.text.strip() not in categories:
|
||||||
|
categories.append(tag.text.strip())
|
||||||
|
|
||||||
|
return BlogPost(
|
||||||
|
id=post_id,
|
||||||
|
title=title,
|
||||||
|
content=content,
|
||||||
|
plain_text=plain_text,
|
||||||
|
categories=categories,
|
||||||
|
links=internal_links,
|
||||||
|
external_links=external_links,
|
||||||
|
timestamp=timestamp,
|
||||||
|
contributor=author,
|
||||||
|
url=f"https://blog.p2pfoundation.net/{slug}/",
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_blog_zip(zip_path: Path, output_path: Optional[Path] = None) -> list[BlogPost]:
|
||||||
|
"""Parse blog posts from a WordPress cache zip file."""
|
||||||
|
console.print(f"[cyan]Parsing blog posts from {zip_path}...[/cyan]")
|
||||||
|
|
||||||
|
posts = []
|
||||||
|
seen_slugs = set()
|
||||||
|
post_id = 100000 # Start high to avoid conflicts with wiki article IDs
|
||||||
|
|
||||||
|
# Pattern to match main blog post HTML files (not feeds, embeds, or date-specific)
|
||||||
|
post_pattern = re.compile(
|
||||||
|
r"blog\.p2pfoundation\.net/public_html/wp-content/cache/page_enhanced/blog\.p2pfoundation\.net/([^/]+)/_index_ssl\.html$"
|
||||||
|
)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||||
|
# Get all matching files
|
||||||
|
html_files = [
|
||||||
|
name for name in zf.namelist()
|
||||||
|
if post_pattern.search(name) and "/feed/" not in name and "/embed/" not in name
|
||||||
|
]
|
||||||
|
|
||||||
|
console.print(f"[green]Found {len(html_files)} blog post files[/green]")
|
||||||
|
|
||||||
|
with Progress() as progress:
|
||||||
|
task = progress.add_task("[cyan]Parsing blog posts...", total=len(html_files))
|
||||||
|
|
||||||
|
for filepath in html_files:
|
||||||
|
match = post_pattern.search(filepath)
|
||||||
|
if not match:
|
||||||
|
progress.advance(task)
|
||||||
|
continue
|
||||||
|
|
||||||
|
slug = match.group(1)
|
||||||
|
|
||||||
|
# Skip duplicates and special pages
|
||||||
|
if slug in seen_slugs:
|
||||||
|
progress.advance(task)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip non-post pages
|
||||||
|
skip_patterns = ["page", "category", "tag", "author", "feed", "wp-", "uploads"]
|
||||||
|
if any(slug.startswith(p) for p in skip_patterns):
|
||||||
|
progress.advance(task)
|
||||||
|
continue
|
||||||
|
|
||||||
|
seen_slugs.add(slug)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with zf.open(filepath) as f:
|
||||||
|
html_content = f.read().decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
post = parse_blog_html(html_content, post_id, slug)
|
||||||
|
if post:
|
||||||
|
posts.append(post)
|
||||||
|
post_id += 1
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"[yellow]Warning: Could not parse {slug}: {e}[/yellow]")
|
||||||
|
|
||||||
|
progress.advance(task)
|
||||||
|
|
||||||
|
console.print(f"[green]Parsed {len(posts)} blog posts[/green]")
|
||||||
|
|
||||||
|
if output_path:
|
||||||
|
console.print(f"[cyan]Saving to {output_path}...[/cyan]")
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump([p.to_dict() for p in posts], f, ensure_ascii=False, indent=2)
|
||||||
|
console.print(f"[green]Saved {len(posts)} blog posts to {output_path}[/green]")
|
||||||
|
|
||||||
|
return posts
|
||||||
|
|
||||||
|
|
||||||
|
def merge_with_wiki_articles(blog_posts: list[BlogPost], wiki_articles_path: Path, output_path: Path):
|
||||||
|
"""Merge blog posts with existing wiki articles."""
|
||||||
|
console.print(f"[cyan]Loading existing wiki articles from {wiki_articles_path}...[/cyan]")
|
||||||
|
|
||||||
|
with open(wiki_articles_path, "r", encoding="utf-8") as f:
|
||||||
|
wiki_articles = json.load(f)
|
||||||
|
|
||||||
|
console.print(f"[green]Loaded {len(wiki_articles)} wiki articles[/green]")
|
||||||
|
|
||||||
|
# Convert blog posts to same format as wiki articles
|
||||||
|
for post in blog_posts:
|
||||||
|
wiki_articles.append({
|
||||||
|
"id": post.id,
|
||||||
|
"title": f"[Blog] {post.title}", # Prefix to distinguish from wiki articles
|
||||||
|
"content": post.content,
|
||||||
|
"plain_text": post.plain_text,
|
||||||
|
"categories": post.categories,
|
||||||
|
"links": post.links,
|
||||||
|
"external_links": post.external_links,
|
||||||
|
"timestamp": post.timestamp,
|
||||||
|
"contributor": post.contributor,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.print(f"[cyan]Saving merged articles to {output_path}...[/cyan]")
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(wiki_articles, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
console.print(f"[green]Saved {len(wiki_articles)} total articles[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""CLI entry point for parsing blog content."""
|
||||||
|
# Look for blog zip file
|
||||||
|
blog_zip = Path("/mnt/c/Users/jeffe/Downloads/blog.p2pfoundation.net.zip")
|
||||||
|
|
||||||
|
if not blog_zip.exists():
|
||||||
|
console.print(f"[red]Blog zip not found at {blog_zip}[/red]")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Parse blog posts
|
||||||
|
blog_output = settings.data_dir / "blog_posts.json"
|
||||||
|
posts = parse_blog_zip(blog_zip, blog_output)
|
||||||
|
|
||||||
|
# Merge with wiki articles
|
||||||
|
wiki_articles = settings.data_dir / "articles.json"
|
||||||
|
if wiki_articles.exists():
|
||||||
|
merged_output = settings.data_dir / "articles_with_blog.json"
|
||||||
|
merge_with_wiki_articles(posts, wiki_articles, merged_output)
|
||||||
|
console.print(f"[green]Merged articles saved to {merged_output}[/green]")
|
||||||
|
console.print("[yellow]To use merged articles, rename articles_with_blog.json to articles.json and re-run embeddings[/yellow]")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -29,7 +29,8 @@ class Settings(BaseSettings):
|
||||||
use_ollama_for_chat: bool = True # Use Ollama for simple Q&A
|
use_ollama_for_chat: bool = True # Use Ollama for simple Q&A
|
||||||
|
|
||||||
# MediaWiki
|
# MediaWiki
|
||||||
mediawiki_api_url: str = "" # Set if you have a live wiki API
|
mediawiki_api_url: str = "https://wiki.p2pfoundation.net/api.php"
|
||||||
|
wiki_cookie_file: Path = Path("/tmp/wiki_cookies.txt")
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class LLMClient:
|
||||||
messages.append({"role": "system", "content": system})
|
messages.append({"role": "system", "content": system})
|
||||||
messages.append({"role": "user", "content": prompt})
|
messages.append({"role": "user", "content": prompt})
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
async with httpx.AsyncClient(timeout=300.0) as client: # 5 min for large content
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
f"{self.ollama_url}/api/chat",
|
f"{self.ollama_url}/api/chat",
|
||||||
json={
|
json={
|
||||||
|
|
|
||||||
484
web/index.html
484
web/index.html
|
|
@ -366,6 +366,271 @@
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wiki Drafts Panel */
|
||||||
|
.drafts-container {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-status.authenticated {
|
||||||
|
border-left: 4px solid var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-status.not-authenticated {
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-badge.admin {
|
||||||
|
background: var(--success);
|
||||||
|
color: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-badge.user {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-title {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-title a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-title a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-meta {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.85em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-preview {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-preview.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-preview {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-preview:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-approve-draft {
|
||||||
|
padding: 10px 24px;
|
||||||
|
background: var(--success);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--bg-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95em;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-approve-draft:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-approve-draft:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-wiki {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-wiki:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Confirmation Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show .modal {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--success);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--bg-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drafts-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drafts-header h2 {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-count {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* Markdown-like formatting */
|
/* Markdown-like formatting */
|
||||||
.message-content p { margin-bottom: 10px; }
|
.message-content p { margin-bottom: 10px; }
|
||||||
.message-content ul, .message-content ol { margin-left: 20px; margin-bottom: 10px; }
|
.message-content ul, .message-content ol { margin-left: 20px; margin-bottom: 10px; }
|
||||||
|
|
@ -381,6 +646,7 @@
|
||||||
<div class="tab active" data-panel="chat">Chat</div>
|
<div class="tab active" data-panel="chat">Chat</div>
|
||||||
<div class="tab" data-panel="ingress">Ingress</div>
|
<div class="tab" data-panel="ingress">Ingress</div>
|
||||||
<div class="tab" data-panel="review">Review Queue</div>
|
<div class="tab" data-panel="review">Review Queue</div>
|
||||||
|
<div class="tab" data-panel="drafts">Wiki Drafts</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -430,6 +696,43 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Wiki Drafts Panel -->
|
||||||
|
<div id="drafts" class="panel">
|
||||||
|
<div class="drafts-container">
|
||||||
|
<div class="drafts-header">
|
||||||
|
<h2>Wiki Drafts</h2>
|
||||||
|
<span class="draft-count" id="draftCount">0</span>
|
||||||
|
<button class="refresh-btn" onclick="loadWikiDrafts()">Refresh</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="authStatus" class="auth-status not-authenticated">
|
||||||
|
<span class="loading"></span> Checking authentication...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="color: var(--text-secondary); margin-bottom: 20px;">
|
||||||
|
Review and approve draft articles to publish them to the main wiki namespace.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="draftsList">
|
||||||
|
<div class="empty-state">Loading drafts from wiki...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Modal -->
|
||||||
|
<div id="confirmModal" class="modal-overlay">
|
||||||
|
<div class="modal">
|
||||||
|
<h3>Approve Draft Article?</h3>
|
||||||
|
<p id="confirmMessage">
|
||||||
|
This will move the draft to the main wiki namespace and make it publicly visible.
|
||||||
|
</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-cancel" onclick="closeModal()">Cancel</button>
|
||||||
|
<button class="btn-confirm" id="confirmBtn" onclick="confirmApproval()">Approve & Publish</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -702,6 +1005,187 @@
|
||||||
|
|
||||||
// Make reviewAction available globally
|
// Make reviewAction available globally
|
||||||
window.reviewAction = reviewAction;
|
window.reviewAction = reviewAction;
|
||||||
|
|
||||||
|
// Wiki Drafts functionality
|
||||||
|
let currentAuthStatus = null;
|
||||||
|
let pendingApprovalTitle = null;
|
||||||
|
|
||||||
|
async function checkAuthStatus() {
|
||||||
|
const authStatus = document.getElementById('authStatus');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/wiki/auth`);
|
||||||
|
currentAuthStatus = await response.json();
|
||||||
|
|
||||||
|
if (currentAuthStatus.authenticated) {
|
||||||
|
const badges = [];
|
||||||
|
if (currentAuthStatus.is_admin) {
|
||||||
|
badges.push('<span class="auth-badge admin">Admin</span>');
|
||||||
|
}
|
||||||
|
if (currentAuthStatus.can_move) {
|
||||||
|
badges.push('<span class="auth-badge user">Can Approve</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
authStatus.className = 'auth-status authenticated';
|
||||||
|
authStatus.innerHTML = `
|
||||||
|
<strong>Logged in as:</strong> ${currentAuthStatus.username}
|
||||||
|
${badges.join(' ')}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
authStatus.className = 'auth-status not-authenticated';
|
||||||
|
authStatus.innerHTML = `
|
||||||
|
<strong style="color: var(--accent);">Not authenticated</strong>
|
||||||
|
<span style="color: var(--text-secondary);">— Wiki cookies required for approval</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
authStatus.className = 'auth-status not-authenticated';
|
||||||
|
authStatus.innerHTML = `
|
||||||
|
<strong style="color: var(--accent);">Error checking auth:</strong> ${error.message}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWikiDrafts() {
|
||||||
|
const draftsList = document.getElementById('draftsList');
|
||||||
|
const draftCount = document.getElementById('draftCount');
|
||||||
|
|
||||||
|
draftsList.innerHTML = '<div class="empty-state"><span class="loading"></span> Loading drafts...</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/wiki/drafts`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
draftCount.textContent = data.count;
|
||||||
|
|
||||||
|
if (data.count === 0) {
|
||||||
|
draftsList.innerHTML = '<div class="empty-state">No draft articles pending review.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draftsList.innerHTML = data.drafts.map(draft => {
|
||||||
|
const title = draft.title.replace('Draft:', '');
|
||||||
|
const wikiUrl = `https://wiki.p2pfoundation.net/${draft.title.replace(/ /g, '_')}`;
|
||||||
|
const timestamp = draft.timestamp ? new Date(draft.timestamp).toLocaleString() : 'Unknown';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="draft-card" id="draft-${encodeURIComponent(title)}">
|
||||||
|
<div class="draft-header">
|
||||||
|
<div class="draft-title">
|
||||||
|
<a href="${wikiUrl}" target="_blank">${title}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="draft-meta">
|
||||||
|
Created: ${timestamp}
|
||||||
|
</div>
|
||||||
|
<div class="draft-actions">
|
||||||
|
<a href="${wikiUrl}" target="_blank" class="btn-view-wiki">View on Wiki</a>
|
||||||
|
<button class="btn-approve-draft"
|
||||||
|
onclick="showApprovalModal('${title.replace(/'/g, "\\'")}')"
|
||||||
|
${!currentAuthStatus?.can_move ? 'disabled title="Login required to approve"' : ''}>
|
||||||
|
Approve & Publish
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
draftsList.innerHTML = `<div class="empty-state" style="color: var(--accent);">
|
||||||
|
Error loading drafts: ${error.message}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showApprovalModal(title) {
|
||||||
|
pendingApprovalTitle = title;
|
||||||
|
const modal = document.getElementById('confirmModal');
|
||||||
|
const message = document.getElementById('confirmMessage');
|
||||||
|
|
||||||
|
message.innerHTML = `
|
||||||
|
This will publish <strong>"${title}"</strong> to the main wiki namespace.<br><br>
|
||||||
|
The article will become publicly visible at:<br>
|
||||||
|
<code>wiki.p2pfoundation.net/${title.replace(/ /g, '_')}</code>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('confirmModal').classList.remove('show');
|
||||||
|
pendingApprovalTitle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmApproval() {
|
||||||
|
if (!pendingApprovalTitle) return;
|
||||||
|
|
||||||
|
const confirmBtn = document.getElementById('confirmBtn');
|
||||||
|
const originalText = confirmBtn.textContent;
|
||||||
|
confirmBtn.disabled = true;
|
||||||
|
confirmBtn.textContent = 'Publishing...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/wiki/approve`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ title: pendingApprovalTitle })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && data.success) {
|
||||||
|
// Success! Remove the card and update count
|
||||||
|
const card = document.getElementById(`draft-${encodeURIComponent(pendingApprovalTitle)}`);
|
||||||
|
if (card) {
|
||||||
|
card.style.background = 'rgba(78, 205, 196, 0.2)';
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="draft-header">
|
||||||
|
<div class="draft-title" style="color: var(--success);">
|
||||||
|
✓ ${pendingApprovalTitle} — Published!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="draft-meta">
|
||||||
|
<a href="${data.url}" target="_blank" style="color: var(--success);">
|
||||||
|
View published article →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update count
|
||||||
|
const count = document.getElementById('draftCount');
|
||||||
|
count.textContent = Math.max(0, parseInt(count.textContent) - 1);
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
alert(`Error: ${data.detail || data.error || 'Approval failed'}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.textContent = originalText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal on outside click
|
||||||
|
document.getElementById('confirmModal').addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('modal-overlay')) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal on Escape key
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make functions available globally
|
||||||
|
window.showApprovalModal = showApprovalModal;
|
||||||
|
window.closeModal = closeModal;
|
||||||
|
window.confirmApproval = confirmApproval;
|
||||||
|
window.loadWikiDrafts = loadWikiDrafts;
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue