frontend: check in html, css, js

This commit is contained in:
Adhityaa 2018-06-03 21:36:17 +05:30
parent 5b7b97a4bf
commit a88dea879b
38 changed files with 3153 additions and 4 deletions

View File

@ -1,7 +1,15 @@
SHELL = bash
# list of JS files to be built
JS_BUILD =
JS_BUILD = jquery.js vue.js highlight.js chartist.js login.js signup.js dashboard.js
jquery.js = jquery.js
vue.js = vue.js
highlight.js = highlight.js
chartist.js = chartist.js
login.js = utils.js http.js auth-common.js login.js
signup.js = utils.js http.js auth-common.js signup.js
dashboard.js = utils.js http.js errors.js self.js dashboard.js dashboard-setting.js dashboard-domain.js dashboard-installation.js dashboard-general.js dashboard-moderation.js dashboard-statistics.js dashboard-import.js dashboard-danger.js
# for each file in $(JS_BUILD), list its composition
@ -29,8 +37,6 @@ JS_PROD_BUILD_FILES = $(addprefix $(JS_PROD_BUILD_DIR)/, $(JS_BUILD))
JS_MINIFIER = uglifyjs
JS_MINIFIER_FLAGS = --compress --mangle
ENV_JS = env.js
SASS_SRC_DIR = sass
SASS_SRC_FILES = $(wildcard $(SASS_SRC_DIR)/*.scss)
CSS_DEVEL_BUILD_DIR = $(DEVEL_BUILD_DIR)/css
@ -69,7 +75,7 @@ $(HTML_PROD_BUILD_FILES): $(HTML_PROD_BUILD_DIR)/%.html: $(HTML_DEVEL_BUILD_DIR)
devel-js: $(JS_DEVEL_BUILD_FILES)
.SECONDEXPANSION:
$(JS_DEVEL_BUILD_FILES): $(JS_DEVEL_BUILD_DIR)/%.js: $$(addprefix $$(JS_SRC_DIR)/, $$(%.js) $$(ENV_JS))
$(JS_DEVEL_BUILD_FILES): $(JS_DEVEL_BUILD_DIR)/%.js: $$(addprefix $$(JS_SRC_DIR)/, $$(%.js))
>$@; \
for f in $^; do \
printf "// %s\n" "$$f" >>$@; \

1
frontend/account.html Normal file
View File

@ -0,0 +1 @@

383
frontend/dashboard.html Normal file
View File

@ -0,0 +1,383 @@
<html>
<head>
<script src="<<<.CdnPrefix>>>/js/jquery.js"></script>
<script src="<<<.CdnPrefix>>>/js/vue.js"></script>
<script src="<<<.CdnPrefix>>>/js/highlight.js"></script>
<script src="<<<.CdnPrefix>>>/js/chartist.js"></script>
<script src="<<<.CdnPrefix>>>/js/dashboard.js"></script>
<link rel="stylesheet" href="<<<.CdnPrefix>>>/css/chartist.css">
<link rel="stylesheet" href="<<<.CdnPrefix>>>/css/dashboard.css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
<title>Commento: Dashboard</title>
</head>
<div id="navbar" class="navbar">
<a href="/" class="navbar-item navbar-logo-text"><img src="/images/logo.svg" class="navbar-logo">Commento</a>
<div class="navbar-item float-right">
<a href="/logout" class="navbar-item">Logout</a>
<a href="/account" class="navbar-item">Account Settings</a>
<div id="owner-name">{{name}}</div>
</div>
</div>
<script>
window.onload = function() {
window.selfGet(function() {
window.vueConstruct(function() {
window.navbarFill();
window.domainRefresh();
$(document).ready(function(){
$("ul.tabs li").click(function(){
var tab_id = $(this).attr("data-tab");
$("ul.tabs li").removeClass("current");
$(".content").removeClass("current");
$(this).addClass("current");
$("#"+tab_id).addClass("current");
});
});
});
});
};
</script>
<div class="global-error" id="global-error"></div>
<div class="global-ok" id="global-ok"></div>
<div id="dashboard" class="dashboard-container">
<div class="pane-left">
<div class="tree" v-if="domains.length == 0 || domains[0].show == false">
<img class="tree-svg" src="/images/tree.svg">
It's so quiet in here.
</div>
<div class="pane-setting" v-for="domain in domains" v-on:click="window.domainSelect(domain.domain)" id="{{domain.hex}}" v-bind:class="{selected: domain.selected}" v-if="domain.show">
<div class="pane-setting-inside">
<div class="setting-title">{{domain.name}}</div>
<div class="setting-subtitle">{{domain.domain}}</div>
</div>
</div>
<div class="pane-setting" id="domain-add" onclick="window.location.hash='#new-domain-modal'">
<div class="pane-setting-inside super-setting">
<div class="super-setting-title">+</div>
<div class="super-setting-text">New Domain</div>
</div>
</div>
</div>
<div class="pane-middle">
<div v-if="showSettings" class="pane-setting" v-for="setting in settings" v-on:click="window.settingSelect(setting.id)" id="{{setting.id}}" v-bind:class="{selected: setting.selected}">
<div class="pane-setting-inside">
<div class="setting-title">{{setting.text}}</div>
<div class="setting-subtitle">{{setting.meaning}}</div>
</div>
</div>
<div class="select-a-domain" v-if="!showSettings">
Select a website on the left.
</div>
</div>
<div class="pane-right">
<!-- Installation -->
<div id="installation-view" class="view hidden">
<div class="view-inside">
<div class="large-view">
<div class="tabs-container">
<div class="tab">
<ul class="tabs">
<li class="tab-link original current" data-tab="install-tab-1">Universal Snippet</li>
</ul>
<div id="install-tab-1" class="content original current">
<div class="import-text">
Copy the following piece of HTML code and paste it where you'd like Commento to load.
</div>
<pre><code id="code-div" class="html"></code></pre>
<div class="text">
And that's it. All your settings, themes, and comments would be automagically loaded. Commento is mobile-responsive too, as it simply fills the container it is put in.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Stats -->
<div id="statistics-view" class="view hidden">
<div class="view-inside">
<div class="mid-view center">
<div class="center center-title">
Analytics
</div>
<div class="stat">
<div class="number">
<div class="digits gray-digits">{{domains[cd].viewsLast30Days.zeros}}</div>
<div class="digits">{{domains[cd].viewsLast30Days.num}}</div>
<div class="digits dark-gray-digits">{{domains[cd].viewsLast30Days.units}}</div>
</div>
<div class="text">
views in the last 30 days
</div>
<div class="graph" id="views-graph"></div>
</div>
<div class="stat">
<div class="number">
<div class="digits gray-digits">{{domains[cd].commentsLast30Days.zeros}}</div>
<div class="digits">{{domains[cd].commentsLast30Days.num}}</div>
<div class="digits dark-gray-digits">{{domains[cd].commentsLast30Days.units}}</div>
</div>
<div class="text">
comments in the last 30 days
</div>
<div class="graph" id="comments-graph"></div>
</div>
</div>
</div>
</div>
<!-- moderation -->
<div id="moderation-view" class="view hidden">
<div class="view-inside">
<div class="small-view mid-view">
<div class="tabs-container">
<div class="tab">
<ul class="tabs">
<li class="tab-link original current" data-tab="mod-tab-1">Moderator List</li>
</ul>
<div id="mod-tab-1" class="content original current">
<div class="pitch">
Moderators have the power to approve and delete comments. To make someone a moderator, add their email address down below. Once added, shiny new moderation buttons will appear on each comment for that person on each page on this domain.
</div>
<div class="email-container">
<div class="email">
<input class="input" type="text" id="new-mod" placeholder="Email">
<button id="new-mod-button" class="email-button" onclick="window.moderatorNewHandler()">Add moderator</button>
</div>
</div>
<div class="mod-emails-container">
<div class="content">
<div class="mod-email" v-for="email in domains[cd].moderators" v-if="domains[cd].moderators.length > 0">
<div class="email">{{email.email}}</div>
<div class="delete" v-on:click="window.moderatorDeleteHandler(email.email)">Delete</div>
<div class="date">added {{email.timeAgo}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- General Settings -->
<div id="general-view" class="view hidden">
<div class="view-inside">
<div class="small-mid-view">
<div class="center center-title">
General Settings
</div>
<div class="box">
<div class="row">
<div class="label">Website Name</div>
<input class="input gray-input" id="cur-domain-name" type="text" :placeholder="domains[cd].origName" v-model="domains[cd].name">
</div>
<div class="row no-border round-check">
<input type="checkbox" class="switch" v-model="domains[cd].autoSpamFilter" id="spam-filtering">
<label for="spam-filtering">Automatic spam filtering</label>
<div class="pitch">
Commento uses Akismet's advanced spam detection techniques to automatically identify and remove spam comments. We strongly recommended you leave enabled.
</div>
</div>
<div class="row no-border round-check">
<input type="checkbox" class="switch" v-model="domains[cd].requireModeration" id="require-moderation">
<label for="require-moderation">Require all comments to be approved manually</label>
<div class="pitch">
Enabling this would require a moderator to approve every comment. Moderators can manually delete comments even if this is not enabled.
</div>
</div>
<div class="row no-border round-check">
<input type="checkbox" class="switch" v-model="domains[cd].requireIdentification" id="require-identification">
<label for="require-identification">Require identification</label>
<div class="pitch">
Enabling this would require all commenters to authenticate themselves (using their Google account, for example). Disabling would optionally allow anonymous comments.
</div>
</div>
<div id="new-domain-error" class="modal-error-box"></div>
</div>
<div class="center">
<button id="save-general-button" onclick="window.generalSaveHandler()" class="button">Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Import Comments -->
<div id="import-view" class="view hidden">
<div class="view-inside">
<div class="large-view">
<div class="tabs-container">
<div class="tab">
<ul class="tabs">
<li class="tab-link original current" data-tab="install-tab-1">Disqus</li>
</ul>
<div id="install-tab-1" class="content original current">
<div class="import-text">
If you're currently using Disqus and want to import all your comments into Commento, you can do so:
<ul>
<li>
Go to <a href="http://disqus.com/admin/discussions/export/">the admin export section</a> in Disqus and click on <b>Export Comments</b>. This should start the process of exporting your comments.
</li>
<li>
After a while, you'll receive an email from Disqus with a link to a compressed archive of all comments and associated data. Copy and paste that link here and start the import process:
<br><br>
<div class="email-container">
<div class="email">
<input class="input" type="text" id="new-mod" placeholder="https://media.disqus.com/uploads/...">
<button class="email-button">Import</button>
</div>
</div>
<div class="subtext-container">
<div class="subtext">
<div>Note: it is strongly recommended you do this only once. Multiple imports for the same domain may have unintended effects.</div>
</div>
</div>
</li>
<li>
We'll automatically download this file, extract it, parse it and import comments into Commento. The URL information will be preserved. By using this service, you grant Commento the permission to download and process your Disqus information.
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Danger Zone -->
<div id="danger-view" class="view hidden">
<div class="view-inside">
<div class="mid-view">
<div class="tabs-container">
<div class="tab">
<ul class="tabs">
<li class="tab-link original current" data-tab="danger-tab-1">Freeze Comments</li>
<li class="tab-link current" data-tab="danger-tab-2">Delete Domain</li>
</ul>
<div id="danger-tab-1" class="content original current">
<div class="box" v-if="domains[cd].state == 'frozen'">
<div class="box-subtitle">
If you desire to re-allow comments again on your website, you can do so. You can, of course, freeze the site again in the future.
</div>
<button onclick="window.location.hash='#unfreeze-domain-modal'" class="button green-button">Unfreeze Domain</button>
</div>
<div class="box" v-if="domains[cd].state != 'frozen'">
<div class="box-subtitle">
If you desire to temporarily freeze new comments (domain-wide), thereby making it read-only, you can do so. You can choose to unfreeze later; this is temporary.
</div>
<button id="orange-button" onclick="window.location.hash='#freeze-domain-modal'" class="button orange-button">Freeze Domain</button>
</div>
</div>
</div>
<div id="danger-tab-2" class="content">
<div class="box">
<div class="box-subtitle">
Want to completely remove Commento from your website? This will permanently delete all comments and there is literally no way to retrieve your data once you do this.
</div>
<button id="big-red-button" class="button big-red-button" onclick="window.location.hash='#delete-domain-modal'">Delete Domain</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="freeze-domain-modal" class="modal-window">
<div class="inside">
<a href="#modal-close" title="Close" class="modal-close"></a>
<div class="modal-title">Freeze Domain</div>
<div class="modal-subtitle">
Are you absolutely sure you want to freeze your domain, thereby making it read-only? You can choose to unfreeze later; this is temporary.
</div>
<div class="modal-contents">
<button id="orange-button" class="button orange-button" onclick="window.domainFreezeHandler()">Freeze Domain</button>
</div>
</div>
</div>
<div id="unfreeze-domain-modal" class="modal-window">
<div class="inside">
<a href="#modal-close" title="Close" class="modal-close"></a>
<div class="modal-title">Unfreeze Domain</div>
<div class="modal-subtitle">
Are you absolutely sure you want to unfreeze your domain? This will re-allow new comments. You can choose to freeze again in the future.
</div>
<div class="modal-contents">
<button id="blue-button" class="button green-button" onclick="window.domainUnfreezeHandler()">Unfreeze Domain</button>
</div>
</div>
</div>
<div id="delete-domain-modal" class="modal-window">
<div class="inside">
<a href="#modal-close" title="Close" class="modal-close"></a>
<div class="modal-title">Delete Domain</div>
<div class="modal-subtitle">
Are you absolutely sure? This will permanently delete all comments and there is literally no way to retrieve your data once you do this.
</div>
<div class="modal-contents">
<button id="big-red-button" class="button big-red-button" onclick="window.domainDeleteHandler()">Delete Domain</button>
</div>
</div>
</div>
<div id="new-domain-modal" class="modal-window">
<div class="inside">
<a href="#modal-close" title="Close" class="modal-close"></a>
<div class="modal-title">Add a New Domain</div>
<div class="modal-contents">
<div class="box">
<div class="box-subtitle">
Remember to double-check the domain, as only connections from this domain will be accepted. You <b>cannot</b> change this in the future. You can change the name, however.
</div>
<div class="row">
<div class="label">Website Name</div>
<input class="input gray-input" id="new-domain-name" type="text" placeholder="Billie Joe's Blog">
</div>
<div class="row">
<div class="label">Website Domain</div>
<input class="input gray-input" id="new-domain-domain" type="text" placeholder="https://blog.billie.com">
</div>
</div>
<div id="new-domain-error" class="modal-error-box"></div>
<div class="center">
<button id="add-site-button" onclick="window.domainNewHandler()" class="button">Add Domain</button>
</div>
</div>
</div>
</div>
</html>

BIN
frontend/images/120x120.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22px" height="22px">
<g id="Home-(v2)" fill="none" fill-rule="evenodd">
<g id="Artboard-3" fill="#CFD7DF">
<path id="Close" d="M11 9.55L7.75 6.3A1.02 1.02 0 0 0 6.3 7.75L9.55 11 6.3 14.25a1.02 1.02 0 0 0 1.45 1.45L11 12.45l3.25 3.25a1.02 1.02 0 0 0 1.45-1.45L12.45 11l3.25-3.25a1.02 1.02 0 0 0-1.45-1.45L11 9.55zM11 22a11 11 0 1 1 0-22 11 11 0 0 1 0 22z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 439 B

10
frontend/images/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

38
frontend/images/tree.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,11 @@
(function (global, document) {
// Prefills the email field from the URL parameter.
global.prefillEmail = function() {
if (paramGet("email") != undefined) {
$("#email").val(paramGet("email"));
$("#password").click();
}
};
} (window, document));

10
frontend/js/chartist.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,41 @@
(function (global, document) {
// Opens the danger zone.
global.dangerOpen = function() {
$(".view").hide();
$("#danger-view").show();
};
// Deletes a domain.
global.domainDeleteHandler = function() {
var data = global.dashboard.$data;
domainDelete(data.domains[data.cd].domain, function(success) {
if (success)
document.location = '/dashboard';
});
}
// Freezes a domain.
global.domainFreezeHandler = function() {
var data = global.dashboard.$data;
data.domains[data.cd].state = "frozen"
domainUpdate(data.domains[data.cd])
document.location.hash = "#modal-close";
}
// Unfreezes a domain.
global.domainUnfreezeHandler = function() {
var data = global.dashboard.$data;
data.domains[data.cd].state = "unfrozen"
domainUpdate(data.domains[data.cd])
document.location.hash = "#modal-close";
}
} (window, document));

View File

@ -0,0 +1,144 @@
(function (global, document) {
// Selects a domain.
global.domainSelect = function(domain) {
var data = global.dashboard.$data;
var domains = data.domains;
for (var i = 0; i < domains.length; i++) {
if (domains[i].domain == domain) {
vs("frozen", domains[i].state == "frozen");
domains[i].selected = true;
data.cd = i;
data.importedComments = domains[i].importedComments;
}
else
domains[i].selected = false;
}
data.showSettings = true;
settingDeselectAll();
$(".view").hide();
};
// Deselects all domains.
global.domainDeselectAll = function() {
var data = global.dashboard.$data;
var domains = data.domains;
for (var i = 0; i < domains.length; i++)
domains[i].selected = false;
}
// Creates a new domain.
global.domainNewHandler = function() {
var json = {
session: global.cookieGet("session"),
name: $("#new-domain-name").val(),
domain: $("#new-domain-domain").val(),
}
global.buttonDisable("#add-site-button");
global.post(global.origin + "/api/domain/new", json, function(resp) {
global.buttonEnable("#add-site-button");
$("#new-domain-name").val("");
$("#new-domain-domain").val("");
document.location.hash = "#modal-close";
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
global.domainRefresh(function() {
global.domainSelect(resp.domain);
global.domainDeselectAll();
global.settingSelect("installation");
});
});
}
// Refreshes the list of domains.
global.domainRefresh = function(callback) {
var json = {
session: global.cookieGet("session"),
};
global.post(global.origin + "/api/domain/list", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
resp.domains = resp.domains.sort(function(a, b) {
var x = a.creationDate; var y = b.creationDate;
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
for (var i = 0; i < resp.domains.length; i++) {
resp.domains[i].show = true;
resp.domains[i].selected = false;
resp.domains[i].origName = resp.domains[i].name;
resp.domains[i].origDomain = resp.domains[i].domain;
resp.domains[i].viewsLast30Days = global.numberify(0);
resp.domains[i].commentsLast30Days = global.numberify(0);
for (var j = 0; j < resp.domains[i].moderators.length; j++) {
resp.domains[i].moderators[j].timeAgo = global.timeSince(
Date.parse(resp.domains[i].moderators[j].addDate));
}
}
global.vs("domains", resp.domains);
if (callback !== undefined)
callback();
});
};
// Updates a domain with the backend.
global.domainUpdate = function(domain, callback) {
var json = {
session: global.cookieGet("session"),
domain: domain,
};
global.post(global.origin + "/api/domain/update", json, function(resp) {
if (callback !== undefined)
callback(resp.success);
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
});
}
// Deletes a domain.
global.domainDelete = function(domain, callback) {
var json = {
session: global.cookieGet("session"),
domain: domain,
};
global.post(global.origin + "/api/domain/delete", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
if (callback !== undefined)
callback(resp.success);
});
}
} (window, document));

View File

@ -0,0 +1,19 @@
(function (global, document) {
// Opens the general settings window.
global.generalOpen = function() {
$(".view").hide();
$("#general-view").show();
};
global.generalSaveHandler = function() {
var data = global.dashboard.$data;
global.buttonDisable("#save-general-button");
global.domainUpdate(data.domains[data.cd], function() {
global.globalOKShow("Settings saved!");
global.buttonEnable("#save-general-button");
});
};
} (window, document));

View File

@ -0,0 +1,33 @@
(function (global, document) {
// Opens the import window.
global.importOpen = function() {
$(".view").hide();
$("#import-view").show();
}
global.importDisqus = function() {
var url = $("#disqus-url").val();
var data = global.dashboard.$data;
var json = {
session: global.cookieGet("session"),
domain: data.domains[data.cd].domain,
url: url,
}
global.buttonDisable("#disqus-import-button");
global.post(global.origin + "/api/import/disqus", json, function(resp) {
global.buttonEnable("#disqus-import-button");
if (!resp.success) {
global.globalErrorShow(resp.message);
return;
}
globalOKShow("Imported " + resp.numImported + " comments!");
});
}
} (window, document));

View File

@ -0,0 +1,22 @@
(function (global, document) {
// Opens the installation view.
global.installationOpen = function() {
var data = global.dashboard.$data;
var html = '' +
'<div id="commento"></div>\n' +
'<script src="' + window.cdn + '/js/commento.js"><\/script>\n' +
'';
$("#code-div").text(html);
$('pre code').each(function(i, block) {
hljs.highlightBlock(block);
});
$(".view").hide();
$("#installation-view").show();
};
} (window, document));

View File

@ -0,0 +1,82 @@
(function (global, document) {
// Opens the moderatiosn settings window.
global.moderationOpen = function() {
$(".view").hide();
$("#moderation-view").show();
};
// Adds a moderator.
global.moderatorNewHandler = function() {
var data = global.dashboard.$data;
var email = $("#new-mod").val();
var json = {
session: global.cookieGet("session"),
domain: data.domains[data.cd].domain,
email: email,
}
var idx = -1;
for (var i = 0; i < data.domains[data.cd].moderators.length; i++) {
if (data.domains[data.cd].moderators[i].email == email) {
idx = i;
break;
}
}
if (idx == -1) {
data.domains[data.cd].moderators.push({"email": email, "timeAgo": "just now"});
global.buttonDisable("#new-mod-button");
global.post(global.origin + "/api/domain/moderator/new", json, function(resp) {
global.buttonEnable("#new-mod-button");
if (!resp.success) {
global.globalErrorShow(resp.message);
return
}
global.globalOKShow("Added a new moderator!");
$("#new-mod").val("");
$("#new-mod").focus();
});
}
else {
global.globalErrorShow("Already a moderator.");
}
}
// Deletes a moderator.
global.moderatorDeleteHandler = function(email) {
var data = global.dashboard.$data;
var json = {
session: global.cookieGet("session"),
domain: data.domains[data.cd].domain,
email: email,
}
var idx = -1;
for (var i = 0; i < data.domains[data.cd].moderators.length; i++) {
if (data.domains[data.cd].moderators[i].email == email) {
idx = i;
break;
}
}
if (idx != -1) {
data.domains[data.cd].moderators.splice(idx, 1);
global.post(global.origin + "/api/domain/moderator/delete", json, function(resp) {
if (!resp.success) {
global.globalErrorShow(resp.message);
return
}
globalOKShow("Removed!");
});
}
}
} (window, document));

View File

@ -0,0 +1,46 @@
(function (global, document) {
// Sets the vue.js toggle to select and deselect panes visually.
function settingSelectCSS(id) {
var data = global.dashboard.$data;
var settings = data.settings;
for (var i = 0; i < settings.length; i++) {
if (settings[i].id == id) {
settings[i].selected = true;
}
else {
settings[i].selected = false;
}
}
}
// Selects a setting.
global.settingSelect = function(id) {
var data = global.dashboard.$data;
var settings = data.settings;
settingSelectCSS(id);
$("ul.tabs li").removeClass("current");
$(".content").removeClass("current");
$(".original").addClass("current");
for (var i = 0; i < settings.length; i++) {
if (id == settings[i].id)
settings[i].open();
}
};
// Deselects all settings.
global.settingDeselectAll = function() {
var data = global.dashboard.$data;
var settings = data.settings;
for (var i = 0; i < settings.length; i++)
settings[i].selected = false;
}
} (window, document));

View File

@ -0,0 +1,100 @@
(function (global, document) {
global.numberify = function(x) {
if (x == 0)
return {"zeros": "000", "num": "", "units": ""}
if (x < 10)
return {"zeros": "00", "num": x, "units": ""}
if (x < 100)
return {"zeros": "0", "num": x, "units": ""}
if (x < 1000)
return {"zeros": "", "num": x, "units": ""}
var res;
if (x < 1000000) {
res = numberify((x/1000).toFixed(0))
res.units = "K"
}
else if (x < 1000000000) {
res = numberify((x/1000000).toFixed(0))
res.units = "M"
}
else if (x < 1000000000000) {
res = numberify((x/1000000000).toFixed(0))
res.units = "B"
}
if (res.num*10 % 10 == 0)
res.num = Math.ceil(res.num);
return res;
}
global.statisticsOpen = function() {
var data = global.dashboard.$data;
var json = {
session: global.cookieGet("session"),
domain: data.domains[data.cd].domain,
}
$(".view").hide();
post(global.origin + "/api/domain/statistics", json, function(resp) {
$("#statistics-view").show();
if (!resp.success) {
globalErrorShow(resp.message);
return;
}
var options = {
showPoint: false,
axisY: {
onlyInteger: true,
showGrid: false,
},
axisX: {
showGrid: false,
},
showArea: true,
};
var views;
var comments;
views = resp.viewsLast30Days;
// views = [0, 1, 4, 16, 14, 12, 10, 25, 13, 5, 20, 25, 12, 57, 46, 64, 4, 36, 7, 80, 43, 86, 121, 6, 74, 94, 83, 73, 140, 89, 25];
comments = resp.commentsLast30Days;
// comments = [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 5, 9, 9, 5, 6, 7, 8, 3, 1, 16, 3, 10, 8, 5, 12, 5, 4, 8, 4, 23, 19];
var labels = new Array();
for (var i = 0; i < views.length; i++) {
if ((views.length-i) % 7 == 0) {
var x = (views.length-i)/7;
labels.push(x + " week" + (x > 1 ? "s" : "") + " ago");
}
else
labels.push("");
}
new Chartist.Line("#views-graph", {
labels: labels,
series: [views],
}, options);
new Chartist.Line("#comments-graph", {
labels: labels,
series: [comments],
}, options);
data.domains[data.cd].viewsLast30Days = numberify(views.reduce(function(a, b) { return a + b; }, 0));
data.domains[data.cd].commentsLast30Days = numberify(comments.reduce(function(a, b) { return a + b; }, 0));
});
}
} (window, document));

89
frontend/js/dashboard.js Normal file
View File

@ -0,0 +1,89 @@
(function (global, document) {
// Sets a vue.js field. Short for "vue set".
function vs(field, value) {
Vue.set(global.dashboard, field, value);
}
global.vs = vs;
// Sets the owner's name in the navbar.
global.navbarFill = function() {
$("#owner-name").text(global.owner.name);
};
// Constructs the vue.js object
global.vueConstruct = function(callback) {
var settings = [
{
"id": "installation",
"text": "Installation",
"meaning": "Install Commento with HTML",
"selected": false,
"open": installationOpen,
},
{
"id": "general",
"text": "General Settings",
"meaning": "Names, domains and the rest",
"selected": false,
"open": generalOpen,
},
{
"id": "moderation",
"text": "Moderation Settings",
"meaning": "Approve and delete comments",
"selected": false,
"open": moderationOpen,
},
{
"id": "statistics",
"text": "Statistics",
"meaning": "Usage and comment statistics",
"selected": false,
"open": statisticsOpen,
},
{
"id": "import",
"text": "Import Comments",
"meaning": "Import from a different service",
"selected": false,
"open": importOpen,
},
{
"id": "danger",
"text": "Danger Zone",
"meaning": "Delete or freeze domain",
"selected": false,
"open": dangerOpen,
},
];
var reactiveData = {
// list of panes; mutable because selection information is stored within
settings: settings,
// list of domains dynamically loaded; obviously mutable
domains: [{show: false, viewsLast30Days: global.numberify(0), commentsLast30Days: global.numberify(0), moderators: []}],
// whether or not to show the settings column; mutable because we do not
// show the column until a domain has been selected
showSettings: false,
// currently selected domain index; obviously mutable
cd: 0, // stands for "current domain"
};
global.dashboard = new Vue({
el: "#dashboard",
data: reactiveData,
});
if (callback !== undefined)
callback();
};
} (window, document));

31
frontend/js/errors.js Normal file
View File

@ -0,0 +1,31 @@
(function (global, document) {
// Registers a given ID for a fade out after 5 seconds.
global.registerHide = function(id) {
var el = $(id);
setTimeout(function() {
$(id).fadeOut("fast");
}, 5000);
}
// Shows a global message on the given label ID and registers it for hiding.
global.showGlobalMessage = function(id, text) {
global.textSet(id, text);
global.registerHide(id);
}
// Shows a global error message.
global.globalErrorShow = function(text) {
global.showGlobalMessage("#global-error", text);
}
// Shows a global success message.
global.globalOKShow = function(text) {
global.showGlobalMessage("#global-ok", text);
}
} (window, document));

3
frontend/js/highlight.js Normal file

File diff suppressed because one or more lines are too long

31
frontend/js/http.js Normal file
View File

@ -0,0 +1,31 @@
(function (global, document) {
// Performs a JSON POST request to the given url with the given data and
// calls the callback function with the JSON response.
global.post = function(url, json, callback) {
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(json),
success: function(data) {
var resp = JSON.parse(data);
callback(resp);
},
});
}
// Performs a GET request and calls the callback function with the JSON
// response.
global.get = function(url, callback) {
$.ajax({
url: url,
type: "GET",
success: function(data) {
var resp = JSON.parse(data);
callback(resp);
},
});
}
} (window, document));

4
frontend/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

73
frontend/js/login.js Normal file
View File

@ -0,0 +1,73 @@
(function (global, document) {
// Shows messages produced from email confirmation attempts.
function displayConfirmedEmail() {
var confirmed = global.paramGet("confirmed");
if (confirmed == "true") {
$("#msg").html("Successfully confirmed! Login to continue.")
}
else if (confirmed == "false") {
$("#err").html("That link has expired.")
}
}
// Shows messages produced from password reset attempts.
function displayChangedPassword() {
var changed = paramGet("changed");
if (changed == "true") {
$("#msg").html("Password changed successfully! Login to continue.")
}
}
// Shows messages produced from completed signups.
function displaySignedUp() {
var signedUp = paramGet("signedUp");
if (signedUp == "true") {
$("#msg").html("Registration successful! Login to continue.")
}
}
// Shows email confirmation and password reset messages, if any.
global.displayMessages = function() {
displayConfirmedEmail();
displayChangedPassword();
displaySignedUp();
};
// Logs the user in and redirects to the dashboard.
global.login = function() {
var all_ok = global.unfilledMark(["#email", "#password"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
global.textSet("#err", "Please make sure all fields are filled");
return;
}
var json = {
"email": $("#email").val(),
"password": $("#password").val(),
};
global.buttonDisable("#login-button");
global.post(global.origin + "/api/owner/login", json, function(resp) {
global.buttonEnable("#login-button");
if (!resp.success) {
global.textSet("#err", resp.message);
return;
}
global.cookieSet("session", resp.session);
document.location = "/dashboard";
});
};
} (window, document));

20
frontend/js/self.js Normal file
View File

@ -0,0 +1,20 @@
(function (global, document) {
// Get self details.
global.selfGet = function(callback) {
var json = {
"session": global.cookieGet("session"),
};
global.post(global.origin + "/api/owner/self", json, function(resp) {
if (!resp.success || !resp.loggedIn) {
document.location = "/login";
return;
}
global.owner = resp.owner;
callback();
});
};
}(window, document));

43
frontend/js/signup.js Normal file
View File

@ -0,0 +1,43 @@
(function (global, document) {
// Signs up the user and redirects to either the login page or the email
// confirmation, depending on whether or not SMTP is configured in the
// backend.
global.signup = function() {
if ($("#password").val() != $("#password2").val()) {
global.textSet("#err", "The two passwords don't match");
return;
}
var all_ok = unfilledMark(["#email", "#name", "#password", "#password2"], function(el) {
el.css("border-bottom", "1px solid red");
});
if (!all_ok) {
global.textSet("#err", "Please make sure all fields are filled");
return;
}
var json = {
"email": $("#email").val(),
"name": $("#name").val(),
"password": $("#password").val(),
};
global.buttonDisable("#signup-button");
post(global.origin + "/api/owner/new", json, function(resp) {
global.buttonEnable("#signup-button")
if (!resp.success) {
global.textSet("#err", resp.message);
return;
}
if (resp.confirmEmail)
document.location = "/confirm-email";
else
document.location = "/login?signedUp=true";
});
};
} (window, document));

108
frontend/js/utils.js Normal file
View File

@ -0,0 +1,108 @@
(function (global, document) {
// Gets a GET parameter in the current URL.
global.paramGet = function(param) {
var pageURL = decodeURIComponent(window.location.search.substring(1));
var urlVariables = pageURL.split('&');
for (var i = 0; i < urlVariables.length; i++) {
var paramURL = urlVariables[i].split('=');
if (paramURL[0] === param)
return paramURL[1] === undefined ? true : paramURL[1];
}
return null;
}
// Sets the disabled attribute in a button.
global.buttonDisable = function(id) {
var el = $(id);
el.attr("disabled", true);
}
// Unsets the disabled attribute in a button.
global.buttonEnable = function(id) {
var el = $(id);
el.attr("disabled", false);
}
// Sets the text on the given label ID.
global.textSet = function(id, text) {
var el = $(id);
el.show();
el.text(text);
}
// Given an array of input IDs, this function calls a callback function with
// the first unfilled ID.
global.unfilledMark = function(fields, callback) {
var all_ok = true;
for (var i = 0; i < fields.length; i++) {
var el = $(fields[i]);
if (el.val() == "") {
callback(el);
}
}
return all_ok;
}
// Gets the value of a cookie.
global.cookieGet = function(name) {
var c = "; " + document.cookie;
var x = c.split("; " + name + "=");
if (x.length == 2)
return x.pop().split(";").shift();
};
// Sets the value of a cookie.
global.cookieSet = function(name, value) {
var expires = "";
var date = new Date();
date.setTime(date.getTime() + (365*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
document.cookie = name + "=" + value + expires + "; path=/";
}
// Converts a date in the past to a human-friendly duration relative to now.
global.timeSince = function(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1)
return interval + " years ago";
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + " months ago";
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + " days ago";
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + " hours ago";
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + " minutes ago";
if (seconds > 5)
return Math.floor(seconds) + " seconds ago";
else
return "just now";
}
} (window, document));

6
frontend/js/vue.js Normal file

File diff suppressed because one or more lines are too long

72
frontend/login.html Normal file
View File

@ -0,0 +1,72 @@
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
<script src="<<<.CdnPrefix>>>/js/jquery.js"></script>
<script src="<<<.CdnPrefix>>>/js/login.js"></script>
<link rel="stylesheet" href="<<<.CdnPrefix>>>/css/auth.css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
<title>Commento: Login</title>
</head>
<div class="navbar">
<a href="/" class="navbar-item navbar-logo-text"><img src="/images/logo.png" class="navbar-logo">Commento</a>
<a href="/login" class="navbar-item"><b>Login</b></a>
<a href="/signup" class="navbar-item">Signup</a>
</div>
<script>
window.onload = function() {
window.prefillEmail();
window.displayMessages();
};
</script>
<div class="auth-form-container">
<div class="auth-form">
<div class="form-title">
Login to continue
</div>
<div class="row">
<div class="label">Email Address</div>
<input class="input" type="text" name="email" id="email" placeholder="example@example.com">
</div>
<div class="row">
<div class="label">Password</div>
<input class="input" type="password" name="password" id="password" placeholder="">
</div>
<div class="err" id="err"></div>
<div class="msg" id="msg"></div>
<button id="button" class="button" onclick="window.login()">Login</button>
<a class="link" href="/forgot">Trouble logging in? Reset your password.</a>
<a class="link" href="/signup">Don't have an account yet? Sign up.</a>
</div>
</div>
<div class="footer">
<div class="footer-inner">
<div class="links">
<div class="link-group">
<div class="header">Your Installation</div>
<a class="link" href="/login">Login</a>
<a class="link" href="/signup">Signup</a>
<a class="link" href="/dashboard">Dashboard</a>
</div>
<div class="link-group">
<div class="header">Documentation</div>
<a class="link" href="https://docs.commento.io/">Documentation</a>
<a class="link" href="https://gitlab.com/commento">Open Source</a>
</div>
<div class="link-group">
<div class="header">About</div>
<a class="link" href="https://commento.io">About Commento</a>
<a class="link" href="https://commento.io/help">Help</a>
</div>
</div>
</div>
</div>
</html>

View File

@ -0,0 +1,147 @@
@import "common-main.scss";
body {
min-height: 100%;
}
.auth-form-container {
position: relative;
top: 100px;
width: 100%;
min-height: calc(100% - 100px);
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 100px;
.auth-form {
width: 400px;
background: white;
border-radius: 3px;
padding: 24px;
margin: 20px;
.form-title {
font-size: 28px;
font-weight: 300;
text-align: center;
padding: 12px;
padding-bottom: 36px;
}
.form-subtitle {
font-size: 15px;
text-align: center;
padding: 12px;
padding-bottom: 24px;
}
.row {
padding-left: 8px;
padding-right: 8px;
margin-bottom: 20px;
border-bottom: 1px solid $gray-1;
input[type=text],
input[type=password] {
font-size: 15px;
display: inline-block;
margin: 0;
padding: 0;
color: #32325d;
border-bottom: 1px solid $gray-1;
margin-right: 20px;
background: $gray-0;
width: 100%;
color: #32325d;
border: none;
outline: none;
line-height: 28px;
background: $white;
}
input[type=text]::placeholder,
input[type=password]::placeholder {
color: $gray-3;
}
.label {
display: inline-block;
color: $gray-5;
width: 100%;
font-size: 12px;
text-transform: uppercase;
}
}
.button {
@extend .shadow;
border: 1px solid $blue-6;
border-radius: 3px;
line-height: 32px;
background: $blue-6;
color: white;
font-weight: bold;
width: 100%;
cursor: pointer;
transition: all 0.2s;
}
.button:hover {
background: $blue-5;
border: 1px solid $blue-5;
}
.button:disabled {
background: $blue-4;
border: 1px solid $blue-4;
cursor: unset;
}
.err, .msg, .link {
display: block;
text-align: center;
margin-top: 12px;
margin-bottom: 12px;
font-size: 14px;
font-weight: bold;
}
.link {
font-weight: normal;
color: $blue-6;
transition: all 0.3s;
}
.link:hover {
color: $blue-6;
}
.err {
color: $red-6;
}
.msg {
color: $green-6;
}
}
@media only screen and (max-width: 1000px) {
.auth-form {
width: 90%;
}
}
}
.cent {
font-size: 12px;
color: $gray-5;
text-align: center;
.gray {
color: $gray-5;
padding-left: 2px;
padding-right: 2px;
border-bottom: 1px solid $gray-3;
}
}

3
frontend/sass/auth.scss Normal file
View File

@ -0,0 +1,3 @@
@import "common-main.scss";
@import "navbar-main.scss";
@import "auth-main.scss";

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,314 @@
$white: #ffffff;
$black: #000000;
$gray-list: (
"0": #f8f9fa,
"1": #f1f3f5,
"2": #e9ecef,
"3": #dee2e6,
"4": #ced4da,
"5": #adb5bd,
"6": #868e96,
"7": #495057,
"8": #343a40,
"9": #212529
);
$gray-0: map-get($gray-list, "0");
$gray-1: map-get($gray-list, "1");
$gray-2: map-get($gray-list, "2");
$gray-3: map-get($gray-list, "3");
$gray-4: map-get($gray-list, "4");
$gray-5: map-get($gray-list, "5");
$gray-6: map-get($gray-list, "6");
$gray-7: map-get($gray-list, "7");
$gray-8: map-get($gray-list, "8");
$gray-9: map-get($gray-list, "9");
$red-list: (
"0": #fff5f5,
"1": #ffe3e3,
"2": #ffc9c9,
"3": #ffa8a8,
"4": #ff8787,
"5": #ff6b6b,
"6": #fa5252,
"7": #f03e3e,
"8": #e03131,
"9": #c92a2a
);
$red-0: map-get($red-list, "0");
$red-1: map-get($red-list, "1");
$red-2: map-get($red-list, "2");
$red-3: map-get($red-list, "3");
$red-4: map-get($red-list, "4");
$red-5: map-get($red-list, "5");
$red-6: map-get($red-list, "6");
$red-7: map-get($red-list, "7");
$red-8: map-get($red-list, "8");
$red-9: map-get($red-list, "9");
$pink-list: (
"0": #fff0f6,
"1": #ffdeeb,
"2": #fcc2d7,
"3": #faa2c1,
"4": #f783ac,
"5": #f06595,
"6": #e64980,
"7": #d6336c,
"8": #c2255c,
"9": #a61e4d
);
$pink-0: map-get($pink-list, "0");
$pink-1: map-get($pink-list, "1");
$pink-2: map-get($pink-list, "2");
$pink-3: map-get($pink-list, "3");
$pink-4: map-get($pink-list, "4");
$pink-5: map-get($pink-list, "5");
$pink-6: map-get($pink-list, "6");
$pink-7: map-get($pink-list, "7");
$pink-8: map-get($pink-list, "8");
$pink-9: map-get($pink-list, "9");
$grape-list: (
"0": #f8f0fc,
"1": #f3d9fa,
"2": #eebefa,
"3": #e599f7,
"4": #da77f2,
"5": #cc5de8,
"6": #be4bdb,
"7": #ae3ec9,
"8": #9c36b5,
"9": #862e9c
);
$grape-0: map-get($grape-list, "0");
$grape-1: map-get($grape-list, "1");
$grape-2: map-get($grape-list, "2");
$grape-3: map-get($grape-list, "3");
$grape-4: map-get($grape-list, "4");
$grape-5: map-get($grape-list, "5");
$grape-6: map-get($grape-list, "6");
$grape-7: map-get($grape-list, "7");
$grape-8: map-get($grape-list, "8");
$grape-9: map-get($grape-list, "9");
$violet-list: (
"0": #f3f0ff,
"1": #e5dbff,
"2": #d0bfff,
"3": #b197fc,
"4": #9775fa,
"5": #845ef7,
"6": #7950f2,
"7": #7048e8,
"8": #6741d9,
"9": #5f3dc4
);
$violet-0: map-get($violet-list, "0");
$violet-1: map-get($violet-list, "1");
$violet-2: map-get($violet-list, "2");
$violet-3: map-get($violet-list, "3");
$violet-4: map-get($violet-list, "4");
$violet-5: map-get($violet-list, "5");
$violet-6: map-get($violet-list, "6");
$violet-7: map-get($violet-list, "7");
$violet-8: map-get($violet-list, "8");
$violet-9: map-get($violet-list, "9");
$indigo-list: (
"0": #edf2ff,
"1": #dbe4ff,
"2": #bac8ff,
"3": #91a7ff,
"4": #748ffc,
"5": #5c7cfa,
"6": #4c6ef5,
"7": #4263eb,
"8": #3b5bdb,
"9": #364fc7
);
$indigo-0: map-get($indigo-list, "0");
$indigo-1: map-get($indigo-list, "1");
$indigo-2: map-get($indigo-list, "2");
$indigo-3: map-get($indigo-list, "3");
$indigo-4: map-get($indigo-list, "4");
$indigo-5: map-get($indigo-list, "5");
$indigo-6: map-get($indigo-list, "6");
$indigo-7: map-get($indigo-list, "7");
$indigo-8: map-get($indigo-list, "8");
$indigo-9: map-get($indigo-list, "9");
$blue-list: (
"0": #e7f5ff,
"1": #d0ebff,
"2": #a5d8ff,
"3": #74c0fc,
"4": #4dabf7,
"5": #339af0,
"6": #228be6,
"7": #1c7ed6,
"8": #1971c2,
"9": #1864ab
);
$blue-0: map-get($blue-list, "0");
$blue-1: map-get($blue-list, "1");
$blue-2: map-get($blue-list, "2");
$blue-3: map-get($blue-list, "3");
$blue-4: map-get($blue-list, "4");
$blue-5: map-get($blue-list, "5");
$blue-6: map-get($blue-list, "6");
$blue-7: map-get($blue-list, "7");
$blue-8: map-get($blue-list, "8");
$blue-9: map-get($blue-list, "9");
$cyan-list: (
"0": #e3fafc,
"1": #c5f6fa,
"2": #99e9f2,
"3": #66d9e8,
"4": #3bc9db,
"5": #22b8cf,
"6": #15aabf,
"7": #1098ad,
"8": #0c8599,
"9": #0b7285
);
$cyan-0: map-get($cyan-list, "0");
$cyan-1: map-get($cyan-list, "1");
$cyan-2: map-get($cyan-list, "2");
$cyan-3: map-get($cyan-list, "3");
$cyan-4: map-get($cyan-list, "4");
$cyan-5: map-get($cyan-list, "5");
$cyan-6: map-get($cyan-list, "6");
$cyan-7: map-get($cyan-list, "7");
$cyan-8: map-get($cyan-list, "8");
$cyan-9: map-get($cyan-list, "9");
$teal-list: (
"0": #e6fcf5,
"1": #c3fae8,
"2": #96f2d7,
"3": #63e6be,
"4": #38d9a9,
"5": #20c997,
"6": #12b886,
"7": #0ca678,
"8": #099268,
"9": #087f5b
);
$teal-0: map-get($teal-list, "0");
$teal-1: map-get($teal-list, "1");
$teal-2: map-get($teal-list, "2");
$teal-3: map-get($teal-list, "3");
$teal-4: map-get($teal-list, "4");
$teal-5: map-get($teal-list, "5");
$teal-6: map-get($teal-list, "6");
$teal-7: map-get($teal-list, "7");
$teal-8: map-get($teal-list, "8");
$teal-9: map-get($teal-list, "9");
$green-list: (
"0": #ebfbee,
"1": #d3f9d8,
"2": #b2f2bb,
"3": #8ce99a,
"4": #69db7c,
"5": #51cf66,
"6": #40c057,
"7": #37b24d,
"8": #2f9e44,
"9": #2b8a3e
);
$green-0: map-get($green-list, "0");
$green-1: map-get($green-list, "1");
$green-2: map-get($green-list, "2");
$green-3: map-get($green-list, "3");
$green-4: map-get($green-list, "4");
$green-5: map-get($green-list, "5");
$green-6: map-get($green-list, "6");
$green-7: map-get($green-list, "7");
$green-8: map-get($green-list, "8");
$green-9: map-get($green-list, "9");
$lime-list: (
"0": #f4fce3,
"1": #e9fac8,
"2": #d8f5a2,
"3": #c0eb75,
"4": #a9e34b,
"5": #94d82d,
"6": #82c91e,
"7": #74b816,
"8": #66a80f,
"9": #5c940d
);
$lime-0: map-get($lime-list, "0");
$lime-1: map-get($lime-list, "1");
$lime-2: map-get($lime-list, "2");
$lime-3: map-get($lime-list, "3");
$lime-4: map-get($lime-list, "4");
$lime-5: map-get($lime-list, "5");
$lime-6: map-get($lime-list, "6");
$lime-7: map-get($lime-list, "7");
$lime-8: map-get($lime-list, "8");
$lime-9: map-get($lime-list, "9");
$yellow-list: (
"0": #fff9db,
"1": #fff8c5,
"2": #ffec99,
"3": #ffe066,
"4": #ffd43b,
"5": #fcc419,
"6": #fab005,
"7": #f59f00,
"8": #f08c00,
"9": #e67700
);
$yellow-0: map-get($yellow-list, "0");
$yellow-1: map-get($yellow-list, "1");
$yellow-2: map-get($yellow-list, "2");
$yellow-3: map-get($yellow-list, "3");
$yellow-4: map-get($yellow-list, "4");
$yellow-5: map-get($yellow-list, "5");
$yellow-6: map-get($yellow-list, "6");
$yellow-7: map-get($yellow-list, "7");
$yellow-8: map-get($yellow-list, "8");
$yellow-9: map-get($yellow-list, "9");
$orange-list: (
"0": #fff4e6,
"1": #ffe8cc,
"2": #ffd8a8,
"3": #ffc078,
"4": #ffa94d,
"5": #ff922b,
"6": #fd7e14,
"7": #f76707,
"8": #e8590c,
"9": #d9480f
);
$orange-0: map-get($orange-list, "0");
$orange-1: map-get($orange-list, "1");
$orange-2: map-get($orange-list, "2");
$orange-3: map-get($orange-list, "3");
$orange-4: map-get($orange-list, "4");
$orange-5: map-get($orange-list, "5");
$orange-6: map-get($orange-list, "6");
$orange-7: map-get($orange-list, "7");
$orange-8: map-get($orange-list, "8");
$orange-9: map-get($orange-list, "9");

View File

@ -0,0 +1,87 @@
@import "colors-main.scss";
html {
font-family: 'Source Sans Pro', sans-serif;
font-size: 14px;
color: $gray-7;
background: $gray-0;
}
body {
margin: 0px;
}
a {
text-decoration: none;
}
a:hover {
cursor: pointer;
}
.shadow {
box-shadow: 0 1px 3px rgba(50,50,93,.15), 0 1px 0 rgba(0,0,0,.02);
}
.footer {
position: relative;
bottom: 0px;
width: 100%;
margin-top: 72px;
.copyright {
align-items: none;
color: $gray-3;
background: $white;
text-align: center;
padding: 12px;
}
.footer-inner {
width: 100%;
background: $white;
display: flex;
justify-content: center;
align-items: center;
.links {
display: flex;
justify-content: center;
width: 600px;
}
.link-group {
margin: 40px;
.header {
text-transform: uppercase;
font-weight: 700;
font-size: 12px;
color: $gray-5;
}
}
.link {
margin-top: 12px;
margin-bottom: 12px;
display: block;
color: $gray-5;
transition: all 0.2s;
}
.link:hover {
color: $gray-7;
}
@media only screen and (max-width: 1000px) {
.link-group {
display: block
}
.links {
display: block;
width: 90%;
}
}
}
}

View File

@ -0,0 +1,918 @@
@import "colors-main.scss";
@import "common-main.scss";
.subscription-nag {
position: absolute;
top: 16px;
left: calc(50% - 200px);
height: 40px;
width: 400px;
border-radius: 3px;
-webkit-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
-moz-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
background: $gray-7;
color: $gray-0;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
z-index: 10000;
animation: shake .5s linear;
-webkit-animation: shake .5s linear;
animation-delay: 1s;
-webkit-animation-delay: 1s;
}
@-webkit-keyframes shake {
8%, 41% {
-webkit-transform: translateX(-5px);
}
25%, 58% {
-webkit-transform: translateX(5px);
}
75% {
-webkit-transform: translateX(-2px);
}
92% {
-webkit-transform: translateX(2px);
}
0%, 100% {
-webkit-transform: translateX(0);
}
}
.global-error, .global-ok {
position: absolute;
bottom: 0px;
left: 0px;
margin: 30px;
padding: 5px;
border: 1px solid $red-6;
background: $red-6;
color: white;
display: none;
z-index: 80000;
}
.global-ok {
border: 1px solid $green-7;
background: $green-7;
}
body {
height: 100%;
}
.tabs-container {
ul.tabs {
margin: 0px;
padding: 0px;
list-style: none;
border-bottom: 1px solid $gray-4;
li {
background: none;
display: inline-block;
padding: 10px 15px;
cursor: pointer;
transition: all 0.1s;
border-bottom: 1px solid transparent;
}
li.current {
border-bottom: 1px solid $blue-6;
transition: all 0.1s;
}
}
.content{
display: none;
padding: 25px;
.pitch {
color: $gray-6;
margin-bottom: 24px;
line-height: 22px;
a { color: $blue-6; }
a:hover { color: $blue-6; }
}
}
.content.current{
display: inherit;
}
}
.email-container {
display: flex;
justify-content: center;
width: 100%;
.email {
@extend .shadow;
border-radius: 4px;
background: $white;
width: 100%;
max-width: 400px;
.input {
height: 40px;
background: $white;
border: none;
outline: none;
padding: 5px;
padding-left: 10px;
width: 250px;
}
.input::placeholder {
color: $gray-5;
}
.email-button {
height: 40px;
min-width: 110px;
float: right;
background: $white;
border: none;
outline: none;
padding: 0px 10px 0px 10px;
border-left: 1px solid $gray-1;
font-size: 12px;
text-transform: uppercase;
text-align: center;
font-weight: bold;
color: $blue-7;
cursor: pointer;
transition: all 0.2s;
}
.email-button:hover {
color: $blue-6;
}
.email-button:disabled {
cursor: default;
color: $gray-6;
}
}
}
.mod-emails-container {
display: flex;
justify-content: center;
border-radius: 3px;
margin-top: 24px;
.content {
display: block;
padding: 0px;
border: 1px solid $gray-2;
border-top: 0px solid transparent;
min-width: 550px;
.mod-email {
display: block;
height: 42px;
border-top: 1px solid $gray-2;
.date,
.delete,
.email {
display: flex;
align-items: center;
margin: 0px 12px;
height: 100%;
}
.email {
float: left;
}
.date {
float: right;
color: $gray-4;
}
.delete {
float: right;
color: $red-6;
cursor: pointer;
}
.delete:hover {
color: $red-7;
}
}
}
}
.round-check {
input[type="checkbox"] {
display: none;
}
input[type="checkbox"] + label {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 5px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
input[type="checkbox"] + label:last-child { margin-bottom: 0; }
input[type="checkbox"] + label:before {
content: '';
display: block;
width: 13px;
height: 13px;
margin-top: 2px;
background: $gray-0;
border: 1px solid $gray-3;
border-radius: 3px;
position: absolute;
left: 0;
top: 0;
transition: all .15s;
}
input[type="checkbox"]:disabled + label:before {
background: $gray-0;
border: 1px solid $gray-4;
opacity: 0.4;
}
input[type="checkbox"]:checked + label:before {
background: $blue-6;
border: 1px solid $blue-6;
}
.pitch {
font-size: 13px;
color: #a0a0a0;
}
}
.dashboard-container {
position: relative;
top: 72px;
width: 100%;
height: calc(100% - 94px);
.pane-left {
padding-top: 20px;
position: absolute;
left: 0px;
border-right: 1px solid $gray-1;
width: 240px;
height: 100%;
.tree {
opacity: 0.4;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.tree-svg {
margin-bottom: 20px;
width: 100px;
}
}
}
.pane-middle {
padding-top: 20px;
position: absolute;
left: 240px;
border-right: 1px solid $gray-1;
width: 280px;
height: 100%;
padding-left: 1px;
}
.pane-right {
position: absolute;
left: 520px;
width: calc(100% - 520px);
height: 100%;
overflow-y: auto;
.hidden {
display: none;
}
.view {
padding: 25px;
padding-top: 35px;
}
.view-inside {
display: flex;
justify-content: center;
}
.tiny-view {
font-size: 15px;
width: 35%;
min-width: 400px;
max-width: 450px;
}
.small-view {
font-size: 15px;
width: 50%;
min-width: 400px;
max-width: 500px;
}
.small-mid-view {
font-size: 15px;
width: 70%;
max-width: 500px;
}
.mid-view {
font-size: 15px;
width: 100%;
max-width: 700px;
}
.large-view {
font-size: 15px;
width: 100%;
max-width: 750px;
}
.center {
text-align: center;
}
}
.select-a-domain {
padding: 20px;
text-align: center;
color: $gray-5;
}
.pane-setting {
width: 100%;
height: 54px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s;
color: $gray-7;
.setting-title, .setting-subtitle {
display: block;
}
.setting-title {
font-weight: bold;
}
.setting-subtitle {
color: $gray-5;
}
.super-setting {
display: flex;
.super-setting-title {
display: inline;
display: flex;
align-items: center;
font-size: 24px;
}
.super-setting-text {
padding-left: 5px;
display: flex;
align-items: center;
line-height: 32px;
height: 45px;
}
}
}
.pane-setting:hover {
color: $gray-6;
-webkit-box-shadow: inset -2px 0px 0 -1px $gray-4;
-moz-box-shadow: inset -2px 0px 0 -1px $gray-4;
box-shadow: inset -2px 0px 0 -1px $gray-4;
}
.selected {
color: $blue-6;
-webkit-box-shadow: inset -2px 0px 0 -1px $blue-6;
-moz-box-shadow: inset -2px 0px 0 -1px $blue-6;
box-shadow: inset -2px 0px 0 -1px $blue-6;
}
.selected:hover {
color: $blue-7;
-webkit-box-shadow: inset -2px 0px 0 -1px $blue-6;
-moz-box-shadow: inset -2px 0px 0 -1px $blue-6;
box-shadow: inset -2px 0px 0 -1px $blue-6;
}
}
.import-text {
font-size: 15px;
color: $gray-6;
line-height: 25px;
a {
color: $blue-6;
border-bottom: 1px solid $blue-6;
}
ul {
list-style: none;
}
li {
position: relative;
margin-bottom: 10px;
}
li::before {
content: '';
display: inline-block;
width: 4px;
height: 4px;
border: solid $gray-7;
border-width: 0 2px 2px 0;
transform: rotate(-45deg);
margin-left: 12px;
margin-right: 12px;
margin-bottom: 2px;
position: absolute;
left: -35px;
top: 10px;
}
.subtext-container {
display: flex;
justify-content: center;
margin: 8px;
padding-bottom: 16px;
.subtext {
max-width: 500px;
font-size: 13px;
line-height: 17px;
text-align: center;
}
}
}
.float-right {
position: absolute;
right: 10px;
}
.modal-window {
position: fixed;
background-color: rgba(82, 95, 127, 0.4);
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
opacity: 0;
pointer-events: none;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
transition: all 0.2s;
.inside {
border: 1px solid white;
border-radius: 6px;
width: 500px;
position: relative;
margin: 10% auto;
background: $gray-0;
color: #444;
}
.modal-error-box {
width: 100%;
color: $red-6;
text-align: center;
margin-bottom: 30px;
font-weight: bold;
}
.modal-title {
padding-top: 15px;
width: 100%;
text-align: center;
font-size: 20px;
}
.modal-subtitle {
text-align: center;
color: $gray-6;
padding: 16px;
}
.modal-contents {
padding: 12px;
min-height: 48px;
}
}
.modal-window:target {
opacity: 1;
pointer-events: auto;
}
.modal-close {
position: absolute;
width: 22px;
height: 22px;
top: 10px;
right: 10px;
background-image: url(/images/close.svg);
cursor: pointer;
}
.modal-close:hover {
color: #000;
}
.box {
padding: 6px;
margin-bottom: 25px;
.box-title {
padding: 6px;
padding-top: 0px;
font-size: 19px;
}
.box-subtitle {
font-size: 14px;
padding: 6px;
color: $gray-6;
margin-bottom: 20px;
}
.gray {
position: absolute;
bottom: 0px;
color: $gray-6;
opacity: 0.8;
a { color: $gray-6; border: none; }
a:hover { color: $gray-6; border: none; }
}
.gray:hover {
opacity: 1;
}
.row {
.gray-input {
background: $gray-0;
}
}
}
.row {
padding-left: 8px;
padding-right: 8px;
margin-bottom: 32px;
border-bottom: 1px solid $gray-2;
.label {
display: inline-block;
color: $gray-5;
width: 100%;
font-size: 12px;
text-transform: uppercase;
}
.input {
font-size: 15px;
display: inline-block;
margin: 0;
padding: 0;
width: 100%;
color: #32325d;
border: none;
outline: none;
line-height: 28px;
background: $gray-0;
}
.input::placeholder {
color: $gray-4;
}
}
.theme {
display: block;
width: calc(100% - 20px);
border: 1px solid white;
-webkit-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
-moz-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
border-radius: 3px;
padding: 10px;
cursor: pointer;
margin-bottom: 20px;
background: white;
opacity: 0.5;
filter: alpha(opacity=50);
transition: all 0.3s;
.theme-title {
font-size: 24px;
text-align: center;
padding: 10px;
}
.theme-subtitle {
font-size: 15px;
text-align: center;
padding: 10px;
color: $gray-5;
}
.theme-image {
width: calc(100% - 40px);
padding: 20px;
}
}
.theme:hover {
opacity: 0.8;
filter: alpha(opacity=80);
}
.selectedtheme {
opacity: 1;
filter: alpha(opacity=100);
}
.selectedtheme:hover {
opacity: 1;
filter: alpha(opacity=100);
}
.no-border {
border: none;
}
.button {
@extend .shadow;
height: 40px;
min-width: 110px;
background: $white;
border: none;
outline: none;
padding: 0px 10px 0px 10px;
font-size: 12px;
text-transform: uppercase;
text-align: center;
font-weight: bold;
color: $blue-7;
cursor: pointer;
transition: all 0.2s;
}
.button:hover {
color: $blue-6;
}
.button:disabled {
cursor: default;
color: $gray-6;
}
.short-button {
width: 80px;
position: absolute;
top: 10px;
}
.stat {
-webkit-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
-moz-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
margin: 20px;
padding: 20px;
display: block;
text-align: center;
width: calc(100% - 80px);
background: white;
border-radius: 3px;
.number {
display: flex;
justify-content: center;
line-height: 42px;
.digits {
display: table-cell;
font-size: 48px;
font-weight: 200;
}
.gray-digits {
color: $gray-3;
}
.gray-7-digits {
color: $gray-7;
}
}
.text {
text-transform: uppercase;
color: $gray-5;
}
}
.center {
text-align: center;
}
.center-title {
font-size: 32px;
font-weight: 300;
margin-bottom: 32px;
}
.text {
color: $gray-6;
line-height: 25px;
}
foreignObject {
white-space: nowrap;
}
.all-done {
margin-top: 40px;
color: $gray-5;
font-size :15px;
font-weight: 400;
}
.mod-button {
width: fit-content;
line-height: 24px;
color: $gray-6;
background: white;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.mod-button:hover {
background: white;
}
.red-button {
border: 1px solid $gray-3;
outline: none;
}
.red-button:hover {
border: 1px solid $red-6;
}
.green-button {
color: $green-7;
}
.green-button:hover {
color: $green-7;
}
.green-button:disabled {
color: $gray-6;
}
.orange-button {
color: $orange-7;
}
.orange-button:hover {
color: $orange-7;
}
.big-red-button {
background: $red-6;
color: white;
}
.big-red-button:hover {
background: $red-5;
color: white;
}
.comment {
-webkit-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
-moz-box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);
width: calc(100% - 40px);
background: white;
border: 1px solid white;
border-radius: 3px;
padding: 20px;
margin-top: 20px;
margin-bottom: 20px;
transition: all 1s;
.comment-inside {
.actions {
float: right;
}
.title {
font-weight: 300;
font-size: 18px;
color: $gray-5;
margin-bottom: 18px;
line-height: 28px;
.postlink {
color: $gray-5;
}
}
.profile {
.avatar {
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 22px;
float: left;
margin-right: 12px;
}
.names {
margin-left: 50px;
.name {
a {
color: $gray-7;
font-weight: bold;
}
}
.time {
color: $gray-4;
}
}
}
.content {
margin-top: 20px;
}
}
}
.move-left {
transform: translateX(10000px);
}
.move-right {
transform: translateX(10000px);
}
.graph {
margin-top: 16px;
height: 300px;
.ct-series-a .ct-line {
stroke: $gray-7;
stroke-width: 1px;
}
.ct-series-a .ct-area {
fill: $gray-7;
}
}
code {
font-family: 'Source Code Pro', monospace;
font-size: 13px;
}

View File

@ -0,0 +1,4 @@
@import "common-main.scss";
@import "navbar-main.scss";
@import "dashboard-main.scss";
@import "tomorrow.scss";

View File

@ -0,0 +1,58 @@
@import "colors-main.scss";
.navbar {
position: absolute;
top: 0px;
display: flex;
width: 100%;
padding-top: 12px;
margin-bottom: 12px;
z-index: 5;
}
.navbar-item {
width: auto;
height: 48px;
display: flex;
align-items: center;
padding-left: 12px;
padding-right: 12px;
font-size: 16px;
color: $gray-5;
background: $gray-0;
}
.navbar-logo-text {
font-size: 16px;
margin-left: 56px;
color: $gray-9;
}
.navbar-logo {
width: 32px;
height: 32px;
padding-right: 8px;
}
@media only screen and (max-width: 1000px) {
.navbar {
display: block;
}
.navbar-item {
display: none;
}
.navbar-logo-text {
display: flex;
margin-left: 16px;
}
.banner-container {
width: calc(100% - 24px);
}
}
.navbar-hamburger {
color: $gray-9;
}

View File

@ -0,0 +1,97 @@
/*
Atom One Light by Daniel Gamage
Original One Light Syntax theme from https://github.com/atom/one-light-syntax
base: #fafafa
mono-1: #383a42
mono-2: #686b77
mono-3: #a0a1a7
hue-1: #0184bb
hue-2: #4078f2
hue-3: #a626a4
hue-4: #50a14f
hue-5: #e45649
hue-5-2: #c91243
hue-6: #986801
hue-6-2: #c18401
*/
.hljs {
display: block;
overflow-x: auto;
padding: 1em;
color: #383a42;
background: #ffffff;
border-radius: 3px;
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #a626a4;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e45649;
}
.hljs-literal {
color: #0184bb;
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta-string {
color: #50a14f;
}
.hljs-built_in,
.hljs-class .hljs-title {
color: #c18401;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #986801;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #4078f2;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

87
frontend/signup.html Normal file
View File

@ -0,0 +1,87 @@
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0">
<script src="<<<.CdnPrefix>>>/js/jquery.js"></script>
<script src="<<<.CdnPrefix>>>/js/signup.js"></script>
<link rel="stylesheet" href="<<<.CdnPrefix>>>/css/auth.css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700|Source+Sans+Pro:200,300,400,700" rel="stylesheet">
<title>Commento: Signup</title>
</head>
<div class="navbar">
<a href="/" class="navbar-item navbar-logo-text"><img src="/images/logo.svg" class="navbar-logo">Commento</a>
<a href="/login" class="navbar-item">Login</a>
<a href="/signup" class="navbar-item"><b>Signup</b></a>
</div>
<script>
window.onload = function() {
window.prefillEmail();
};
</script>
<div class="auth-form-container">
<div class="auth-form">
<div class="form-title">
Create an account
</div>
<div class="row">
<div class="label">Email Address</div>
<input class="input" type="text" name="email" id="email" placeholder="example@example.com">
</div>
<div class="row">
<div class="label">Full Name</div>
<input class="input" type="text" name="name" id="name" placeholder="Billie Joe Armstrong">
</div>
<div class="row">
<div class="label">Password</div>
<input class="input" type="password" name="password" id="password" placeholder="">
</div>
<div class="row">
<div class="label">Confirm Password</div>
<input class="input" type="password" name="password2" id="password2" placeholder="">
</div>
<input type="hidden" name="plan" id="plan" value="">
<div class="err" id="err"></div>
<p class="cent">
By signing up, you agree to our
<a href="/terms" class="gray">Terms and Conditions</a> and
<a href="/privacy" class="gray">Privacy Policy</a>
</p>
<button id="signup-button" class="button" onclick="window.signup()">Sign up</button>
<a class="link" href="/login">Already have an account? Login instead.</a>
</div>
</div>
<div class="footer">
<div class="footer-inner">
<div class="links">
<div class="link-group">
<div class="header">Your Installation</div>
<a class="link" href="/login">Login</a>
<a class="link" href="/signup">Signup</a>
<a class="link" href="/dashboard">Dashboard</a>
</div>
<div class="link-group">
<div class="header">Documentation</div>
<a class="link" href="https://docs.commento.io/">Documentation</a>
<a class="link" href="https://gitlab.com/commento">Open Source</a>
</div>
<div class="link-group">
<div class="header">About</div>
<a class="link" href="https://commento.io">About Commento</a>
<a class="link" href="https://commento.io/help">Help</a>
</div>
</div>
</div>
</div>
</html>