Created
October 5, 2020 20:37
-
-
Save pfrazee/f453560014259428c9bb1e1d6d89dc55 to your computer and use it in GitHub Desktop.
HPM app source (MVP of the app)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>HPM</title> | |
<style> | |
body { | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 0 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1><a href="/">HPM</a></h1> | |
<form class="search-form"> | |
<p> | |
Hyper Package Manager | |
<input type="text" name="q" placeholder="Search for packages"> | |
</p> | |
</form> | |
<hpm-app></hpm-app> | |
<script type="module" src="/ui.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const hpm = { | |
async publish (opts = {}, packageName, packageUrl) { | |
validation.required('package-name', packageName) | |
validation.id('package-name', packageName) | |
validation.required('package-url', packageUrl) | |
validation.url('package-url', packageUrl) | |
packageUrl = (new URL(packageUrl)).origin | |
var profileDrive = beaker.hyperdrive.drive(this.env.get('home')) | |
await profileDrive.mkdir('/hpm').catch(e => undefined) | |
await profileDrive.mkdir('/hpm/pkgs').catch(e => undefined) | |
await profileDrive.writeFile(`/hpm/pkgs/${packageName}.goto`, '', { | |
metadata: { | |
href: packageUrl, | |
title: packageName | |
} | |
}) | |
return `${packageName} published` | |
}, | |
async unpublish (opts = {}, packageName) { | |
validation.required('package-name', packageName) | |
validation.id('package-name', packageName) | |
var profileDrive = beaker.hyperdrive.drive(this.env.get('home')) | |
try { | |
var st = await profileDrive.stat(`/hpm/pkgs/${packageName}.goto`) | |
} catch { | |
throw new Error(`Package "${packageName}" does not exist`) | |
} | |
await profileDrive.unlink(`/hpm/pkgs/${packageName}.goto`) | |
return `${packageName} unpublished` | |
}, | |
async ls (opts = {}, userUrl) { | |
if (userUrl) { | |
validation.url('user-url', userUrl) | |
} | |
var profileDrive = beaker.hyperdrive.drive(userUrl || this.env.get('home')) | |
var files = await profileDrive.query({path: '/hpm/pkgs/*.goto'}) | |
return files.map(entry => { | |
let id = entry.path.split('/').pop().split('.').slice(0, -1).join('.') | |
let {href} = entry.stat.metadata | |
return `${id} - ${href}` | |
}).join('\n') | |
}, | |
async search (opts = {}, query) { | |
validation.required('query', query) | |
var {results} = await beaker.index.gql(` | |
query Search ($search: String!) { | |
results: records(search: $search, paths: ["/hpm/pkgs/*.goto"]) { | |
path | |
metadata | |
site { | |
title | |
} | |
} | |
} | |
`, {search: query}) | |
return results.map(result => { | |
let id = result.path.split('/').pop().split('.').slice(0, -1).join('.') | |
let {href} = result.metadata | |
return `${result.site.title} / ${id} - ${href}` | |
}).join('\n') | |
} | |
} | |
const validation = { | |
required (name, v) { | |
if (!v) { | |
throw new Error(`${name} is required`) | |
} | |
}, | |
id (name, v) { | |
if (!v) return | |
var re = /^[a-z0-9-]+$/ | |
if (!re.test(v)) { | |
throw new Error(`${name} must be all-lowercase alpha-numeric (dashes allowed)`) | |
} | |
}, | |
url (name, v) { | |
if (!v) return | |
try { | |
let urlp = new URL(v) | |
if (urlp.protocol !== 'hyper:') throw new Error() | |
} catch (e) { | |
throw new Error(`${name} must be a hyper:// URL`) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"title": "HPM", | |
"description": "Hyper Package Manager", | |
"commands": [ | |
{ | |
"name": "hpm", | |
"help": "Hyper package manager", | |
"subcommands": [ | |
{ | |
"name": "publish", | |
"help": "Publish a hyper package", | |
"usage": "hpm publish {name} {url}" | |
}, | |
{ | |
"name": "unpublish", | |
"help": "Unpublish a hyper package", | |
"usage": "hpm publish {name}" | |
}, | |
{ | |
"name": "ls", | |
"help": "List packages published by a user (default self)", | |
"usage": "hpm ls [{user-url}]" | |
}, | |
{ | |
"name": "search", | |
"help": "Find a package published by users in your network", | |
"usage" : "hpm search {query}" | |
} | |
] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var searchQuery = (new URLSearchParams(location.search)).get('q') | |
customElements.define('hpm-app', class extends HTMLElement { | |
constructor () { | |
super() | |
this.session = undefined | |
this.load() | |
} | |
async load () { | |
this.session = await beaker.session.get() | |
console.log(this.session) | |
this.render() | |
} | |
render () { | |
if (this.session) { | |
if (searchQuery) { | |
return this.renderLoggedInSearch() | |
} | |
return this.renderLoggedIn() | |
} | |
this.renderLoggedOut() | |
} | |
renderLoggedInSearch () { | |
this.innerHTML = ` | |
<p><strong>Logged in as ${this.session.user.title}</strong></p> | |
<hr> | |
<h2>Search results for "${makeSafe(searchQuery)}"</h2> | |
<hpm-feed search="${makeSafe(searchQuery)}"></hpm-feed> | |
` | |
} | |
renderLoggedIn () { | |
this.innerHTML = ` | |
<p><strong>Logged in as ${this.session.user.title}</strong></p> | |
<hr> | |
<h2>Feed</h2> | |
<hpm-feed></hpm-feed> | |
<hr> | |
<h2>My Packages</h2> | |
<hpm-my-packages profile-url="${this.session.user.url}"></hpm-my-packages> | |
` | |
} | |
renderLoggedOut () { | |
this.innerHTML = ` | |
<p><strong>Log in to get started <button class="login">Log In</button></strong></p> | |
` | |
this.attachEventHandlers() | |
} | |
attachEventHandlers () { | |
this.querySelector('.login').addEventListener('click', async e => { | |
await beaker.session.request({ | |
permissions: { | |
publicFiles: [ | |
{path: '/hpm/pkgs/*.goto', access: 'write'} | |
] | |
} | |
}) | |
window.location.reload() | |
}) | |
} | |
}) | |
customElements.define('hpm-feed', class extends HTMLElement { | |
constructor () { | |
super() | |
this.load() | |
} | |
async load () { | |
var search = this.getAttribute('search') | |
if (search) { | |
var {records} = await beaker.index.gql(` | |
query ($search: String!) { | |
records (search: $search, paths: ["/hpm/pkgs/*.goto"] sort: crtime reverse: true limit: 10) { | |
path | |
ctime | |
metadata | |
site { url title } | |
} | |
} | |
`, {search}) | |
} else { | |
var {records} = await beaker.index.gql(` | |
query { | |
records (paths: ["/hpm/pkgs/*.goto"] sort: crtime reverse: true limit: 10) { | |
path | |
ctime | |
metadata | |
site { url title } | |
} | |
} | |
`) | |
} | |
console.log(records) | |
if (records.length === 0) { | |
this.innerHTML = `<p>No results</p>` | |
return | |
} | |
var recordHtmls = [] | |
for (let record of records) { | |
let id = record.path.split('/').pop().split('.').slice(0, -1).join('.') | |
recordHtmls.push(` | |
<p> | |
<a href="${makeSafe(encodeURI(record.metadata.href))}">${makeSafe(id)}</a> | |
- by <a href="${makeSafe(encodeURI(record.site.url))}">${makeSafe(record.site.title)}</a> | |
on ${(new Date(record.ctime)).toLocaleDateString()} | |
</p> | |
`) | |
} | |
this.innerHTML = recordHtmls.join('\n') | |
} | |
}) | |
customElements.define('hpm-my-packages', class extends HTMLElement { | |
constructor () { | |
super() | |
this.load() | |
} | |
async load () { | |
var profileUrl = this.getAttribute('profile-url') | |
var {records} = await beaker.index.gql(` | |
query ($profileUrl: String!) { | |
records (paths: ["/hpm/pkgs/*.goto"] origins: [$profileUrl]) { | |
path | |
metadata | |
} | |
} | |
`, {profileUrl}) | |
records.sort((a, b) => { | |
return getId(a.path).localeCompare(getId(b.path)) | |
}) | |
console.log(records) | |
var recordHtmls = [] | |
for (let record of records) { | |
let id = getId(record.path) | |
recordHtmls.push(` | |
<li> | |
<a href="${makeSafe(encodeURI(record.metadata.href))}">${makeSafe(id)}</a> | |
</li> | |
`) | |
} | |
this.innerHTML = `<ul>${recordHtmls.join('\n')}</ul>` | |
} | |
}) | |
function makeSafe (str) { | |
return (str || '').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') | |
} | |
function getId (path) { | |
return path.split('/').pop().split('.').slice(0, -1).join('.') | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment