diff --git a/.cspell.json b/.cspell.json index 5c27fc5..353ba67 100644 --- a/.cspell.json +++ b/.cspell.json @@ -47,6 +47,7 @@ "socio", "ssbc", "summerfruit", + "sulphurpool", "systemctl", "systemd", "unfollow", diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e9ab8ce..5e7ae7d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,3 @@ ## What's the problem you want solved? ## Is there a solution you'd like to recommend? - -## What version or commit of Oasis are you using? diff --git a/.huskyrc b/.huskyrc deleted file mode 100644 index 0526a23..0000000 --- a/.huskyrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "hooks": { - "pre-commit": "npm test" - } -} diff --git a/package-lock.json b/package-lock.json index 27cc621..7cac1a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -297,10 +297,30 @@ "urijs": "^1.19.2" }, "dependencies": { - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } } } }, @@ -975,34 +995,6 @@ "requires": { "ms": "2.0.0" } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } } } }, @@ -1254,7 +1246,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", @@ -1269,9 +1260,9 @@ "optional": true }, "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, "class-list": { @@ -1489,12 +1480,6 @@ "typewiselite": "^1.0.0" } }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2042,6 +2027,11 @@ "type": "^1.0.1" } }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -2818,11 +2808,6 @@ "ms": "2.0.0" } }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2962,15 +2947,6 @@ "locate-path": "^3.0.0" } }, - "find-versions": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", - "dev": true, - "requires": { - "semver-regex": "^2.0.0" - } - }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3137,9 +3113,9 @@ } }, "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" }, "forwarded": { "version": "0.1.2", @@ -3199,13 +3175,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", @@ -3532,9 +3501,9 @@ "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" }, "html-element": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/html-element/-/html-element-2.3.0.tgz", - "integrity": "sha512-axsAv89JAxk/zSSOn+jK5dJ1eAUVSkienyIcruAe/kD5skC/E/HxjFfCfNYv8+A9m7BXR9kfL4ZD1ZobUfUWzQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/html-element/-/html-element-2.3.1.tgz", + "integrity": "sha512-xnFt2ZkbFcjc+JoAtg3Hl89VeEZDjododu4VCPkRvFmBTHHA9U1Nt6hLUWfW2O+6Sl/rT1hHK/PivleX3PdBJQ==", "requires": { "class-list": "~0.1.1" } @@ -3588,78 +3557,21 @@ } }, "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", - "inherits": "2.0.4", + "inherits": "2.0.3", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" - } - }, - "husky": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", - "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.5.1", - "cosmiconfig": "^6.0.0", - "find-versions": "^3.2.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^4.2.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, @@ -3948,14 +3860,6 @@ "dev": true, "requires": { "ci-info": "^1.5.0" - }, - "dependencies": { - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - } } }, "is-date-object": { @@ -4153,9 +4057,9 @@ } }, "is-valid-domain": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.11.tgz", - "integrity": "sha512-N+XmAifLwbpAf6d5GM5DliNOZZrq2wnmdsAuhM2gyVaKAoJQIBz4emiPC4cnh4cIGiIqg0QvAa7sCpvGkN4WCg==" + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.14.tgz", + "integrity": "sha512-MTUz/3y25zTtutAfwrLyFK+1l2IL4bcq2iHVdYHIPQbvBJLunlYu9dsQdtLwD9HKPDyxCDlKnSbGcRwvjVeCxA==" }, "is-whitespace-character": { "version": "1.0.4", @@ -4346,11 +4250,6 @@ "requires": { "ms": "2.0.0" } - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" } } }, @@ -4365,12 +4264,9 @@ } }, "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "requires": { - "any-promise": "^1.1.0" - } + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" }, "koa-convert": { "version": "1.2.0", @@ -4379,6 +4275,16 @@ "requires": { "co": "^4.6.0", "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "requires": { + "any-promise": "^1.1.0" + } + } } }, "koa-mount": { @@ -4388,18 +4294,6 @@ "requires": { "debug": "^4.0.1", "koa-compose": "^4.1.0" - }, - "dependencies": { - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } } }, "koa-send": { @@ -4802,9 +4696,9 @@ "dev": true }, "looper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", - "integrity": "sha1-Zs0Md0rz1P7axTeU90LbVtqPCew=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", + "integrity": "sha1-LvpUw7HLq6m5Su4uWRSwvlf7t0k=" }, "lossy-store": { "version": "1.2.4", @@ -5182,13 +5076,6 @@ "separator-escape": "0.0.0", "socks": "^2.2.3", "stream-to-pull-stream": "^1.7.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } } }, "multiserver-address": { @@ -5357,9 +5244,9 @@ } }, "node-gyp-build": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.0.tgz", - "integrity": "sha512-4oiumOLhCDU9Rronz8PZ5S4IvT39H5+JEv/hps9V8s7RSLhsac0TCP78ulnHXOo8X1wdpPiTayGlM1jr4IbnaQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.1.tgz", + "integrity": "sha512-XyCKXsqZfLqHep1hhsMncoXuUNt/cXCjg1+8CLbu69V1TKuPiOeSGbL9n+k/ByKH8UT0p4rdIX8XkTRZV0i7Sw==", "optional": true }, "node-releases": { @@ -5773,19 +5660,9 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } - } + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { "version": "4.0.0", @@ -5805,51 +5682,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, "pkg-up": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", @@ -5859,15 +5691,6 @@ "find-up": "^3.0.0" } }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, "postcss": { "version": "7.0.27", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", @@ -6409,13 +6232,6 @@ "integrity": "sha1-jdYjFCY+Wc9Qlur7sSeitu8xBzU=", "requires": { "looper": "~3.0.0" - }, - "dependencies": { - "looper": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", - "integrity": "sha1-LvpUw7HLq6m5Su4uWRSwvlf7t0k=" - } } }, "pull-traverse": { @@ -6434,6 +6250,13 @@ "integrity": "sha1-/DuG/uvRkgx64pdpHiP3BfiFUvA=", "requires": { "looper": "^2.0.0" + }, + "dependencies": { + "looper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", + "integrity": "sha1-Zs0Md0rz1P7axTeU90LbVtqPCew=" + } } }, "pull-write": { @@ -6544,12 +6367,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { "bytes": "3.1.0", - "http-errors": "1.7.3", + "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -6988,11 +6811,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scope-analyzer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.0.5.tgz", - "integrity": "sha512-+U5H0417mnTEstCD5VwOYO7V4vYuSqwqjFap40ythe67bhMFL5C3UgPwyBv7KDJsqUBIKafOD57xMlh1rN7eaw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.1.1.tgz", + "integrity": "sha512-azEAihtQ9mEyZGhfgTJy3IbOWEzeOrYbg7NcYEshPKnKd+LZmC3TNd5dmDxbLBsTG/JVWmCp+vDJ03vJjeXMHg==", "requires": { "array-from": "^2.1.1", + "dash-ast": "^1.0.0", "es6-map": "^0.1.5", "es6-set": "^0.1.5", "es6-symbol": "^3.1.1", @@ -7041,12 +6865,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", @@ -7056,12 +6874,6 @@ "semver": "^5.0.3" } }, - "semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", - "dev": true - }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -7284,9 +7096,9 @@ } }, "tweetnacl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz", - "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" } } }, @@ -7296,9 +7108,9 @@ "integrity": "sha512-8AVzr9VHueXqfzfkzUA0aXe/Q4XG3UTmhlP6Pt+HQc5bbAPIJFo7ZIMh9tvn+99QuiMcyDJdYumegGAczl0N+g==" }, "sodium-native": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-2.4.6.tgz", - "integrity": "sha512-Ro9lhTjot8M01nwKLXiqLSmjR7B8o+Wg4HmJUjEShw/q6XPlNMzjPkA1VJKaMH8SO8fJ/sggAKVwreTaFszS2Q==", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-2.4.9.tgz", + "integrity": "sha512-mbkiyA2clyfwAyOFIzMvsV6ny2KrKEIhFVASJxWfsmgfUEymgLIS2MLHHcGIQMkrcKhPErRaMR5Dzv0EEn+BWg==", "optional": true, "requires": { "ini": "^1.3.5", @@ -7412,26 +7224,6 @@ "pull-stream": "^3.6.0", "ssb-config": "^3.4.4", "ssb-keys": "^7.2.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "requires": { - "minimist": "^1.2.5" - } - }, - "ssb-keys": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", - "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", - "requires": { - "chloride": "^2.2.8", - "mkdirp": "~0.5.0", - "private-box": "^0.3.0" - } - } } }, "ssb-config": { @@ -7609,33 +7401,21 @@ } }, "ssb-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.0.tgz", - "integrity": "sha512-qxbVBYB5CsxWPEFg6qe+98hL6Jbs0rztA5zYsoQmYqz2+j3EhhIuXMTki92K4xREOCA2x45FFdOjDFy7ReDpBA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/ssb-keys/-/ssb-keys-7.2.2.tgz", + "integrity": "sha512-FPeyYU/3LpxcagnbmVWE+Q/qzg6keqeOBPbD7sEH9UKixUASeufPKiORDgh8nVX7J9Z+0vUaHt/WG999kGjvVQ==", "requires": { "chloride": "^2.2.8", "mkdirp": "~0.5.0", "private-box": "^0.3.0" }, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, "mkdirp": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "requires": { "minimist": "^1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } } } } @@ -7662,9 +7442,9 @@ } }, "ssb-markdown": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ssb-markdown/-/ssb-markdown-6.0.5.tgz", - "integrity": "sha512-isJeAb7KgVXD1e+uNoF9dHYaD167Aew/VsofZJC1DDfL8tPG7xtjGuB+QRluE2/5ilLej2hE8mq27DcFT2a8Ww==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/ssb-markdown/-/ssb-markdown-6.0.6.tgz", + "integrity": "sha512-rPJryeplNPAfJHBTqtg/GQVggjepFTgzPzmgnwD28MFHb2kAnbGo+M+oLsUb8UGkqE2jmV8fdmtiajYJYLOB5g==", "requires": { "emoji-regex": "^8.0.0", "hashtag-regex": "^2.1.0", @@ -8024,24 +7804,29 @@ "dev": true }, "static-eval": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.3.tgz", - "integrity": "sha512-zsxDGucfAh8T339sSKgpFbvg15Fms2IVaJGC+jqp0bVsxhcpM+iMeAI8weNo8dmf4OblgifTBUoyk1vGVtYw2w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.5.tgz", + "integrity": "sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA==", "requires": { "escodegen": "^1.11.1" }, "dependencies": { "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" } } }, @@ -8090,13 +7875,6 @@ "requires": { "looper": "^3.0.0", "pull-stream": "^3.2.3" - }, - "dependencies": { - "looper": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", - "integrity": "sha1-LvpUw7HLq6m5Su4uWRSwvlf7t0k=" - } } }, "string-width": { @@ -9183,7 +8961,8 @@ "which-pm-runs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "optional": true }, "wide-align": { "version": "1.1.3", @@ -9353,11 +9132,6 @@ "yargs-parser": "^18.1.1" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", diff --git a/package.json b/package.json index 1131ed6..f6be6e9 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,13 @@ "dev": "nodemon --inspect src/index.js --debug --no-open", "fix": "common-good fix", "start": "node src/index.js", - "test": "common-good check --dependency-check-suffix '-i husky -i changelog-version -i mkdirp -i nodemon -i stylelint-config-recommended'", + "test": "common-good check --dependency-check-suffix '-i changelog-version -i mkdirp -i nodemon -i stylelint-config-recommended'", "preversion": "npm test", "version": "changelog-version && git add CHANGELOG.md" }, "dependencies": { "@fraction/base16-css": "^1.1.0", - "@fraction/flotilla": "^4.0.0", + "@fraction/flotilla": "^4.0.1", "@koa/router": "^8.0.0", "debug": "^4.1.1", "env-paths": "^2.2.0", @@ -44,7 +44,7 @@ "sharp": "^0.23.0", "ssb-client": "^4.9.0", "ssb-config": "^3.4.4", - "ssb-markdown": "^6.0.5", + "ssb-markdown": "^6.0.6", "ssb-mentions": "^0.5.2", "ssb-msgs": "^5.2.0", "ssb-ref": "^2.13.9", @@ -68,7 +68,6 @@ "@types/yargs": "^15.0.4", "changelog-version": "^1.0.1", "common-good": "^2.0.3", - "husky": "^4.0.0", "mkdirp": "^1.0.0", "nodemon": "^2.0.2", "stylelint-config-recommended": "^3.0.0" diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..5850869 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +set -ex + +BASEDIR="$(dirname "$0")" +TARGET_VERSION="12.16.1" + +cd "$BASEDIR/.." + +git clean -fdx + +mkdir -p vendor +cd vendor + +get_tgz () { + TARGET_PLATFORM="$1" + TARGET="node-v$TARGET_VERSION-$TARGET_PLATFORM-x64" + ARCHIVE="$TARGET.tar.gz" + URL="https://nodejs.org/dist/v$TARGET_VERSION/$ARCHIVE" + TARGET_NODE="$TARGET/bin/node" + + wget "$URL" + tar -xvf "$ARCHIVE" "$TARGET_NODE" + rm -f "$ARCHIVE" +} + +get_zip () { + TARGET_PLATFORM="$1" + TARGET="node-v$TARGET_VERSION-$TARGET_PLATFORM-x64" + ARCHIVE="$TARGET.zip" + URL="https://nodejs.org/dist/v$TARGET_VERSION/$ARCHIVE" + TARGET_NODE="$TARGET/node.exe" + + wget "$URL" + unzip "$ARCHIVE" "$TARGET_NODE" + rm -f "$ARCHIVE" +} + +get_tgz darwin +get_tgz linux +get_zip win + +cd .. + +# Avoid building anything from source. +npm ci --only=prod --ignore-scripts --no-audit --no-fund +# More trouble than it's worth :) +rm -rf ./node_modules/sharp + +export GOARCH="amd64" + +# Darwin (shell script) +export GOOS="darwin" +OUTFILE="oasis-$GOOS-$GOARCH" +go build -ldflags "-X main.node=vendor/node-v$TARGET_VERSION-darwin-x64/bin/node" -o "$OUTFILE" scripts/oasis.go +chmod +x "$OUTFILE" + +# Linux (ELF executable) +export GOOS="linux" +OUTFILE="oasis-$GOOS-$GOARCH" +go build -ldflags "-X main.node=vendor/node-v$TARGET_VERSION-linux-x64/bin/node" -o "$OUTFILE" scripts/oasis.go +chmod +x "$OUTFILE" + +# Windows (batch file) +export GOOS="windows" +OUTFILE="oasis-$GOOS-$GOARCH.exe" +go build -ldflags "-X main.node=vendor\\node-v$TARGET_VERSION-win-x64\\bin\\node" -o "$OUTFILE" scripts/oasis.go +chmod +x "$OUTFILE" + +# I think if the zip already exists it's adding files to the existing archive? +ZIP_PATH="/tmp/oasis-x64.zip" + +rm -f "$ZIP_PATH" +zip -r "$ZIP_PATH" . -x ".git/**" + +git clean -fdx + diff --git a/scripts/oasis.go b/scripts/oasis.go new file mode 100644 index 0000000..b7bb29b --- /dev/null +++ b/scripts/oasis.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" +) + +// The relative path to the `node` binary depends on the platform, so we +// pass this via an `-ldflags` hack I don't completely understand. In my +// head this is similar to how GCC lets you use `-D` to define a macro to +// be inserted by the preprocessor. +var node string + +func main() { + // The problem with relative paths is that they only work when + // you run `./oasis-platform-x64`, but not when you run a command + // like `./path/to/oasis-platform-x64`. To resolve this problem + // we need to put together an absolute path, which we can build + // with the first argument (the relative path of this executable) + // and the relative path of either the `node` binary or the + // source code directory so that we can run `node src`. + node := filepath.Join(filepath.Dir(os.Args[0]), node) + src := filepath.Join(filepath.Dir(os.Args[0]), "src") + + // We know that the command will be the absolute path to `node` + // and the first argument will be the absolute path to the `src` + // directory, but we need to get collect the rest of the arguments + // programatically by pulling them out of the `os.Args` slice and + // putting them in a new slice called `args`. + args := []string{src} + for i := 1; i < len(os.Args); i++ { + args = append(args, os.Args[i]) + } + + // This seems to execute the script and pass-through all of the + // arguments we want, *plus* it hooks up stdout and stderr, but + // the exit code of Oasis doesn't seem to be passed through. This + // is easy to test with a command like: + // + // ./oasis-platform-x64 --port -1 + // + // This should give an exit code of 1, but it seems to exit 0. :/ + cmd := exec.Command(node, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // This catches problems like "no such file or directory" if the + // `node` variable points to a path where there isn't a binary. + // + // TODO: I think we're supposed to handle the exit code here. + err := cmd.Run() + if err != nil { + fmt.Println(err) + } +} diff --git a/src/assets/style.css b/src/assets/style.css index bb8f9ba..c04feb7 100644 --- a/src/assets/style.css +++ b/src/assets/style.css @@ -109,6 +109,7 @@ h6 { ul, ol { padding-left: var(--whole); + margin-left: var(--nano); } a { @@ -124,13 +125,22 @@ button { border-radius: var(--common-radius); } -section > footer > a, -section > footer > form > button { +section header a { + display: flex; + color: var(--fg-status); + text-decoration: none; + font-weight: bold; + margin-right: var(--micro); + margin-left: var(--micro); +} + +section > footer > div > a, +section > footer > div > form > button { color: var(--fg-status); font-weight: bold; } -section > footer > form > button { +section > footer > div > form > button { display: inline-block; border: 0; background: transparent; @@ -186,13 +196,14 @@ textarea:focus { border-color: var(--blue); } +/* Prevent button styles being applied to heart button */ button:focus, button:hover { background-color: var(--fg-light); } -section > footer > form > button:hover, -section > footer > form > button:focus { +section > div > footer > form > button:hover, +section > div > footer > form > button:focus { background-color: transparent; } @@ -240,6 +251,11 @@ section video { box-sizing: border-box; } +section > h1 { + margin-top: 0; + padding-top: 0; +} + .profile > img, .profile > h1 { display: inline-block; @@ -278,15 +294,6 @@ section audio { width: 100%; } -section > :first-child { - margin-top: 0; - padding-top: 0; -} - -.content > :last-child { - margin-bottom: 0; -} - @media screen { html { min-height: 100%; @@ -305,15 +312,6 @@ nav { margin: var(--whole) 0; } -section header a { - display: flex; - color: var(--fg-status); - text-decoration: none; - font-weight: bold; - margin-right: var(--micro); - margin-left: var(--micro); -} - nav > ul > li > a { color: var(--fg); text-decoration: none; @@ -335,11 +333,20 @@ section { } section > header { - height: var(--line); - display: flex; - margin-bottom: var(--whole); - justify-content: space-between; + background: var(--bg); color: var(--fg-status); + height: var(--line); + margin-bottom: calc(-1 * var(--milli)); + margin-top: calc(-1 * var(--milli)); + padding-bottom: var(--milli); + padding-top: var(--milli); + position: sticky; + top: 0; +} + +section > header > div { + display: flex; + justify-content: space-between; } section header a > .avatar { @@ -350,9 +357,8 @@ section header a > .avatar { } section header span { - display: flex; + display: inline-flex; } - section header .author > a:first-child { margin-left: 0; color: var(--fg-light); @@ -384,17 +390,23 @@ section > .centered-footer { } section > footer { - display: flex; - justify-content: space-between; - margin-top: var(--whole); color: var(--fg-status); } -section > footer > * { +section > footer br { + display: none; +} + +section > footer > div { + display: flex; + justify-content: space-between; +} + +section > footer > div > * { text-decoration: none; } -section > footer > form > button.liked { +section > footer > div > form > button.liked { color: var(--red); } @@ -417,7 +429,6 @@ nav > ul > li { } .profile { - margin-top: var(--whole); display: flex; margin-bottom: var(--whole); } @@ -434,12 +445,17 @@ progress { border-color: var(--blue); } -/* content warning! */ summary { padding: var(--milli); - margin: 0; + margin-top: var(--whole); cursor: pointer; - border: var(--pico) solid var(--bg-selection); + background: var(--bg); + border-radius: var(--common-radius); + list-style-type: "+ "; +} + +details[open] > summary { + list-style-type: "− "; } .md-mention { @@ -517,6 +533,63 @@ hr { * be wise to nest these recursively on the thread view, which would make it so * that we don't need any inline CSS anymore. */ -.indent > section { - margin-left: 2rem; +.indent { + padding-left: 1rem; + border-left: var(--micro) solid var(--bg-selection); +} + +.theme-preview { + width: calc(100% / 15); + height: var(--whole); + margin-top: var(--whole); + display: inline-block; +} + +.theme-preview-00 { + background-color: var(--base00); +} +.theme-preview-01 { + background-color: var(--base01); +} +.theme-preview-02 { + background-color: var(--base02); +} +.theme-preview-03 { + background-color: var(--base03); +} +.theme-preview-04 { + background-color: var(--base04); +} +.theme-preview-05 { + background-color: var(--base05); +} +.theme-preview-06 { + background-color: var(--base06); +} +.theme-preview-07 { + background-color: var(--base07); +} +.theme-preview-08 { + background-color: var(--base08); +} +.theme-preview-09 { + background-color: var(--base09); +} +.theme-preview-0A { + background-color: var(--base0A); +} +.theme-preview-0B { + background-color: var(--base0B); +} +.theme-preview-0C { + background-color: var(--base0C); +} +.theme-preview-0D { + background-color: var(--base0D); +} +.theme-preview-0E { + background-color: var(--base0E); +} +.theme-preview-0F { + background-color: var(--base0F); } diff --git a/src/cli.js b/src/cli.js index 70595a8..8749e83 100644 --- a/src/cli.js +++ b/src/cli.js @@ -43,4 +43,9 @@ module.exports = (presets, defaultConfigFile) => default: _.get(presets, "debug", false), type: "boolean", }) + .options("theme", { + describe: "The theme to use, if a theme hasn't been set in the cookies", + default: _.get(presets, "theme", "atelier-sulphurpool-light"), + type: "string", + }) .epilog(`The defaults can be configured in ${defaultConfigFile}.`).argv; diff --git a/src/http.js b/src/http.js index 7984021..017a493 100644 --- a/src/http.js +++ b/src/http.js @@ -33,7 +33,7 @@ const http = ({ host, port, middleware }) => { "img-src 'self'", "form-action 'self'", "media-src 'self'", - "style-src 'self' 'unsafe-inline'", + "style-src 'self'", ].join("; "); // Disallow scripts. diff --git a/src/index.js b/src/index.js index 2b3bea5..d8ca8a3 100755 --- a/src/index.js +++ b/src/index.js @@ -165,6 +165,7 @@ const { settingsView, topicsView, summaryView, + threadsView, } = require("./views"); let sharp; @@ -175,8 +176,6 @@ try { // Optional dependency } -const defaultTheme = "atelier-sulphurPool-light".toLowerCase(); - const readmePath = path.join(__dirname, "..", "README.md"); const packagePath = path.join(__dirname, "..", "package.json"); @@ -263,6 +262,10 @@ router const messages = await post.latestSummaries(); ctx.body = await summaryView({ messages }); }) + .get("/public/latest/threads", async (ctx) => { + const messages = await post.latestThreads(); + ctx.body = await threadsView({ messages }); + }) .get("/author/:feed", async (ctx) => { const { feed } = ctx.params; const author = async (feedId) => { @@ -326,7 +329,7 @@ router ctx.body = await hashtagView({ hashtag, messages }); }) .get("/theme.css", (ctx) => { - const theme = ctx.cookies.get("theme") || defaultTheme; + const theme = ctx.cookies.get("theme") || config.theme; const packageName = "@fraction/base16-css"; const filePath = `${packageName}/src/base16-${theme}.css`; @@ -511,7 +514,7 @@ router ctx.body = await image({ blobId, imageSize: Number(imageSize) }); }) .get("/settings/", async (ctx) => { - const theme = ctx.cookies.get("theme") || defaultTheme; + const theme = ctx.cookies.get("theme") || config.theme; const getMeta = async ({ theme }) => { const status = await meta.status(); const peers = await meta.peers(); diff --git a/src/models.js b/src/models.js index 15b4a16..fd04094 100644 --- a/src/models.js +++ b/src/models.js @@ -899,6 +899,50 @@ module.exports = ({ cooler, isPublic }) => { return messages; }, + latestThreads: async () => { + const ssb = await cooler.open(); + + const myFeedId = ssb.id; + + const options = configure({ + type: "post", + private: false, + }); + + const source = ssb.messagesByType(options); + + const messages = await new Promise((resolve, reject) => { + pull( + source, + pull.filter( + (message) => + typeof message.value.content !== "string" && + message.value.content.root == null + ), + pull.take(maxMessages), + pullParallelMap(async (message, cb) => { + // Retrieve a preview of this post's comments / thread + const thread = await post.fromThread(message.key); + lodash.set( + message, + "value.meta.thread", + await transform(ssb, thread, myFeedId) + ); + cb(null, message); + }), + pull.filter((message) => message.value.meta.thread.length > 1), + pull.collect((err, collectedMessages) => { + if (err) { + reject(err); + } else { + resolve(transform(ssb, collectedMessages, myFeedId)); + } + }) + ); + }); + + return messages; + }, popular: async ({ period }) => { const ssb = await cooler.open(); @@ -1056,17 +1100,22 @@ module.exports = ({ cooler, isPublic }) => { debug("getting root ancestor of %s", msg.key); if (typeof msg.value.content === "string") { - debug("private message"); // Private message we can't decrypt, stop looking for parents. - resolve(parents); - } - - if (msg.value.content.type !== "post") { + debug("private message"); + if (parents.length > 0) { + // If we already have some parents, return those. + resolve(parents); + } else { + // If we don't know of any parents, resolve this message. + resolve(msg); + } + } else if (msg.value.content.type !== "post") { debug("not a post"); resolve(msg); - } - - if (isLooseReply(msg) && ssbRef.isMsg(msg.value.content.fork)) { + } else if ( + isLooseReply(msg) && + ssbRef.isMsg(msg.value.content.fork) + ) { debug("reply, get the parent"); try { // It's a message reply, get the parent! diff --git a/src/views/i18n.js b/src/views/i18n.js index d29069e..5e5988b 100644 --- a/src/views/i18n.js +++ b/src/views/i18n.js @@ -30,6 +30,11 @@ const i18n = { strong("Topics and some comments"), " from yourself and people you follow, sorted by recency. Select the timestamp of any post to see the rest of the thread.", ], + threads: "Threads", + threadsDescription: [ + strong("Posts that have comments"), + " from people you follow and your extended network, sorted by recency. Select the timestamp of any post to see the rest of the thread.", + ], profile: "Profile", manualMode: "Manual Mode", mentions: "Mentions", @@ -125,7 +130,7 @@ const i18n = { ], theme: "Theme", themeIntro: - "Choose from any theme you'd like. The default theme is Atelier-SulphurPool-Light.", + "Choose from any theme you'd like. The default theme is Atelier-SulphurPool-Light. You can also set your theme in the default configuration file.", setTheme: "Set theme", language: "Language", languageDescription: @@ -151,11 +156,11 @@ const i18n = { searchLabel: "Add word(s) to look for in downloaded messages.", // posts and comments commentDescription: ({ parentUrl }) => [ - "commented on ", + " commented on ", a({ href: parentUrl }, " thread"), ], replyDescription: ({ parentUrl }) => [ - "replied to ", + " replied to ", a({ href: parentUrl }, " message"), ], mysteryDescription: "posted a mysterious message", @@ -322,11 +327,11 @@ const i18n = { "Buscá las siguientes palabras por los mensajes que tenés descargados.", // posts and comments commentDescription: ({ parentUrl }) => [ - "comentado en el hilo ", + " comentado en el hilo ", a({ href: parentUrl }, ""), ], replyDescription: ({ parentUrl }) => [ - "respondido al mensaje ", + " respondido al mensaje ", a({ href: parentUrl }, ""), ], mysteryDescription: "publicó un mensaje misterioso", @@ -490,11 +495,11 @@ const i18n = { "Füge Wörte hinzu nach denen in heruntergeladenen Nachrichten gesucht werden soll.", // posts and comments commentDescription: ({ parentUrl }) => [ - "kommentierte auf ", + " kommentierte auf ", a({ href: parentUrl }, " Thread"), ], replyDescription: ({ parentUrl }) => [ - "antwortete auf ", + " antwortete auf ", a({ href: parentUrl }, " Nachricht"), ], mysteryDescription: "veröffentlichte eine mysteriöse Nachricht", diff --git a/src/views/index.js b/src/views/index.js index 9974f46..d48e297 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -8,6 +8,7 @@ const MarkdownIt = require("markdown-it"); const { a, article, + br, body, button, details, @@ -119,6 +120,11 @@ const template = (...elements) => { emoji: "🗒️", text: i18n.summaries, }), + navLink({ + href: "/public/latest/threads", + emoji: "🧵", + text: i18n.threads, + }), navLink({ href: "/profile", emoji: "🐱", text: i18n.profile }), navLink({ href: "/mentions", emoji: "💬", text: i18n.mentions }), navLink({ href: "/inbox", emoji: "✉️", text: i18n.private }), @@ -220,42 +226,139 @@ const postInAside = (msg) => { class: messageClasses.join(" "), }, header( - span( - { class: "author" }, - a( - { href: url.author }, - img({ class: "avatar", src: url.avatar, alt: "" }), - msg.value.meta.author.name + div( + span( + { class: "author" }, + a( + { href: url.author }, + img({ class: "avatar", src: url.avatar, alt: "" }), + msg.value.meta.author.name + ), + postOptions[msg.value.meta.postType] ), - postOptions[msg.value.meta.postType] - ), - span( - { class: "time" }, - isPrivate ? "🔒" : null, - a({ href: url.link }, timeAgo) + span( + { class: "time" }, + isPrivate ? "🔒" : null, + a({ href: url.link }, nbsp, timeAgo) + ) ) ), articleContent, footer( - form( - { action: url.likeForm, method: "post" }, - button( - { - name: "voteValue", - type: "submit", - value: likeButton.value, - class: likeButton.class, - }, - `❤ ${likeCount}` - ) + div( + form( + { action: url.likeForm, method: "post" }, + button( + { + name: "voteValue", + type: "submit", + value: likeButton.value, + class: likeButton.class, + }, + `❤ ${likeCount}` + ) + ), + a({ href: url.comment }, i18n.comment), + isPrivate || isRoot || isFork + ? null + : a({ href: url.reply }, nbsp, i18n.reply), + a({ href: url.json }, nbsp, i18n.json) ), - a({ href: url.comment }, i18n.comment), - isPrivate || isRoot || isFork ? null : a({ href: url.reply }, i18n.reply), - a({ href: url.json }, i18n.json) + br() ) ); }; +const thread = (messages) => { + // this first loop is preprocessing to enable auto-expansion of forks when a + // message in the fork is linked to + + let lookingForTarget = true; + let shallowest = Infinity; + + for (let i = messages.length - 1; i >= 0; i--) { + const msg = messages[i]; + const depth = lodash.get(msg, "value.meta.thread.depth", 0); + + if (lookingForTarget) { + const isThreadTarget = Boolean( + lodash.get(msg, "value.meta.thread.target", false) + ); + + if (isThreadTarget) { + lookingForTarget = false; + } + } else { + if (depth < shallowest) { + lodash.set(msg, "value.meta.thread.ancestorOfTarget", true); + shallowest = depth; + } + } + } + + const msgList = []; + for (let i = 0; i < messages.length; i++) { + const j = i + 1; + + const currentMsg = messages[i]; + const nextMsg = messages[j]; + + const depth = (msg) => { + // will be undefined when checking depth(nextMsg) when currentMsg is the + // last message in the thread + if (msg === undefined) return 0; + return lodash.get(msg, "value.meta.thread.depth", 0); + }; + + msgList.push(post({ msg: currentMsg }).outerHTML); + + if (depth(currentMsg) < depth(nextMsg)) { + const isAncestor = Boolean( + lodash.get(currentMsg, "value.meta.thread.ancestorOfTarget", false) + ); + msgList.push(`
`); + + const nextAuthor = lodash.get(nextMsg, "value.meta.author.name"); + const nextSnippet = postSnippet( + lodash.get(nextMsg, "value.content.text") + ); + + msgList.push(summary(`${nextAuthor}: ${nextSnippet}`).outerHTML); + } else if (depth(currentMsg) > depth(nextMsg)) { + // getting more shallow + const diffDepth = depth(currentMsg) - depth(nextMsg); + + const shallowList = []; + for (let d = 0; d < diffDepth; d++) { + // on the way up it might go several depths at once + shallowList.push("
"); + } + + msgList.push(shallowList); + } + } + + const htmlStrings = lodash.flatten(msgList); + return div({}, { innerHTML: htmlStrings.join("") }); +}; + +const postSnippet = (text) => { + const max = 40; + + text = text.trim().split("\n", 3).join("\n"); + // this is taken directly from patchwork. i'm not entirely sure what this + // regex is doing + text = text.replace(/_|`|\*|#|^\[@.*?]|\[|]|\(\S*?\)/g, "").trim(); + text = text.replace(/:$/, ""); + text = text.trim().split("\n", 1)[0].trim(); + + if (text.length > max) { + text = text.substring(0, max - 1) + "…"; + } + + return text; +}; + /** * Render a section containing a link that takes users to the context for a * thread preview. @@ -316,7 +419,7 @@ const postAside = ({ key, value }) => { const fragments = postsToShow.map(postInAside); if (thread.length > THREAD_PREVIEW_LENGTH + 1) { - fragments.push(section(footer(continueThreadComponent(thread, isComment)))); + fragments.push(section(continueThreadComponent(thread, isComment))); } return div({ class: "indent" }, fragments); @@ -352,8 +455,6 @@ const post = ({ msg, aside = false }) => { const { name } = msg.value.meta.author; const timeAgo = msg.value.meta.timestamp.received.since.replace("~", ""); - const depth = lodash.get(msg, "value.meta.thread.depth", 0); - const markdownContent = markdown( msg.value.content.text, msg.value.content.mentions @@ -415,22 +516,23 @@ const post = ({ msg, aside = false }) => { { id: msg.key, class: messageClasses.join(" "), - style: `margin-left: ${depth}rem;`, }, header( - span( - { class: "author" }, - a( - { href: url.author }, - img({ class: "avatar", src: url.avatar, alt: "" }), - name + div( + span( + { class: "author" }, + a( + { href: url.author }, + img({ class: "avatar", src: url.avatar, alt: "" }), + name + ), + postOptions[msg.value.meta.postType] ), - postOptions[msg.value.meta.postType] - ), - span( - { class: "time" }, - isPrivate ? "🔒" : null, - a({ href: url.link }, timeAgo) + span( + { class: "time" }, + isPrivate ? "🔒" : null, + a({ href: url.link }, nbsp, timeAgo) + ) ) ), articleContent, @@ -447,21 +549,26 @@ const post = ({ msg, aside = false }) => { div({ id: `centered-footer-${encoded.key}`, class: "centered-footer" }), footer( - form( - { action: url.likeForm, method: "post" }, - button( - { - name: "voteValue", - type: "submit", - value: likeButton.value, - class: likeButton.class, - }, - `❤ ${likeCount}` - ) + div( + form( + { action: url.likeForm, method: "post" }, + button( + { + name: "voteValue", + type: "submit", + value: likeButton.value, + class: likeButton.class, + }, + `❤ ${likeCount}` + ) + ), + a({ href: url.comment }, i18n.comment), + isPrivate || isRoot || isFork + ? null + : a({ href: url.reply }, nbsp, i18n.reply), + a({ href: url.json }, nbsp, i18n.json) ), - a({ href: url.comment }, i18n.comment), - isPrivate || isRoot || isFork ? null : a({ href: url.reply }, i18n.reply), - a({ href: url.json }, i18n.json) + br() ) ); @@ -524,7 +631,8 @@ exports.authorView = ({ relationship.following === true && relationship.blocking === false; - const contactFormType = areFollowing ? i18n.unfollow : i18n.follow; + const contactFormType = areFollowing ? "unfollow" : "follow"; + const contactFormTypeLabel = i18n[contactFormType]; const contactForm = relationship === null @@ -538,7 +646,7 @@ exports.authorView = ({ { type: "submit", }, - contactFormType + contactFormTypeLabel ) ); @@ -572,7 +680,7 @@ exports.authorView = ({ const prefix = section( { class: "message" }, - header( + div( { class: "profile" }, img({ class: "avatar", src: avatarUrl }), h1(name) @@ -583,12 +691,15 @@ exports.authorView = ({ }), description !== "" ? article({ innerHTML: markdown(description) }) : null, footer( - a({ href: `/likes/${encodeURIComponent(feedId)}` }, i18n.viewLikes), - span(relationshipText), - contactForm, - relationship === null - ? a({ href: `/profile/edit` }, i18n.editProfile) - : null + div( + a({ href: `/likes/${encodeURIComponent(feedId)}` }, i18n.viewLikes), + span(nbsp, relationshipText), + contactForm, + relationship === null + ? a({ href: `/profile/edit` }, nbsp, i18n.editProfile) + : null + ), + br() ) ); @@ -699,8 +810,7 @@ exports.publishCustomView = async () => { ); }; -exports.threadView = ({ messages }) => - template(messages.map((msg) => post({ msg }))); +exports.threadView = ({ messages }) => template(thread(messages)); exports.markdownView = ({ text }) => { const rawHtml = md.render(text); @@ -766,14 +876,16 @@ exports.settingsView = ({ status, peers, theme, themeNames, version }) => { stopButton, ]); - const peerList = (peers || []).map(([, data]) => { - return li( - a( - { href: `/author/${encodeURIComponent(data.key)}` }, - data.name || data.host || data.key - ) - ); - }); + const peerList = (peers || []) + .filter(([, data]) => data.state === "connected") + .map(([, data]) => { + return li( + a( + { href: `/author/${encodeURIComponent(data.key)}` }, + data.name || data.host || data.key + ) + ); + }); const themeElements = themeNames.map((cur) => { const isCurrentTheme = cur === theme; @@ -805,13 +917,7 @@ exports.settingsView = ({ status, peers, theme, themeNames, version }) => { const base16Elements = base16.map((base) => div({ - style: { - "background-color": `var(--base${base})`, - width: `${(1 / base16.length) * 100}%`, - height: "1em", - "margin-top": "1em", - display: "inline-block", - }, + class: `theme-preview theme-preview-${base}`, }) ); @@ -944,6 +1050,15 @@ exports.summaryView = ({ messages }) => { }); }; +exports.threadsView = ({ messages }) => { + return messageListView({ + messages, + viewTitle: i18n.threads, + viewDescription: i18n.threadsDescription, + aside: true, + }); +}; + exports.replyView = async ({ messages, myFeedId }) => { const replyForm = `/reply/${encodeURIComponent( messages[messages.length - 1].key