mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
deploy: a0ae4125c5
This commit is contained in:
parent
d91c983f14
commit
474ca4894a
4 changed files with 622 additions and 1 deletions
227
css/demo.css
Normal file
227
css/demo.css
Normal file
|
@ -0,0 +1,227 @@
|
|||
#demo {
|
||||
padding-top: 3.5rem;
|
||||
padding-bottom: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#demo .all-users {
|
||||
position: relative;
|
||||
height: 640px;
|
||||
}
|
||||
|
||||
#demo .user {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#demo h3 {
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
text-align: left;
|
||||
padding: 0 0 5px 7px;
|
||||
color: #ffffbb;
|
||||
mix-blend-mode: difference;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
#demo .user .terminal {
|
||||
text-align: left;
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid white;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 10px;
|
||||
color: white;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#demo .user .terminal:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: #666 url(/img/topbar.svg) no-repeat 8px center;
|
||||
border-radius: 10px 10px 0 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#demo .user .terminal input,
|
||||
#demo .user .terminal .input,
|
||||
#demo .user .terminal .display {
|
||||
font-family: Menlo, "Lucida Console", Monaco, monospace;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.27px;
|
||||
line-height: 28px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#demo .user .terminal input,
|
||||
#demo .user .terminal .input {
|
||||
color: white;
|
||||
display: block;
|
||||
height: 30px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#demo .user .terminal input {
|
||||
background-color: transparent;
|
||||
display: none;
|
||||
left: 25px;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
#demo .user .terminal .display {
|
||||
color: white;
|
||||
text-align: left;
|
||||
bottom: 30px;
|
||||
z-index: 1;
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display .info {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display .error {
|
||||
color: #ff6347;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display .sent span.group {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display .received span.group {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display span {
|
||||
font-family: Menlo, Monaco, "Lucida Console", monospace;
|
||||
text-align: left;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display span.recipient {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display span.sender {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
#demo .user .terminal .display span.secret {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
#demo .alice {
|
||||
left: -30px;
|
||||
top: 102px;
|
||||
}
|
||||
|
||||
#demo .bob {
|
||||
left: 425px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#demo .tom {
|
||||
left: 425px;
|
||||
top: 378px;
|
||||
}
|
||||
|
||||
#demo .all-users .terminal {
|
||||
width: 410px;
|
||||
}
|
||||
|
||||
#demo .alice .terminal {
|
||||
height: 340px;
|
||||
}
|
||||
|
||||
#demo .bob .terminal {
|
||||
height: 305px;
|
||||
}
|
||||
|
||||
#demo .tom .terminal {
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
|
||||
/* Demo buttons */
|
||||
#demo button {
|
||||
position: absolute;
|
||||
width: 140px;
|
||||
height: 40px;
|
||||
bottom: 85px;
|
||||
font-size: 16px;
|
||||
border-radius: 34px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
background-color: #0053d0;
|
||||
}
|
||||
|
||||
.dark #demo button {
|
||||
color: black;
|
||||
background-color: #70f0f9;
|
||||
}
|
||||
|
||||
#demo button:disabled {
|
||||
filter: brightness(75%);
|
||||
}
|
||||
|
||||
#demo .run-demo {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
#demo .run-faster {
|
||||
left: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#demo .try-it {
|
||||
left: 180px;
|
||||
}
|
||||
|
||||
@media (max-width: 1240px) {
|
||||
#demo .all-users {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#demo .alice {
|
||||
position: relative !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#demo button,
|
||||
#demo .bob,
|
||||
#demo .tom {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 570px) {
|
||||
#demo .alice .terminal {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
#demo .alice .terminal {
|
||||
width: 330px;
|
||||
height: 420px;
|
||||
}
|
||||
}
|
|
@ -669,7 +669,7 @@ window.addEventListener('scroll',changeHeaderBg);
|
|||
<p>Once you have configured your theme in the app, export it to a file and give it a descriptive name – e.g., <code>example.theme</code></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Export your app database, and import a <a href="./themes/simplex-chat.sample.zip">sample chat database</a>.</p>
|
||||
<p>Export your app database, and import a <a href="./themes/simplex-chat.sample.zip">sample chat database</a> - the passphrase is <code>passphrase</code>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Make three screenshots - the list of conversations with opened profile picker, conversation and privacy settings.</p>
|
||||
|
|
347
js/demo.js
Normal file
347
js/demo.js
Normal file
|
@ -0,0 +1,347 @@
|
|||
(async function () {
|
||||
let DELAY = 0;
|
||||
const DISTR = 0.33;
|
||||
|
||||
class User {
|
||||
constructor(name) {
|
||||
this.userWindow = document.querySelector(`#demo .user.${name}`);
|
||||
this.terminal = this.userWindow.querySelector(`.terminal`);
|
||||
this.input = this.terminal.querySelector(".input");
|
||||
this.demoInput = this.terminal.querySelector("input");
|
||||
this.resetInput();
|
||||
this.setupDemo();
|
||||
this.group = [];
|
||||
this.display = this.terminal.querySelector(".display");
|
||||
this.setupMoveWindow();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.resetInput();
|
||||
this.display.innerHTML = "";
|
||||
}
|
||||
|
||||
setGroup(groupName, users) {
|
||||
this.users = users;
|
||||
this.group = users.filter((u) => u !== this);
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
tryDemo() {
|
||||
this.reset();
|
||||
show(this.demoInput);
|
||||
this.demoInput.value = "";
|
||||
}
|
||||
|
||||
async send(to, message, typeTo, paste, secret) {
|
||||
await this._sendMsg(`@${to.name}`, message, typeTo, paste, secret);
|
||||
await to.receive(this, toSecret(secret, message));
|
||||
await delay(20);
|
||||
}
|
||||
|
||||
async sendGroup(message, typeTo, paste) {
|
||||
await this._sendMsg(`#${this.groupName}`, message, typeTo, paste);
|
||||
await this.receiveGroup(message);
|
||||
await delay(10);
|
||||
}
|
||||
|
||||
async _sendMsg(toStr, message, typeTo, paste, secret) {
|
||||
await this.type(`${toStr} `, !typeTo);
|
||||
if (secret) await this.type("#");
|
||||
await this.type(message, paste);
|
||||
if (secret) await this.type("#");
|
||||
await delay(10);
|
||||
this.resetInput();
|
||||
this.show("sent", `${toStr} ${toSecret(secret, message)}`);
|
||||
}
|
||||
|
||||
async type(str, paste) {
|
||||
if (paste) {
|
||||
await delay(10);
|
||||
this.input.insertAdjacentHTML("beforeend", str);
|
||||
} else {
|
||||
for (const char of str) {
|
||||
await delay(isAlpha(char) ? 1 : 2);
|
||||
this.input.insertAdjacentHTML("beforeend", char);
|
||||
}
|
||||
}
|
||||
await delay(2);
|
||||
}
|
||||
|
||||
resetInput() {
|
||||
this.input.innerHTML = "> ";
|
||||
show(this.demoInput, false);
|
||||
}
|
||||
|
||||
async receive(from, message, edit, group) {
|
||||
await delay(10);
|
||||
let g = group ? `#${this.groupName} ` : "";
|
||||
this.show("received", `${g}${from.name}> ${message}`, edit);
|
||||
}
|
||||
|
||||
async receiveGroup(message, edit) {
|
||||
await Promise.all(this.group.map((u) => u.receive(this, message, edit, true)));
|
||||
}
|
||||
|
||||
show(mode, str, edit) {
|
||||
if (edit && this.lastMessage) {
|
||||
this.lastMessage.innerHTML = highlight(str);
|
||||
return;
|
||||
}
|
||||
this.display.insertAdjacentHTML("beforeend", `<div class="${mode}">${highlight(str)}</div>`);
|
||||
}
|
||||
|
||||
setupDemo() {
|
||||
if (!this.demoInput) return;
|
||||
let editMode = false;
|
||||
|
||||
on("keypress", this.demoInput, async ({ key }) => {
|
||||
if (key === "Enter") {
|
||||
const edit = editMode;
|
||||
editMode = false;
|
||||
const [to, ...words] = this.demoInput.value.trim().split(" ");
|
||||
const message = words.join(" ");
|
||||
switch (to[0]) {
|
||||
case undefined:
|
||||
if (message !== "") {
|
||||
this.show("error", "Message should start with @user or #group");
|
||||
}
|
||||
break;
|
||||
case "@":
|
||||
await this.sendInput(to.slice(1), message, edit);
|
||||
break;
|
||||
case "#":
|
||||
await this.sendInputGroup(to.slice(1), message, edit);
|
||||
break;
|
||||
default:
|
||||
this.show("error", "Message should start with @user or #group");
|
||||
}
|
||||
} else if (this.demoInput.value === "" && key !== "@" && key !== "#") {
|
||||
const channel = this.currentChannel();
|
||||
if (channel) this.demoInput.value = channel + " ";
|
||||
}
|
||||
});
|
||||
on("keydown", this.demoInput, async (e) => {
|
||||
switch (e.key) {
|
||||
case "ArrowUp":
|
||||
if (this.demoInput.value === "" && this.lastMessage) {
|
||||
const str = (this.demoInput.value = this.lastMessage.innerText);
|
||||
editMode = true;
|
||||
await delay(0);
|
||||
this.demoInput.selectionStart = str.length;
|
||||
}
|
||||
break;
|
||||
case "Tab": {
|
||||
e.preventDefault();
|
||||
const userIndex = this.users.indexOf(this);
|
||||
const nextIndex = (userIndex + 1) % this.users.length;
|
||||
this.users[nextIndex].demoInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async sendInput(name, message, edit) {
|
||||
if (name === this.name) {
|
||||
this.show("error", "Can't send message to yourself");
|
||||
return;
|
||||
}
|
||||
const recipient = this.group.find((u) => u.name === name);
|
||||
if (recipient === undefined) {
|
||||
const knownNames = this.group.map((u) => `@${u.name}`).join(", ") + ` or @${this.name}`;
|
||||
this.show("error", `Unknown recipient @${name} (try ${knownNames})`);
|
||||
return;
|
||||
}
|
||||
this.show("sent", `@${name} ${message}`, edit);
|
||||
this.demoInput.value = "";
|
||||
await recipient.receive(this, message, edit);
|
||||
}
|
||||
|
||||
async sendInputGroup(name, message, edit) {
|
||||
if (name !== this.groupName) {
|
||||
this.show("error", `Unknown group #${name} (try #team)`);
|
||||
return;
|
||||
}
|
||||
this.show("sent", `#${name} ${message}`, edit);
|
||||
this.demoInput.value = "";
|
||||
await this.receiveGroup(message, edit);
|
||||
}
|
||||
|
||||
get lastMessage() {
|
||||
const messages = this.display.childNodes;
|
||||
return messages[messages.length - 1];
|
||||
}
|
||||
|
||||
currentChannel() {
|
||||
return this.lastMessage && toContact(this.lastMessage.childNodes[0].innerHTML);
|
||||
}
|
||||
|
||||
setupMoveWindow() {
|
||||
let moving = false;
|
||||
let startX, startY;
|
||||
const user = this.userWindow;
|
||||
const parent = user.parentNode;
|
||||
|
||||
on("mousedown", this.terminal, (e) => {
|
||||
if (e.clientY - this.terminal.getBoundingClientRect().top > 20) return;
|
||||
moving = true;
|
||||
startX = user.offsetLeft - e.clientX;
|
||||
startY = user.offsetTop - e.clientY;
|
||||
parent.removeChild(user);
|
||||
parent.appendChild(user);
|
||||
});
|
||||
on("mouseup", this.terminal, () => (moving = false));
|
||||
on("mouseleave", this.terminal, () => (moving = false));
|
||||
on("mousemove", this.terminal, (e) => {
|
||||
if (!moving) return;
|
||||
user.style.left = e.clientX + startX + "px";
|
||||
user.style.top = e.clientY + startY + "px";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toContact(str) {
|
||||
return str.endsWith(">") ? "@" + str.slice(0, -4) : str;
|
||||
}
|
||||
|
||||
function setGroup(groupName, users) {
|
||||
users.forEach((u) => u.setGroup(groupName, users));
|
||||
}
|
||||
|
||||
const alice = new User("alice");
|
||||
const bob = new User("bob");
|
||||
const tom = new User("tom");
|
||||
const team = [alice, bob, tom];
|
||||
setGroup("team", team);
|
||||
|
||||
async function chatDemo() {
|
||||
team.forEach((u) => u.reset());
|
||||
await alice.sendGroup("please review my PR project/site#72", true);
|
||||
await tom.sendGroup("anybody got application key 🔑?");
|
||||
await bob.sendGroup("looking at it now @alice 👀");
|
||||
await alice.sendGroup("thanks @bob!");
|
||||
await alice.sendGroup("will DM @tom");
|
||||
await alice.send(tom, "w3@o6CewoZx#%$SQETXbWnus", true, true, true);
|
||||
await tom.send(alice, "you're the savior 🙏!");
|
||||
await alice.send(bob, "please check the tests too", true);
|
||||
await bob.send(alice, "all looks good 👍");
|
||||
await alice.send(bob, "thank you!");
|
||||
DELAY = 80;
|
||||
}
|
||||
|
||||
const invitation =
|
||||
"smp::example.com:5223#1XNE1m2E1m0lm92​WG​Ket9CL6+lO742Vy5​G6nsrkvgs8=::St9hPY+k6nfrbaXj::rsa:MII​BoTANBgkqhkiG9w0B​AQEFAAOCAY4AMIIBiQKCAQEA03XGpEqh3faDN​Gl06pPhaT==";
|
||||
|
||||
async function establishConnection() {
|
||||
team.forEach((u) => u.reset());
|
||||
await alice.type("/add bob");
|
||||
await delay(10);
|
||||
alice.resetInput();
|
||||
// alice.show("/add bob");
|
||||
alice.show("sent", "pass this invitation to your contact (via any channel):");
|
||||
alice.show("sent", " ");
|
||||
alice.show("sent", invitation);
|
||||
alice.show("sent", " ");
|
||||
alice.show("sent", "and ask them to connect:");
|
||||
alice.show("sent", "/c name_for_you invitation_above");
|
||||
await delay(20);
|
||||
await bob.type("/connect alice ");
|
||||
await bob.type(invitation, true);
|
||||
await delay(20);
|
||||
bob.resetInput();
|
||||
await bob.show("received", "/connect alice " + invitation);
|
||||
await delay(10);
|
||||
bob.show("received", "@alice connected");
|
||||
await delay(2);
|
||||
alice.show("received", "@bob connected");
|
||||
await alice.send(bob, "hello bob");
|
||||
await bob.send(alice, "hi alice");
|
||||
}
|
||||
|
||||
await chatDemo();
|
||||
const RUN_DEMO = "#demo .run-demo";
|
||||
const RUN_FASTER = "#demo .run-faster";
|
||||
const TRY_IT = "#demo .try-it";
|
||||
onClick(RUN_DEMO, runChatDemo);
|
||||
// onClick(RUN_DEMO, establishConnection);
|
||||
onClick(RUN_FASTER, () => (DELAY /= 2));
|
||||
onClick(TRY_IT, tryChatDemo);
|
||||
|
||||
async function runChatDemo() {
|
||||
show(RUN_DEMO, false);
|
||||
show(RUN_FASTER);
|
||||
enable(TRY_IT, false);
|
||||
await chatDemo();
|
||||
show(RUN_DEMO);
|
||||
show(RUN_FASTER, false);
|
||||
enable(TRY_IT);
|
||||
}
|
||||
|
||||
function tryChatDemo() {
|
||||
team.forEach((u) => u.tryDemo());
|
||||
alice.demoInput.focus();
|
||||
}
|
||||
|
||||
async function delay(units) {
|
||||
// delay is random with `1 +/- DISTR` range
|
||||
const ms = units * DELAY * (1 - DISTR + 2 * DISTR * Math.random());
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function highlight(str) {
|
||||
return str
|
||||
.replace(/(@[a-z]+)([^0-9]|$)/gi, `<span class="recipient">$1</span>$2`)
|
||||
.replace(/([a-z]+>)([^0-9]|$)/gi, `<span class="sender">$1</span>$2`)
|
||||
.replace(/(#[a-z]+)([^0-9]|$)/gi, `<span class="group">$1</span>$2`)
|
||||
.replace(/#([^\s]+)#([\s]|$)/gi, `#<span class="secret">$1</span>#$2`);
|
||||
}
|
||||
|
||||
function toSecret(secret, message) {
|
||||
return secret ? `#${message}#` : message;
|
||||
}
|
||||
|
||||
function isAlpha(c) {
|
||||
c = c.toUpperCase();
|
||||
return c >= "A" && c <= "Z";
|
||||
}
|
||||
|
||||
let flipper = setInterval(flipProblem, 10000);
|
||||
|
||||
onClick("#problem .pagination", () => {
|
||||
clearInterval(flipper);
|
||||
flipper = setInterval(flipProblem, 20000);
|
||||
});
|
||||
|
||||
function flipProblem() {
|
||||
if (isElementInViewport(document.getElementById("problem"))) {
|
||||
window.location.hash =
|
||||
window.location.hash === "#problem-explained" ? "#problem-intro" : "#problem-explained";
|
||||
}
|
||||
}
|
||||
|
||||
function isElementInViewport(el) {
|
||||
if (!el) return false;
|
||||
const r = el.getBoundingClientRect();
|
||||
return r.bottom >= 0 && r.top <= window.innerHeight;
|
||||
}
|
||||
|
||||
function onClick(selector, handler, enable = true) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) on("click", el, handler, enable);
|
||||
}
|
||||
|
||||
function on(event, el, handler, enable = true) {
|
||||
const method = enable ? "addEventListener" : "removeEventListener";
|
||||
el[method](event, handler);
|
||||
}
|
||||
|
||||
function show(selector, visible = true) {
|
||||
const el = typeof selector === "string" ? document.querySelector(selector) : selector;
|
||||
if (el) el.style.display = visible ? "block" : "none";
|
||||
}
|
||||
|
||||
function enable(selector, enabled = true) {
|
||||
const el = document.querySelector(selector);
|
||||
el.disabled = enabled ? "" : "true";
|
||||
}
|
||||
})();
|
47
js/docs.js
47
js/docs.js
|
@ -1,4 +1,51 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (window.location.pathname.endsWith('cli.html')) {
|
||||
const cliHeader = document.querySelector('h1')
|
||||
const demoSection = document.createElement('section')
|
||||
demoSection.id = 'demo'
|
||||
demoSection.innerHTML = `
|
||||
<div class="all-users">
|
||||
<div class="user alice">
|
||||
<h3>@alice</h3>
|
||||
<div class="terminal">
|
||||
<div class="display"></div>
|
||||
<div class="input"></div>
|
||||
<input type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="user bob">
|
||||
<h3>@bob</h3>
|
||||
<div class="terminal">
|
||||
<div class="display"></div>
|
||||
<div class="input"></div>
|
||||
<input type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="user tom">
|
||||
<h3>@tom</h3>
|
||||
<div class="terminal">
|
||||
<div class="display"></div>
|
||||
<div class="input"></div>
|
||||
<input type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="run-demo">Run demo</button>
|
||||
<button class="run-faster">Faster!</button>
|
||||
<button class="try-it">Try it!</button>
|
||||
</div>
|
||||
`
|
||||
cliHeader.parentNode.insertBefore(demoSection, cliHeader.nextSibling)
|
||||
|
||||
const demoScript = document.createElement('script')
|
||||
demoScript.src = '/js/demo.js'
|
||||
document.body.appendChild(demoScript)
|
||||
|
||||
const demoStyles = document.createElement('link')
|
||||
demoStyles.rel = 'stylesheet'
|
||||
demoStyles.href = '/css/demo.css'
|
||||
document.head.appendChild(demoStyles)
|
||||
}
|
||||
|
||||
const imgs = document.querySelectorAll('p img')
|
||||
imgs.forEach(img => {
|
||||
console.log(img.height)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue