mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-16 10:49:26 +03:00
Merge branch 'master' of github.com:urbit/interface
This commit is contained in:
commit
ab87d0d620
132
apps/publish/gulpfile.js
Normal file
132
apps/publish/gulpfile.js
Normal file
@ -0,0 +1,132 @@
|
||||
var gulp = require('gulp');
|
||||
var cssimport = require('gulp-cssimport');
|
||||
var cssnano = require('gulp-cssnano');
|
||||
var rollup = require('gulp-better-rollup');
|
||||
var sucrase = require('@sucrase/gulp-plugin');
|
||||
var minify = require('gulp-minify');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var resolve = require('rollup-plugin-node-resolve');
|
||||
var commonjs = require('rollup-plugin-commonjs');
|
||||
var replace = require('rollup-plugin-replace');
|
||||
var json = require('rollup-plugin-json');
|
||||
var builtins = require('rollup-plugin-node-builtins');
|
||||
var rootImport = require('rollup-plugin-root-import');
|
||||
var globals = require('rollup-plugin-node-globals');
|
||||
|
||||
/***
|
||||
Main config options
|
||||
***/
|
||||
|
||||
var urbitrc = require('./.urbitrc');
|
||||
|
||||
/***
|
||||
End main config options
|
||||
***/
|
||||
|
||||
gulp.task('css-bundle', function() {
|
||||
return gulp
|
||||
.src('src/index.css')
|
||||
.pipe(cssimport())
|
||||
.pipe(cssnano())
|
||||
.pipe(gulp.dest('./urbit/app/write/css'));
|
||||
});
|
||||
|
||||
gulp.task('jsx-transform', function(cb) {
|
||||
return gulp.src('src/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('js-imports', function(cb) {
|
||||
return gulp.src('dist/index.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component' ],
|
||||
'node_modules/react-is/index.js': [ 'isValidElementType' ],
|
||||
}
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('development')
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
json(),
|
||||
globals(),
|
||||
builtins(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('./urbit/app/write/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('js-minify', function () {
|
||||
return gulp.src('./urbit/app/write/js/index.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('./urbit/app/write/js/'));
|
||||
});
|
||||
|
||||
gulp.task('js-cachebust', function(cb) {
|
||||
return Promise.resolve(
|
||||
exec('git log', function (err, stdout, stderr) {
|
||||
let firstLine = stdout.split("\n")[0];
|
||||
let commitHash = firstLine.split(' ')[1].substr(0, 10);
|
||||
let newFilename = "index-" + commitHash + "-min.js";
|
||||
|
||||
exec('mv ./urbit/app/write/js/index-min.js ./urbit/app/write/js/' + newFilename);
|
||||
})
|
||||
);
|
||||
})
|
||||
|
||||
gulp.task('urbit-copy', function () {
|
||||
let ret = gulp.src('urbit/**/*');
|
||||
|
||||
urbitrc.URBIT_PIERS.forEach(function(pier) {
|
||||
ret = ret.pipe(gulp.dest(pier));
|
||||
});
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
gulp.task('js-bundle-dev', gulp.series('jsx-transform', 'js-imports'));
|
||||
gulp.task('js-bundle-prod', gulp.series('jsx-transform', 'js-imports', 'js-minify', 'js-cachebust'))
|
||||
|
||||
gulp.task('bundle-dev',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-dev'
|
||||
),
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('bundle-prod',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-prod'
|
||||
),
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('default', gulp.series('bundle-dev'));
|
||||
gulp.task('watch', gulp.series('default', function() {
|
||||
gulp.watch('src/**/*.js', gulp.parallel('js-bundle-dev'));
|
||||
gulp.watch('src/**/*.css', gulp.parallel('css-bundle'));
|
||||
|
||||
gulp.watch('urbit/**/*', gulp.parallel('urbit-copy'));
|
||||
}));
|
7133
apps/publish/package-lock.json
generated
Normal file
7133
apps/publish/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
apps/publish/package.json
Normal file
42
apps/publish/package.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "urbit-apps",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@sucrase/gulp-plugin": "^2.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"rollup": "^1.6.0",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-cssimport": "^6.0.0",
|
||||
"gulp-cssnano": "^2.1.2",
|
||||
"gulp-minify": "^3.1.0",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-json": "^2.3.0",
|
||||
"rollup-plugin-node-builtins": "^2.1.2",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-root-import": "^0.2.3",
|
||||
"sucrase": "^3.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.20.1",
|
||||
"mousetrap": "^1.6.1",
|
||||
"react": "^16.5.2",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"urbit-ob": "^3.1.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
}
|
||||
}
|
54
apps/publish/src/css/custom.css
Normal file
54
apps/publish/src/css/custom.css
Normal file
@ -0,0 +1,54 @@
|
||||
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
|
||||
margin-block-end: unset;
|
||||
margin-block-start: unset;
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
textarea, select, input, button { outline: none; }
|
||||
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.body-regular {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.body-large {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.label-regular {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.label-small-mono {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
}
|
||||
|
||||
.body-regular-400 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.plus-font {
|
||||
font-size: 48px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.fw-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bg-v-light-gray {
|
||||
background-color: #f9f9f9;
|
||||
}
|
63
apps/publish/src/css/fonts.css
Normal file
63
apps/publish/src/css/fonts.css
Normal file
@ -0,0 +1,63 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff");
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
2
apps/publish/src/css/tachyons.css
Normal file
2
apps/publish/src/css/tachyons.css
Normal file
File diff suppressed because one or more lines are too long
4
apps/publish/src/index.css
Normal file
4
apps/publish/src/index.css
Normal file
@ -0,0 +1,4 @@
|
||||
@import 'css/tachyons.css';
|
||||
@import 'css/fonts.css';
|
||||
@import 'css/custom.css';
|
||||
|
33
apps/publish/src/index.js
Normal file
33
apps/publish/src/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
import "/lib/object-extensions";
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Root } from '/components/root';
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { subscription } from "/subscription";
|
||||
import * as util from '/lib/util';
|
||||
import _ from 'lodash';
|
||||
|
||||
console.log('app running');
|
||||
|
||||
/*
|
||||
Common variables:
|
||||
|
||||
station : ~zod/club
|
||||
circle : club
|
||||
host : zod
|
||||
*/
|
||||
|
||||
api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
subscription.start();
|
||||
|
||||
window.util = util;
|
||||
window._ = _;
|
||||
|
||||
ReactDOM.render((
|
||||
<Root />
|
||||
), document.querySelectorAll("#root")[0]);
|
189
apps/publish/src/js/api.js
Normal file
189
apps/publish/src/js/api.js
Normal file
@ -0,0 +1,189 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import { uuid } from '/lib/util';
|
||||
|
||||
class UrbitApi {
|
||||
setAuthTokens(authTokens) {
|
||||
this.authTokens = authTokens;
|
||||
this.bindPaths = [];
|
||||
}
|
||||
|
||||
// keep default bind to hall, since its bind procedure more complex for now AA
|
||||
bind(path, method, ship = this.authTokens.ship, appl = "hall", success, fail) {
|
||||
console.log('binding to ...', appl, ", path: ", path, ", as ship: ", ship, ", by method: ", method);
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.urb.subscribe(ship, appl, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
fail(err);
|
||||
});
|
||||
}
|
||||
|
||||
hall(data) {
|
||||
this.action("hall", "hall-action", data);
|
||||
}
|
||||
|
||||
coll(data) {
|
||||
this.action("collections", "collections-action", data);
|
||||
}
|
||||
|
||||
setOnboardingBit(value) {
|
||||
this.action("collections", "json", { onboard: value });
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.urb.poke(ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Special actions
|
||||
*/
|
||||
|
||||
permit(cir, aud, message) {
|
||||
this.hall({
|
||||
permit: {
|
||||
nom: cir,
|
||||
sis: aud,
|
||||
inv: true
|
||||
}
|
||||
});
|
||||
|
||||
if (message) {
|
||||
this.invite(cir, aud);
|
||||
}
|
||||
}
|
||||
|
||||
permitCol(cir, aud, message, nom) {
|
||||
this.hall({
|
||||
permit: {
|
||||
nom: cir,
|
||||
sis: aud,
|
||||
inv: true
|
||||
}
|
||||
});
|
||||
|
||||
if (message) {
|
||||
this.inviteCol(cir, aud, nom);
|
||||
}
|
||||
}
|
||||
|
||||
invite(cir, aud) {
|
||||
let audInboxes = aud.map((aud) => `~${aud}/i`);
|
||||
let inviteMessage = {
|
||||
aud: audInboxes,
|
||||
ses: [{
|
||||
inv: {
|
||||
inv: true,
|
||||
cir: `~${window.ship}/${cir}`
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.hall({
|
||||
phrase: inviteMessage
|
||||
});
|
||||
}
|
||||
|
||||
inviteCol(cir, aud, nom) {
|
||||
let audInboxes = aud.map((aud) => `~${aud}/i`);
|
||||
let inviteMessage = {
|
||||
aud: audInboxes,
|
||||
ses: [{
|
||||
app: {
|
||||
app: nom,
|
||||
sep: {
|
||||
inv: {
|
||||
inv: true,
|
||||
cir: `~${window.ship}/${cir}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.hall({
|
||||
phrase: inviteMessage
|
||||
});
|
||||
}
|
||||
|
||||
message(aud, words) {
|
||||
let msg = {
|
||||
aud,
|
||||
ses: [{
|
||||
lin: {
|
||||
msg: words,
|
||||
pat: false
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.hall({
|
||||
phrase: msg
|
||||
});
|
||||
}
|
||||
|
||||
source(nom, sub) {
|
||||
this.hall({
|
||||
source: {
|
||||
nom: "inbox",
|
||||
sub: sub,
|
||||
srs: [nom]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
create(nom, priv) {
|
||||
this.hall({
|
||||
create: {
|
||||
nom: nom,
|
||||
des: "chatroom",
|
||||
sec: priv ? "village" : "channel"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ire(aud, uid, msg) {
|
||||
let message = {
|
||||
aud: aud,
|
||||
ses: [{
|
||||
ire: {
|
||||
top: uid,
|
||||
sep: {
|
||||
lin: {
|
||||
msg: msg,
|
||||
pat: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
this.hall({
|
||||
phrase: message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export let api = new UrbitApi();
|
||||
window.api = api;
|
164
apps/publish/src/js/components/chat.js
Normal file
164
apps/publish/src/js/components/chat.js
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Message } from '/components/lib/message';
|
||||
import { ChatHeader } from '/components/lib/chat-header';
|
||||
import { ChatInput } from '/components/lib/chat-input';
|
||||
|
||||
import { prettyShip, getMessageContent, isDMStation } from '/lib/util';
|
||||
|
||||
export class ChatScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
station: props.match.params.ship + "/" + props.match.params.station,
|
||||
circle: props.match.params.station,
|
||||
host: props.match.params.ship,
|
||||
message: "",
|
||||
pendingMessages: [],
|
||||
numPeople: 0
|
||||
};
|
||||
|
||||
|
||||
this.onScrollStop = this.onScrollStop.bind(this);
|
||||
this.buildMessage = this.buildMessage.bind(this);
|
||||
this.setPendingMessage = this.setPendingMessage.bind(this);
|
||||
|
||||
this.scrollbarRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (isDMStation(this.state.station)) {
|
||||
let cir = this.state.station.split("/")[1];
|
||||
this.props.api.hall({
|
||||
newdm: {
|
||||
sis: cir.split(".")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.scrollIfLocked();
|
||||
this.updateNumPeople();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props } = this;
|
||||
if ((props.match.params.ship != prevProps.match.params.ship) ||
|
||||
(props.match.params.station != prevProps.match.params.station)) {
|
||||
this.setState({
|
||||
station: props.match.params.ship + "/" + props.match.params.station,
|
||||
circle: props.match.params.station,
|
||||
host: props.match.params.ship,
|
||||
message: "",
|
||||
pendingMessages: [],
|
||||
numPeople: 0
|
||||
});
|
||||
}
|
||||
|
||||
this.updateNumPeople();
|
||||
this.updateNumMessagesLoaded(prevProps, prevState);
|
||||
}
|
||||
|
||||
updateNumPeople() {
|
||||
let conf = this.props.configs[this.state.station] || {};
|
||||
let sis = _.get(conf, 'con.sis');
|
||||
let numPeople = !!sis ? sis.length : 0;
|
||||
if (numPeople !== this.state.numPeople) {
|
||||
this.setState({ numPeople });
|
||||
}
|
||||
}
|
||||
|
||||
updateNumMessagesLoaded(prevProps, prevState) {
|
||||
let station = prevProps.store.messages.stations[this.state.station] || [];
|
||||
let numMessages = station.length;
|
||||
|
||||
if (numMessages > prevState.numMessages && this.scrollbarRef.current) {
|
||||
this.setState({
|
||||
numMessages: numMessages
|
||||
});
|
||||
|
||||
this.scrollIfLocked();
|
||||
}
|
||||
}
|
||||
|
||||
scrollIfLocked() {
|
||||
if (this.state.scrollLocked && this.scrollbarRef.current) {
|
||||
this.scrollbarRef.current.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
onScrollStop() {
|
||||
let scroll = this.scrollbarRef.current.getValues();
|
||||
|
||||
this.setState({
|
||||
scrollLocked: (scroll.top === 1)
|
||||
});
|
||||
|
||||
if (scroll.top === 0) {
|
||||
this.requestChatBatch();
|
||||
}
|
||||
}
|
||||
|
||||
setPendingMessage(message) {
|
||||
this.setState({
|
||||
pendingMessages: this.state.pendingMessages.concat({...message, pending: true})
|
||||
});
|
||||
}
|
||||
|
||||
buildMessage(msg) {
|
||||
let details = msg.printship ? null : getMessageContent(msg);
|
||||
|
||||
if (msg.printship) {
|
||||
return (
|
||||
<a
|
||||
className="vanilla hoverline text-600 text-mono"
|
||||
href={prettyShip(msg.aut)[1]}>
|
||||
{prettyShip(`~${msg.aut}`)[0]}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Message msg={msg} details={details} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let messages = this.props.store.messages.stations[this.state.station] || [];
|
||||
messages = [...messages, ...this.state.pendingMessages];
|
||||
|
||||
let chatMessages = messages.map(this.buildMessage);
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
|
||||
<div style={{ flexBasis:72 }}>
|
||||
<ChatHeader title={this.state.circle} numPeople={this.state.numPeople} />
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<Scrollbars
|
||||
ref={this.scrollbarRef}
|
||||
renderTrackHorizontal={props => <div style={{display: "none"}}/>}
|
||||
onScrollStop={this.onScrollStop}
|
||||
renderView={props => <div {...props} />}
|
||||
style={{ height: '100%' }}
|
||||
autoHide>
|
||||
{chatMessages}
|
||||
</Scrollbars>
|
||||
</div>
|
||||
<div style={{ flexBasis:112 }}>
|
||||
<ChatInput
|
||||
api={this.props.api}
|
||||
store={this.props.store}
|
||||
station={this.state.station}
|
||||
circle={this.state.circle}
|
||||
scrollbarRef={this.scrollbarRef}
|
||||
setPendingMessage={this.setPendingMessage}
|
||||
placeholder='Message...' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
27
apps/publish/src/js/components/collection-item.js
Normal file
27
apps/publish/src/js/components/collection-item.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export class CollectionItem extends Component {
|
||||
|
||||
onClick() {
|
||||
const { props } = this;
|
||||
console.log("collection-item clicked!");
|
||||
// props.history.push(props.url);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let selectedCss = !!props.selected ? 'bg-light-gray' : 'bg-white pointer';
|
||||
return (
|
||||
<div className={'pa3 ' + selectedCss} onClick={this.onClick.bind(this)}>
|
||||
<div className='w-100 v-mid'>
|
||||
<h3 className='w-60 dib sans-serif'>{props.title}</h3>
|
||||
<p className='w-40 tr dib sans-serif gray'>{props.datetime}</p>
|
||||
</div>
|
||||
<p className='pt2 sans-serif gray'>{props.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
27
apps/publish/src/js/components/collection-list.js
Normal file
27
apps/publish/src/js/components/collection-list.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export class CollectionList extends Component {
|
||||
render() {
|
||||
console.log("collection-list.props", this.props);
|
||||
|
||||
let listItems = this.props.list.map((coll) => {
|
||||
return (
|
||||
<p className="w-100">
|
||||
{coll.data.info.title}
|
||||
</p>
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
console.log(listItems);
|
||||
|
||||
return (
|
||||
<div className="w-100">
|
||||
{listItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
26
apps/publish/src/js/components/lib/chat-header.js
Normal file
26
apps/publish/src/js/components/lib/chat-header.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { isUrl, uuid, isDMStation } from '/lib/util';
|
||||
|
||||
|
||||
export class ChatHeader extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="w-100 pa2 mb3 cf">
|
||||
<div className="fl">
|
||||
<p className="f3 sans-serif">{this.props.title}</p>
|
||||
<div>
|
||||
<p className="dib mid-gray mr2 sans-serif">{this.props.numPeople} Participants</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fr">
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
131
apps/publish/src/js/components/lib/chat-input.js
Normal file
131
apps/publish/src/js/components/lib/chat-input.js
Normal file
@ -0,0 +1,131 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { isUrl, uuid, isDMStation } from '/lib/util';
|
||||
|
||||
|
||||
export class ChatInput extends Component {
|
||||
|
||||
/*
|
||||
Props:
|
||||
- station
|
||||
- api
|
||||
- store
|
||||
- circle
|
||||
- placeholder
|
||||
- setPendingMessage
|
||||
- scrollbarRef
|
||||
*/
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
message: ""
|
||||
};
|
||||
|
||||
this.textareaRef = React.createRef();
|
||||
|
||||
this.messageSubmit = this.messageSubmit.bind(this);
|
||||
this.messageChange = this.messageChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.bindShortcuts();
|
||||
}
|
||||
|
||||
bindShortcuts() {
|
||||
Mousetrap(this.textareaRef.current).bind('enter', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.messageSubmit(e);
|
||||
});
|
||||
}
|
||||
|
||||
messageChange(event) {
|
||||
this.setState({message: event.target.value});
|
||||
}
|
||||
|
||||
messageSubmit() {
|
||||
let aud, sep;
|
||||
let wen = Date.now();
|
||||
let uid = uuid();
|
||||
let aut = window.ship;
|
||||
|
||||
let config = this.props.store.configs[this.state.station];
|
||||
|
||||
if (isDMStation(this.props.station)) {
|
||||
aud = this.props.station
|
||||
.split("/")[1]
|
||||
.split(".")
|
||||
.map((mem) => `~${mem}/${this.props.circle}`);
|
||||
|
||||
} else {
|
||||
aud = [this.props.station];
|
||||
}
|
||||
|
||||
if (isUrl(this.state.message)) {
|
||||
sep = {
|
||||
url: this.state.message
|
||||
}
|
||||
} else {
|
||||
sep = {
|
||||
lin: {
|
||||
msg: this.state.message,
|
||||
pat: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let message = {
|
||||
uid,
|
||||
aut,
|
||||
wen,
|
||||
aud,
|
||||
sep,
|
||||
};
|
||||
|
||||
this.props.api.hall({
|
||||
convey: [message]
|
||||
});
|
||||
|
||||
this.props.setPendingMessage(message);
|
||||
|
||||
console.log('ending message submit');
|
||||
|
||||
this.setState({
|
||||
message: ""
|
||||
});
|
||||
|
||||
// TODO: Push to end of event queue to let pendingMessages render before scrolling
|
||||
// There's probably a better way to do this
|
||||
setTimeout(() => {
|
||||
if (this.props.scrollbarRef.current) this.props.scrollbarRef.current.scrollToBottom();
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="w-100 pa2 mb3 cf flex">
|
||||
<div className="fl mr2" style={{ flexBasis: 48 }}>
|
||||
<Sigil ship={window.ship} size={44} />
|
||||
</div>
|
||||
<div className="fr" style={{ flexGrow: 1, border: "2px solid #e6e6e6" }}>
|
||||
<textarea className="w-100 h-100 bn sans-serif pa2"
|
||||
style={{
|
||||
resize: "none"
|
||||
}}
|
||||
ref={this.textareaRef}
|
||||
placeholder={this.props.placeholder}
|
||||
value={this.state.message}
|
||||
onChange={this.messageChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
40
apps/publish/src/js/components/lib/chat-list.js
Normal file
40
apps/publish/src/js/components/lib/chat-list.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { Component } from 'react';
|
||||
import { getStationDetails } from '/services';
|
||||
|
||||
export class ChatList extends Component {
|
||||
componentDidMount() {
|
||||
let path = `/public`;
|
||||
|
||||
this.props.api.bind(path, "PUT", this.props.hostship.slice(1));
|
||||
}
|
||||
|
||||
renderChats() {
|
||||
if (this.props.store.public[this.props.hostship]) {
|
||||
const chats = this.props.store.public[this.props.hostship].map((cir) => {
|
||||
const deets = getStationDetails(cir)
|
||||
if (deets.type == "stream-chat" || deets.type == "stream-dm") {
|
||||
return (
|
||||
<div className="mt-2 text-500">
|
||||
<a href={`/~chat/{cir}`}>{cir}</a>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return chats;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const chats = this.renderChats();
|
||||
return (
|
||||
<div>
|
||||
<div className="text-600 mt-8">Chats</div>
|
||||
{chats}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
30
apps/publish/src/js/components/lib/elapsed.js
Normal file
30
apps/publish/src/js/components/lib/elapsed.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import { secToString, esoo } from '/lib/util';
|
||||
// display elapsed time by converting galactic time to client time
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
1531938314116 // "javascript unix time"
|
||||
To:
|
||||
4m // "elapsed timestring from current time"
|
||||
*/
|
||||
|
||||
export class Elapsed extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// console.log('elapsed props...', props);
|
||||
}
|
||||
|
||||
renderTime() {
|
||||
let parsed = esoo(this.props.timestring);
|
||||
const serverTime = new Date(parsed ? parsed : this.props.timestring);
|
||||
const clientTime = new Date(); // local
|
||||
return secToString((clientTime - serverTime) / 1000).split(' ')[0];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span className={this.props.classes}>-{this.renderTime()}</span>
|
||||
)
|
||||
}
|
||||
}
|
74
apps/publish/src/js/components/lib/icon.js
Normal file
74
apps/publish/src/js/components/lib/icon.js
Normal file
@ -0,0 +1,74 @@
|
||||
import React, { Component } from 'react';
|
||||
import { IconInbox } from '/components/lib/icons/icon-inbox';
|
||||
import { IconComment } from '/components/lib/icons/icon-comment';
|
||||
import { IconSig } from '/components/lib/icons/icon-sig';
|
||||
import { IconDecline } from '/components/lib/icons/icon-decline';
|
||||
import { IconUser } from '/components/lib/icons/icon-user';
|
||||
|
||||
export class Icon extends Component {
|
||||
render() {
|
||||
let iconElem = null;
|
||||
|
||||
switch(this.props.type) {
|
||||
case "icon-stream-chat":
|
||||
iconElem = <span className="icon-stream-chat"></span>;
|
||||
break;
|
||||
case "icon-stream-dm":
|
||||
iconElem = <span className="icon-stream-dm"></span>;
|
||||
break;
|
||||
case "icon-collection-index":
|
||||
iconElem = <span className="icon-collection"></span>;
|
||||
break;
|
||||
case "icon-collection-post":
|
||||
iconElem = <span className="icon-collection-post"></span>;
|
||||
break;
|
||||
case "icon-collection-comment":
|
||||
iconElem = <span className="icon-collection icon-collection-comment"></span>;
|
||||
break;
|
||||
case "icon-panini":
|
||||
// TODO: Should icons be display: block, inline, or inline-blocks?
|
||||
// 1) Should naturally flow inline
|
||||
// 2) But can't make icon-panini naturally inline without hacks like
|
||||
iconElem = <div className="icon-panini"></div>
|
||||
break;
|
||||
case "icon-x":
|
||||
iconElem = <span className="icon-x"></span>
|
||||
break;
|
||||
case "icon-decline":
|
||||
iconElem = <IconDecline />
|
||||
break;
|
||||
case "icon-lus":
|
||||
iconElem = <span className="icon-lus"></span>
|
||||
break;
|
||||
case "icon-inbox":
|
||||
iconElem = <IconInbox />
|
||||
break;
|
||||
case "icon-comment":
|
||||
iconElem = <IconComment />
|
||||
break;
|
||||
case "icon-sig":
|
||||
iconElem = <IconSig />
|
||||
break;
|
||||
case "icon-user":
|
||||
iconElem = <IconUser />
|
||||
break;
|
||||
case "icon-ellipsis":
|
||||
iconElem = (
|
||||
<div className="icon-ellipsis-wrapper icon-label">
|
||||
<div className="icon-ellipsis-dot"></div>
|
||||
<div className="icon-ellipsis-dot"></div>
|
||||
<div className="icon-ellipsis-dot"></div>
|
||||
</div>
|
||||
)
|
||||
break;
|
||||
}
|
||||
|
||||
let className = this.props.label ? "icon-label" : "";
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{iconElem}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
11
apps/publish/src/js/components/lib/icons/icon-check.js
Normal file
11
apps/publish/src/js/components/lib/icons/icon-check.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconCheck extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M14.9999 4.63293L13.2766 3L6.1698 9.7341L2.72327 6.46823L1 8.10117L6.16992 13L7.24512 11.9812L7.89319 11.3671L14.9999 4.63293Z" fill="white"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
17
apps/publish/src/js/components/lib/icons/icon-comment.js
Normal file
17
apps/publish/src/js/components/lib/icons/icon-comment.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconComment extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="12" height="12">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M9.2 10.4L14 14V2H2V10.4H9.2ZM3.2 9.2H9.35486C9.48986 9.2 9.62096 9.24554 9.72686 9.32924L12.8 11.7578V3.2H3.2V9.2Z" fill="black"/>
|
||||
<path d="M3.2 9.2H9.35486C9.48986 9.2 9.62096 9.24554 9.72686 9.32924L12.8 11.7578V3.2H3.2V9.2Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<rect width="16" height="16" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
11
apps/publish/src/js/components/lib/icons/icon-cross.js
Normal file
11
apps/publish/src/js/components/lib/icons/icon-cross.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconCross extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M8 6.28568L3.71436 2L2.00012 3.71429L6.28577 7.99994L2 12.2857L3.71423 14L8 9.71423L12.2858 14L14 12.2857L9.71436 7.99997L14 3.71429L12.2856 2.00003L8 6.28568Z" fill="black"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
13
apps/publish/src/js/components/lib/icons/icon-decline.js
Normal file
13
apps/publish/src/js/components/lib/icons/icon-decline.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconDecline extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon/Decline">
|
||||
<path id="Union" fillRule="evenodd" clipRule="evenodd" d="M6.28577 7.99992L2 12.2857L3.71423 14L8 9.71422L12.2858 14L14 12.2857L9.71423 7.99997L14 3.71428L12.2856 1.99998L8 6.28568L3.71436 2L2.00012 3.71428L6.28577 7.99992Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
11
apps/publish/src/js/components/lib/icons/icon-inbox.js
Normal file
11
apps/publish/src/js/components/lib/icons/icon-inbox.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconInbox extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M8 6C9.65686 6 11 4.65686 11 3H13C13.5523 3 14 3.44772 14 4V12C14 12.5523 13.5523 13 13 13H3C2.44771 13 2 12.5523 2 12V4C2 3.44772 2.44771 3 3 3H5C5 4.65686 6.34314 6 8 6Z" fill="black"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
11
apps/publish/src/js/components/lib/icons/icon-sig.js
Normal file
11
apps/publish/src/js/components/lib/icons/icon-sig.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconSig extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 6H12.6564C12.4097 7.51007 11.8238 8.48322 10.7445 8.48322C8.86344 8.48322 7.78414 6 5.03965 6C2.54185 6 1.2467 7.71141 1 11H3.34361C3.59031 9.48993 4.17621 8.51678 5.25551 8.51678C7.19824 8.51678 8.18502 11 10.9912 11C13.3965 11 14.7533 9.28859 15 6Z" fill="black"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
9
apps/publish/src/js/components/lib/icons/icon-user.js
Normal file
9
apps/publish/src/js/components/lib/icons/icon-user.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconUser extends Component {
|
||||
render() {
|
||||
return (
|
||||
<svg fill="none" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path clipRule="evenodd" d="m8 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm4.667 12h1.333c0-2.761-2.686-5-6-5s-6 2.239-6 5z" fill="#000" fillRule="evenodd"/></svg>
|
||||
)
|
||||
}
|
||||
}
|
18
apps/publish/src/js/components/lib/icons/sigil.js
Normal file
18
apps/publish/src/js/components/lib/icons/sigil.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import { sealDict } from '/components/lib/seal-dict';
|
||||
|
||||
export class Sigil extends Component {
|
||||
render() {
|
||||
let prefix = this.props.prefix ? JSON.parse(this.props.prefix) : false;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-black"
|
||||
style={{ flexBasis: 48, padding: 4, paddingBottom: 0 }}>
|
||||
{
|
||||
sealDict.getSeal(this.props.ship, this.props.size, prefix)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
69
apps/publish/src/js/components/lib/message.js
Normal file
69
apps/publish/src/js/components/lib/message.js
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { Component } from 'react';
|
||||
import { isDMStation, getMessageContent } from '/lib/util';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import classnames from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
export class Message extends Component {
|
||||
buildPostTitle(messageDetails) {
|
||||
if (messageDetails.postUrl) {
|
||||
return (
|
||||
<a className="pr-12 text-600 underline"
|
||||
href={messageDetails.postUrl}>
|
||||
{messageDetails.postTitle}
|
||||
</a>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderContent(type) {
|
||||
if (type === "text") {
|
||||
return this.buildPostTitle(this.props.details);
|
||||
} else if (type === "url") {
|
||||
if (/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF)$/.exec(this.props.details.content)) {
|
||||
return (
|
||||
<img src={this.props.details.content} style={{width:"30%"}}></img>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<a href={this.props.details.content} target="_blank">{this.props.details.content}</a>
|
||||
)
|
||||
}
|
||||
} else if (type === "exp") {
|
||||
return (
|
||||
<div className="text-body">
|
||||
<div className="text-mono">{this.props.details.content}</div>
|
||||
<pre className="text-mono mt-0">{this.props.details.res}</pre>
|
||||
</div>
|
||||
)
|
||||
} else if (['new item', 'edited item'].includes(type)) {
|
||||
return <span className="text-body" dangerouslySetInnerHTML={{__html: this.props.details.snip}}></span>
|
||||
} else if (type === "lin") {
|
||||
return (
|
||||
<p className="sans-serif">{this.props.details.content}</p>
|
||||
);
|
||||
} else {
|
||||
return <span className="text-mono">{'<unknown message type>'}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="w-100 pa2 mb3 cf flex">
|
||||
<div className="fl mr2">
|
||||
<Sigil ship={this.props.msg.aut} size={44} />
|
||||
</div>
|
||||
<div className="fr" style={{ flexGrow: 1 }}>
|
||||
<div className="mb2">
|
||||
<p className="sans-serif gray dib mr2">~{this.props.msg.aut}</p>
|
||||
<p className="sans-serif gray dib">{moment.unix(this.props.msg.wen).format('hh:mm')}</p>
|
||||
</div>
|
||||
{this.renderContent(this.props.details.type)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
90
apps/publish/src/js/components/lib/seal-dict.js
Normal file
90
apps/publish/src/js/components/lib/seal-dict.js
Normal file
@ -0,0 +1,90 @@
|
||||
import React, { Component } from 'react';
|
||||
import { pour } from '/vendor/sigils-1.2.5';
|
||||
import _ from 'lodash';
|
||||
|
||||
const ReactSVGComponents = {
|
||||
svg: p => {
|
||||
return (
|
||||
<svg {...p.attr} version={'1.1'} xmlns={'http://www.w3.org/2000/svg'}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</svg>
|
||||
)
|
||||
},
|
||||
circle: p => {
|
||||
return (
|
||||
<circle {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</circle>
|
||||
)
|
||||
},
|
||||
rect: p => {
|
||||
return (
|
||||
<rect {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</rect>
|
||||
)
|
||||
},
|
||||
path: p => {
|
||||
return (
|
||||
<path {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</path>
|
||||
)
|
||||
},
|
||||
g: p => {
|
||||
return (
|
||||
<g {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</g>
|
||||
)
|
||||
},
|
||||
polygon: p => {
|
||||
return (
|
||||
<polygon {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</polygon>
|
||||
)
|
||||
},
|
||||
line: p => {
|
||||
return (
|
||||
<line {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</line>
|
||||
)
|
||||
},
|
||||
polyline: p => {
|
||||
return (
|
||||
<polyline {...p.attr}>
|
||||
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
|
||||
</polyline>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SealDict {
|
||||
constructor() {
|
||||
this.dict = {};
|
||||
}
|
||||
|
||||
getPrefix(patp) {
|
||||
return patp.length === 3 ? patp : patp.substr(0, 3);
|
||||
}
|
||||
|
||||
getSeal(patp, size, prefix) {
|
||||
if (patp.length > 13) {
|
||||
patp = "tiz";
|
||||
}
|
||||
|
||||
let sigilShip = prefix ? this.getPrefix(patp) : patp;
|
||||
let key = `${sigilShip}+${size}`;
|
||||
|
||||
if (!this.dict[key]) {
|
||||
this.dict[key] = pour({size: size, patp: sigilShip, renderer: ReactSVGComponents, margin: 0, colorway: ["#fff", "#000"]})
|
||||
}
|
||||
|
||||
return this.dict[key];
|
||||
}
|
||||
}
|
||||
|
||||
const sealDict = new SealDict;
|
||||
export { sealDict }
|
70
apps/publish/src/js/components/root.js
Normal file
70
apps/publish/src/js/components/root.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route } from "react-router-dom";
|
||||
import Mousetrap from 'mousetrap';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { Skeleton } from '/components/skeleton';
|
||||
import { Sidebar } from '/components/sidebar';
|
||||
import { CollectionList } from '/components/collection-list';
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = store.collections;
|
||||
|
||||
console.log("root.state", this.state);
|
||||
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Route exact path="/~publish"
|
||||
render={ (props) => {
|
||||
return (
|
||||
<div className="cf h-100 w-100 absolute">
|
||||
<div className="fl w-100 h3">
|
||||
<h1>Publish</h1>
|
||||
</div>
|
||||
<div className="fl flex w-100 h-100">
|
||||
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
|
||||
<p className="fl w-100 h2 bb">
|
||||
Latest
|
||||
</p>
|
||||
</div>
|
||||
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
|
||||
<p className="fl w-100 h2 bb">
|
||||
Subs
|
||||
</p>
|
||||
<CollectionList
|
||||
list={this.state.subs}
|
||||
/>
|
||||
</div>
|
||||
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
|
||||
<p className="fl w-100 h2 bb">
|
||||
Pubs
|
||||
</p>
|
||||
<CollectionList
|
||||
list={this.state.pubs}
|
||||
/>
|
||||
</div>
|
||||
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
|
||||
<p className="fl w-100 h2 bb">
|
||||
Create Button? idk
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
26
apps/publish/src/js/components/sidebar-item.js
Normal file
26
apps/publish/src/js/components/sidebar-item.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export class SidebarItem extends Component {
|
||||
|
||||
onClick() {
|
||||
const { props } = this;
|
||||
props.history.push('/~chat/' + props.title);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let selectedCss = !!props.selected ? 'bg-light-gray' : 'bg-white pointer';
|
||||
return (
|
||||
<div className={'pa3 ' + selectedCss} onClick={this.onClick.bind(this)}>
|
||||
<div className='w-100 v-mid'>
|
||||
<h3 className='w-60 dib sans-serif'>{props.title}</h3>
|
||||
<p className='w-40 tr dib sans-serif gray'>{props.datetime}</p>
|
||||
</div>
|
||||
<p className='pt2 sans-serif gray'>{props.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
58
apps/publish/src/js/components/sidebar.js
Normal file
58
apps/publish/src/js/components/sidebar.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getMessageContent } from '/lib/util';
|
||||
import { SidebarItem } from '/components/sidebar-item';
|
||||
|
||||
export class Sidebar extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
let station = props.match.params.ship + '/' + props.match.params.station;
|
||||
|
||||
let sidebarItems = props.circles.map((cir) => {
|
||||
let msg = props.messagePreviews[cir];
|
||||
let parsed = getMessageContent(msg);
|
||||
let wen = moment.unix(msg.wen / 1000).from(moment.utc());
|
||||
|
||||
return (
|
||||
<CollectionItem
|
||||
title={cir}
|
||||
description={parsed.content}
|
||||
datetime={wen}
|
||||
selected={station === cir}
|
||||
history={props.history}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
|
||||
<div className="pl3 pr3 pt2 pb2 cf">
|
||||
<h2 className="dib lh-title sans-serif w-50 f2">Publish</h2>
|
||||
<a className="dib tr lh-title sans-serif w-50 f4 underline">+ New</a>
|
||||
</div>
|
||||
<div className='mt2 pl3 pr3 mb2 w-100'>
|
||||
<div>My Collections</div>
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<Scrollbars
|
||||
ref={this.scrollbarRef}
|
||||
renderTrackHorizontal={props => <div style={{display: "none"}}/>}
|
||||
onScrollStop={this.onScrollStop}
|
||||
renderView={props => <div {...props} />}
|
||||
style={{ height: '100%' }}
|
||||
autoHide>
|
||||
{sidebarItems}
|
||||
</Scrollbars>
|
||||
</div>
|
||||
<div className='mt2 pl3 pr3 mb2 w-100'>
|
||||
<div>My Subscriptions</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
19
apps/publish/src/js/components/skeleton.js
Normal file
19
apps/publish/src/js/components/skeleton.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="cf h-100 w-100 absolute flex">
|
||||
<div className="fl h-100 br overflow-x-hidden" style={{ flexBasis: 320 }}>
|
||||
{this.props.sidebar}
|
||||
</div>
|
||||
<div className="h-100 fr" style={{ flexGrow: 1 }}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
9
apps/publish/src/js/lib/object-extensions.js
Normal file
9
apps/publish/src/js/lib/object-extensions.js
Normal file
@ -0,0 +1,9 @@
|
||||
Object.arrayify = (obj) => {
|
||||
let ret = [];
|
||||
Object.keys(obj).forEach((key) => {
|
||||
ret.push({key, value: obj[key]});
|
||||
})
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
35
apps/publish/src/js/lib/report-keys.js
Normal file
35
apps/publish/src/js/lib/report-keys.js
Normal file
@ -0,0 +1,35 @@
|
||||
export const REPORT_KEYS = [
|
||||
'landscape.prize',
|
||||
// /circle/<cir_name>/grams
|
||||
// call automatically on inbox
|
||||
// call automatically on /urbit-meta
|
||||
// call automatically on any DM circles created
|
||||
'circle.gram',
|
||||
'circle.nes',
|
||||
// /circle/<cir_name>/config-l
|
||||
// used for loading inbox config
|
||||
'circle.cos.loc',
|
||||
// /circle/<cir_name>/config-r
|
||||
// used for loading inbox's sources' configs
|
||||
'circle.cos.rem',
|
||||
// /circle/<cir_name>/config-l
|
||||
// used for fora topic creation....maybe? let me check
|
||||
|
||||
'circle.config',
|
||||
'circle.config.dif.full',
|
||||
// /circle/<cir_name>/config-l
|
||||
// used for subscription / unsubscription
|
||||
'circle.config.dif.source',
|
||||
// /circles, required for initialization
|
||||
'circles',
|
||||
|
||||
// frontend specific, no server calls
|
||||
'menu.toggle',
|
||||
'config.ext',
|
||||
'inbox.sources-loaded',
|
||||
'circle.read',
|
||||
'dm.new',
|
||||
'dm.clear',
|
||||
];
|
||||
|
||||
|
354
apps/publish/src/js/lib/util.js
Normal file
354
apps/publish/src/js/lib/util.js
Normal file
@ -0,0 +1,354 @@
|
||||
import _ from 'lodash';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export const AGGREGATOR_COLL = "c";
|
||||
export const AGGREGATOR_INBOX = "aggregator-inbox";
|
||||
export const AGGREGATOR_NAMES = [AGGREGATOR_INBOX, AGGREGATOR_COLL];
|
||||
|
||||
export function capitalize(str) {
|
||||
return `${str[0].toUpperCase()}${str.substr(1)}`;
|
||||
}
|
||||
|
||||
// takes a galactic (urbit) time and converts to 8601
|
||||
export function esoo(str) {
|
||||
|
||||
var dubb = function(num) {
|
||||
return num < 10 ? '0' + parseInt(num) : parseInt(num);
|
||||
}
|
||||
|
||||
const p = /\~(\d\d\d\d).(\d\d?).(\d\d?)..(\d\d?).(\d\d?).(\d\d?)/.exec(str);
|
||||
|
||||
if (p) {
|
||||
return `${p[1]}-${dubb(p[2])}-${dubb(p[3])}T${dubb(p[4])}:${dubb(p[5])}:${dubb(p[6])}Z`
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// check if hostname follows ship.*.urbit.org scheme
|
||||
export function isProxyHosted(hostName) {
|
||||
const r = /([a-z,-]+)\.(.+\.)?urbit\.org/.exec(hostName);
|
||||
if (r && urbitOb.isValidPatp(r[1])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getQueryParams() {
|
||||
if (window.location.search !== "") {
|
||||
return JSON.parse('{"' + decodeURI(window.location.search.substr(1).replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}');
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function isAggregator(station) {
|
||||
let cir = station.split("/")[1]
|
||||
return AGGREGATOR_NAMES.includes(cir);
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
1531943107869 // "javascript unix time"
|
||||
To:
|
||||
"48711y 2w 5d 11m 9s" // "stringified time increments"
|
||||
*/
|
||||
|
||||
export function secToString(secs) {
|
||||
if (secs <= 0) {
|
||||
return 'Completed';
|
||||
}
|
||||
secs = Math.floor(secs)
|
||||
var min = 60;
|
||||
var hour = 60 * min;
|
||||
var day = 24 * hour;
|
||||
var week = 7 * day;
|
||||
var year = 52 * week;
|
||||
var fy = function(s) {
|
||||
if (s < year) {
|
||||
return ['', s];
|
||||
} else {
|
||||
return [Math.floor(s / year) + 'y', s % year];
|
||||
}
|
||||
}
|
||||
var fw = function(tup) {
|
||||
var str = tup[0];
|
||||
var sec = tup[1];
|
||||
if (sec < week) {
|
||||
return [str, sec];
|
||||
} else {
|
||||
return [str + ' ' + Math.floor(sec / week) + 'w', sec % week];
|
||||
}
|
||||
}
|
||||
var fd = function(tup) {
|
||||
var str = tup[0];
|
||||
var sec = tup[1];
|
||||
if (sec < day) {
|
||||
return [str, sec];
|
||||
} else {
|
||||
return [str + ' ' + Math.floor(sec / day) + 'd', sec % day];
|
||||
}
|
||||
}
|
||||
var fh = function(tup) {
|
||||
var str = tup[0];
|
||||
var sec = tup[1];
|
||||
if (sec < hour) {
|
||||
return [str, sec];
|
||||
} else {
|
||||
return [str + ' ' + Math.floor(sec / hour) + 'h', sec % hour];
|
||||
}
|
||||
}
|
||||
var fm = function(tup) {
|
||||
var str = tup[0];
|
||||
var sec = tup[1];
|
||||
if (sec < min) {
|
||||
return [str, sec];
|
||||
} else {
|
||||
return [str + ' ' + Math.floor(sec / min) + 'm', sec % min];
|
||||
}
|
||||
}
|
||||
var fs = function(tup) {
|
||||
var str = tup[0];
|
||||
var sec = tup[1];
|
||||
return str + ' ' + sec + 's';
|
||||
}
|
||||
return fs(fm(fh(fd(fw(fy(secs)))))).trim();
|
||||
}
|
||||
|
||||
export function uuid() {
|
||||
let str = "0v"
|
||||
str += Math.ceil(Math.random()*8)+"."
|
||||
for (var i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random()*10000000).toString(32);
|
||||
_str = ("00000"+_str).substr(-5,5);
|
||||
str += _str+".";
|
||||
}
|
||||
|
||||
return str.slice(0,-1);
|
||||
}
|
||||
|
||||
export function isPatTa(str) {
|
||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
|
||||
return !!r;
|
||||
}
|
||||
|
||||
export function isValidStation(st) {
|
||||
let tokens = st.split("/")
|
||||
|
||||
if (tokens.length !== 2) return false;
|
||||
|
||||
return urbitOb.isValidPatp(tokens[0]) && isPatTa(tokens[1]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
To:
|
||||
(javascript Date object)
|
||||
*/
|
||||
export function daToDate(st) {
|
||||
var dub = function(n) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
};
|
||||
var da = st.split('..');
|
||||
var bigEnd = da[0].split('.');
|
||||
var lilEnd = da[1].split('.');
|
||||
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
return new Date(ds);
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
(javascript Date object)
|
||||
To:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
*/
|
||||
|
||||
export function dateToDa(d, mil) {
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
`${(d.getUTCMonth() + 1)}.` +
|
||||
`${fil(d.getUTCDate())}..` +
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
// ascending for clarity
|
||||
// export function sortSrc(circleArray, topic=false){
|
||||
// let sc = circleArray.map((c) => util.parseCollCircle(c)).filter((pc) => typeof pc != 'undefined' && typeof pc.top == 'undefined');
|
||||
// return sc.map((src) => src.coll).sort((a, b) => util.daToDate(a) - util.daToDate(b));
|
||||
// }
|
||||
|
||||
export function arrayEqual(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.length != b.length) return false;
|
||||
|
||||
// If you don't care about the order of the elements inside
|
||||
// the array, you should sort both arrays here.
|
||||
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function deSig(ship) {
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
// use urbit.org proxy if it's not on our ship
|
||||
export function foreignUrl(shipName, own, urlFrag) {
|
||||
if (deSig(shipName) != deSig(own)) {
|
||||
return `http://${deSig(shipName)}.urbit.org${urlFrag}`
|
||||
} else {
|
||||
return urlFrag
|
||||
}
|
||||
}
|
||||
|
||||
// shorten comet names
|
||||
export function prettyShip(ship) {
|
||||
const sp = ship.split('-');
|
||||
return [sp.length == 9 ? `${sp[0]}_${sp[8]}`: ship, ship[0] === '~' ? `/~profile/${ship}` : `/~profile/~${ship}`];
|
||||
}
|
||||
|
||||
export function profileUrl(ship) {
|
||||
return `/~landscape/profile/~${ship}`;
|
||||
}
|
||||
|
||||
export function isDMStation(station) {
|
||||
let host = station.split('/')[0].substr(1);
|
||||
let circle = station.split('/')[1];
|
||||
|
||||
return (
|
||||
station.indexOf('.') !== -1 &&
|
||||
circle.indexOf(host) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
export function isRootCollection(station) {
|
||||
return station.split("/")[1] === "c";
|
||||
}
|
||||
|
||||
// maybe do fancier stuff later
|
||||
export function isUrl(string) {
|
||||
const r = /^http|^www|\.com$/.exec(string)
|
||||
if (r) {
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function getMessageContent(msg) {
|
||||
let ret;
|
||||
|
||||
const MESSAGE_TYPES = {
|
||||
'sep.app.sep.fat.sep.lin.msg': 'app',
|
||||
'sep.app.sep.lin.msg': 'app',
|
||||
'sep.app.sep.inv': (msg) => {
|
||||
let sta = msg.sep.app.sep.inv.cir;
|
||||
let [hos, cir] = sta.split('/');
|
||||
|
||||
return {
|
||||
type: 'inv',
|
||||
msg: msg,
|
||||
content: {
|
||||
nom: msg.sep.app.app,
|
||||
sta: sta,
|
||||
hos: hos,
|
||||
inv: msg.sep.app.sep.inv.inv
|
||||
}
|
||||
}
|
||||
},
|
||||
'sep.inv': (msg) => {
|
||||
let sta = msg.sep.inv.cir;
|
||||
let [hos, cir] = sta.split('/');
|
||||
|
||||
return {
|
||||
type: 'inv',
|
||||
msg: msg,
|
||||
content: {
|
||||
nom: cir,
|
||||
inv: msg.sep.inv.inv,
|
||||
hos,
|
||||
sta,
|
||||
cir
|
||||
}
|
||||
}
|
||||
},
|
||||
'sep.fat': (msg) => {
|
||||
let type = msg.sep.fat.tac.text;
|
||||
let station = msg.aud[0];
|
||||
let jason = JSON.parse(msg.sep.fat.sep.lin.msg);
|
||||
let content = (type.includes('collection')) ? null : jason.content;
|
||||
let par = jason.path.slice(0, -1);
|
||||
|
||||
return {
|
||||
type: msg.sep.fat.tac.text,
|
||||
msg: msg,
|
||||
contentType: jason.type,
|
||||
content: content,
|
||||
snip: jason.snip,
|
||||
author: jason.author,
|
||||
host: jason.host,
|
||||
date: jason.date,
|
||||
path: jason.path,
|
||||
postTitle: jason.name,
|
||||
postUrl: `/~landscape/collections/${jason.host}/${jason.path.slice(2).join('/')}`,
|
||||
}
|
||||
},
|
||||
'sep.lin.msg': 'lin',
|
||||
'sep.ire.sep.lin': (msg) => {
|
||||
return {
|
||||
type: "lin",
|
||||
msg: msg,
|
||||
content: msg.sep.ire.sep.lin.msg,
|
||||
replyUid: msg.sep.ire.top
|
||||
}
|
||||
},
|
||||
'sep.ire': 'ire',
|
||||
'sep.url': 'url',
|
||||
'sep.exp': (msg) => {
|
||||
return {
|
||||
type: "exp",
|
||||
msg: msg,
|
||||
content: msg.sep.exp.exp,
|
||||
res: msg.sep.exp.res.join('\n')
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Object.arrayify(MESSAGE_TYPES).some(({key, value}) => {
|
||||
if (_.has(msg, key)) {
|
||||
if (typeof value === "string") {
|
||||
ret = {
|
||||
type: value,
|
||||
msg: msg,
|
||||
content: _.get(msg, key)
|
||||
}
|
||||
} else if (typeof value === "function") {
|
||||
ret = value(msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof ret === "undefined") {
|
||||
ret = {type: "unknown"};
|
||||
console.log("ASSERT: unknown message type on ", msg)
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
window.getMessageContent = getMessageContent;
|
19
apps/publish/src/js/reducers/circles.js
Normal file
19
apps/publish/src/js/reducers/circles.js
Normal file
@ -0,0 +1,19 @@
|
||||
export class CirclesReducer {
|
||||
reduce(reports, store) {
|
||||
reports.forEach((rep) => {
|
||||
switch (rep.type) {
|
||||
case "circles":
|
||||
if (rep.data.add) {
|
||||
store.circles = [...store.circles, rep.data.cir]
|
||||
} else {
|
||||
store.circles = rep.data
|
||||
}
|
||||
break;
|
||||
|
||||
case "landscape.prize":
|
||||
store.circles = [...store.circles, rep.data["circles-our"]];
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
123
apps/publish/src/js/reducers/configs.js
Normal file
123
apps/publish/src/js/reducers/configs.js
Normal file
@ -0,0 +1,123 @@
|
||||
import { isAggregator } from '/lib/util';
|
||||
|
||||
export class ConfigsReducer {
|
||||
reduce(reports, store) {
|
||||
reports.forEach(rep => {
|
||||
let stationName;
|
||||
let stations = {};
|
||||
|
||||
switch (rep.type) {
|
||||
case "circle.gram":
|
||||
this.processGramConfigs([rep.data], store.configs);
|
||||
break;
|
||||
case "circle.nes":
|
||||
this.processGramConfigs(rep.data, store.configs);
|
||||
break;
|
||||
case "circle.cos.loc":
|
||||
stationName = `~${rep.from.ship}/${rep.from.path.split("/")[2]}`;
|
||||
stations[stationName] = rep.data;
|
||||
this.addConfigs(stations, store.configs);
|
||||
break;
|
||||
case "circle.cos.rem":
|
||||
this.addConfigs(rep.data, store.configs);
|
||||
break;
|
||||
case "circle.pes.loc":
|
||||
stationName = `~${rep.from.ship}/${rep.from.path.split("/")[2]}`;
|
||||
this.updateConfig({pes: rep.data}, store.configs[stationName]);
|
||||
break;
|
||||
case "circle.config.dif.source":
|
||||
stationName = `~${rep.from.ship}/${rep.from.path.split("/")[2]}`;
|
||||
this.updateConfig(rep.data, store.configs[stationName]);
|
||||
break;
|
||||
case "circle.config.dif.full":
|
||||
stationName = rep.data.src[0]; // TODO: API weirdness; we have to get name of new station from new station config's src property. Should maybe return a dict.
|
||||
stations[stationName] = rep.data;
|
||||
this.addConfigs(stations, store.configs);
|
||||
break;
|
||||
case "circle.config.dif.permit": // TODO: This is very wonky, should be fixed with API discussion
|
||||
stationName = rep.data.cir;
|
||||
this.updateConfig(rep.data.dif.permit, store.configs[stationName]);
|
||||
break;
|
||||
case "circle.config.dif.remove":
|
||||
delete store.configs[rep.data.cir];
|
||||
break;
|
||||
case "config.ext":
|
||||
store.configs[rep.data.station] = store.configs[rep.data.station] || {};
|
||||
store.configs[rep.data.station].extConf = rep.data.extConf;
|
||||
break;
|
||||
case "circle.read":
|
||||
store.configs[rep.data.station] = store.configs[rep.data.station] || {};
|
||||
store.configs[rep.data.station].lastReadNum = rep.data.lastReadNum;
|
||||
break;
|
||||
|
||||
case "circle.config":
|
||||
let readChange = _.get(rep.data, 'dif.read', null);
|
||||
|
||||
if (readChange) {
|
||||
store.configs[rep.data.cir] = {...store.configs[rep.data.cir], red: readChange};
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "landscape.prize":
|
||||
rep.data.circles.forEach(c => {
|
||||
store.configs[c.circle] = c.config || {};
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processGramConfigs(grams, storeConfigs) {
|
||||
grams.forEach(gram => {
|
||||
let tac = _.get(gram, 'gam.sep.fat.tac.text', null);
|
||||
if (tac && ['new item', 'edited item'].includes(tac)) {
|
||||
let conf = _.get(gram, 'gam.sep.fat.sep.lin.msg', null);
|
||||
if (conf) {
|
||||
let parsedConf = JSON.parse(conf);
|
||||
if (parsedConf['parent-config']) {
|
||||
storeConfigs[gram.gam.aud[0]] = {
|
||||
...storeConfigs[gram.gam.aud[0]],
|
||||
...{ extConf: parsedConf['parent-config'] }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addConfigs(configs, storeConfigs) {
|
||||
Object.keys(configs)
|
||||
.forEach((cos) => {
|
||||
storeConfigs[cos] = storeConfigs[cos] || {};
|
||||
Object.assign(storeConfigs[cos], configs[cos]);
|
||||
});
|
||||
}
|
||||
|
||||
updateConfig(data, station) {
|
||||
if (!station) return;
|
||||
|
||||
if (data.src) {
|
||||
if (data.add) {
|
||||
station.src.push(data.src);
|
||||
} else {
|
||||
station.src = station.src.filter((val) => val !== data.src);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.sis) {
|
||||
if (data.add) {
|
||||
station.con.sis = station.con.sis.concat(data.sis);
|
||||
} else {
|
||||
station.con.sis = station.con.sis.filter((val) => !data.sis.includes(val));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.pes) {
|
||||
station.pes = station.pes || {};
|
||||
|
||||
Object.assign(station.pes, data.pes);
|
||||
}
|
||||
}
|
||||
}
|
54
apps/publish/src/js/reducers/dms.js
Normal file
54
apps/publish/src/js/reducers/dms.js
Normal file
@ -0,0 +1,54 @@
|
||||
// let newSep = {
|
||||
// sep: {
|
||||
// inv: {
|
||||
// inv: true,
|
||||
// cir: "~zod/null"
|
||||
// }
|
||||
// },
|
||||
// wen: (new Date()).getTime()
|
||||
// };
|
||||
|
||||
// import { isDMStation, getMessageContent } from '/lib/util';
|
||||
// import _ from 'lodash';
|
||||
//
|
||||
// export class DmsReducer {
|
||||
// reduce(reports, store) {
|
||||
// reports.forEach((rep) => {
|
||||
// switch (rep.type) {
|
||||
// case "circles":
|
||||
// if (_.isArray(rep.data)) {
|
||||
// let newStations = rep.data.filter(station => isDMStation(`${rep.from.ship}/${station}`));
|
||||
// store.dms.stations = _.uniq([...store.dms.stations, ...newStations]);
|
||||
// store.dms.stored = true;
|
||||
// } else if (rep.data.cir) {
|
||||
// if (rep.data.add) {
|
||||
// store.dms.stations = _.uniq([...store.dms.stations, rep.data.cir]);
|
||||
// } else {
|
||||
// store.dms.stations = _.filter(store.dms.stations, s => s !== rep.data.cir);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
//
|
||||
// case "circle.gram":
|
||||
// this.addStationsFromInvites([rep.data], store);
|
||||
// break;
|
||||
//
|
||||
// case "circle.nes":
|
||||
// this.addStationsFromInvites(rep.data, store);
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// addStationFromInvite(msgs, store) {
|
||||
// let inviteStations = [];
|
||||
// msgs.forEach(msg => {
|
||||
// let msgContent = getMessageContent(msg);
|
||||
// if (msgContent.type === "inv" && isDMStation(msgContent.content.sta)) {
|
||||
// inviteStations.push(msgContent.content.sta);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// store.dms.stations = [...store.dms.stations, ...inviteStations];
|
||||
// }
|
||||
// }
|
191
apps/publish/src/js/reducers/messages.js
Normal file
191
apps/publish/src/js/reducers/messages.js
Normal file
@ -0,0 +1,191 @@
|
||||
import _ from 'lodash';
|
||||
import { isDMStation, isRootCollection, getMessageContent } from '/lib/util';
|
||||
|
||||
const INBOX_MESSAGE_COUNT = 30;
|
||||
|
||||
export class MessagesReducer {
|
||||
reduce(reports, store) {
|
||||
reports.forEach((rep) => {
|
||||
let fromCircle = rep.from && rep.from.path.split("/")[2];
|
||||
let fromInbox = fromCircle === "inbox";
|
||||
|
||||
switch (rep.type) {
|
||||
case "circle.nes":
|
||||
this.processMessages(rep.data, store);
|
||||
break;
|
||||
case "circle.gram":
|
||||
this.processMessages([rep.data], store);
|
||||
break;
|
||||
case "circle.config.dif.remove":
|
||||
delete store.messages.stations[rep.data.cir];
|
||||
break;
|
||||
case "circle.cos.loc":
|
||||
if (fromInbox) {
|
||||
store.messages.inbox.config = rep.data;
|
||||
store.messages.inbox.src = rep.data.src;
|
||||
this.storeInboxMessages(store);
|
||||
}
|
||||
break;
|
||||
case "circle.config.dif.source":
|
||||
if (fromInbox) {
|
||||
if (rep.data.add) {
|
||||
store.messages.inbox.src = [...store.messages.inbox.src, rep.data.src];
|
||||
} else {
|
||||
store.messages.inbox.src = store.messages.inbox.src.filter(src => src !== rep.data.src);
|
||||
}
|
||||
this.storeInboxMessages(store);
|
||||
}
|
||||
break;
|
||||
|
||||
case "circle.config":
|
||||
fromInbox = rep.data.cir.includes("inbox");
|
||||
if (fromInbox && _.get(rep.data, 'dif.source', null)) {
|
||||
if (rep.data.dif.source.add) {
|
||||
store.messages.inbox.src = [...store.messages.inbox.src, rep.data.dif.source.src];
|
||||
} else {
|
||||
store.messages.inbox.src = store.messages.inbox.src.filter(src => src !== rep.data.dif.source.src);
|
||||
}
|
||||
this.storeInboxMessages(store);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "landscape.prize":
|
||||
if (rep.data.inbox) {
|
||||
store.messages.inbox.src = [...store.messages.inbox.src, ...rep.data.inbox.config.src];
|
||||
store.messages.inbox.config = rep.data.inbox.config;
|
||||
this.processMessages(rep.data.inbox.messages, store);
|
||||
this.processMessages(rep.data.invites, store);
|
||||
this.storeInboxMessages(store);
|
||||
} else {
|
||||
console.log("WEIRD: no inbox property in landscape.prize?")
|
||||
}
|
||||
|
||||
// if (fromInbox) {
|
||||
// if (rep.data.add) {
|
||||
// store.messages.inbox.src = [...store.messages.inbox.src, rep.data.src];
|
||||
// } else {
|
||||
// store.messages.inbox.src = store.messages.inbox.src.filter(src => src !== rep.data.src);
|
||||
// }
|
||||
// this.storeInboxMessages(store);
|
||||
// }
|
||||
break;
|
||||
|
||||
case "dm.new": {
|
||||
store.messages.notifications = [...store.messages.notifications, ...rep.data];
|
||||
break;
|
||||
}
|
||||
|
||||
case "dm.clear": {
|
||||
store.messages.notifications = store.messages.notifications.filter(n => !rep.data.includes(n.uid));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processMessages(messages, store) {
|
||||
let msgs = messages.filter(m => {
|
||||
return !m.gam.aud.some(st => isRootCollection(st));
|
||||
});
|
||||
this.storeStationMessages(msgs, store);
|
||||
this.storeInboxMessages(store);
|
||||
}
|
||||
|
||||
// TODO: Make this more like storeInboxMessages
|
||||
storeStationMessages(messages, store) {
|
||||
messages.forEach((message) => {
|
||||
let msg = message.gam;
|
||||
msg.num = message.num;
|
||||
msg.aud.forEach((aud) => {
|
||||
let msgClone = { ...msg, aud: [aud] };
|
||||
let station = store.messages.stations[aud]
|
||||
|
||||
if (!station) {
|
||||
store.messages.stations[aud] = [msgClone];
|
||||
} else if (station.findIndex(o => o.uid === msgClone.uid) === -1) {
|
||||
let newest = true;
|
||||
|
||||
for (let i = 0; i < station.length; i++) {
|
||||
if (msgClone.wen < station[i].wen) {
|
||||
station.splice(i, 0, msgClone);
|
||||
newest = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newest) station.push(msgClone);
|
||||
|
||||
// Print messages by date, for debugging:
|
||||
// for (let msgClone of station.messages) {
|
||||
// console.log(`msgClone ${msg.uid}: ${msg.wen}`);
|
||||
// }
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
storeInboxMessages(store) {
|
||||
let messages = store.messages.inbox.src.reduce((msgs, src) => {
|
||||
let msgGroup = store.messages.stations[src];
|
||||
if (!msgGroup) return msgs;
|
||||
return msgs.concat(msgGroup.filter(this.filterInboxMessages)); // filter out app & accepted invite msgs
|
||||
}, []);
|
||||
|
||||
let ret = _(messages)
|
||||
.sort((a, b) => b.wen - a.wen) // sort by date
|
||||
// sort must come before uniqBy! if uniqBy detects a dupe, it takes
|
||||
// earlier element in the array. since we want later timestamps to
|
||||
// override, sort first
|
||||
.uniqBy('uid') // dedupe
|
||||
.slice(0, INBOX_MESSAGE_COUNT) // grab the first 30 or so
|
||||
.value(); // unwrap lodash chain
|
||||
// for (let msg of ret) {
|
||||
// console.log(`msg ${msg.uid}: ${msg.wen}`);
|
||||
// }
|
||||
|
||||
// store.messages.inbox.messages = [
|
||||
// {
|
||||
// aud: ["~zod/marzod.zod"],
|
||||
// aut: "zod",
|
||||
// sep: { lin: {
|
||||
// msg: "Hey marzod!"
|
||||
// }},
|
||||
// uid: "0v4.85q7h.25nnt.5mhop.92c1u.3rhsa",
|
||||
// wen: 1538084786999,
|
||||
// }, {
|
||||
// aud: ["~zod/marzod.zod"],
|
||||
// aut: "marzod",
|
||||
// sep: { lin: {
|
||||
// msg: "oh hey zod"
|
||||
// }},
|
||||
// uid: "0v4.85q7h.25nnt.5mhop.92c1u.3rhfa",
|
||||
// wen: 1538084787000,
|
||||
// },
|
||||
// ...ret
|
||||
// ];
|
||||
store.messages.inbox.messages = ret;
|
||||
}
|
||||
|
||||
// Filter out of inbox:
|
||||
// - app messages
|
||||
// - accepted invites
|
||||
// - all DM invites (should automatically accept)
|
||||
filterInboxMessages(msg) {
|
||||
let msgDetails = getMessageContent(msg);
|
||||
let typeApp = msgDetails.type === "app";
|
||||
let typeInv = msgDetails.type === "inv";
|
||||
// let isDmInvite = typeInv && isDMStation(msgDetails.content);
|
||||
let isInboxMsg = msg.aud[0].split("/")[1] === "inbox";
|
||||
let isEditUpdate = msgDetails.type === "edited item";
|
||||
// let hasResponded = typeInv && msgDetails.content === "~zod/null";
|
||||
|
||||
if (typeApp) return false;
|
||||
if (typeInv) return false;
|
||||
// if (hasResponded) return false;
|
||||
if (isEditUpdate) return false;
|
||||
if (isInboxMsg) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
92
apps/publish/src/js/reducers/names.js
Normal file
92
apps/publish/src/js/reducers/names.js
Normal file
@ -0,0 +1,92 @@
|
||||
import { getStationDetails } from '/services';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class NamesReducer {
|
||||
reduce(reports, store) {
|
||||
reports.forEach((rep) => {
|
||||
let ships = {};
|
||||
let details;
|
||||
|
||||
switch (rep.type) {
|
||||
case "circle.cos.loc":
|
||||
ships[rep.from.ship] = [rep.from.path.split("/")[2]];
|
||||
rep.data.con.sis.forEach((mem) => ships[mem] = []);
|
||||
|
||||
this.storeNames(ships, store.names);
|
||||
break;
|
||||
|
||||
case "circle.cos.rem":
|
||||
Object.arrayify(rep.data).forEach(({key: station, value: config}) => {
|
||||
let details = getStationDetails(station);
|
||||
|
||||
if (ships[details.host]) {
|
||||
ships[details.host] = _.uniq(ships[details.host].concat(details.cir));
|
||||
} else {
|
||||
ships[details.host] = [details.cir];
|
||||
}
|
||||
|
||||
config.con.sis.forEach((mem) => {
|
||||
if (!ships[mem]) ships[mem] = [];
|
||||
});
|
||||
});
|
||||
this.storeNames(ships, store.names);
|
||||
break;
|
||||
case "circle.nes":
|
||||
this.storeMessagesNames(rep.data, store.names);
|
||||
break;
|
||||
case "circle.gram":
|
||||
this.storeMessagesNames([rep.data], store.names);
|
||||
break;
|
||||
// case "circle.pes.loc":
|
||||
// stationName = `~${rep.from.ship}/${rep.from.path.split("/")[2]}`;
|
||||
// this.updateConfig({pes: rep.data}, store.configs[stationName]);
|
||||
// break;
|
||||
case "circle.config.dif.source":
|
||||
details = getStationDetails(rep.data.src);
|
||||
ships[details.host] = [details.cir];
|
||||
this.storeNames(ships, store.names);
|
||||
break;
|
||||
case "circle.config.dif.full":
|
||||
details = getStationDetails(rep.data.src[0]); // TODO: API weirdness; we have to get name of new station from new station config's src property. Should maybe return a dict.
|
||||
ships[details.host] = [details.cir];
|
||||
this.storeNames(ships, store.names);
|
||||
break;
|
||||
case "circle.config.dif.permit": // TODO: This is very wonky, should be fixed with API discussion
|
||||
details = getStationDetails(rep.data.cir); // TODO: API weirdness; we have to get name of new station from new station config's src property. Should maybe return a dict.
|
||||
ships[details.host] = [details.cir];
|
||||
this.storeNames(ships, store.names);
|
||||
// case "circle.config.dif.remove":
|
||||
// delete store.names[rep.data.cir];
|
||||
// break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
storeMessagesNames(messages, storeNames) {
|
||||
let ships = {};
|
||||
|
||||
messages.forEach((message) => {
|
||||
let msg = message.gam;
|
||||
msg.aud.forEach((aud) => {
|
||||
let details = getStationDetails(aud); // TODO: API weirdness; we have to get name of new station from new station config's src property. Should maybe return a dict.
|
||||
ships[details.host] = [details.cir];
|
||||
});
|
||||
|
||||
ships[msg.aut] = ships[msg.aut] || [];
|
||||
});
|
||||
|
||||
this.storeNames(ships, storeNames);
|
||||
}
|
||||
|
||||
storeNames(ships, storeNames) {
|
||||
Object.arrayify(ships).forEach(({key: ship, value: stations}) => {
|
||||
let sttns = stations.filter(s => s !== "c");
|
||||
|
||||
if (!storeNames[ship]) {
|
||||
storeNames[ship] = sttns;
|
||||
} else {
|
||||
storeNames[ship] = _.uniq(sttns.concat(storeNames[ship]))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
33
apps/publish/src/js/reducers/public.js
Normal file
33
apps/publish/src/js/reducers/public.js
Normal file
@ -0,0 +1,33 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class PublicReducer {
|
||||
reduce(reports, store) {
|
||||
reports.forEach((rep) => {
|
||||
if (rep.type == "public") {
|
||||
if (_.isArray(rep.data)) {
|
||||
rep.data.forEach((c) => {
|
||||
this.storeCircle(`~${rep.from.ship}`, c, store.public);
|
||||
})
|
||||
} else {
|
||||
if (rep.data.add) {
|
||||
this.storeCircle(`~${rep.from.ship}`, rep.data.cir, store.public);
|
||||
} else {
|
||||
this.removeCircle(`~${rep.from.ship}`, rep.data.cir, store.public);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
storeCircle(ship, circle, storePublic) {
|
||||
if (!storePublic[ship]) {
|
||||
storePublic[ship] = [circle];
|
||||
} else {
|
||||
if (storePublic[ship].indexOf(circle) === -1) {
|
||||
storePublic[ship] = [...storePublic[ship], circle];
|
||||
}
|
||||
}
|
||||
}
|
||||
removeCircle(ship, circle, storePublic) {
|
||||
storePublic[ship] = storePublic[ship].filter((e) => e !== circle);
|
||||
}
|
||||
}
|
43
apps/publish/src/js/services.js
Normal file
43
apps/publish/src/js/services.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { isDMStation } from '/lib/util';
|
||||
import { warehouse } from '/warehouse';
|
||||
import { api } from '/api';
|
||||
|
||||
export function getStationDetails(station) {
|
||||
let host = station.split("/")[0].substr(1);
|
||||
let config = warehouse.store.configs[station];
|
||||
|
||||
let ret = {
|
||||
type: "none",
|
||||
station: station,
|
||||
host: host,
|
||||
cir: station.split("/")[1],
|
||||
};
|
||||
|
||||
let circleParts = ret.cir.split("-");
|
||||
|
||||
if (ret.cir === "c") {
|
||||
ret.type = "aggregator";
|
||||
} else if (isDMStation(station)) {
|
||||
ret.type = "stream-dm";
|
||||
} else {
|
||||
ret.type = "stream-chat";
|
||||
}
|
||||
|
||||
switch (ret.type) {
|
||||
case "stream-chat":
|
||||
ret.stationUrl = `/~chat/${station}`;
|
||||
ret.stationTitle = ret.cir;
|
||||
break;
|
||||
case "stream-dm":
|
||||
ret.stationTitle = ret.cir
|
||||
.split(".")
|
||||
.filter((mem) => mem !== api.authTokens.ship)
|
||||
.map((mem) => `~${mem}`)
|
||||
.join(", ");;
|
||||
ret.stationUrl = `/~landscape/stream?station=${station}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
18
apps/publish/src/js/store.js
Normal file
18
apps/publish/src/js/store.js
Normal file
@ -0,0 +1,18 @@
|
||||
class Store {
|
||||
constructor() {
|
||||
this.collections = window.injectedState;
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(data) {
|
||||
console.log("store.handleEvent", data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
window.store = store;
|
56
apps/publish/src/js/subscription.js
Normal file
56
apps/publish/src/js/subscription.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { api } from '/api';
|
||||
import _ from 'lodash';
|
||||
import { store } from '/store';
|
||||
|
||||
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
console.log("subscription.start", window.injectedState);
|
||||
this.initializeLandscape();
|
||||
this.setCleanupTasks();
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
setCleanupTasks() {
|
||||
window.addEventListener("beforeunload", e => {
|
||||
api.bindPaths.forEach(p => {
|
||||
this.wipeSubscription(p);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
wipeSubscription(path) {
|
||||
api.hall({
|
||||
wipe: {
|
||||
sub: [{
|
||||
hos: api.authTokens.ship,
|
||||
pax: path
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
initializeLandscape() {
|
||||
api.bind(`/primary`, "PUT", api.authTokens.ship, 'write',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
console.log("subscription.handleEvent", diff);
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
api.bind(`/primary`, "PUT", api.authTokens.ship, 'write',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
export let subscription = new Subscription();
|
1
apps/publish/src/js/vendor/sigils-1.2.5.js
vendored
Normal file
1
apps/publish/src/js/vendor/sigils-1.2.5.js
vendored
Normal file
File diff suppressed because one or more lines are too long
895
apps/publish/urbit/app/write.hoon
Normal file
895
apps/publish/urbit/app/write.hoon
Normal file
@ -0,0 +1,895 @@
|
||||
::
|
||||
:: /app/write.hoon
|
||||
::
|
||||
/- hall, *write
|
||||
/+ *server, *write
|
||||
::
|
||||
/= index
|
||||
/^ $-(json manx)
|
||||
/: /===/app/write/index /!noun/
|
||||
::
|
||||
/= js
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/write/js/index /js/
|
||||
::
|
||||
/= css
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/write/css/index /css/
|
||||
::
|
||||
|%
|
||||
::
|
||||
+$ move [bone card]
|
||||
::
|
||||
+$ card
|
||||
$% [%info wire toro:clay]
|
||||
[%poke wire dock poke]
|
||||
[%perm wire desk path rite:clay]
|
||||
[%peer wire dock path]
|
||||
[%pull wire dock ~]
|
||||
[%quit ~]
|
||||
[%diff diff]
|
||||
[%build wire ? schematic:ford]
|
||||
[%kill wire ~]
|
||||
[%connect wire binding:http-server term]
|
||||
[%http-response http-event:http]
|
||||
[%disconnect binding:http-server]
|
||||
==
|
||||
::
|
||||
+$ poke
|
||||
$% [%hall-action action:hall]
|
||||
[%write-action action]
|
||||
==
|
||||
::
|
||||
+$ diff
|
||||
$% [%hall-rumor rumor:hall]
|
||||
[%json json]
|
||||
[%write-collection collection]
|
||||
[%write-rumor rumor]
|
||||
==
|
||||
::
|
||||
--
|
||||
::
|
||||
|_ [bol=bowl:gall sat=state]
|
||||
::
|
||||
++ this .
|
||||
:: +our-beak: beak for this app, with case set to current invocation date
|
||||
::
|
||||
++ our-beak /(scot %p our.bol)/[q.byk.bol]/(scot %da now.bol)
|
||||
:: +allowed-by: checks if ship :who is allowed by the permission rules in :dic
|
||||
::
|
||||
++ allowed-by
|
||||
|= [who=@p dic=dict:clay]
|
||||
^- ?
|
||||
?: =(who our.bol) &
|
||||
=/ in-list=?
|
||||
?| (~(has in p.who.rul.dic) who)
|
||||
::
|
||||
%- ~(rep by q.who.rul.dic)
|
||||
|= [[@ta cru=crew:clay] out=_|]
|
||||
?: out &
|
||||
(~(has in cru) who)
|
||||
==
|
||||
?: =(%black mod.rul.dic)
|
||||
!in-list
|
||||
in-list
|
||||
:: +write-file: write file at path
|
||||
::
|
||||
++ write-file
|
||||
=, space:userlib
|
||||
|= [pax=path cay=cage]
|
||||
^- move
|
||||
=. pax (weld our-beak pax)
|
||||
[ost.bol %info (weld /write-file pax) (foal pax cay)]
|
||||
::
|
||||
++ update-udon-front
|
||||
|= [fro=(map knot cord) udon=@t]
|
||||
^- @t
|
||||
%- of-wain:format
|
||||
=/ tum (trip udon)
|
||||
=/ id (find ";>" tum)
|
||||
?~ id
|
||||
%+ weld (front-to-wain fro)
|
||||
(to-wain:format (crip (weld ";>\0a" tum)))
|
||||
%+ weld (front-to-wain fro)
|
||||
(to-wain:format (crip (slag u.id tum)))
|
||||
::
|
||||
++ front-to-wain
|
||||
|= a=(map knot cord)
|
||||
^- wain
|
||||
=/ entries=wain
|
||||
%+ turn ~(tap by a)
|
||||
|= b=[knot cord]
|
||||
=/ c=[term cord] (,[term cord] b)
|
||||
(crip " [{<-.c>} {<+.c>}]")
|
||||
::
|
||||
?~ entries ~
|
||||
;: weld
|
||||
[':- :~' ~]
|
||||
entries
|
||||
[' ==' ~]
|
||||
==
|
||||
::
|
||||
++ prep
|
||||
|= old=(unit *)
|
||||
^- (quip move _this)
|
||||
~& write-prep+act.bol
|
||||
?~ old
|
||||
:_ this
|
||||
[ost.bol %connect / [~ /'~publish'] %write]~
|
||||
[~ this(sat (state u.old))]
|
||||
::
|
||||
++ poke-noun
|
||||
|= a=*
|
||||
^- (quip move _this)
|
||||
?. =(src.bol our.bol)
|
||||
[~ this]
|
||||
?+ a
|
||||
[~ this]
|
||||
::
|
||||
%test-build
|
||||
=/ schema=schematic:ford
|
||||
:-
|
||||
:*
|
||||
%bake
|
||||
%write-info
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /fora/write/web]
|
||||
==
|
||||
:*
|
||||
%bake
|
||||
%write-post
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /post-1/fora/write/web]
|
||||
==
|
||||
:_ this
|
||||
[ost.bol %build /test/build %.n schema]~
|
||||
::
|
||||
%print-subs
|
||||
~& sup.bol
|
||||
[~ this]
|
||||
::
|
||||
%kill-all-builds
|
||||
:_ this
|
||||
:~ [ost.bol %kill /collection/fora ~]
|
||||
[ost.bol %kill /post/fora/post-1 ~]
|
||||
[ost.bol %kill /comments/fora/post-1 ~]
|
||||
[ost.bol %kill /post/fora/post-2 ~]
|
||||
[ost.bol %kill /comments/fora/post-2 ~]
|
||||
[ost.bol %kill /post/fora/post-3 ~]
|
||||
[ost.bol %kill /comments/fora/post-3 ~]
|
||||
==
|
||||
::
|
||||
::
|
||||
%send-diff
|
||||
=/ rum=json (frond:enjs:format %poke-noun ~)
|
||||
=/ mov=(list move)
|
||||
%+ turn (prey:pubsub:userlib /primary bol)
|
||||
|= [=bone *]
|
||||
[bone %diff %json rum]
|
||||
~& mov+mov
|
||||
[mov this]
|
||||
::
|
||||
%peer
|
||||
~& %peer
|
||||
:_ this
|
||||
[ost.bol %peer /collection/fora [~zod %write] /collection/fora]~
|
||||
::
|
||||
%pull
|
||||
~& %pull
|
||||
=/ wir=wire /collection/fora
|
||||
:_ this
|
||||
[ost.bol %pull wir [~zod %write] ~]~
|
||||
::
|
||||
%flush-state
|
||||
[~ this(sat *state)]
|
||||
::
|
||||
%print-state
|
||||
~& sat
|
||||
[~ this]
|
||||
::
|
||||
==
|
||||
::
|
||||
++ da
|
||||
|_ moves=(list move)
|
||||
::
|
||||
++ da-this .
|
||||
::
|
||||
++ da-done
|
||||
^- (quip move _this)
|
||||
[(flop moves) this]
|
||||
::
|
||||
++ da-emit
|
||||
|= mov=move
|
||||
%_ da-this
|
||||
moves [mov moves]
|
||||
==
|
||||
::
|
||||
++ da-emil
|
||||
|= mov=(list move)
|
||||
%_ da-this
|
||||
moves (welp (flop mov) moves)
|
||||
==
|
||||
::
|
||||
++ da-change
|
||||
|= del=delta
|
||||
^+ da-this
|
||||
?- -.del
|
||||
::
|
||||
%collection
|
||||
=/ old=(unit collection)
|
||||
?: =(our.bol who.del)
|
||||
(~(get by pubs.sat) col.del)
|
||||
(~(get by subs.sat) who.del col.del)
|
||||
=/ new=collection
|
||||
?~ old
|
||||
[dat.del ~ ~]
|
||||
[dat.del pos.u.old com.u.old]
|
||||
=? pubs.sat =(our.bol who.del)
|
||||
(~(put by pubs.sat) col.del new)
|
||||
=? subs.sat !=(our.bol who.del)
|
||||
(~(put by subs.sat) [who.del col.del] new)
|
||||
(da-emil (affection del))
|
||||
::
|
||||
%post
|
||||
=/ old=(unit collection)
|
||||
?: =(our.bol who.del)
|
||||
(~(get by pubs.sat) col.del)
|
||||
(~(get by subs.sat) who.del col.del)
|
||||
=/ new=collection
|
||||
?~ old
|
||||
[[%.n ~] (my [pos.del dat.del] ~) ~]
|
||||
[col.u.old (~(put by pos.u.old) pos.del dat.del) com.u.old]
|
||||
=? pubs.sat =(our.bol who.del)
|
||||
(~(put by pubs.sat) col.del new)
|
||||
=? subs.sat !=(our.bol who.del)
|
||||
(~(put by subs.sat) [who.del col.del] new)
|
||||
=. da-this (da-insert who.del col.del pos.del)
|
||||
(da-emil (affection del))
|
||||
::
|
||||
%comments
|
||||
=/ old=(unit collection)
|
||||
?: =(our.bol who.del)
|
||||
(~(get by pubs.sat) col.del)
|
||||
(~(get by subs.sat) who.del col.del)
|
||||
=/ new=collection
|
||||
?~ old
|
||||
[[%.n ~] ~ (my [pos.del dat.del] ~)]
|
||||
[col.u.old pos.u.old (~(put by com.u.old) pos.del dat.del)]
|
||||
=? pubs.sat =(our.bol who.del)
|
||||
(~(put by pubs.sat) col.del new)
|
||||
=? subs.sat !=(our.bol who.del)
|
||||
(~(put by subs.sat) [who.del col.del] new)
|
||||
(da-emil (affection del))
|
||||
::
|
||||
%total
|
||||
=? pubs.sat =(our.bol who.del)
|
||||
(~(put by pubs.sat) col.del dat.del)
|
||||
=? subs.sat !=(our.bol who.del)
|
||||
(~(put by subs.sat) [who.del col.del] dat.del)
|
||||
::
|
||||
=/ posts=(list @tas) ~(tap in ~(key by pos.dat.del))
|
||||
=. da-this
|
||||
|-
|
||||
?~ posts
|
||||
da-this
|
||||
%= $
|
||||
da-this (da-insert who.del col.del i.posts)
|
||||
posts t.posts
|
||||
==
|
||||
(da-emil (affection del))
|
||||
::
|
||||
==
|
||||
::
|
||||
++ da-insert
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
^+ da-this
|
||||
:: assume we've read our own posts
|
||||
::
|
||||
=? unread.sat !=(who our.bol)
|
||||
(~(put in unread.sat) who coll post)
|
||||
:: insertion sort into latest
|
||||
::
|
||||
=/ new-date=@da (need (get-date-for-index who coll post))
|
||||
=/ pre=(list [@p @tas @tas]) ~
|
||||
=/ suf=(list [@p @tas @tas]) latest.sat
|
||||
|
||||
=. latest.sat
|
||||
|-
|
||||
?~ suf
|
||||
(weld pre [who coll post]~)
|
||||
=/ i-date=@da (need (get-date-for-index i.suf))
|
||||
?: (gte new-date i-date)
|
||||
(weld pre [[who coll post] suf])
|
||||
%= $
|
||||
suf t.suf
|
||||
pre (snoc pre i.suf)
|
||||
==
|
||||
da-this
|
||||
--
|
||||
:: +bake: apply delta
|
||||
::
|
||||
++ bake
|
||||
|= del=delta
|
||||
^- (quip move _this)
|
||||
da-done:(da-change:da del)
|
||||
:: +affection: rumors to interested
|
||||
::
|
||||
++ affection
|
||||
|= del=delta
|
||||
^- (list move)
|
||||
%- zing
|
||||
%+ turn ~(tap by sup.bol)
|
||||
|= [b=bone s=ship p=path]
|
||||
^- (list move)
|
||||
=/ rum=(unit rumor) (feel p del)
|
||||
?~ rum
|
||||
~
|
||||
[b %diff %write-rumor u.rum]~
|
||||
:: +feel: delta to rumor
|
||||
::
|
||||
++ feel
|
||||
|= [query=wire del=delta]
|
||||
^- (unit rumor)
|
||||
?+ query
|
||||
~
|
||||
[%primary ~]
|
||||
[~ del]
|
||||
::
|
||||
[%collection @t ~]
|
||||
=/ coll=@tas i.t.query
|
||||
?: =(coll col.del)
|
||||
[~ del]
|
||||
~
|
||||
::
|
||||
==
|
||||
::
|
||||
++ get-date-for-index
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
^- (unit @da)
|
||||
=/ col=(unit collection)
|
||||
?: =(our.bol who)
|
||||
(~(get by pubs.sat) coll)
|
||||
(~(get by subs.sat) who coll)
|
||||
?~ col ~
|
||||
=/ pos=(unit (each [post-info manx] tang))
|
||||
(~(get by pos.u.col) post)
|
||||
?~ pos ~
|
||||
?: ?=(%.n -.u.pos) ~
|
||||
[~ date-created.-.p.u.pos]
|
||||
::
|
||||
++ made
|
||||
|= [wir=wire wen=@da mad=made-result:ford]
|
||||
^- (quip move _this)
|
||||
?+ wir
|
||||
[~ this]
|
||||
::
|
||||
[%collection @t ~]
|
||||
=/ col=@tas i.t.wir
|
||||
=/ awa (~(get by awaiting.sat) col)
|
||||
::
|
||||
=/ dat=(each collection-info tang)
|
||||
?: ?=([%incomplete *] mad)
|
||||
[%.n tang.mad]
|
||||
?: ?=([%error *] build-result.mad)
|
||||
[%.n message.build-result.mad]
|
||||
?> ?=(%bake +<.build-result.mad)
|
||||
?> ?=(%write-info p.cage.build-result.mad)
|
||||
[%.y (collection-info q.q.cage.build-result.mad)]
|
||||
::
|
||||
?~ awa
|
||||
(bake [%collection our.bol col dat])
|
||||
=. builds.u.awa (~(del in builds.u.awa) wir)
|
||||
?~ partial.u.awa
|
||||
?~ builds.u.awa
|
||||
:: one-off build, make delta and process it
|
||||
::
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake [%collection our.bol col dat])
|
||||
:: 1st part of multi-part, store partial delta and don't process it
|
||||
::
|
||||
=/ del=delta [%total our.bol col dat ~ ~]
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
::
|
||||
?~ builds.u.awa
|
||||
:: last part of multipart, update partial delta and process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
dat
|
||||
pos.dat.u.partial.u.awa
|
||||
com.dat.u.partial.u.awa
|
||||
==
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake del)
|
||||
:: nth part of multi-part, update partial delta and don't process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
dat
|
||||
pos.dat.u.partial.u.awa
|
||||
com.dat.u.partial.u.awa
|
||||
==
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
::
|
||||
[%post @t @t ~]
|
||||
=/ col=@tas i.t.wir
|
||||
=/ pos=@tas i.t.t.wir
|
||||
=/ awa (~(get by awaiting.sat) col)
|
||||
::
|
||||
=/ dat=(each [post-info manx] tang)
|
||||
?: ?=([%incomplete *] mad)
|
||||
[%.n tang.mad]
|
||||
?: ?=([%error *] build-result.mad)
|
||||
[%.n message.build-result.mad]
|
||||
?> ?=(%bake +<.build-result.mad)
|
||||
?> ?=(%write-post p.cage.build-result.mad)
|
||||
[%.y (,[post-info manx] q.q.cage.build-result.mad)]
|
||||
::
|
||||
?~ awa
|
||||
(bake [%post our.bol col pos dat])
|
||||
=. builds.u.awa (~(del in builds.u.awa) wir)
|
||||
?~ partial.u.awa
|
||||
?~ builds.u.awa
|
||||
:: one-off build, make delta and process it
|
||||
::
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake [%post our.bol col pos dat])
|
||||
:: 1st part of multi-part, store partial delta and don't process it
|
||||
::
|
||||
=/ del=delta [%total our.bol col [%.n ~] (my [pos dat] ~) ~]
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
::
|
||||
?~ builds.u.awa
|
||||
:: last part of multipart, update partial delta and process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
col.dat.u.partial.u.awa
|
||||
(~(put by pos.dat.u.partial.u.awa) pos dat)
|
||||
com.dat.u.partial.u.awa
|
||||
==
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake del)
|
||||
:: nth part of multi-part, update partial delta and don't process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
col.dat.u.partial.u.awa
|
||||
(~(put by pos.dat.u.partial.u.awa) pos dat)
|
||||
com.dat.u.partial.u.awa
|
||||
==
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
::
|
||||
[%comments @t @t ~]
|
||||
=/ col=@tas i.t.wir
|
||||
=/ pos=@tas i.t.t.wir
|
||||
=/ awa (~(get by awaiting.sat) col)
|
||||
::
|
||||
=/ dat=(each (list [comment-info manx]) tang)
|
||||
?: ?=([%incomplete *] mad)
|
||||
[%.n tang.mad]
|
||||
?: ?=([%error *] build-result.mad)
|
||||
[%.n message.build-result.mad]
|
||||
?> ?=(%bake +<.build-result.mad)
|
||||
?> ?=(%write-comments p.cage.build-result.mad)
|
||||
[%.y (,(list [comment-info manx]) q.q.cage.build-result.mad)]
|
||||
::
|
||||
?~ awa
|
||||
(bake [%comments our.bol col pos dat])
|
||||
=. builds.u.awa (~(del in builds.u.awa) wir)
|
||||
?~ partial.u.awa
|
||||
?~ builds.u.awa
|
||||
:: one-off build, make delta and process it
|
||||
::
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake [%comments our.bol col pos dat])
|
||||
:: 1st part of multi-part, store partial delta and don't process it
|
||||
::
|
||||
=/ del=delta [%total our.bol col [%.n ~] ~ (my [pos dat] ~)]
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
::
|
||||
?~ builds.u.awa
|
||||
:: last part of multipart, update partial delta and process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
col.dat.u.partial.u.awa
|
||||
pos.dat.u.partial.u.awa
|
||||
(~(put by com.dat.u.partial.u.awa) pos dat)
|
||||
==
|
||||
=. awaiting.sat (~(del by awaiting.sat) col)
|
||||
(bake del)
|
||||
:: nth part of multi-part, update partial delta and don't process it
|
||||
::
|
||||
?> ?=(%total -.u.partial.u.awa)
|
||||
=/ del=delta
|
||||
:* %total
|
||||
our.bol
|
||||
col
|
||||
col.dat.u.partial.u.awa
|
||||
pos.dat.u.partial.u.awa
|
||||
(~(put by com.dat.u.partial.u.awa) pos dat)
|
||||
==
|
||||
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
|
||||
[~ this]
|
||||
==
|
||||
::
|
||||
++ poke-write-action
|
||||
|= act=action
|
||||
^- (quip move _this)
|
||||
?- -.act
|
||||
::
|
||||
%new-collection
|
||||
:: XX check permissions of src.bol
|
||||
:: XX check if file already exists
|
||||
=/ conf=collection-info
|
||||
:* our.bol
|
||||
title.act
|
||||
name.act
|
||||
com.act
|
||||
edit.act
|
||||
now.bol
|
||||
now.bol
|
||||
==
|
||||
:: XX set permissions
|
||||
:: XX automatically serve collection
|
||||
:: (add to set of builds)
|
||||
=/ pax=path /web/write/[name.act]/write-info
|
||||
::
|
||||
=/ wir=wire /collection/[name.act]
|
||||
=/ schema=schematic:ford
|
||||
:* %bake
|
||||
%write-info
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[name.act]/write/web]
|
||||
==
|
||||
:_ this
|
||||
:~ (write-file pax %write-info !>(conf))
|
||||
[ost.bol %build wir %.y schema]
|
||||
==
|
||||
::
|
||||
%new-post
|
||||
:: XX check permissions of src.bol
|
||||
:: XX check if file already exists
|
||||
:: XX check if coll doesn't exist
|
||||
=. content.act (cat 3 content.act '\0a') :: XX fix udon parser
|
||||
=/ front=(map knot cord)
|
||||
%- my
|
||||
:~ [%creator (scot %p src.bol)]
|
||||
[%title title.act]
|
||||
[%collection coll.act]
|
||||
[%filename name.act]
|
||||
[%comments com.act]
|
||||
[%date-created (scot %da now.bol)]
|
||||
[%last-modified (scot %da now.bol)]
|
||||
[%pinned %false]
|
||||
==
|
||||
:: XX set permissions
|
||||
:: XX add to set of builds
|
||||
=/ pax=path /web/write/[coll.act]/[name.act]/udon
|
||||
=/ out=@t (update-udon-front front content.act)
|
||||
::
|
||||
=/ post-wir=wire /post/[coll.act]/[name.act]
|
||||
=/ post-schema=schematic:ford
|
||||
:* %bake
|
||||
%write-post
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[name.act]/[coll.act]/write/web]
|
||||
==
|
||||
::
|
||||
=/ comments-wir=wire /comments/[coll.act]/[name.act]
|
||||
=/ comments-schema=schematic:ford
|
||||
:* %bake
|
||||
%write-comments
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[name.act]/[coll.act]/write/web]
|
||||
==
|
||||
:_ this
|
||||
:~ (write-file pax %udon !>(out))
|
||||
[ost.bol %build comments-wir %.y comments-schema]
|
||||
[ost.bol %build post-wir %.y post-schema]
|
||||
==
|
||||
::
|
||||
%new-comment
|
||||
:: XX check permissions of src.bol
|
||||
:: XX check if file already exists
|
||||
=. content.act (cat 3 content.act '\0a') :: XX fix udon parser
|
||||
=/ front=(map knot cord)
|
||||
%- my
|
||||
:~ [%creator (scot %p src.bol)]
|
||||
[%collection coll.act]
|
||||
[%post post.act]
|
||||
[%date-created (scot %da now.bol)]
|
||||
[%last-modified (scot %da now.bol)]
|
||||
==
|
||||
:: XX set permissions
|
||||
:: XX add to set of builds
|
||||
=/ pax=path /web/write/[coll.act]/[post.act]/(scot %da now.bol)/udon
|
||||
=/ out=@t (update-udon-front front content.act)
|
||||
:_ this
|
||||
[(write-file pax %udon !>(out))]~
|
||||
::
|
||||
%delete
|
||||
[~ this]
|
||||
::
|
||||
%edit-collection
|
||||
[~ this]
|
||||
::
|
||||
%edit-post
|
||||
[~ this]
|
||||
::
|
||||
%edit-comment
|
||||
[~ this]
|
||||
::
|
||||
%invite
|
||||
[~ this]
|
||||
::
|
||||
:: %serve:
|
||||
::
|
||||
%serve
|
||||
:: XX specialize this check for subfiles
|
||||
?: (~(has by pubs.sat) coll.act)
|
||||
[~ this]
|
||||
=/ files=(list path)
|
||||
.^((list path) %ct (weld our-beak /web/write/[coll.act]))
|
||||
=/ all=[moves=(list move) builds=(set wire)]
|
||||
%+ roll files
|
||||
|= [pax=path out=[moves=(list move) builds=(set wire)]]
|
||||
?+ pax
|
||||
out
|
||||
::
|
||||
[%web %write @tas %write-info ~]
|
||||
?> =(coll.act i.t.t.pax)
|
||||
=/ wir=wire /collection/[coll.act]
|
||||
=/ schema=schematic:ford
|
||||
:* %bake
|
||||
%write-info
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[coll.act]/write/web]
|
||||
==
|
||||
%= out
|
||||
moves [[ost.bol %build wir %.y schema] moves.out]
|
||||
builds (~(put in builds.out) wir)
|
||||
==
|
||||
::
|
||||
[%web %write @tas @tas %udon ~]
|
||||
?> =(coll.act i.t.t.pax)
|
||||
=/ post i.t.t.t.pax
|
||||
=/ post-wir=wire /post/[coll.act]/[post]
|
||||
=/ post-schema=schematic:ford
|
||||
:* %bake
|
||||
%write-post
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[post]/[coll.act]/write/web]
|
||||
==
|
||||
::
|
||||
=/ comments-wir=wire /comments/[coll.act]/[post]
|
||||
=/ comments-schema=schematic:ford
|
||||
:* %bake
|
||||
%write-comments
|
||||
*coin
|
||||
[[our.bol q.byk.bol] /[post]/[coll.act]/write/web]
|
||||
==
|
||||
%= out
|
||||
moves
|
||||
:* [ost.bol %build post-wir %.y post-schema]
|
||||
[ost.bol %build comments-wir %.y comments-schema]
|
||||
moves.out
|
||||
==
|
||||
::
|
||||
builds
|
||||
(~(uni in builds.out) (sy post-wir comments-wir ~))
|
||||
==
|
||||
::
|
||||
==
|
||||
:- moves.all
|
||||
%= this
|
||||
awaiting.sat (~(put by awaiting.sat) coll.act builds.all ~)
|
||||
==
|
||||
::
|
||||
:: %unserve:
|
||||
::
|
||||
%unserve
|
||||
:: XX pull subscriptions for unserved collections
|
||||
::
|
||||
=/ col=(unit collection) (~(get by pubs.sat) coll.act)
|
||||
?~ col
|
||||
~| [%non-existent-collection coll.act] !!
|
||||
=/ kills=(list move)
|
||||
%+ roll ~(tap by pos.u.col)
|
||||
|= [[post=@tas *] out=(list move)]
|
||||
:* [ost.bol %kill /post/[coll.act]/[post] ~]
|
||||
[ost.bol %kill /comments/[coll.act]/[post] ~]
|
||||
out
|
||||
==
|
||||
::
|
||||
=/ new-latest=(list [@p @tas @tas])
|
||||
%+ skip latest.sat
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
?& =(who our.bol)
|
||||
=(coll coll.act)
|
||||
==
|
||||
::
|
||||
=/ new-unread=(set [@p @tas @tas])
|
||||
%- sy
|
||||
%+ skip ~(tap in unread.sat)
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
?& =(who our.bol)
|
||||
=(coll coll.act)
|
||||
==
|
||||
::
|
||||
:- [[ost.bol %kill /collection/[coll.act] ~] kills]
|
||||
%= this
|
||||
pubs.sat (~(del by pubs.sat) coll.act)
|
||||
awaiting.sat (~(del by awaiting.sat) coll.act)
|
||||
latest.sat new-latest
|
||||
unread.sat new-unread
|
||||
==
|
||||
::
|
||||
:: %subscribe:
|
||||
::
|
||||
%subscribe
|
||||
=/ wir=wire /collection/[coll.act]
|
||||
:_ this
|
||||
[ost.bol %peer wir [who.act %write] wir]~
|
||||
::
|
||||
:: %unsubscribe:
|
||||
::
|
||||
%unsubscribe
|
||||
=/ new-latest=(list [@p @tas @tas])
|
||||
%+ skip latest.sat
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
?& =(who our.bol)
|
||||
=(coll coll.act)
|
||||
==
|
||||
::
|
||||
=/ new-unread=(set [@p @tas @tas])
|
||||
%- sy
|
||||
%+ skip ~(tap in unread.sat)
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
?& =(who our.bol)
|
||||
=(coll coll.act)
|
||||
==
|
||||
=/ wir=wire /collection/[coll.act]
|
||||
:- [ost.bol %pull wir [who.act %write] ~]~
|
||||
%= this
|
||||
subs.sat (~(del by subs.sat) who.act coll.act)
|
||||
latest.sat new-latest
|
||||
unread.sat new-unread
|
||||
==
|
||||
::
|
||||
==
|
||||
::
|
||||
++ bound
|
||||
|= [wir=wire success=? binding=binding:http-server]
|
||||
^- (quip move _this)
|
||||
[~ this]
|
||||
::
|
||||
:: +poke-handle-http-request: received on a new connection established
|
||||
::
|
||||
++ poke-handle-http-request
|
||||
%- (require-authorization:app ost.bol move this)
|
||||
|= =inbound-request:http-server
|
||||
^- (quip move _this)
|
||||
::
|
||||
=/ request-line (parse-request-line url.request.inbound-request)
|
||||
?+ request-line
|
||||
:_ this
|
||||
[ost.bol %http-response not-found:app]~
|
||||
:: styling
|
||||
::
|
||||
[[[~ %css] [%'~publish' %index ~]] ~]
|
||||
:_ this
|
||||
[ost.bol %http-response (css-response:app css)]~
|
||||
:: scripting
|
||||
::
|
||||
[[[~ %js] [%'~publish' %index ~]] ~]
|
||||
:_ this
|
||||
[ost.bol %http-response (js-response:app js)]~
|
||||
::
|
||||
::
|
||||
[[~ [%'~publish' ~]] ~]
|
||||
=/ hym=manx (index (state-to-json sat))
|
||||
:_ this
|
||||
[ost.bol %http-response (manx-response:app hym)]~
|
||||
::
|
||||
::
|
||||
[[~ [%'~publish' @t ~]] ~]
|
||||
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
|
||||
?~ who
|
||||
:_ this
|
||||
[ost.bol %http-response not-found:app]~
|
||||
=/ hym=manx
|
||||
;div: {<u.who>} root page
|
||||
:_ this
|
||||
[ost.bol %http-response (manx-response:app hym)]~
|
||||
::
|
||||
:: forum view
|
||||
::
|
||||
[[~ [%'~publish' @t @t ~]] ~]
|
||||
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
|
||||
=/ coll=@tas i.t.t.site.request-line
|
||||
:: ?~ who
|
||||
:_ this
|
||||
[ost.bol %http-response not-found:app]~
|
||||
::
|
||||
:: post view
|
||||
::
|
||||
[[~ [%'~publish' @t @t @t ~]] ~]
|
||||
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
|
||||
=/ coll=@tas i.t.t.site.request-line
|
||||
=/ post=@tas i.t.t.t.site.request-line
|
||||
:: ?~ who
|
||||
:_ this
|
||||
[ost.bol %http-response not-found:app]~
|
||||
:: local request
|
||||
::
|
||||
::
|
||||
==
|
||||
::
|
||||
++ peer-primary
|
||||
|= wir=wire
|
||||
^- (quip move _this)
|
||||
?. =(our.bol src.bol)
|
||||
:: only we are allowed to subscribe on primary
|
||||
::
|
||||
:_ this
|
||||
[ost.bol %quit ~]~
|
||||
[~ this]
|
||||
::
|
||||
++ pull
|
||||
|= wir=wire
|
||||
^- (quip move _this)
|
||||
[~ this]
|
||||
::
|
||||
++ peer-collection
|
||||
|= wir=wire
|
||||
^- (quip move _this)
|
||||
?. ?=([@tas ~] wir)
|
||||
[~ this]
|
||||
:: XX handle permissions for foreign subscriptions
|
||||
::
|
||||
=/ coll=@tas i.wir
|
||||
=/ col=(unit collection) (~(get by pubs.sat) coll)
|
||||
?~ col
|
||||
[~ this]
|
||||
=/ rum=rumor
|
||||
[%total our.bol coll u.col]
|
||||
:_ this
|
||||
[ost.bol %diff %write-rumor rum]~
|
||||
::
|
||||
++ diff-write-rumor
|
||||
|= [wir=wire rum=rumor]
|
||||
^- (quip move _this)
|
||||
(bake rum)
|
||||
::
|
||||
:: +poke-handle-http-cancel: received when a connection was killed
|
||||
::
|
||||
++ poke-handle-http-cancel
|
||||
|= =inbound-request:http-server
|
||||
^- (quip move _this)
|
||||
[~ this]
|
||||
::
|
||||
--
|
2
apps/publish/urbit/app/write/css/index.css
Normal file
2
apps/publish/urbit/app/write/css/index.css
Normal file
File diff suppressed because one or more lines are too long
21
apps/publish/urbit/app/write/index.hoon
Normal file
21
apps/publish/urbit/app/write/index.hoon
Normal file
@ -0,0 +1,21 @@
|
||||
|= inject=json
|
||||
^- manx
|
||||
;html
|
||||
::
|
||||
;head
|
||||
;title: Write
|
||||
;meta(charset "utf-8");
|
||||
;meta
|
||||
=name "viewport"
|
||||
=content "width=device-width, initial-scale=1, shrink-to-fit=no";
|
||||
;link(rel "stylesheet", href "/~publish/index.css");
|
||||
;script@"/~/channel/channel.js";
|
||||
;script@"/session.js";
|
||||
;script: window.injectedState = {(en-json:html inject)}
|
||||
==
|
||||
::
|
||||
;body
|
||||
;div#root;
|
||||
;script@"/~publish/index.js";
|
||||
==
|
||||
==
|
58471
apps/publish/urbit/app/write/js/index.js
Normal file
58471
apps/publish/urbit/app/write/js/index.js
Normal file
File diff suppressed because it is too large
Load Diff
188
apps/publish/urbit/lib/write.hoon
Normal file
188
apps/publish/urbit/lib/write.hoon
Normal file
@ -0,0 +1,188 @@
|
||||
/- *write
|
||||
/+ elem-to-react-json
|
||||
|%
|
||||
::
|
||||
++ front-to-post-info
|
||||
|= fro=(map knot cord)
|
||||
^- post-info
|
||||
=/ got ~(got by fro)
|
||||
~| %invalid-frontmatter
|
||||
:* (slav %p (got %creator))
|
||||
(got %title)
|
||||
(got %collection)
|
||||
(got %filename)
|
||||
(comment-config (got %comments))
|
||||
(slav %da (got %date-created))
|
||||
(slav %da (got %last-modified))
|
||||
(rash (got %pinned) (fuss %true %false))
|
||||
==
|
||||
::
|
||||
++ front-to-comment-info
|
||||
|= fro=(map knot cord)
|
||||
^- comment-info
|
||||
=/ got ~(got by fro)
|
||||
~| %invalid-frontmatter
|
||||
:* (slav %p (got %creator))
|
||||
(got %collection)
|
||||
(got %post)
|
||||
(slav %da (got %date-created))
|
||||
(slav %da (got %last-modified))
|
||||
==
|
||||
::
|
||||
++ collection-info-to-json
|
||||
|= con=collection-info
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ :- %owner [%s (scot %p owner.con)]
|
||||
:- %title [%s title.con]
|
||||
:- %comments [%s comments.con]
|
||||
:- %allow-edit [%s allow-edit.con]
|
||||
:- %date-created (time:enjs:format date-created.con)
|
||||
:- %last-modified (time:enjs:format last-modified.con)
|
||||
:- %filename [%s filename.con]
|
||||
==
|
||||
::
|
||||
++ post-info-to-json
|
||||
|= info=post-info
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ :- %creator [%s (scot %p creator.info)]
|
||||
:- %title [%s title.info]
|
||||
:- %comments [%s comments.info]
|
||||
:- %date-created (time:enjs:format date-created.info)
|
||||
:- %last-modified (time:enjs:format last-modified.info)
|
||||
:- %pinned [%b pinned.info]
|
||||
:- %filename [%s filename.info]
|
||||
:- %collection [%s collection.info]
|
||||
==
|
||||
::
|
||||
++ comment-info-to-json
|
||||
|= info=comment-info
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ :- %creator [%s (scot %p creator.info)]
|
||||
:- %date-created (time:enjs:format date-created.info)
|
||||
:- %last-modified (time:enjs:format last-modified.info)
|
||||
:- %post [%s post.info]
|
||||
:- %collection [%s collection.info]
|
||||
==
|
||||
::
|
||||
++ tang-to-json
|
||||
|= tan=tang
|
||||
%- wall:enjs:format
|
||||
%- zing
|
||||
%+ turn tan
|
||||
|= a=tank
|
||||
(wash [0 80] a)
|
||||
::
|
||||
++ string-to-symbol
|
||||
|= tap=tape
|
||||
^- @tas
|
||||
%- crip
|
||||
%+ turn tap
|
||||
|= a=@
|
||||
?: ?| &((gte a 'a') (lte a 'z'))
|
||||
&((gte a '0') (lte a '9'))
|
||||
==
|
||||
a
|
||||
?: &((gte a 'A') (lte a 'Z'))
|
||||
(add 32 a)
|
||||
'-'
|
||||
::
|
||||
++ collection-build-to-json
|
||||
|= bud=(each collection-info tang)
|
||||
^- json
|
||||
?: ?=(%.y -.bud)
|
||||
(collection-info-to-json +.bud)
|
||||
(tang-to-json +.bud)
|
||||
::
|
||||
++ post-build-to-json
|
||||
|= bud=(each [post-info manx] tang)
|
||||
^- json
|
||||
?: ?=(%.y -.bud)
|
||||
%- pairs:enjs:format
|
||||
:~ info+(post-info-to-json +<.bud)
|
||||
body+(elem-to-react-json +>.bud)
|
||||
==
|
||||
(tang-to-json +.bud)
|
||||
::
|
||||
++ comment-build-to-json
|
||||
|= bud=(each (list [comment-info manx]) tang)
|
||||
^- json
|
||||
?: ?=(%.y -.bud)
|
||||
:- %a
|
||||
%+ turn p.bud
|
||||
|= [com=comment-info man=manx]
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ info+(comment-info-to-json com)
|
||||
body+(elem-to-react-json man)
|
||||
==
|
||||
(tang-to-json +.bud)
|
||||
::
|
||||
++ total-build-to-json
|
||||
|= col=collection
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ info+(collection-build-to-json col.col)
|
||||
:+ %posts
|
||||
%a
|
||||
%+ turn ~(tap in ~(key by pos.col))
|
||||
|= post=@tas
|
||||
^- json
|
||||
=/ post-build (~(got by pos.col) post)
|
||||
=/ comm-build (~(got by com.col) post)
|
||||
%- pairs:enjs:format
|
||||
:~ name+s+post
|
||||
post+(post-build-to-json post-build)
|
||||
comments+(comment-build-to-json comm-build)
|
||||
==
|
||||
==
|
||||
::
|
||||
++ state-to-json
|
||||
|= sat=state
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ :+ %pubs
|
||||
%a
|
||||
%+ turn ~(tap by pubs.sat)
|
||||
|= [nom=@tas col=collection]
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ [%coll s+nom]
|
||||
[%data (total-build-to-json col)]
|
||||
==
|
||||
::
|
||||
:+ %subs
|
||||
%a
|
||||
%+ turn ~(tap by subs.sat)
|
||||
|= [[who=@p nom=@tas] col=collection]
|
||||
^- json
|
||||
%- pairs:enjs:format
|
||||
:~ [%coll s+nom]
|
||||
[%who (ship:enjs:format who)]
|
||||
[%data (total-build-to-json col)]
|
||||
==
|
||||
::
|
||||
:+ %latest
|
||||
%a
|
||||
%+ turn latest.sat
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
%- pairs:enjs:format
|
||||
:~ who+(ship:enjs:format who)
|
||||
coll+s+coll
|
||||
post+s+post
|
||||
==
|
||||
::
|
||||
:+ %unread
|
||||
%a
|
||||
%+ turn ~(tap in unread.sat)
|
||||
|= [who=@p coll=@tas post=@tas]
|
||||
%- pairs:enjs:format
|
||||
:~ who+(ship:enjs:format who)
|
||||
coll+s+coll
|
||||
post+s+post
|
||||
==
|
||||
==
|
||||
::
|
||||
--
|
187
apps/publish/urbit/mar/write/action.hoon
Normal file
187
apps/publish/urbit/mar/write/action.hoon
Normal file
@ -0,0 +1,187 @@
|
||||
::
|
||||
:::: /hoon/action/write/mar
|
||||
::
|
||||
/? 309
|
||||
/- write
|
||||
=, format
|
||||
::
|
||||
|_ act=action:write
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ tank >act<
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun action:write
|
||||
++ json
|
||||
|= jon=^json
|
||||
%- action:write
|
||||
=< (action jon)
|
||||
|%
|
||||
++ action
|
||||
%- of:dejs
|
||||
:~ new-collection+new-collection
|
||||
new-post+new-post
|
||||
new-comment+new-comment
|
||||
::
|
||||
delete+item-id
|
||||
::
|
||||
edit-collection+edit-collection
|
||||
edit-post+edit-post
|
||||
edit-comment+edit-comment
|
||||
::
|
||||
invite+invite
|
||||
::
|
||||
serve+serve
|
||||
unserve+unserve
|
||||
::
|
||||
subscribe+subscribe
|
||||
unsubscribe+unsubscribe
|
||||
::
|
||||
==
|
||||
::
|
||||
++ new-collection
|
||||
%- ot:dejs
|
||||
:~ name+(su:dejs sym)
|
||||
title+so:dejs
|
||||
comments+comment-config
|
||||
allow-edit+edit-config
|
||||
perm+perm-config
|
||||
==
|
||||
::
|
||||
++ new-post
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
name+(su:dejs sym)
|
||||
title+so:dejs
|
||||
comments+comment-config
|
||||
perm+perm-config
|
||||
content+so:dejs
|
||||
==
|
||||
::
|
||||
++ new-comment
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
name+(su:dejs sym)
|
||||
content+so:dejs
|
||||
==
|
||||
::
|
||||
++ edit-collection
|
||||
%- ot:dejs
|
||||
:~ name+(su:dejs sym)
|
||||
title+so:dejs
|
||||
comments+comment-config
|
||||
allow-edit+edit-config
|
||||
perm+perm-config
|
||||
==
|
||||
::
|
||||
++ edit-post
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
name+(su:dejs sym)
|
||||
title+so:dejs
|
||||
comments+comment-config
|
||||
perm+perm-config
|
||||
content+so:dejs
|
||||
==
|
||||
::
|
||||
++ edit-comment
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
name+(su:dejs sym)
|
||||
id+(su:dejs sym)
|
||||
content+so:dejs
|
||||
==
|
||||
::
|
||||
++ comment-config
|
||||
%- su:dejs
|
||||
;~(pose (jest %open) (jest %closed) (jest %none))
|
||||
::
|
||||
++ edit-config
|
||||
%- su:dejs
|
||||
;~(pose (jest %post) (jest %comment) (jest %all) (jest %none))
|
||||
::
|
||||
++ perm-config
|
||||
%- ot:dejs
|
||||
:~ :- %read
|
||||
%- ot:dejs
|
||||
:~ mod+(su:dejs ;~(pose (jest %black) (jest %white)))
|
||||
who+whoms
|
||||
==
|
||||
:- %write
|
||||
%- ot:dejs
|
||||
:~ mod+(su:dejs ;~(pose (jest %black) (jest %white)))
|
||||
who+whoms
|
||||
== ==
|
||||
::
|
||||
++ whoms
|
||||
|= jon=^json
|
||||
^- (set whom:clay)
|
||||
=/ x ((ar:dejs (su:dejs fed:ag)) jon)
|
||||
%- (set whom:clay)
|
||||
%- ~(run in (sy x))
|
||||
|=(w=@ [& w])
|
||||
::
|
||||
++ item-id
|
||||
|= jon=^json
|
||||
^- item-id:write
|
||||
?> ?=(%a -.jon) :: must be array
|
||||
?< ?=(~ +.jon) :: must have at least one item
|
||||
?> ?=([%s @t] -.+.jon) :: first item must be string
|
||||
=/ coll=@tas (slav %tas +.-.+.jon) :: get first item as @tas
|
||||
?~ +.+.jon :: if only one item, return it
|
||||
coll
|
||||
?> ?=([%s @t] -.+.+.jon) :: second item must be string
|
||||
=/ post=@tas (slav %tas +.-.+.+.jon) :: get second item as @tas
|
||||
?~ +.+.+.jon :: if two items, return them
|
||||
[coll post]
|
||||
?> ?=([%s @t] -.+.+.+.jon) :: third item must be string
|
||||
=/ comm=@tas (slav %tas +.-.+.+.+.jon) :: get third item as @tas
|
||||
?> ?=(~ +.+.+.+.jon) :: no fourth item
|
||||
[coll post comm]
|
||||
::
|
||||
++ invite
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
who+(ar:dejs (su:dejs fed:ag))
|
||||
==
|
||||
::
|
||||
++ serve
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
==
|
||||
::
|
||||
++ unserve
|
||||
%- ot:dejs
|
||||
:~ coll+(su:dejs sym)
|
||||
==
|
||||
::
|
||||
++ subscribe
|
||||
%- ot:dejs
|
||||
:~ who+(su:dejs fed:ag)
|
||||
coll+(su:dejs sym)
|
||||
==
|
||||
::
|
||||
++ unsubscribe
|
||||
%- ot:dejs
|
||||
:~ who+(su:dejs fed:ag)
|
||||
coll+(su:dejs sym)
|
||||
==
|
||||
::
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
84
apps/publish/urbit/mar/write/info.hoon
Normal file
84
apps/publish/urbit/mar/write/info.hoon
Normal file
@ -0,0 +1,84 @@
|
||||
::
|
||||
:::: /hoon/info/write/mar
|
||||
::
|
||||
/- write
|
||||
!:
|
||||
|_ con=collection-info:write
|
||||
::
|
||||
::
|
||||
++ grow
|
||||
|%
|
||||
++ mime
|
||||
:- /text/x-write-info
|
||||
(as-octs:mimes:html (of-wain:format txt))
|
||||
++ txt
|
||||
^- wain
|
||||
:~ (cat 3 'owner: ' (scot %p owner.con))
|
||||
(cat 3 'title: ' title.con)
|
||||
(cat 3 'filename: ' filename.con)
|
||||
(cat 3 'comments: ' comments.con)
|
||||
(cat 3 'allow-edit: ' allow-edit.con)
|
||||
(cat 3 'date-created: ' (scot %da date-created.con))
|
||||
(cat 3 'last-modified: ' (scot %da last-modified.con))
|
||||
==
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ mime
|
||||
|= [mite:eyre p=octs:eyre]
|
||||
(txt (to-wain:format q.p))
|
||||
++ txt
|
||||
|= txs=(pole @t)
|
||||
^- collection-info:write
|
||||
:: TODO: putting ~ instead of * breaks this but shouldn't
|
||||
::
|
||||
?> ?= $: owner=@t
|
||||
title=@t
|
||||
filename=@t
|
||||
comments=@t
|
||||
allow-edit=@t
|
||||
date-created=@t
|
||||
last-modified=@t
|
||||
*
|
||||
==
|
||||
txs
|
||||
::
|
||||
:* %+ rash owner.txs
|
||||
;~(pfix (jest 'owner: ~') fed:ag)
|
||||
::
|
||||
%+ rash title.txs
|
||||
;~(pfix (jest 'title: ') (cook crip (star next)))
|
||||
::
|
||||
%+ rash filename.txs
|
||||
;~(pfix (jest 'filename: ') (cook crip (star next)))
|
||||
::
|
||||
%+ rash comments.txs
|
||||
;~ pfix
|
||||
(jest 'comments: ')
|
||||
%+ cook comment-config:write
|
||||
;~(pose (jest %open) (jest %closed) (jest %none))
|
||||
==
|
||||
::
|
||||
%+ rash allow-edit.txs
|
||||
;~ pfix
|
||||
(jest 'allow-edit: ')
|
||||
%+ cook edit-config:write
|
||||
;~(pose (jest %post) (jest %comment) (jest %all) (jest %none))
|
||||
==
|
||||
::
|
||||
%+ rash date-created.txs
|
||||
;~ pfix
|
||||
(jest 'date-created: ~')
|
||||
(cook year when:so)
|
||||
==
|
||||
::
|
||||
%+ rash last-modified.txs
|
||||
;~ pfix
|
||||
(jest 'last-modified: ~')
|
||||
(cook year when:so)
|
||||
==
|
||||
==
|
||||
++ noun collection-info:write
|
||||
--
|
||||
++ grad %mime
|
||||
--
|
47
apps/publish/urbit/mar/write/rumor.hoon
Normal file
47
apps/publish/urbit/mar/write/rumor.hoon
Normal file
@ -0,0 +1,47 @@
|
||||
/- *write
|
||||
/+ *write, elem-to-react-json
|
||||
|_ rum=rumor
|
||||
++ grab
|
||||
|%
|
||||
++ noun rumor
|
||||
--
|
||||
++ grow
|
||||
|%
|
||||
++ noun rum
|
||||
++ json
|
||||
=, enjs:format
|
||||
%+ frond -.rum
|
||||
?- -.rum
|
||||
%collection
|
||||
%- pairs
|
||||
:~ [%coll s+col.rum]
|
||||
[%who (ship who.rum)]
|
||||
[%data (collection-build-to-json dat.rum)]
|
||||
==
|
||||
::
|
||||
%post
|
||||
%- pairs
|
||||
:~ [%coll s+col.rum]
|
||||
[%post s+pos.rum]
|
||||
[%who (ship who.rum)]
|
||||
[%data (post-build-to-json dat.rum)]
|
||||
==
|
||||
::
|
||||
%comments
|
||||
%- pairs
|
||||
:~ [%coll s+col.rum]
|
||||
[%post s+pos.rum]
|
||||
[%who (ship who.rum)]
|
||||
[%data (comment-build-to-json dat.rum)]
|
||||
==
|
||||
::
|
||||
%total
|
||||
%- pairs
|
||||
:~ [%coll s+col.rum]
|
||||
[%who (ship who.rum)]
|
||||
[%data (total-build-to-json dat.rum)]
|
||||
==
|
||||
==
|
||||
::
|
||||
--
|
||||
--
|
24
apps/publish/urbit/ren/write/comments.hoon
Normal file
24
apps/publish/urbit/ren/write/comments.hoon
Normal file
@ -0,0 +1,24 @@
|
||||
/- write
|
||||
/+ write, cram, elem-to-react-json
|
||||
/= args /$ ,[beam *]
|
||||
/= result
|
||||
/^ (list [comment-info:write manx])
|
||||
/;
|
||||
|= $= comments
|
||||
%+ map knot
|
||||
$: comment-front=(map knot cord)
|
||||
comment-content=manx
|
||||
~
|
||||
==
|
||||
^- (list [comment-info:write manx])
|
||||
:: XX sort this list
|
||||
%+ turn ~(tap by comments)
|
||||
|= [fil=knot front=(map knot cord) content=manx ~]
|
||||
^- [comment-info:write manx]
|
||||
[(front-to-comment-info:write front) content]
|
||||
::
|
||||
/_
|
||||
/. /&front&/udon/
|
||||
/&elem&/udon/
|
||||
==
|
||||
result
|
17
apps/publish/urbit/ren/write/post.hoon
Normal file
17
apps/publish/urbit/ren/write/post.hoon
Normal file
@ -0,0 +1,17 @@
|
||||
/- write
|
||||
/+ write, cram, elem-to-react-json
|
||||
/= args /$ ,[beam *]
|
||||
/= result
|
||||
/^ [post-info:write manx]
|
||||
/;
|
||||
|= $: post-front=(map knot cord)
|
||||
post-content=manx
|
||||
~
|
||||
==
|
||||
:- (front-to-post-info:write post-front)
|
||||
post-content
|
||||
::
|
||||
/. /&front&/udon/
|
||||
/&elem&/udon/
|
||||
==
|
||||
result
|
123
apps/publish/urbit/sur/write.hoon
Normal file
123
apps/publish/urbit/sur/write.hoon
Normal file
@ -0,0 +1,123 @@
|
||||
|%
|
||||
+$ item-id
|
||||
$? coll=@tas
|
||||
[coll=@tas post=@tas]
|
||||
[coll=@tas post=@tas comment=@tas]
|
||||
==
|
||||
::
|
||||
+$ action
|
||||
$% $: %new-collection
|
||||
name=@tas
|
||||
title=@t
|
||||
com=comment-config
|
||||
edit=edit-config
|
||||
perm=perm-config
|
||||
==
|
||||
::
|
||||
$: %new-post
|
||||
coll=@tas
|
||||
name=@tas
|
||||
title=@t
|
||||
com=comment-config
|
||||
perm=perm-config
|
||||
content=@t
|
||||
==
|
||||
::
|
||||
[%new-comment coll=@tas post=@tas content=@t]
|
||||
::
|
||||
[%delete item-id]
|
||||
::
|
||||
$: %edit-collection
|
||||
name=@tas
|
||||
title=@t
|
||||
com=comment-config
|
||||
edit=edit-config
|
||||
perm=perm-config
|
||||
==
|
||||
::
|
||||
$: %edit-post
|
||||
coll=@tas
|
||||
name=@tas
|
||||
title=@t
|
||||
com=comment-config
|
||||
perm=perm-config
|
||||
content=@t
|
||||
==
|
||||
::
|
||||
[%edit-comment coll=@tas post=@tas id=@tas content=@t]
|
||||
::
|
||||
[%invite coll=@tas who=(list ship)]
|
||||
::
|
||||
[%serve coll=@tas]
|
||||
[%unserve coll=@tas]
|
||||
::
|
||||
[%subscribe who=@p coll=@tas]
|
||||
[%unsubscribe who=@p coll=@tas]
|
||||
::
|
||||
==
|
||||
::
|
||||
+$ collection-info
|
||||
$: owner=@p
|
||||
title=@t
|
||||
filename=@tas
|
||||
comments=comment-config
|
||||
allow-edit=edit-config
|
||||
date-created=@da
|
||||
last-modified=@da
|
||||
==
|
||||
::
|
||||
+$ post-info
|
||||
$: creator=@p
|
||||
title=@t
|
||||
collection=@tas
|
||||
filename=@tas
|
||||
comments=comment-config
|
||||
date-created=@da
|
||||
last-modified=@da
|
||||
pinned=?
|
||||
==
|
||||
::
|
||||
+$ comment-info
|
||||
$: creator=@p
|
||||
collection=@tas
|
||||
post=@tas
|
||||
date-created=@da
|
||||
last-modified=@da
|
||||
==
|
||||
::
|
||||
+$ perm-config [read=rule:clay write=rule:clay]
|
||||
::
|
||||
::
|
||||
+$ comment-config $?(%open %closed %none)
|
||||
::
|
||||
::
|
||||
+$ edit-config $?(%post %comment %all %none)
|
||||
::
|
||||
+$ rumor delta
|
||||
:: $% [%collection who=@p col=@tas dat=(each collection-info tang)]
|
||||
:: [%post who=@p col=@tas pos=@tas dat=(each [post-info manx] tang)]
|
||||
:: [%comments who=@p col=@tas pos=@tas dat=(each (list [comment-info manx]) tang)]
|
||||
:: [%serve who=@p nom=@tas col=collection]
|
||||
:: ==
|
||||
::
|
||||
+$ collection
|
||||
$: col=(each collection-info tang)
|
||||
pos=(map @tas (each [post-info manx] tang))
|
||||
com=(map @tas (each (list [comment-info manx]) tang))
|
||||
==
|
||||
::
|
||||
+$ state
|
||||
$: pubs=(map @tas collection)
|
||||
subs=(map [ship @tas] collection)
|
||||
awaiting=(map @tas [builds=(set wire) partial=(unit delta)])
|
||||
latest=(list [who=ship coll=@tas post=@tas])
|
||||
unread=(set [who=ship coll=@tas post=@tas])
|
||||
==
|
||||
::
|
||||
+$ delta
|
||||
$% [%collection who=@p col=@tas dat=(each collection-info tang)]
|
||||
[%post who=@p col=@tas pos=@tas dat=(each [post-info manx] tang)]
|
||||
[%comments who=@p col=@tas pos=@tas dat=(each (list [comment-info manx]) tang)]
|
||||
[%total who=@p col=@tas dat=collection]
|
||||
==
|
||||
--
|
@ -1,2 +1,6 @@
|
||||
#!/bin/bash
|
||||
cp urbitrc ./apps/*/.urbitrc
|
||||
cp urbitrc ./apps/chat/.urbitrc
|
||||
cp urbitrc ./apps/launch/.urbitrc
|
||||
cp urbitrc ./apps/modulo/.urbitrc
|
||||
cp urbitrc ./apps/publish/.urbitrc
|
||||
cp urbitrc ./apps/weather/.urbitrc
|
||||
|
Loading…
Reference in New Issue
Block a user