frontend, api: allow custom emails as commenters

I'm really sorry this came out as one huge commit, but I'm afraid
I can't split this up.

Closes https://gitlab.com/commento/commento-ce/issues/9
This commit is contained in:
Adhityaa 2018-06-10 22:45:56 +05:30
parent e42f77b4eb
commit 2020405e8b
25 changed files with 837 additions and 267 deletions

View File

@ -8,7 +8,7 @@ import (
func TestCommentApproveBasics(t *testing.T) { func TestCommentApproveBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
commentHex, _ := commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC()) commentHex, _ := commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())

View File

@ -151,5 +151,6 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
"requireIdentification": d.RequireIdentification, "requireIdentification": d.RequireIdentification,
"isFrozen": d.State == "frozen", "isFrozen": d.State == "frozen",
"isModerator": isModerator, "isModerator": isModerator,
"configuredOauths": configuredOauths,
}) })
} }

View File

@ -9,7 +9,7 @@ import (
func TestCommentListBasics(t *testing.T) { func TestCommentListBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC()) commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
commentNew(commenterHex, "example.com", "/path.html", "root", "**bar**", "approved", time.Now().UTC()) commentNew(commenterHex, "example.com", "/path.html", "root", "**bar**", "approved", time.Now().UTC())
@ -65,7 +65,7 @@ func TestCommentListEmpty(t *testing.T) {
func TestCommentListSelfUnapproved(t *testing.T) { func TestCommentListSelfUnapproved(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC()) commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())

View File

@ -116,5 +116,5 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
writeBody(w, response{"success": true, "commentHex": commentHex}) writeBody(w, response{"success": true, "commentHex": commentHex, "approved": state == "approved"})
} }

View File

@ -8,9 +8,9 @@ import (
func TestCommentVoteBasics(t *testing.T) { func TestCommentVoteBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google") cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google", "")
cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google") cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google", "")
cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google") cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google", "")
c0, _ := commentNew(cr0, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC()) c0, _ := commentNew(cr0, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())

View File

@ -7,7 +7,7 @@ import (
func TestCommenterGetByHexBasics(t *testing.T) { func TestCommenterGetByHexBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
c, err := commenterGetByHex(commenterHex) c, err := commenterGetByHex(commenterHex)
if err != nil { if err != nil {
@ -33,7 +33,7 @@ func TestCommenterGetByHexEmpty(t *testing.T) {
func TestCommenterGetBySession(t *testing.T) { func TestCommenterGetBySession(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew() session, _ := commenterSessionNew()
@ -63,7 +63,7 @@ func TestCommenterGetBySessionEmpty(t *testing.T) {
func TestCommenterGetByName(t *testing.T) { func TestCommenterGetByName(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew() session, _ := commenterSessionNew()

71
api/commenter_login.go Normal file
View File

@ -0,0 +1,71 @@
package main
import (
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
func commenterLogin(email string, password string) (string, error) {
if email == "" || password == "" {
return "", errorMissingField
}
statement := `
SELECT commenterHex, passwordHash
FROM commenters
WHERE email = $1 AND provider = 'commento';
`
row := db.QueryRow(statement, email)
var commenterHex string
var passwordHash string
if err := row.Scan(&commenterHex, &passwordHash); err != nil {
return "", errorInvalidEmailPassword
}
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)); err != nil {
// TODO: is this the only possible error?
return "", errorInvalidEmailPassword
}
session, err := randomHex(32)
if err != nil {
logger.Errorf("cannot create session hex: %v", err)
return "", errorInternal
}
statement = `
INSERT INTO
commenterSessions (session, commenterHex, creationDate)
VALUES ($1, $2, $3 );
`
_, err = db.Exec(statement, session, commenterHex, time.Now().UTC())
if err != nil {
logger.Errorf("cannot insert session token: %v\n", err)
return "", errorInternal
}
return session, nil
}
func commenterLoginHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Email *string `json:"email"`
Password *string `json:"password"`
}
var x request
if err := unmarshalBody(r, &x); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
session, err := commenterLogin(*x.Email, *x.Password)
if err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
writeBody(w, response{"success": true, "session": session})
}

View File

@ -0,0 +1,58 @@
package main
import (
"testing"
)
func TestCommenterLoginBasics(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
t.Errorf("expected error not found when logging in without creating an account")
return
}
commenterNew("test@example.com", "Test", "undefined", "undefined", "commento", "hunter2")
if _, err := commenterLogin("test@example.com", "hunter2"); err != nil {
t.Errorf("unexpected error when logging in: %v", err)
return
}
if _, err := commenterLogin("test@example.com", "h******"); err == nil {
t.Errorf("expected error not found when given wrong password")
return
}
if session, err := commenterLogin("test@example.com", "hunter2"); session == "" {
t.Errorf("empty session on successful login: %v", err)
return
}
}
func TestCommenterLoginEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterLogin("test@example.com", ""); err == nil {
t.Errorf("expected error not found when passing empty password")
return
}
commenterNew("test@example.com", "Test", "undefined", "", "commenter", "hunter2")
if _, err := commenterLogin("test@example.com", ""); err == nil {
t.Errorf("expected error not found when passing empty password")
return
}
}
func TestCommenterLoginNonCommento(t *testing.T) {
failTestOnError(t, setupTestEnv())
commenterNew("test@example.com", "Test", "undefined", "undefined", "google", "")
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
t.Errorf("expected error not found logging into a non-Commento account")
return
}
}

View File

@ -1,28 +1,74 @@
package main package main
import ( import (
"golang.org/x/crypto/bcrypt"
"net/http"
"time" "time"
) )
func commenterNew(email string, name string, link string, photo string, provider string) (string, error) { func commenterNew(email string, name string, link string, photo string, provider string, password string) (string, error) {
if email == "" || name == "" || link == "" || photo == "" || provider == "" { if email == "" || name == "" || link == "" || photo == "" || provider == "" {
return "", errorMissingField return "", errorMissingField
} }
if provider == "commento" && password == "" {
return "", errorMissingField
}
if _, err := commenterGetByEmail(provider, email); err == nil {
return "", errorEmailAlreadyExists
}
commenterHex, err := randomHex(32) commenterHex, err := randomHex(32)
if err != nil { if err != nil {
return "", errorInternal return "", errorInternal
} }
passwordHash := []byte{}
if (password != "") {
passwordHash, err = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logger.Errorf("cannot generate hash from password: %v\n", err)
return "", errorInternal
}
}
statement := ` statement := `
INSERT INTO INSERT INTO
commenters (commenterHex, email, name, link, photo, provider, joinDate) commenters (commenterHex, email, name, link, photo, provider, passwordHash, joinDate)
VALUES ($1, $2, $3, $4, $5, $6, $7 ); VALUES ($1, $2, $3, $4, $5, $6, $7, $8 );
` `
_, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, time.Now().UTC()) _, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, string(passwordHash), time.Now().UTC())
if err != nil { if err != nil {
logger.Errorf("cannot insert commenter: %v", err)
return "", errorInternal return "", errorInternal
} }
return commenterHex, nil return commenterHex, nil
} }
func commenterNewHandler(w http.ResponseWriter, r *http.Request) {
type request struct {
Email *string `json:"email"`
Name *string `json:"name"`
Website *string `json:"website"`
Password *string `json:"password"`
}
var x request
if err := unmarshalBody(r, &x); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
// TODO: add gravatar?
// TODO: email confirmation if provider = commento?
// TODO: email confirmation if provider = commento?
if _, err := commenterNew(*x.Email, *x.Name, *x.Website, "undefined", "commento", *x.Password); err != nil {
writeBody(w, response{"success": false, "message": err.Error()})
return
}
writeBody(w, response{"success": true, "confirmEmail": smtpConfigured})
}

View File

@ -7,7 +7,7 @@ import (
func TestCommenterNewBasics(t *testing.T) { func TestCommenterNewBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google"); err != nil { if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err != nil {
t.Errorf("unexpected error creating new commenter: %v", err) t.Errorf("unexpected error creating new commenter: %v", err)
return return
} }
@ -16,13 +16,22 @@ func TestCommenterNewBasics(t *testing.T) {
func TestCommenterNewEmpty(t *testing.T) { func TestCommenterNewEmpty(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google"); err == nil { if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err == nil {
t.Errorf("expected error not found creating new commenter with empty email") t.Errorf("expected error not found creating new commenter with empty email")
return return
} }
if _, err := commenterNew("", "", "", "", ""); err == nil { if _, err := commenterNew("", "", "", "", "", ""); err == nil {
t.Errorf("expected error not found creating new commenter with empty everything") t.Errorf("expected error not found creating new commenter with empty everything")
return return
} }
} }
func TestCommenterNewCommento(t *testing.T) {
failTestOnError(t, setupTestEnv())
if _, err := commenterNew("test@example.com", "Test", "undefined", "", "commento", ""); err == nil {
t.Errorf("expected error not found creating new commento account with empty password")
return
}
}

View File

@ -7,7 +7,7 @@ import (
func TestCommenterSessionGetBasics(t *testing.T) { func TestCommenterSessionGetBasics(t *testing.T) {
failTestOnError(t, setupTestEnv()) failTestOnError(t, setupTestEnv())
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google") commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
session, _ := commenterSessionNew() session, _ := commenterSessionNew()

View File

@ -29,8 +29,8 @@ func parseConfig() error {
"SMTP_PORT": "", "SMTP_PORT": "",
"SMTP_FROM_ADDRESS": "", "SMTP_FROM_ADDRESS": "",
"OAUTH_GOOGLE_KEY": "", "GOOGLE_KEY": "",
"OAUTH_GOOGLE_SECRET": "", "GOOGLE_SECRET": "",
} }
for key, value := range defaults { for key, value := range defaults {

View File

@ -2,7 +2,11 @@ package main
import () import ()
var configuredOauths []string
func oauthConfigure() error { func oauthConfigure() error {
configuredOauths = []string{}
if err := googleOauthConfigure(); err != nil { if err := googleOauthConfigure(); err != nil {
return err return err
} }

View File

@ -37,5 +37,7 @@ func googleOauthConfigure() error {
Endpoint: google.Endpoint, Endpoint: google.Endpoint,
} }
configuredOauths = append(configuredOauths, "google");
return nil return nil
} }

View File

@ -64,7 +64,7 @@ func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
link = "undefined" link = "undefined"
} }
commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google") commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google", "")
if err != nil { if err != nil {
fmt.Fprintf(w, "Error: %s", err.Error()) fmt.Fprintf(w, "Error: %s", err.Error())
return return

View File

@ -21,6 +21,8 @@ func initAPIRouter(router *mux.Router) error {
router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST") router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST")
router.HandleFunc("/api/commenter/session/new", commenterSessionNewHandler).Methods("GET") router.HandleFunc("/api/commenter/session/new", commenterSessionNewHandler).Methods("GET")
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
router.HandleFunc("/api/commenter/self", commenterSelfHandler).Methods("POST") router.HandleFunc("/api/commenter/self", commenterSelfHandler).Methods("POST")
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET") router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")

View File

@ -0,0 +1,2 @@
ALTER TABLE commenters
ADD passwordHash TEXT NOT NULL DEFAULT '';

View File

@ -153,10 +153,10 @@
<div class="pitch"> <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. 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>
<div class="email-container"> <div class="commento-email-container">
<div class="email"> <div class="commento-email">
<input class="input" type="text" id="new-mod" placeholder="Email"> <input class="commento-input" type="text" id="new-mod" placeholder="Email">
<button id="new-mod-button" class="email-button" onclick="window.moderatorNewHandler()">Add moderator</button> <button id="new-mod-button" class="commento-email-button" onclick="window.moderatorNewHandler()">Add moderator</button>
</div> </div>
</div> </div>
<div class="mod-emails-container"> <div class="mod-emails-container">
@ -244,10 +244,10 @@
<br><br> <br><br>
<div class="email-container"> <div class="commento-email-container">
<div class="email"> <div class="commento-email">
<input class="input" type="text" id="new-mod" placeholder="https://media.disqus.com/uploads/..."> <input class="commento-input" type="text" id="new-mod" placeholder="https://media.disqus.com/uploads/...">
<button class="email-button">Import</button> <button class="commento-email-button">Import</button>
</div> </div>
</div> </div>
<div class="subtext-container"> <div class="subtext-container">

View File

@ -11,6 +11,43 @@
// override that. // override that.
var ID_ROOT = "commento";
var ID_MAIN_AREA = "commento-main-area";
var ID_LOGIN_BOX_CONTAINER = "commento-login-box-container";
var ID_LOGIN_BOX = "commento-login-box";
var ID_LOGIN_BOX_HEADER = "commento-login-box-header";
var ID_LOGIN_BOX_SUBTITLE = "commento-login-box-subtitle";
var ID_LOGIN_BOX_EMAIL_INPUT = "commento-login-box-email-input";
var ID_LOGIN_BOX_PASSWORD_INPUT = "commento-login-box-password-input";
var ID_LOGIN_BOX_NAME_INPUT = "commento-login-box-name-input";
var ID_LOGIN_BOX_WEBSITE_INPUT = "commento-login-box-website-input";
var ID_LOGIN_BOX_EMAIL_BUTTON = "commento-login-box-email-button";
var ID_LOGIN_BOX_LOGIN_LINK_CONTAINER = "commento-login-box-login-link-container";
var ID_LOGIN_BOX_LOGIN_LINK = "commento-login-box-login-link";
var ID_LOGIN_BOX_HR = "commento-login-box-hr";
var ID_LOGIN_BOX_OAUTH_PRETEXT = "commento-login-box-oauth-pretext";
var ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER = "commento-login-box-oauth-buttons-container";
var ID_ERROR = "commento-error";
var ID_COMMENTS_AREA = "commento-comments-area";
var ID_SUPER_CONTAINER = "commento-textarea-super-container-";
var ID_TEXTAREA_CONTAINER = "commento-textarea-container-";
var ID_TEXTAREA = "commento-textarea-";
var ID_CARD = "commento-comment-card-";
var ID_BODY = "commento-comment-body-";
var ID_SUBTITLE = "commento-comment-subtitle-";
var ID_SCORE = "commento-comment-score-";
var ID_OPTIONS = "commento-comment-options-";
var ID_EDIT = "commento-comment-edit-";
var ID_REPLY = "commento-comment-reply-";
var ID_COLLAPSE = "commento-comment-collapse-";
var ID_UPVOTE = "commento-comment-upvote-";
var ID_DOWNVOTE = "commento-comment-downvote-";
var ID_APPROVE = "commento-comment-approve-";
var ID_REMOVE = "commento-comment-remove-";
var ID_CONTENTS = "commento-comment-contents-";
var ID_SUBMIT_BUTTON = "commento-submit-button-";
var origin = global.commento_origin; var origin = global.commento_origin;
var cdn = global.commento_cdn; var cdn = global.commento_cdn;
var root = null; var root = null;
@ -24,6 +61,8 @@
var chosenAnonymous = false; var chosenAnonymous = false;
var shownSubmitButton = {"root": false}; var shownSubmitButton = {"root": false};
var shownReply = {}; var shownReply = {};
var configuredOauths = [];
var loginBoxType = "signup";
function $(id) { function $(id) {
@ -169,27 +208,37 @@
var loggedContainer = create("div"); var loggedContainer = create("div");
var loggedInAs = create("div"); var loggedInAs = create("div");
var name = create("a"); var name = create("a");
var photo = create("img"); var avatar;
var logout = create("div"); var logout = create("div");
var color = colorGet(resp.commenter.name);
classAdd(loggedContainer, "logged-container"); classAdd(loggedContainer, "logged-container");
classAdd(loggedInAs, "logged-in-as"); classAdd(loggedInAs, "logged-in-as");
classAdd(name, "name"); classAdd(name, "name");
classAdd(photo, "photo");
classAdd(logout, "logout"); classAdd(logout, "logout");
name.innerText = resp.commenter.name; name.innerText = resp.commenter.name;
logout.innerText = "Logout"; logout.innerText = "Logout";
attr(name, "href", resp.commenter.link);
if (resp.commenter.provider == "google") {
attr(photo, "src", resp.commenter.photo + "?sz=50");
} else {
attr(photo, "src", resp.commenter.photo);
}
attr(logout, "onclick", "logout()"); attr(logout, "onclick", "logout()");
attr(name, "href", resp.commenter.link);
if (resp.commenter.photo == "undefined") {
avatar = create("div");
avatar.style["background"] = color;
avatar.style["boxShadow"] = "0px 0px 0px 2px " + color;
avatar.innerHTML = resp.commenter.name[0].toUpperCase();
classAdd(avatar, "avatar");
} else {
avatar = create("img");
if (resp.commenter.provider == "google") {
attr(avatar, "src", resp.commenter.photo + "?sz=50");
} else {
attr(avatar, "src", resp.commenter.photo);
}
classAdd(avatar, "avatar-img");
}
append(loggedInAs, photo); append(loggedInAs, avatar);
append(loggedInAs, name); append(loggedInAs, name);
append(loggedContainer, loggedInAs); append(loggedContainer, loggedInAs);
append(loggedContainer, logout); append(loggedContainer, logout);
@ -280,6 +329,10 @@
isFrozen = resp.isFrozen; isFrozen = resp.isFrozen;
comments = resp.comments; comments = resp.comments;
commenters = resp.commenters; commenters = resp.commenters;
configuredOauths = resp.configuredOauths;
if (!resp.requireModeration)
configuredOauths.push("anonymous");
cssLoad(cdn + "/css/commento.css"); cssLoad(cdn + "/css/commento.css");
@ -335,43 +388,25 @@
if (!isAuthenticated && !chosenAnonymous) { if (!isAuthenticated && !chosenAnonymous) {
var buttonsContainer = create("div"); var buttonsContainer = create("div");
var createButton = create("div");
if (!isMobile()) { classAdd(buttonsContainer, "create-container");
classAdd(buttonsContainer, "buttons-container");
classAdd(textarea, "blurred-textarea");
} else {
classAdd(textarea, "hidden");
classAdd(buttonsContainer, "mobile-buttons-container");
classAdd(buttonsContainer, "opaque");
}
var oauths = ["google"]; classAdd(createButton, "button");
if (!requireIdentification) classAdd(createButton, "create-button");
oauths.push("anonymous");
for (var i = 0; i < oauths.length; i++) {
var oauthButton = create("button");
classAdd(oauthButton, "button");
classAdd(oauthButton, oauths[i] + "-button");
if (isMobile())
classAdd(oauthButton, "opaque");
attr(oauthButton, "onclick", "commentoAuth('" + oauths[i] + "', '" + id + "')");
oauthButton.innerText = oauths[i][0].toUpperCase() + oauths[i].slice(1);
append(buttonsContainer, oauthButton);
}
attr(createButton, "onclick", "loginBoxShow()");
attr(textarea, "disabled", true); attr(textarea, "disabled", true);
createButton.innerText = "Create an Account";
append(buttonsContainer, createButton);
append(textareaContainer, buttonsContainer); append(textareaContainer, buttonsContainer);
} }
else {
attr(textarea, "placeholder", "Join the discussion!"); attr(textarea, "placeholder", "Join the discussion!");
attr(textarea, "onclick", "showSubmitButton('" + id + "')"); attr(textarea, "onclick", "showSubmitButton('" + id + "')");
}
textarea.oninput = autoExpander(textarea); textarea.oninput = autoExpander(textarea);
@ -382,6 +417,7 @@
} }
function rootCreate(callback) { function rootCreate(callback) {
var mainArea = $(ID_MAIN_AREA);
var commentsArea = create("div"); var commentsArea = create("div");
commentsArea.id = ID_COMMENTS_AREA; commentsArea.id = ID_COMMENTS_AREA;
@ -390,8 +426,9 @@
commentsArea.innerHTML = ""; commentsArea.innerHTML = "";
append(root, textareaCreate("root")); append(mainArea, textareaCreate("root"));
append(root, commentsArea); append(mainArea, commentsArea);
append(root, mainArea);
call(callback); call(callback);
} }
@ -439,7 +476,7 @@
$(ID_COMMENTS_AREA).innerHTML = ""; $(ID_COMMENTS_AREA).innerHTML = "";
commentsRender(); commentsRender();
if (requireModeration && !isModerator) { if (!resp.approved) {
if (id == "root") { if (id == "root") {
var body = $(ID_SUPER_CONTAINER + id); var body = $(ID_SUPER_CONTAINER + id);
prepend(body, messageCreate("Your comment is under moderation.")); prepend(body, messageCreate("Your comment is under moderation."));
@ -511,26 +548,6 @@
return score + " point"; return score + " point";
} }
var ID_ERROR = "commento-error";
var ID_COMMENTS_AREA = "commento-comments-area";
var ID_SUPER_CONTAINER = "commento-textarea-super-container-";
var ID_TEXTAREA_CONTAINER = "commento-textarea-container-";
var ID_TEXTAREA = "commento-textarea-";
var ID_CARD = "commento-comment-card-";
var ID_BODY = "commento-comment-body-";
var ID_SUBTITLE = "commento-comment-subtitle-";
var ID_SCORE = "commento-comment-score-";
var ID_OPTIONS = "commento-comment-options-";
var ID_EDIT = "commento-comment-edit-";
var ID_REPLY = "commento-comment-reply-";
var ID_COLLAPSE = "commento-comment-collapse-";
var ID_UPVOTE = "commento-comment-upvote-";
var ID_DOWNVOTE = "commento-comment-downvote-";
var ID_APPROVE = "commento-comment-approve-";
var ID_REMOVE = "commento-comment-remove-";
var ID_CONTENTS = "commento-comment-contents-";
var ID_SUBMIT_BUTTON = "commento-submit-button-";
function commentsRecurse(parentMap, parentHex) { function commentsRecurse(parentMap, parentHex) {
var cur = parentMap[parentHex]; var cur = parentMap[parentHex];
if (!cur || !cur.length) { if (!cur || !cur.length) {
@ -639,7 +656,6 @@
} }
attr(edit, "onclick", "startEdit('" + comment.commentHex + "')"); attr(edit, "onclick", "startEdit('" + comment.commentHex + "')");
attr(reply, "onclick", "replyShow('" + comment.commentHex + "')");
attr(collapse, "onclick", "commentCollapse('" + comment.commentHex + "')"); attr(collapse, "onclick", "commentCollapse('" + comment.commentHex + "')");
attr(approve, "onclick", "commentApprove('" + comment.commentHex + "')"); attr(approve, "onclick", "commentApprove('" + comment.commentHex + "')");
attr(remove, "onclick", "commentDelete('" + comment.commentHex + "')"); attr(remove, "onclick", "commentDelete('" + comment.commentHex + "')");
@ -657,9 +673,16 @@
attr(upvote, "onclick", "vote('" + comment.commentHex + "', 0, 1)"); attr(upvote, "onclick", "vote('" + comment.commentHex + "', 0, 1)");
attr(downvote, "onclick", "vote('" + comment.commentHex + "', 0, -1)"); attr(downvote, "onclick", "vote('" + comment.commentHex + "', 0, -1)");
} }
} else if (!chosenAnonymous) {
attr(upvote, "onclick", "replyShow('" + comment.commentHex + "')");
} }
else {
attr(upvote, "onclick", "loginBoxShow()");
attr(downvote, "onclick", "loginBoxShow()");
}
if (isAuthenticated || chosenAnonymous)
attr(reply, "onclick", "replyShow('" + comment.commentHex + "')");
else
attr(reply, "onclick", "loginBoxShow()");
if (isAuthenticated) { if (isAuthenticated) {
if (isModerator) { if (isModerator) {
@ -680,11 +703,9 @@
if (false) // replace when edit is implemented if (false) // replace when edit is implemented
append(options, edit); append(options, edit);
if (isAuthenticated) {
append(options, upvote); append(options, upvote);
append(options, downvote); append(options, downvote);
append(options, reply); append(options, reply);
}
if (isModerator) { if (isModerator) {
if (comment.state == "unapproved") if (comment.state == "unapproved")
@ -906,16 +927,11 @@
append(el, submit); append(el, submit);
} }
global.commentoAuth = function(provider, id) { global.commentoAuth = function(provider) {
if (provider == "anonymous") { if (provider == "anonymous") {
cookieSet("session", "anonymous"); cookieSet("session", "anonymous");
chosenAnonymous = true; chosenAnonymous = true;
refreshAll(function() { refreshAll();
if (id != "root")
global.replyShow(id);
$(ID_TEXTAREA + id).click();
$(ID_TEXTAREA + id).focus();
});
return; return;
} }
@ -933,12 +949,7 @@
var interval = setInterval(function() { var interval = setInterval(function() {
if (popup.closed) { if (popup.closed) {
refreshAll(function() { refreshAll();
if (id != "root")
global.replyShow(id);
$(ID_TEXTAREA + id).click();
$(ID_TEXTAREA + id).focus();
});
clearInterval(interval); clearInterval(interval);
} }
}, 250); }, 250);
@ -946,17 +957,305 @@
} }
function refreshAll(callback) { function refreshAll(callback) {
$("commento").innerHTML = ""; $(ID_ROOT).innerHTML = "";
shownSubmitButton = {"root": false}; shownSubmitButton = {"root": false};
shownReply = {}; shownReply = {};
main(callback); main(callback);
} }
function loginBoxCreate() {
var loginBoxContainer = create("div");
loginBoxContainer.id = ID_LOGIN_BOX_CONTAINER;
append(root, loginBoxContainer);
}
global.signupRender = function() {
var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
var loginBox = create("div");
var header = create("div");
var subtitle = create("div");
var emailContainer = create("div");
var email = create("div");
var emailInput = create("input");
var emailButton = create("button");
var loginLinkContainer = create("div");
var loginLink = create("a");
var hr = create("hr");
var oauthPretext = create("div");
var oauthButtonsContainer = create("div");
var oauthButtons = create("div");
var close = create("div");
loginBox.id = ID_LOGIN_BOX;
header.id = ID_LOGIN_BOX_HEADER;
subtitle.id = ID_LOGIN_BOX_SUBTITLE;
emailInput.id = ID_LOGIN_BOX_EMAIL_INPUT;
emailButton.id = ID_LOGIN_BOX_EMAIL_BUTTON;
loginLink.id = ID_LOGIN_BOX_LOGIN_LINK;
loginLinkContainer.id = ID_LOGIN_BOX_LOGIN_LINK_CONTAINER;
hr.id = ID_LOGIN_BOX_HR;
oauthPretext.id = ID_LOGIN_BOX_OAUTH_PRETEXT;
oauthButtonsContainer.id = ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER;
header.innerText = "Create an account to join the discussion!";
classAdd(loginBoxContainer, "login-box-container");
classAdd(loginBox, "login-box");
classAdd(header, "login-box-header");
classAdd(subtitle, "login-box-subtitle");
classAdd(emailContainer, "email-container");
classAdd(email, "email");
classAdd(emailInput, "input");
classAdd(emailButton, "email-button");
classAdd(loginLinkContainer, "login-link-container");
classAdd(loginLink, "login-link");
classAdd(oauthPretext, "login-box-subtitle");
classAdd(oauthButtonsContainer, "oauth-buttons-container");
classAdd(oauthButtons, "oauth-buttons");
classAdd(close, "login-box-close");
emailButton.innerText = "Continue";
loginLink.innerText = "Already have an account? Log in.";
subtitle.innerText = "Sign up with your email to vote and comment.";
oauthPretext.innerText = "Or proceed with social login.";
attr(loginBoxContainer, "style", "display: none; opacity: 0;");
attr(emailInput, "name", "email");
attr(emailInput, "placeholder", "Email address");
attr(emailInput, "type", "text");
attr(emailButton, "onclick", "passwordAsk()");
attr(loginLink, "onclick", "loginSwitch()");
attr(close, "onclick", "loginBoxClose()");
for (var i = 0; i < configuredOauths.length; i++) {
var button = create("button");
classAdd(button, "button");
classAdd(button, configuredOauths[i] + "-button");
button.innerText = configuredOauths[i];
attr(button, "onclick", "commentoAuth('" + configuredOauths[i] + "')");
append(oauthButtons, button);
}
append(loginBox, header);
append(loginBox, subtitle);
append(email, emailInput);
append(email, emailButton);
append(emailContainer, email);
append(loginBox, emailContainer);
append(loginLinkContainer, loginLink);
append(loginBox, loginLinkContainer);
append(loginBox, hr);
if (configuredOauths.length > 0) {
append(loginBox, oauthPretext);
append(oauthButtonsContainer, oauthButtons);
append(loginBox, oauthButtonsContainer);
}
append(loginBox, close);
loginBoxType = "signup";
loginBoxContainer.innerHTML = "";
append(loginBoxContainer, loginBox);
}
global.loginSwitch = function() {
var header = $(ID_LOGIN_BOX_HEADER);
var subtitle = $(ID_LOGIN_BOX_SUBTITLE);
var loginLink = $(ID_LOGIN_BOX_LOGIN_LINK);
var hr = $(ID_LOGIN_BOX_HR);
var oauthButtonsContainer = $(ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER);
var oauthPretext = $(ID_LOGIN_BOX_OAUTH_PRETEXT);
header.innerText = "Login to continue";
loginLink.innerText = "Don't have an account? Sign up.";
subtitle.innerText = "Enter your email address to start with.";
attr(loginLink, "onclick", "signupSwitch()");
loginBoxType = "login";
remove(hr);
remove(oauthPretext);
remove(oauthButtonsContainer);
}
global.signupSwitch = function() {
loginBoxClose();
loginBoxShow();
}
function loginUP(username, password) {
var json = {
email: username,
password: password,
};
post(origin + "/api/commenter/login", json, function(resp) {
if (!resp.success) {
loginBoxClose();
errorShow(resp.message);
return
}
cookieSet("session", resp.session);
refreshAll();
});
}
global.login = function() {
var email = $(ID_LOGIN_BOX_EMAIL_INPUT);
var password = $(ID_LOGIN_BOX_PASSWORD_INPUT);
loginUP(email.value, password.value);
}
global.signup = function() {
var email = $(ID_LOGIN_BOX_EMAIL_INPUT);
var name = $(ID_LOGIN_BOX_NAME_INPUT);
var website = $(ID_LOGIN_BOX_WEBSITE_INPUT);
var password = $(ID_LOGIN_BOX_PASSWORD_INPUT);
var json = {
email: email.value,
name: name.value,
website: website.value,
password: password.value,
};
post(origin + "/api/commenter/new", json, function(resp) {
if (!resp.success) {
loginBoxClose();
errorShow(resp.message);
return
}
loginUP(email.value, password.value);
});
}
global.passwordAsk = function() {
var loginBox = $(ID_LOGIN_BOX);
var subtitle = $(ID_LOGIN_BOX_SUBTITLE);
var emailInput = $(ID_LOGIN_BOX_EMAIL_INPUT);
var emailButton = $(ID_LOGIN_BOX_EMAIL_BUTTON);
var loginLinkContainer = $(ID_LOGIN_BOX_LOGIN_LINK_CONTAINER);
var hr = $(ID_LOGIN_BOX_HR);
var oauthButtonsContainer = $(ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER);
var oauthPretext = $(ID_LOGIN_BOX_OAUTH_PRETEXT);
remove(emailButton);
remove(loginLinkContainer);
if (loginBoxType == "signup") {
remove(hr);
remove(oauthPretext);
remove(oauthButtonsContainer);
}
var order, id, type, placeholder;
if (loginBoxType == "signup") {
var order = ["name", "website", "password"];
var id = [ID_LOGIN_BOX_NAME_INPUT, ID_LOGIN_BOX_WEBSITE_INPUT, ID_LOGIN_BOX_PASSWORD_INPUT];
var type = ["text", "text", "password"];
var placeholder = ["Real Name", "Website (Optional)", "Password"];
}
else {
var order = ["password"];
var id = [ID_LOGIN_BOX_PASSWORD_INPUT];
var type = ["password"];
var placeholder = ["Password"];
}
subtitle.innerText = "Finish the rest of your profile to complete."
for (var i = 0; i < order.length; i++) {
var fieldContainer = create("div");
var field = create("div");
var fieldInput = create("input");
fieldInput.id = id[i];
classAdd(fieldContainer, "email-container");
classAdd(field, "email");
classAdd(fieldInput, "input");
attr(fieldInput, "name", order[i]);
attr(fieldInput, "type", type[i]);
attr(fieldInput, "placeholder", placeholder[i]);
append(field, fieldInput);
append(fieldContainer, field);
if (order[i] == "password") {
var fieldButton = create("button");
classAdd(fieldButton, "email-button");
fieldButton.innerText = loginBoxType;
if (loginBoxType == "signup")
attr(fieldButton, "onclick", "signup()");
else
attr(fieldButton, "onclick", "login()");
append(field, fieldButton);
}
append(loginBox, fieldContainer);
}
}
function mainAreaCreate() {
var mainArea = create("div");
mainArea.id = ID_MAIN_AREA;
classAdd(mainArea, "main-area");
append(root, mainArea);
}
global.loginBoxClose = function() {
var mainArea = $(ID_MAIN_AREA);
var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
classRemove(mainArea, "blurred");
attr(loginBoxContainer, "style", "display: none");
}
global.loginBoxShow = function() {
var mainArea = $(ID_MAIN_AREA);
var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
global.signupRender();
classAdd(mainArea, "blurred");
attr(loginBoxContainer, "style", "");
window.location.hash = ID_LOGIN_BOX_CONTAINER;
$(ID_LOGIN_BOX_EMAIL_INPUT).focus();
}
function main(callback) { function main(callback) {
root = $("commento"); root = $(ID_ROOT);
loginBoxCreate();
errorElementCreate(); errorElementCreate();
mainAreaCreate();
selfGet(function() { selfGet(function() {
commentsGet(function() { commentsGet(function() {
rootCreate(function() { rootCreate(function() {

View File

@ -14,9 +14,9 @@ input[type=text]::placeholder {
textarea::placeholder { textarea::placeholder {
color: #aaa; color: #aaa;
font-size: 22px; font-size: 20px;
padding-top: 13px;
display: flex; display: flex;
line-height: 80px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
text-align: center; text-align: center;
@ -28,7 +28,7 @@ textarea {
padding: 8px; padding: 8px;
outline: none; outline: none;
overflow: auto; overflow: auto;
min-height: 75px; min-height: 100px;
width: 100%; width: 100%;
} }
@ -41,65 +41,42 @@ textarea {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&:hover {
.commento-button, .commento-buttons-container::before {
opacity: 1;
transform: translate(0px,-3px);
}
.commento-submit-button { .commento-submit-button {
transform: none; transform: none;
} }
.commento-blurred-textarea {
opacity: .7;
transform: scale(.95);
filter: blur(4px);
}
@media only screen and (max-width: 550px) {
.commento-buttons-container::before {
display: none;
}
}
}
} }
.commento-buttons-container { .commento-oauth-buttons-container {
display: inline-block; display: flex;
justify-content: center;
}
.commento-oauth-buttons,
.commento-create-container {
display: flex;
flex-direction: column;
align-items: center;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
opacity: 1;
} }
.commento-mobile-buttons-container::before, .commento-oauth-buttons {
.commento-buttons-container::before { display: contents;
content: "Authenticate with"; }
display: inline-flex;
.commento-create-container::before {
content: "Want to add to the discussion?";
display: block;
text-align: center;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-weight: bold; font-weight: bold;
line-height: 24px;
font-size: 14px; font-size: 14px;
padding: 6px; padding: 6px;
color: $gray-8; color: $gray-8;
transition: all 0.3s;
opacity: 0;
outline: none; outline: none;
} }
.commento-mobile-buttons-container::before {
content: "To join the discussion, authenticate with";;
display: block;
text-align: center;
}
@media only screen and (max-width: 550px) {
.commento-buttons-container::before {
display: none;
}
}
.commento-button { .commento-button {
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
@ -118,31 +95,25 @@ textarea {
color: #fff; color: #fff;
width: 100px; width: 100px;
margin-left: 5px; margin-left: 5px;
margin-left: 5px; margin-right: 5px;
opacity: 0;
}
.commento-opaque {
opacity: 1;
}
.commento-opaque::before {
opacity: 1;
} }
.commento-google-button { .commento-google-button {
transition: all 0.3s;
background: #dd4b39; background: #dd4b39;
text-transform: uppercase;
font-size: 12px;
} }
.commento-github-button { .commento-github-button {
transition: all 0.3s;
background: #000000; background: #000000;
text-transform: uppercase;
font-size: 12px;
} }
.commento-anonymous-button { .commento-anonymous-button {
transition: all 0.3s;
background: #096fa6; background: #096fa6;
text-transform: uppercase;
font-size: 12px;
} }
.commento-blurred-textarea { .commento-blurred-textarea {
@ -152,7 +123,6 @@ textarea {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
justify-content: space-between; justify-content: space-between;
transition: 0.3s all;
will-change: transform; will-change: transform;
} }
@ -160,7 +130,6 @@ textarea {
.commento-delete-button, .commento-delete-button,
.commento-submit-button { .commento-submit-button {
margin-top: 10px; margin-top: 10px;
opacity: 1;
font-size: 14px; font-size: 14px;
width: -moz-fit-content; width: -moz-fit-content;
width: -webkit-fit-content; width: -webkit-fit-content;
@ -169,6 +138,11 @@ textarea {
width: fit-content; width: fit-content;
} }
.commento-create-button {
width: 150px;
background: $pink-9;
}
.commento-submit-button { .commento-submit-button {
float: right; float: right;
background: $indigo-7; background: $indigo-7;

View File

@ -5,6 +5,7 @@
text-align: left; text-align: left;
margin-bottom: 16px; margin-bottom: 16px;
position: relative; position: relative;
height: 38px;
.commento-logout { .commento-logout {
cursor: pointer; cursor: pointer;
@ -23,15 +24,7 @@
font-weight: bold; font-weight: bold;
position: absolute; position: absolute;
top: 6px; top: 6px;
left: 64px; left: 48px;
}
.commento-photo {
width: 32px;
height: 32px;
border-radius: 50%;
margin-left: 16px;
margin-right: 16px;
} }
} }
} }

View File

@ -0,0 +1,92 @@
@import "colors-main.scss";
.commento-login-box-container {
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 0px;
overflow: visible;
.commento-login-box {
width: 90%;
max-width: 500px;
min-height: 125px;
background: $gray-1;
box-shadow: 0 4px 6px rgba(50,50,93,.11),0 1px 3px rgba(0,0,0,.08);
border: 1px solid transparent;
border-radius: 3px;
z-index: 100;
position: absolute;
top: 8px;
padding: 16px;
opacity: 1;
transition: opacity 0.2s;
hr {
border: none;
background: $gray-2;
height: 1px;
}
.commento-login-box-header {
font-size: 20px;
font-weight: 200;
text-align: center;
color: $pink-8;
margin: 16px;
}
.commento-login-box-subtitle {
color: $gray-6;
text-align: center;
font-size: 15px;
margin: 12px 0px;
}
@import "email-main.scss";
.commento-login-link-container {
margin: 16px;
width: calc(100% - 32px);
text-align: center;
.commento-login-link {
font-size: 15px;
font-weight: bold;
border-bottom: none;
}
}
.commento-login-box-close {
position: absolute;
right: 16px;
top: 16px;
width: 16px;
height: 16px;
opacity: 0.3;
}
.commento-login-box-close:hover {
opacity: 1;
cursor: pointer;
}
.commento-login-box-close:before, .commento-login-box-close:after {
position: absolute;
left: 7px;
content: ' ';
height: 17px;
width: 2px;
background-color: $gray-7;
}
.commento-login-box-close:before {
transform: rotate(45deg);
}
.commento-login-box-close:after {
transform: rotate(-45deg);
}
}
}

View File

@ -1,11 +1,14 @@
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
#commento { #commento {
font-family: "Source Sans Pro", "Segoe UI", Roboto, "Helvetica Neue", sans-serif; font-family: "Source Sans Pro", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
font-size: 14px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
color: #50596c; color: #50596c;
overflow-x: hidden; overflow-x: hidden;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
padding: 8px; padding: 8px;
min-height: 350px;
@import "colors-main.scss"; @import "colors-main.scss";
@import "common-main.scss"; @import "common-main.scss";
@ -15,11 +18,20 @@
@import "commento-input.scss"; @import "commento-input.scss";
@import "commento-logged.scss"; @import "commento-logged.scss";
@import "commento-buttons.scss"; @import "commento-buttons.scss";
@import "commento-login.scss";
.commento-hidden { .commento-hidden {
display: none; display: none;
} }
.commento-blurred {
filter: blur(4px);
}
.commento-main-area {
transition: filter 0.2s;
}
.commento-error-box { .commento-error-box {
width: 100%; width: 100%;
border-radius: 4px; border-radius: 4px;
@ -43,15 +55,6 @@
background: $blue-1; background: $blue-1;
} }
.commento-card {
padding: 12px 0px 0px 12px;
margin-top: 16px;
border-top: 1px solid #f0f0f0;
.commento-header {
padding-bottom: 12px;
}
.commento-avatar { .commento-avatar {
width: 34px; width: 34px;
height: 34px; height: 34px;
@ -78,6 +81,15 @@
margin-right: 10px; margin-right: 10px;
} }
.commento-card {
padding: 12px 0px 0px 12px;
margin-top: 16px;
border-top: 1px solid #f0f0f0;
.commento-header {
padding-bottom: 12px;
}
.commento-avatar::after { .commento-avatar::after {
content:""; content:"";
display:block; display:block;

View File

@ -105,60 +105,7 @@ body {
} }
} }
.email-container { @import "email-main.scss";
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 { .mod-emails-container {
display: flex; display: flex;

View File

@ -0,0 +1,58 @@
@import "colors-main.scss";
@import "common-main.scss";
.commento-email-container {
display: flex;
justify-content: center;
width: 100%;
margin: 8px 0px;
.commento-email {
@extend .shadow;
border-radius: 4px;
background: $white;
width: 100%;
max-width: 400px;
.commento-input {
height: 40px;
background: $white;
border: none;
outline: none;
padding: 5px;
padding-left: 10px;
width: calc(100% - 120px);
}
.commento-input::placeholder {
color: $gray-5;
}
.commento-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;
}
.commento-email-button:hover {
color: $blue-6;
}
.commento-email-button:disabled {
cursor: default;
color: $gray-6;
}
}
}