Add multiple identities, allowing login and logout
This commit is contained in:
parent
e4be14f0c8
commit
a72128b590
|
@ -5,12 +5,15 @@ const bodyParser = require("body-parser");
|
||||||
const Client = require("ssb-client");
|
const Client = require("ssb-client");
|
||||||
const ssbKeys = require("ssb-keys");
|
const ssbKeys = require("ssb-keys");
|
||||||
const ssbConfig = require("./ssb-config");
|
const ssbConfig = require("./ssb-config");
|
||||||
const { asyncRouter } = require("./utils");
|
const { asyncRouter, writeKey } = require("./utils");
|
||||||
const queries = require("./queries");
|
const queries = require("./queries");
|
||||||
const serveBlobs = require("./serve-blobs");
|
const serveBlobs = require("./serve-blobs");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
|
const leftpad = require("left-pad"); // I don't believe I'm depending on this
|
||||||
|
const debug = require("debug")("express");
|
||||||
|
|
||||||
let ssbServer;
|
let ssbServer;
|
||||||
let context = {};
|
let mode = process.env.MODE || "server";
|
||||||
|
|
||||||
let homeFolder =
|
let homeFolder =
|
||||||
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
||||||
|
@ -19,8 +22,6 @@ let ssbSecret = ssbKeys.loadOrCreateSync(
|
||||||
);
|
);
|
||||||
Client(ssbSecret, ssbConfig, async (err, server) => {
|
Client(ssbSecret, ssbConfig, async (err, server) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
const whoami = await server.whoami();
|
|
||||||
context.profile = await queries.getProfile(server, whoami.id);
|
|
||||||
|
|
||||||
ssbServer = server;
|
ssbServer = server;
|
||||||
console.log("SSB Client ready");
|
console.log("SSB Client ready");
|
||||||
|
@ -37,7 +38,46 @@ app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
app.use(express.static("public"));
|
app.use(express.static("public"));
|
||||||
app.use((_req, res, next) => {
|
app.use(cookieParser());
|
||||||
|
app.use(async (req, res, next) => {
|
||||||
|
if (!ssbServer) {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("Waiting for SSB to load...");
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
req.context = {};
|
||||||
|
try {
|
||||||
|
if (mode == "client") {
|
||||||
|
const whoami = await server.whoami();
|
||||||
|
req.context.profile = await queries.getProfile(server, whoami.id);
|
||||||
|
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
const identities = await ssbServer.identities.list();
|
||||||
|
const key = req.cookies["ssb_key"];
|
||||||
|
if (!key) return next();
|
||||||
|
|
||||||
|
const parsedKey = JSON.parse(key);
|
||||||
|
if (!identities.includes(parsedKey.id)) {
|
||||||
|
const filename =
|
||||||
|
"secret_" + leftpad(identities.length - 1, 2, "0") + ".butt";
|
||||||
|
|
||||||
|
writeKey(key, `/identities/${filename}`);
|
||||||
|
ssbServer.identities.refresh();
|
||||||
|
}
|
||||||
|
req.context.profile = await queries.getProfile(ssbServer, parsedKey.id);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.use((req, res, next) => {
|
||||||
res.locals.profileUrl = profileUrl;
|
res.locals.profileUrl = profileUrl;
|
||||||
res.locals.imageUrl = (blob) => {
|
res.locals.imageUrl = (blob) => {
|
||||||
const imageHash = blob && typeof blob == "object" ? blob.link : blob;
|
const imageHash = blob && typeof blob == "object" ? blob.link : blob;
|
||||||
|
@ -50,41 +90,74 @@ app.use((_req, res, next) => {
|
||||||
}
|
}
|
||||||
return "/images/no-avatar.png";
|
return "/images/no-avatar.png";
|
||||||
};
|
};
|
||||||
res.locals.context = context;
|
res.locals.context = req.context;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = asyncRouter(app);
|
const router = asyncRouter(app);
|
||||||
|
|
||||||
router.get("/", async (_req, res) => {
|
router.get("/", async (req, res) => {
|
||||||
if (!ssbServer) {
|
if (!req.context.profile) {
|
||||||
setTimeout(() => {
|
return res.render("index");
|
||||||
res.redirect("/");
|
|
||||||
}, 500);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (!req.context.profile.name) {
|
||||||
if (!context.profile.name) {
|
|
||||||
return res.redirect("/about");
|
return res.redirect("/about");
|
||||||
}
|
}
|
||||||
|
|
||||||
const [posts, friends, vanishingMessages] = await Promise.all([
|
const [posts, friends, vanishingMessages] = await Promise.all([
|
||||||
queries.getPosts(ssbServer, context.profile),
|
queries.getPosts(ssbServer, req.context.profile),
|
||||||
queries.getFriends(ssbServer, context.profile),
|
queries.getFriends(ssbServer, req.context.profile),
|
||||||
queries.getVanishingMessages(ssbServer, context.profile),
|
queries.getVanishingMessages(ssbServer, req.context.profile),
|
||||||
]);
|
]);
|
||||||
res.render("index", {
|
res.render("home", {
|
||||||
posts,
|
posts,
|
||||||
friends,
|
friends,
|
||||||
vanishingMessages,
|
vanishingMessages,
|
||||||
profile: context.profile,
|
profile: req.context.profile,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/login", async (req, res) => {
|
||||||
|
const submittedKey = req.body.ssb_key;
|
||||||
|
|
||||||
|
// From ssb-keys
|
||||||
|
const reconstructKeys = (keyfile) => {
|
||||||
|
var privateKey = keyfile
|
||||||
|
.replace(/\s*\#[^\n]*/g, "")
|
||||||
|
.split("\n")
|
||||||
|
.filter((x) => x)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
var keys = JSON.parse(privateKey);
|
||||||
|
const hasSigil = (x) => /^(@|%|&)/.test(x);
|
||||||
|
|
||||||
|
if (!hasSigil(keys.id)) keys.id = "@" + keys.public;
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedKey = reconstructKeys(submittedKey);
|
||||||
|
res.cookie("ssb_key", JSON.stringify(decodedKey));
|
||||||
|
|
||||||
|
decodedKey.private = "[removed]";
|
||||||
|
debug("Login with key", decodedKey);
|
||||||
|
|
||||||
|
res.redirect("/");
|
||||||
|
} catch (e) {
|
||||||
|
debug("Error on login", e);
|
||||||
|
res.send("Invalid key");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/logout", async (_req, res) => {
|
||||||
|
res.clearCookie("ssb_key");
|
||||||
|
res.redirect("/");
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/profile/:id", async (req, res) => {
|
router.get("/profile/:id", async (req, res) => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
|
|
||||||
if (id == context.profile.id) {
|
if (id == req.context.profile.id) {
|
||||||
return res.redirect("/");
|
return res.redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +174,7 @@ router.post("/publish", async (req, res) => {
|
||||||
await ssbServer.publish({
|
await ssbServer.publish({
|
||||||
type: "post",
|
type: "post",
|
||||||
text: req.body.message,
|
text: req.body.message,
|
||||||
root: context.profile.id,
|
root: req.context.profile.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.redirect("/");
|
res.redirect("/");
|
||||||
|
@ -165,13 +238,13 @@ router.get("/about", (_req, res) => {
|
||||||
router.post("/about", async (req, res) => {
|
router.post("/about", async (req, res) => {
|
||||||
const name = req.body.name;
|
const name = req.body.name;
|
||||||
|
|
||||||
if (name != context.profile.name) {
|
if (name != req.context.profile.name) {
|
||||||
await ssbServer.publish({
|
await ssbServer.publish({
|
||||||
type: "about",
|
type: "about",
|
||||||
about: context.profile.id,
|
about: req.context.profile.id,
|
||||||
name: name,
|
name: name,
|
||||||
});
|
});
|
||||||
context.profile.name = name;
|
req.context.profile.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.redirect("/");
|
res.redirect("/");
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Monkeypatched to include the refresh function
|
||||||
|
var leftpad = require("left-pad");
|
||||||
|
var path = require("path");
|
||||||
|
var mkdirp = require("mkdirp");
|
||||||
|
var fs = require("fs");
|
||||||
|
var ssbKeys = require("ssb-keys");
|
||||||
|
var create = require("ssb-validate").create;
|
||||||
|
|
||||||
|
function toTarget(t) {
|
||||||
|
return "object" === typeof t ? t && t.link : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.name = "identities";
|
||||||
|
exports.version = "1.0.0";
|
||||||
|
exports.manifest = {
|
||||||
|
main: "sync",
|
||||||
|
list: "async",
|
||||||
|
create: "async",
|
||||||
|
publishAs: "async",
|
||||||
|
help: "sync",
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.init = function (sbot, config) {
|
||||||
|
var dir = path.join(config.path, "identities");
|
||||||
|
mkdirp.sync(dir);
|
||||||
|
|
||||||
|
function readKeys() {
|
||||||
|
return fs
|
||||||
|
.readdirSync(dir)
|
||||||
|
.filter(function (name) {
|
||||||
|
return /^secret_\d+\.butt$/.test(name);
|
||||||
|
})
|
||||||
|
.map(function (file) {
|
||||||
|
return ssbKeys.loadSync(path.join(dir, file));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = readKeys();
|
||||||
|
|
||||||
|
var locks = {};
|
||||||
|
|
||||||
|
sbot.addUnboxer({
|
||||||
|
key: function (content) {
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var key = ssbKeys.unboxKey(content, keys[i]);
|
||||||
|
if (key) return key;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: function (content, key) {
|
||||||
|
return ssbKeys.unboxBody(content, key);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
main: function () {
|
||||||
|
return sbot.id;
|
||||||
|
},
|
||||||
|
refresh: function () {
|
||||||
|
keys = readKeys();
|
||||||
|
},
|
||||||
|
list: function (cb) {
|
||||||
|
cb(
|
||||||
|
null,
|
||||||
|
[sbot.id].concat(
|
||||||
|
keys.map(function (e) {
|
||||||
|
return e.id;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
create: function (cb) {
|
||||||
|
var filename = "secret_" + leftpad(keys.length, 2, "0") + ".butt";
|
||||||
|
ssbKeys.create(path.join(dir, filename), function (err, newKeys) {
|
||||||
|
keys.push(newKeys);
|
||||||
|
cb(err, newKeys.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
publishAs: function (opts, cb) {
|
||||||
|
var id = opts.id;
|
||||||
|
if (locks[id]) return cb(new Error("already writing"));
|
||||||
|
var _keys =
|
||||||
|
sbot.id === id
|
||||||
|
? sbot.keys
|
||||||
|
: keys.find(function (e) {
|
||||||
|
return id === e.id;
|
||||||
|
});
|
||||||
|
if (!_keys) return cb(new Error("must provide id of listed identities"));
|
||||||
|
var content = opts.content;
|
||||||
|
|
||||||
|
var recps = [].concat(content.recps).map(toTarget);
|
||||||
|
|
||||||
|
if (content.recps && !opts.private)
|
||||||
|
return cb(new Error("recps set, but opts.private not set"));
|
||||||
|
else if (!content.recps && opts.private)
|
||||||
|
return cb(new Error("opts.private set, but content.recps not set"));
|
||||||
|
else if (!!content.recps && opts.private) {
|
||||||
|
if (!Array.isArray(content.recps) || !~recps.indexOf(id))
|
||||||
|
return cb(
|
||||||
|
new Error(
|
||||||
|
"content.recps must be an array containing publisher id:" +
|
||||||
|
id +
|
||||||
|
" was:" +
|
||||||
|
JSON.stringify(recps) +
|
||||||
|
" indexOf:" +
|
||||||
|
recps.indexOf(id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
content = ssbKeys.box(content, recps);
|
||||||
|
}
|
||||||
|
|
||||||
|
locks[id] = true;
|
||||||
|
sbot.getLatest(id, function (err, data) {
|
||||||
|
var state = data
|
||||||
|
? {
|
||||||
|
id: data.key,
|
||||||
|
sequence: data.value.sequence,
|
||||||
|
timestamp: data.value.timestamp,
|
||||||
|
queue: [],
|
||||||
|
}
|
||||||
|
: { id: null, sequence: null, timestamp: null, queue: [] };
|
||||||
|
sbot.add(
|
||||||
|
create(
|
||||||
|
state,
|
||||||
|
_keys,
|
||||||
|
config.caps && config.caps.sign,
|
||||||
|
content,
|
||||||
|
Date.now()
|
||||||
|
),
|
||||||
|
function (err, a, b) {
|
||||||
|
delete locks[id];
|
||||||
|
cb(err, a, b);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
help: function () {
|
||||||
|
return require("./help");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,17 +1,15 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const { writeKey } = require("./utils");
|
||||||
|
|
||||||
let homeFolder =
|
|
||||||
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
|
||||||
let ssbFolder = `${homeFolder}/.${process.env.CONFIG_FOLDER || "social"}`;
|
|
||||||
let secretPath = `${ssbFolder}/secret`;
|
|
||||||
let envKey =
|
let envKey =
|
||||||
process.env.SSB_KEY &&
|
process.env.SSB_KEY &&
|
||||||
Buffer.from(process.env.SSB_KEY, "base64").toString("utf8");
|
Buffer.from(process.env.SSB_KEY, "base64").toString("utf8");
|
||||||
if (envKey) {
|
if (envKey) {
|
||||||
console.log("Using env SSB_KEY");
|
try {
|
||||||
fs.mkdirSync(ssbFolder, { recursive: true });
|
writeKey(envKey, "/secret");
|
||||||
fs.writeFileSync(secretPath, envKey);
|
console.log("Writing SSB_KEY from env");
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Server = require("ssb-server");
|
const Server = require("ssb-server");
|
||||||
|
@ -28,7 +26,7 @@ Server.use(require("ssb-master"))
|
||||||
.use(require("ssb-friends"))
|
.use(require("ssb-friends"))
|
||||||
.use(require("ssb-query"))
|
.use(require("ssb-query"))
|
||||||
.use(require("ssb-device-address"))
|
.use(require("ssb-device-address"))
|
||||||
.use(require("ssb-identities"))
|
.use(require("./monkeypatch/ssb-identities"))
|
||||||
.use(require("ssb-peer-invites"))
|
.use(require("ssb-peer-invites"))
|
||||||
.use(require("ssb-blobs"))
|
.use(require("ssb-blobs"))
|
||||||
.use(require("ssb-private"));
|
.use(require("ssb-private"));
|
||||||
|
|
|
@ -1,16 +1,4 @@
|
||||||
module.exports.promisify = (method, options = null) => {
|
const fs = require("fs");
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const callback = (err, result) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return resolve(result);
|
|
||||||
};
|
|
||||||
if (options) {
|
|
||||||
method(options, callback);
|
|
||||||
} else {
|
|
||||||
method(callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.asyncRouter = (app) => {
|
module.exports.asyncRouter = (app) => {
|
||||||
const debug = require("debug")("router");
|
const debug = require("debug")("router");
|
||||||
|
@ -33,3 +21,14 @@ module.exports.asyncRouter = (app) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.writeKey = (key, path) => {
|
||||||
|
let homeFolder =
|
||||||
|
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
||||||
|
let ssbFolder = `${homeFolder}/.${process.env.CONFIG_FOLDER || "social"}`;
|
||||||
|
let secretPath = `${ssbFolder}${path}`;
|
||||||
|
|
||||||
|
// Same options ssb-keys use
|
||||||
|
fs.mkdirSync(ssbFolder, { recursive: true });
|
||||||
|
fs.writeFileSync(secretPath, key, { mode: 0x100, flag: "wx" });
|
||||||
|
};
|
||||||
|
|
|
@ -436,6 +436,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||||
},
|
},
|
||||||
|
"cookie-parser": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.0",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^3.3.1",
|
"chokidar": "^3.3.1",
|
||||||
|
"cookie-parser": "^1.4.5",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"ejs": "^3.0.2",
|
"ejs": "^3.0.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
|
|
@ -19,13 +19,16 @@
|
||||||
<a href="/debug">Debug</a>
|
<a href="/debug">Debug</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="right-items">
|
<div class="right-items">
|
||||||
<a href="/" style="margin-right: 10px">
|
<form action="/search" method="GET" style="margin-right: 10px">
|
||||||
<%= context.profile.name %> (<%= context.profile.id.slice(0, 8) %>)
|
|
||||||
</a>
|
|
||||||
<form action="/search" method="GET">
|
|
||||||
<div class="search-icon">🔎</div>
|
<div class="search-icon">🔎</div>
|
||||||
<input type="search" class="input-search" name="query" placeholder="Search for people...">
|
<input type="search" class="input-search" name="query" placeholder="Search for people...">
|
||||||
</form>
|
</form>
|
||||||
|
<a href="/" style="margin-right: 10px">
|
||||||
|
<%= context.profile.name %> (<%= context.profile.id.slice(0, 8) %>)
|
||||||
|
</a>
|
||||||
|
<a href="/logout" style="margin-right: 10px">
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
<%- include('_header') %>
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="about">
|
||||||
|
<img class="profile-pic" src="<%= profileImageUrl(profile) %>" />
|
||||||
|
<h1><%= profile.name %></h1>
|
||||||
|
|
||||||
|
<%= profile.description %>
|
||||||
|
|
||||||
|
<h2>Friends</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<% friends.map(friend => { %>
|
||||||
|
<li>
|
||||||
|
<a href="<%= profileUrl(friend.content.contact) %>">
|
||||||
|
<%= friend.content.contactProfile?.name %> (<%= friend.content.contact.slice(0, 8) %>)
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<% }) %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wall">
|
||||||
|
<% if (vanishingMessages.length > 0) { %>
|
||||||
|
<div class="vanishing-messages" style="padding-bottom: 20px">
|
||||||
|
<h2>Vanishing Messages</h2>
|
||||||
|
<% vanishingMessages.reverse().map(message => { %>
|
||||||
|
<span>
|
||||||
|
<button class="vanishing-message" data-key="<%= message.key %>">
|
||||||
|
<div><img src="<%= profileImageUrl(message.value.authorProfile) %>" class="post-profile-pic" /></div>
|
||||||
|
<div><%= message.value.authorProfile.name %></div>
|
||||||
|
</button>
|
||||||
|
<div class="overlay"></div>
|
||||||
|
<div class="modal">
|
||||||
|
<a href="<%= profileUrl(message.value.authorProfile.id) %>" class="modal-header">
|
||||||
|
<img src="<%= profileImageUrl(message.value.authorProfile) %>" class="post-profile-pic" style="padding-right: 10px" />
|
||||||
|
<%= message.value.authorProfile.name %>
|
||||||
|
</a>
|
||||||
|
<div class="modal-body">
|
||||||
|
<%= message.value.content.text %>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
after you close this box the message will be gone forever
|
||||||
|
<button class="modal-close">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<h2>Your Wall</h2>
|
||||||
|
|
||||||
|
<form action="/publish" method="POST">
|
||||||
|
<textarea name="message"></textarea>
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<% posts.map(post => { %>
|
||||||
|
<%- include('_post', { post }) %>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%- include('_footer') %>
|
|
@ -1,66 +1,10 @@
|
||||||
<%- include('_header') %>
|
<h1>Welcome to Social</h1>
|
||||||
|
|
||||||
<div class="columns">
|
<h2>Login</h2>
|
||||||
<div class="about">
|
|
||||||
<img class="profile-pic" src="<%= profileImageUrl(profile) %>" />
|
|
||||||
<h1><%= profile.name %></h1>
|
|
||||||
|
|
||||||
<%= profile.description %>
|
<form method="POST" action="/login">
|
||||||
|
<textarea name="ssb_key" id="" cols="30" rows="10"></textarea>
|
||||||
|
<input type="submit" value="Login">
|
||||||
|
</form>
|
||||||
|
|
||||||
<h2>Friends</h2>
|
<h2>Create account</h2>
|
||||||
|
|
||||||
<ul>
|
|
||||||
<% friends.map(friend => { %>
|
|
||||||
<li>
|
|
||||||
<a href="<%= profileUrl(friend.content.contact) %>">
|
|
||||||
<%= friend.content.contactProfile?.name %> (<%= friend.content.contact.slice(0, 8) %>)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="wall">
|
|
||||||
<% if (vanishingMessages.length > 0) { %>
|
|
||||||
<div class="vanishing-messages" style="padding-bottom: 20px">
|
|
||||||
<h2>Vanishing Messages</h2>
|
|
||||||
<% vanishingMessages.reverse().map(message => { %>
|
|
||||||
<span>
|
|
||||||
<button class="vanishing-message" data-key="<%= message.key %>">
|
|
||||||
<div><img src="<%= profileImageUrl(message.value.authorProfile) %>" class="post-profile-pic" /></div>
|
|
||||||
<div><%= message.value.authorProfile.name %></div>
|
|
||||||
</button>
|
|
||||||
<div class="overlay"></div>
|
|
||||||
<div class="modal">
|
|
||||||
<a href="<%= profileUrl(message.value.authorProfile.id) %>" class="modal-header">
|
|
||||||
<img src="<%= profileImageUrl(message.value.authorProfile) %>" class="post-profile-pic" style="padding-right: 10px" />
|
|
||||||
<%= message.value.authorProfile.name %>
|
|
||||||
</a>
|
|
||||||
<div class="modal-body">
|
|
||||||
<%= message.value.content.text %>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
after you close this box the message will be gone forever
|
|
||||||
<button class="modal-close">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<h2>Your Wall</h2>
|
|
||||||
|
|
||||||
<form action="/publish" method="POST">
|
|
||||||
<textarea name="message"></textarea>
|
|
||||||
<input type="submit" value="Send" />
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<% posts.map(post => { %>
|
|
||||||
<%- include('_post', { post }) %>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%- include('_footer') %>
|
|
Loading…
Reference in New Issue