Mobile reading secret

This commit is contained in:
Rogerio Chaves 2020-04-22 22:15:18 +02:00
parent 912c623048
commit 9fb8e50463
No known key found for this signature in database
GPG Key ID: E6AF5440509B1D94
13 changed files with 274 additions and 49 deletions

View File

@ -14,6 +14,7 @@ const {
uploadPicture,
identityFilename,
ssbFolder,
isPhone,
} = require("./utils");
const queries = require("./queries");
const serveBlobs = require("./serve-blobs");
@ -26,7 +27,6 @@ const sgMail = require("@sendgrid/mail");
const ejs = require("ejs");
const cookieEncrypter = require("cookie-encrypter");
const expressLayouts = require("express-ejs-layouts");
const isMobile = require("ismobilejs").default;
let ssbServer;
let mode = process.env.MODE || "client";
@ -85,7 +85,7 @@ const cookieOptions = {
httpOnly: true,
signed: true,
expires: new Date(253402300000000), // Friday, 31 Dec 9999 23:59:59 GMT, nice date from stackoverflow
sameSite: true,
sameSite: "Lax",
};
app.use(cookieParser(cookieSecret));
app.use(cookieEncrypter(cookieSecret));
@ -160,7 +160,7 @@ router.get("/", { public: true }, async (req, res) => {
if (!req.context.profile) {
return res.render("index");
}
if (isMobile(req.headers["user-agent"]).phone) {
if (isPhone(req)) {
return res.redirect("/mobile");
}
@ -178,10 +178,9 @@ router.get("/", { public: true }, async (req, res) => {
});
router.get("/mobile", async (req, res) => {
// TODO
// if (!isMobile(req.headers["user-agent"]).phone) {
// return res.redirect("/");
// }
if (!isPhone(req)) {
return res.redirect("/");
}
const posts = await queries.getPosts(ssbServer, req.context.profile);
@ -192,6 +191,20 @@ router.get("/mobile", async (req, res) => {
});
});
router.get("/mobile/secrets", async (req, res) => {
if (!isPhone(req)) {
return res.redirect("/");
}
const secretMessages = await queries.getSecretMessages(ssbServer, req.context.profile);
res.render("mobile/secrets", {
secretMessages,
profile: req.context.profile,
layout: "mobile/_layout",
});
});
router.get("/login", { public: true }, (_req, res) => {
res.render("login", { mode });
});

View File

View File

@ -3,6 +3,7 @@ const leftpad = require("left-pad"); // I don't believe I'm depending on this
const pull = require("pull-stream");
const split = require("split-buffer");
const metrics = require("./metrics");
const isMobile = require("ismobilejs").default;
module.exports.asyncRouter = (app) => {
const debug = require("debug")("router");
@ -112,3 +113,7 @@ module.exports.promisePull = (...streams) =>
});
module.exports.mapValues = (x) => x.map((y) => y.value);
module.exports.isPhone = (req) => {
return isMobile(req.headers["user-agent"]).phone;
};

88
app/public/js/mobile.js Normal file
View File

@ -0,0 +1,88 @@
/**
* Compose Post
*/
const composePost = document.querySelector(".js-compose-post");
if (composePost) {
const composeButton = document.querySelector(".js-publish-button");
composePost.addEventListener("focus", () => {
composeButton.style.display = "block";
});
}
/**
* Modal
*/
const openModalFor = (elem, onConfirm, afterClose = null) => {
const overlay = elem.parentElement.querySelector(".overlay");
const modal = elem.parentElement.querySelector(".modal");
const confirmButtons = elem.parentElement.querySelectorAll(".modal-confirm");
const steps = elem.parentElement.querySelectorAll(".js-step");
overlay.hidden = false;
modal.hidden = false;
const onClose = () => {
overlay.hidden = true;
modal.hidden = true;
steps.forEach((step) => {
step.style.display = "none";
});
if (steps[0]) steps[0].style.display = "flex";
if (afterClose) afterClose();
};
const nextOrConfirm = () => {
if (steps.length == 0) {
onConfirm();
} else {
let currentStep;
steps.forEach((step, index) => {
if (currentStep == index) {
step.style.display = "flex";
} else if (step.style.display != "none") {
currentStep = index;
currentStep++;
if (currentStep < steps.length) step.style.display = "none";
}
});
if (currentStep == steps.length) {
onConfirm();
}
}
};
overlay.addEventListener("click", onClose);
Array.from(confirmButtons).forEach((button) =>
button.addEventListener("click", nextOrConfirm)
);
return { close: onClose };
};
/**
* Secret Messages Reading
*/
const messages = document.querySelectorAll(".js-secret-message");
messages.forEach((message) => {
message.addEventListener("click", () => {
const afterClose = () => {
const parent = message.parentElement;
const composeMessage = parent.parentElement.querySelector(
".js-compose-secret-message"
);
composeMessage.style.display = "flex";
parent.parentElement.removeChild(parent);
};
const modal = openModalFor(message, () => modal.close(), afterClose);
fetch("/vanish", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "keys=" + encodeURIComponent(message.dataset.keys),
});
});
});

View File

@ -1,6 +1,9 @@
body {
background: #fff;
padding-bottom: 70px;
display: flex;
flex-direction: column;
min-height: 100vh;
}
nav {
@ -42,3 +45,55 @@ nav a.selected {
.post {
padding: 10px;
}
.modal {
top: 100%;
left: 0;
transform: none;
min-width: auto;
width: 100%;
height: 100%;
animation: slide 0.5s forwards;
}
@keyframes slide {
100% {
top: 0;
}
}
.modal-group {
height: 100%;
display: flex;
flex-direction: column;
}
.modal-body {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
}
.modal-footer {
border-bottom: 1px solid #ddd;
background: #000;
}
.secret-chat {
width: 100%;
max-width: none;
min-width: auto;
flex-grow: 1;
padding: 0;
}
.secret-button {
padding: 8px 14px;
font-size: 16px;
}
.secret-chat .link-profile-pic {
margin-right: 12px;
}

View File

@ -1,4 +1,4 @@
</main>
<script src="/js/index.js"></script>
<script src="/js/desktop.js"></script>
</body>
</html>

View File

@ -1,25 +1,23 @@
<div style="padding-top: 15px;">
<% posts.map(post => { %>
<% if (!post.content.text) return %>
<div class="post">
<div>
<a href="<%= profileUrl(post.author) %>">
<img src="<%= profileImageUrl(post.authorProfile) %>" class="post-profile-pic" />
</a>
</div>
<div class="post-content">
<a href="<%= profileUrl(post.author) %>" class="no-link-style">
<b><%= post.authorProfile.name %></b>
</a>
<% let text = post.content.text %>
<% if (typeof dont_cut == "undefined") { %>
<% text = post.content.text.slice(0, 140) %>
<% if (post.content.text.length > 140) text += "..." %>
<% } %>
<% text.split("\n").map((line, index) => { %>
<%- index > 0 ? "<br />" : "" %><%= line %>
<% }) %>
</div>
<% posts.map(post => { %>
<% if (!post.content.text) return %>
<div class="post">
<div>
<a href="<%= profileUrl(post.author) %>">
<img src="<%= profileImageUrl(post.authorProfile) %>" class="post-profile-pic" />
</a>
</div>
<% }) %>
</div>
<div class="post-content">
<a href="<%= profileUrl(post.author) %>" class="no-link-style">
<b><%= post.authorProfile.name %></b>
</a>
<% let text = post.content.text %>
<% if (typeof dont_cut == "undefined") { %>
<% text = post.content.text.slice(0, 140) %>
<% if (post.content.text.length > 140) text += "..." %>
<% } %>
<% text.split("\n").map((line, index) => { %>
<%- index > 0 ? "<br />" : "" %><%= line %>
<% }) %>
</div>
</div>
<% }) %>

View File

@ -27,15 +27,15 @@
</form>
<h2 style="margin: 0">Your Wall</h2>
<% if (posts.length > 0) { %>
<%- include('_posts', { posts }) %>
<% } else { %>
<div style="padding-top: 15px;">
<div style="padding: 15px;">
<% if (posts.length > 0) { %>
<%- include('_posts', { posts }) %>
<% } else { %>
<div class="post">
You have no posts yet, publish something!
</div>
</div>
<% } %>
<% } %>
</div>
</div>
<div class="friends-communities">

View File

@ -14,23 +14,23 @@
</div>
</header>
<nav>
<a href="/" class="<%= context.path == "/mobile" ? "selected" : "" %>">
<a href="/mobile" class="<%= context.path == "/mobile" ? "selected" : "" %>">
<div class="nav-icon">🙂</div>
Profile
</a>
<a href="/secrets">
<a href="/mobile/secrets" class="<%= context.path == "/mobile/secrets" ? "selected" : "" %>">
<div class="nav-icon">🤫</div>
Secrets
</a>
<a href="/friends">
<a href="/mobile/friends" class="<%= context.path == "/mobile/friends" ? "selected" : "" %>">
<div class="nav-icon">👨‍👧‍👦</div>
Friends
</a>
<a href="/communities">
<a href="/mobile/communities" class="<%= context.path == "/mobile/communities" ? "selected" : "" %>">
<div class="nav-icon">🌆</div>
Communities
</a>
<a href="/search">
<a href="/mobile/search" class="<%= context.path == "/mobile/search" ? "selected" : "" %>">
<div class="nav-icon">💬</div>
Search
</a>
@ -38,5 +38,7 @@
</nav>
<%- body %>
<script src="/js/mobile.js"></script>
</body>
</html>

View File

@ -8,6 +8,12 @@
</div>
<% if (posts.length > 0) { %>
<form action="/publish" method="POST" style="padding: 0px 8px 5px 8px;">
<textarea name="message" class="compose-post js-compose-post" placeholder="Post something on your wall..."></textarea>
<div class="reverse-columns">
<input type="submit" value="Publish" style="display: none; margin: 5px 0" class="js-publish-button" />
</div>
</form>
<%- include('../_posts', { posts }) %>
<% } else { %>
<div style="padding-top: 15px;">

View File

@ -0,0 +1,58 @@
<div class="secret-chat">
<div class="column-side-padding" <%= secretMessages.length > 0 ? "hidden" : "" %> style="margin: 25px 0 20px 0; font-size: 14px;">
👻 You don't have any secret messages yet
</div>
<% secretMessages.map(chat => { %>
<div>
<div>
<button class="secret-button js-compose-secret-message" style="<%= chat.messages.length == 0 ? "" : "display: none" %>" data-url="<%= profileUrl(chat.authorProfile.id, "/publish_secret") %>">
<img class="link-profile-pic" src="<%= profileImageUrl(chat.authorProfile) %>" class="post-profile-pic" />
<div style="flex-grow: 1;">
<%= chat.authorProfile.name %> <br />
<small>
No new secrets
</small>
</div>
</button>
<%- include('../secrets/_compose_single', { profile: chat.authorProfile }) %>
</div>
<div>
<button class="secret-button js-secret-message" style="<%= chat.messages.length == 0 ? "display: none" : "" %>" data-keys="<%= chat.messages.map(m => m.key).join(",") %>">
<img class="link-profile-pic" src="<%= profileImageUrl(chat.authorProfile) %>" class="post-profile-pic" />
<div style="flex-grow: 1;">
<%= chat.authorProfile.name %> <br />
<small>
👁 <%= chat.messages.length == 1 ? "1 new message" : chat.messages.length + " new messages" %>
</small>
</div>
</button>
<div class="overlay" hidden></div>
<div class="modal" hidden>
<% chat.messages.reverse().map((message, index) => { %>
<div class="modal-group js-step" <%- index > 0 ? 'style="display: none"' : "" %>>
<div class="modal-footer">
<% if (index == chat.messages.length - 1) { %>
<span></span>
<button class="modal-confirm">Close</button>
<% } else { %>
<span></span>
<button class="modal-confirm">Next</button>
<% } %>
</div>
<a href="<%= profileUrl(chat.authorProfile.id) %>" class="modal-header">
<img src="<%= profileImageUrl(chat.authorProfile) %>" class="post-profile-pic" />
<div style="padding-left: 10px"><%= chat.authorProfile.name %></div>
</a>
<div class="modal-body">
<div style="margin-top: -60px"><%= message.value.content.text %></div>
</div>
</div>
<% }) %>
</div>
</div>
</div>
<% }) %>
</div>

View File

@ -63,15 +63,15 @@
</form>
<h2 style="margin: 0"><%= profile.name %>'s Wall</h2>
<% if (posts.length == 0) { %>
<div style="padding-top: 15px">
<div style="padding-top: 15px;">
<% if (posts.length == 0) { %>
<div class="post">
No posts yet
</div>
</div>
<% } else { %>
<%- include('_posts', { posts }) %>
<% } %>
<% } else { %>
<%- include('_posts', { posts }) %>
<% } %>
</div>
</div>
<div class="friends-communities">