diff --git a/README.md b/README.md index 0b29f48..837f17f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Want more? Check out [`install.md`](https://github.com/fraction/oasis/blob/maste ## Resources -- [Contributing](https://github.com/fraction/oasis/blob/master/docs/contributing.md) +- [Architecture](https://github.com/fraction/oasis/blob/master/docs/architecture.md) - [Help](https://github.com/fraction/oasis/issues/new/choose) - [Roadmap](https://github.com/fraction/oasis/blob/master/docs/roadmap.md) - [Security Policy](https://github.com/fraction/oasis/blob/master/docs/security.md) diff --git a/docs/contributing.md b/docs/architecture.md similarity index 100% rename from docs/contributing.md rename to docs/architecture.md diff --git a/package-lock.json b/package-lock.json index 0862fc6..cffc4ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,26 +36,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -159,28 +139,6 @@ "@babel/code-frame": "^7.8.3", "@babel/parser": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - } } }, "@babel/traverse": { @@ -200,26 +158,6 @@ "lodash": "^4.17.13" }, "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -274,6 +212,19 @@ "ssb-tangle": "^1.0.1", "ssb-unix-socket": "^1.0.0", "ssb-ws": "^6.2.3" + }, + "dependencies": { + "ssb-tangle": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ssb-tangle/-/ssb-tangle-1.0.1.tgz", + "integrity": "sha512-Miu42xjISxwQGX1J59VC1FgMmLQShILZeYXOhCL5aavoYm7nzeykrEM//pU55pVlUTAbXLttvhH56IDXTPX/Kw==", + "requires": { + "is-my-json-valid": "^2.20.0", + "pull-stream": "^3.6.11", + "ssb-ref": "^2.13.9", + "ssb-sort": "^1.1.3" + } + } } }, "@nodelib/fs.scandir": { @@ -985,7 +936,6 @@ "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -2915,13 +2865,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", diff --git a/package.json b/package.json index 97314f9..ef808b9 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "ssb-mentions": "^0.5.0", "ssb-msgs": "^5.2.0", "ssb-ref": "^2.13.9", + "ssb-tangle": "^1.0.1", "ssb-thread-schema": "^1.1.1", "yargs": "^15.0.0" }, diff --git a/src/assets/favicon.svg b/src/assets/favicon.svg index 7e53386..64a5bf7 100644 --- a/src/assets/favicon.svg +++ b/src/assets/favicon.svg @@ -1,4 +1,4 @@ - + oasis favicon - 🏝️ + 🏝️ diff --git a/src/assets/style.css b/src/assets/style.css index 01d454b..4ee5676 100644 --- a/src/assets/style.css +++ b/src/assets/style.css @@ -46,6 +46,7 @@ --common-radius: var(--micro); --measure: calc(var(--peta) + var(--mega)); --line: 1.5rem; + --code-size: 85%; } * { @@ -67,6 +68,7 @@ html { line-height: 1.5; margin: 0; padding: 0; + overflow-y: scroll; } main { @@ -194,7 +196,7 @@ section code { overflow-wrap: break-word; padding: 0.125em 0.25em; margin: 0; - font-size: 85%; + font-size: var(--code-size); background-color: var(--bg); border-radius: var(--common-radius); border: var(--pico) solid var(--bg-status); @@ -315,6 +317,8 @@ section { margin: var(--whole) 0; word-wrap: break-word; background: var(--bg); + width: 100%; + box-sizing: border-box; } section > header { @@ -462,3 +466,26 @@ hr { justify-content: space-between; margin: var(--whole) 0; } + +/* sidebar only appears on big screens */ +@media (min-width: calc(45rem)) { + body > nav > ul { + justify-content: right; + flex-direction: column; + margin-right: var(--kilo); + position: sticky; + top: var(--whole); + } + body > nav > ul > li { + margin-bottom: var(--whole); + } + main { + width: 100%; + max-width: var(--measure); + } + body { + display: flex; + justify-content: center; + max-width: none; + } +} diff --git a/src/http.js b/src/http.js index 549369a..879195f 100644 --- a/src/http.js +++ b/src/http.js @@ -3,6 +3,9 @@ const koaStatic = require("koa-static"); const path = require("path"); const mount = require("koa-mount"); +/** + * @param {{ host: string, port: number, routes: any }} input + */ module.exports = ({ host, port, routes }) => { const assets = new Koa(); assets.use(koaStatic(path.join(__dirname, "assets"))); @@ -10,10 +13,10 @@ module.exports = ({ host, port, routes }) => { const app = new Koa(); module.exports = app; - app.on("error", e => { + app.on("error", err => { // Output full error objects - e.message = e.stack; - e.expose = true; + err.message = err.stack; + err.expose = true; return null; }); diff --git a/src/index.js b/src/index.js index 455b1ed..205e4e0 100755 --- a/src/index.js +++ b/src/index.js @@ -99,15 +99,17 @@ router const publicPopular = async ({ period }) => { const messages = await post.popular({ period }); - const option = somePeriod => - li( - period === somePeriod - ? a({ class: "current", href: `./${somePeriod}` }, somePeriod) - : a({ href: `./${somePeriod}` }, somePeriod) + const option = somePeriod => { + const lowerPeriod = somePeriod.toLowerCase(); + return li( + period === lowerPeriod + ? a({ class: "current", href: `./${lowerPeriod}` }, somePeriod) + : a({ href: `./${lowerPeriod}` }, somePeriod) ); + }; const prefix = nav( - ul(option("day"), option("week"), option("month"), option("year")) + ul(option("Day"), option("Week"), option("Month"), option("Year")) ); return publicView({ diff --git a/src/models/index.js b/src/models/index.js index 989e5be..5bd8396 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -21,6 +21,7 @@ const defaultOptions = { meta: true }; +/** @param {object[]} customOptions */ const configure = (...customOptions) => Object.assign({}, defaultOptions, ...customOptions); @@ -161,14 +162,15 @@ module.exports = cooler => { dest: feedId }); + // TODO: Refactor to stop doing awful string comparison. if (isFollowing === true && isBlocking === false) { - return "you are following"; + return "You are following"; } else if (isFollowing === false && isBlocking === true) { - return "you are blocking"; + return "You are blocking"; } else if (isFollowing === false && isBlocking === false) { - return "you are not following or blocking"; + return "You are not following or blocking"; } else { - return "you are following and blocking (!)"; + return "You are following and blocking (!)"; } } }; @@ -1105,6 +1107,7 @@ module.exports = cooler => { models.post = post; models.vote = { + /** @param {{messageKey: string, value: {}, recps: []}} input */ publish: async ({ messageKey, value, recps }) => { const ssb = await cooler.connect(); const branch = await cooler.get(ssb.tangle.branch, messageKey); diff --git a/src/models/markdown.js b/src/models/markdown.js index 390b81b..82c270e 100644 --- a/src/models/markdown.js +++ b/src/models/markdown.js @@ -4,20 +4,27 @@ const md = require("ssb-markdown"); const ssbMessages = require("ssb-msgs"); const ssbRef = require("ssb-ref"); -const toUrl = (mentions = []) => { - const mentionNames = {}; +/** @param {{ link: string}[]} mentions */ +const toUrl = mentions => { + /** @type {{name: string, link: string}[]} */ + const mentionNames = []; - ssbMessages.links(mentions, "feed").forEach(link => { - if (link.name && typeof link.name === "string") { - const name = link.name.charAt(0) === "@" ? link.name : `@${link.name}`; - mentionNames[name] = link.link; + /** @param {{ link: string, name: string}} arg */ + const handleLink = ({ name, link }) => { + if (typeof name === "string") { + const atName = name.charAt(0) === "@" ? name : `@${name}`; + mentionNames.push({ name: atName, link }); } - }); + }; - return ref => { + ssbMessages.links(mentions, "feed").forEach(handleLink); + + /** @param {string} ref */ + const urlHandler = ref => { // @mentions - if (ref in mentionNames) { - return `/author/${encodeURIComponent(mentionNames[ref])}`; + const found = mentionNames.find(({ name }) => name === ref); + if (found !== undefined) { + return `/author/${encodeURIComponent(found.link)}`; } if (ssbRef.isFeedId(ref)) { @@ -34,9 +41,15 @@ const toUrl = (mentions = []) => { } return ""; }; + + return urlHandler; }; -module.exports = (input, mentions) => +/** + * @param {string} input + * @param {{name: string, link: string}[]} mentions + */ +module.exports = (input, mentions = []) => md.block(input, { toUrl: toUrl(mentions) }); diff --git a/src/ssb.js b/src/ssb.js index b7a46eb..2ccc63c 100644 --- a/src/ssb.js +++ b/src/ssb.js @@ -7,6 +7,7 @@ const ssbClient = require("ssb-client"); const ssbConfig = require("ssb-config"); const flotilla = require("@fraction/flotilla"); +const ssbTangle = require("ssb-tangle"); const debug = require("debug")("oasis"); const server = flotilla(ssbConfig); @@ -24,6 +25,15 @@ const rawConnect = () => if (err) { reject(err); } else { + if (api.tangle === undefined) { + // HACK: SSB-Tangle isn't available in Patchwork, but we want that + // compatibility. This code automatically injects SSB-Tangle into our + // stack so that we don't get weird errors when using Patchwork. + // + // See: https://github.com/fraction/oasis/issues/21 + api.tangle = ssbTangle.init(api); + } + resolve(api); } }); diff --git a/src/views/index.js b/src/views/index.js index 70d21af..8078a99 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -46,9 +46,9 @@ exports.authorView = ({ const mention = `[@${name}](${feedId})`; const markdownMention = highlightJs.highlight("markdown", mention).value; - const areFollowing = relationship === "you are following"; + const areFollowing = relationship === "You are following"; - const contactFormType = areFollowing ? "unfollow" : "follow"; + const contactFormType = areFollowing ? "Unfollow" : "Follow"; // We're on our own profile! const contactForm = @@ -82,7 +82,7 @@ exports.authorView = ({ ? article({ innerHTML: description }) : null, footer( - a({ href: `/likes/${encodeURIComponent(feedId)}` }, "view likes"), + a({ href: `/likes/${encodeURIComponent(feedId)}` }, "View likes"), span(relationship), contactForm ) @@ -150,7 +150,7 @@ exports.commentView = async ({ messages, myFeedId, parentMessage }) => { { type: "submit" }, - "comment" + "Comment" ) ) ); @@ -260,7 +260,7 @@ exports.metaView = ({ status, peers, theme, themeNames }) => { form( { action: "/theme.css", method: "post" }, select({ name: "theme" }, ...themeElements), - button({ type: "submit" }, "set theme") + button({ type: "submit" }, "Set theme") ), base16Elements, h2("Status"), @@ -301,7 +301,7 @@ exports.publicView = ({ messages, prefix = null }) => { ". Messages cannot be edited or deleted." ), textarea({ required: true, name: "text" }), - button({ type: "submit" }, "submit") + button({ type: "submit" }, "Submit") ) ), messages.map(msg => post({ msg })) @@ -355,7 +355,7 @@ exports.replyView = async ({ messages, myFeedId }) => { { type: "submit" }, - "reply" + "Reply" ) ) ); @@ -376,7 +376,7 @@ exports.searchView = ({ messages, query }) => { type: "submit" }, - "submit" + "Submit" ) ) ), diff --git a/src/views/post.js b/src/views/post.js index 033d9d4..6e0593f 100644 --- a/src/views/post.js +++ b/src/views/post.js @@ -152,9 +152,9 @@ module.exports = ({ msg }) => { `❤ ${likeCount}` ) ), - a({ href: url.comment }, "comment"), - isPrivate || isRoot || isFork ? null : a({ href: url.reply }, "reply"), - a({ href: url.json }, "json") + a({ href: url.comment }, "Comment"), + isPrivate || isRoot || isFork ? null : a({ href: url.reply }, "Reply"), + a({ href: url.json }, "JSON") ) ); diff --git a/src/views/template.js b/src/views/template.js index ad5afca..1bc3c09 100644 --- a/src/views/template.js +++ b/src/views/template.js @@ -43,13 +43,13 @@ module.exports = (...elements) => { body( nav( ul( - li(a({ href: "/" }, "popular")), - li(a({ href: "/public/latest" }, "latest")), - li(a({ href: "/inbox" }, "inbox")), - li(a({ href: "/mentions" }, "mentions")), - li(a({ href: "/profile" }, "profile")), - li(a({ href: "/search" }, "search")), - li(a({ href: "/meta" }, "meta")) + li(a({ href: "/" }, "Popular")), + li(a({ href: "/public/latest" }, "Latest")), + li(a({ href: "/inbox" }, "Inbox")), + li(a({ href: "/mentions" }, "Mentions")), + li(a({ href: "/profile" }, "Profile")), + li(a({ href: "/search" }, "Search")), + li(a({ href: "/meta" }, "Meta")) ) ), main({ id: "content" }, elements)