mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-10 14:40:47 +02:00
Merge branch 'master' into ja-track-changes
This commit is contained in:
@@ -25,8 +25,15 @@ module.exports =
|
||||
announcementIndex = _.findIndex announcements, (announcement)->
|
||||
announcement.id == lastSeenBlogId
|
||||
|
||||
if announcementIndex != -1
|
||||
announcements = announcements.slice(0, announcementIndex)
|
||||
announcements = _.map announcements, (announcement, index)->
|
||||
if announcementIndex == -1
|
||||
read = false
|
||||
else if index >= announcementIndex
|
||||
read = true
|
||||
else
|
||||
read = false
|
||||
announcement.read = read
|
||||
return announcement
|
||||
|
||||
logger.log announcementsLength:announcements?.length, user_id:user_id, "returning announcements"
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ module.exports = AuthenticationController =
|
||||
return next()
|
||||
else
|
||||
logger.log url:req.url, "user trying to access endpoint not in global whitelist"
|
||||
AuthenticationController._setRedirectInSession(req)
|
||||
return res.redirect "/login"
|
||||
|
||||
httpAuth: basicAuth (user, pass)->
|
||||
|
||||
+23
-5
@@ -4,6 +4,7 @@ UserGetter = require "../User/UserGetter"
|
||||
CollaboratorsHandler = require('./CollaboratorsHandler')
|
||||
CollaboratorsInviteHandler = require('./CollaboratorsInviteHandler')
|
||||
logger = require('logger-sharelatex')
|
||||
Settings = require('settings-sharelatex')
|
||||
EmailHelper = require "../Helpers/EmailHelper"
|
||||
EditorRealTimeController = require("../Editor/EditorRealTimeController")
|
||||
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
|
||||
@@ -21,6 +22,16 @@ module.exports = CollaboratorsInviteController =
|
||||
return next(err)
|
||||
res.json({invites: invites})
|
||||
|
||||
_checkShouldInviteEmail: (email, callback=(err, shouldAllowInvite)->) ->
|
||||
if Settings.restrictInvitesToExistingAccounts == true
|
||||
logger.log {email}, "checking if user exists with this email"
|
||||
UserGetter.getUser {email: email}, {_id: 1}, (err, user) ->
|
||||
return callback(err) if err?
|
||||
userExists = user? and user?._id?
|
||||
callback(null, userExists)
|
||||
else
|
||||
callback(null, true)
|
||||
|
||||
inviteToProject: (req, res, next) ->
|
||||
projectId = req.params.Project_id
|
||||
email = req.body.email
|
||||
@@ -37,13 +48,20 @@ module.exports = CollaboratorsInviteController =
|
||||
if !email? or email == ""
|
||||
logger.log {projectId, email, sendingUserId}, "invalid email address"
|
||||
return res.sendStatus(400)
|
||||
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
|
||||
CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)->
|
||||
if err?
|
||||
logger.err {projectId, email, sendingUserId}, "error creating project invite"
|
||||
logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address"
|
||||
return next(err)
|
||||
logger.log {projectId, email, sendingUserId}, "invite created"
|
||||
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
|
||||
return res.json {invite: invite}
|
||||
if !shouldAllowInvite
|
||||
logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address"
|
||||
return res.json {invite: null, error: 'cannot_invite_non_user'}
|
||||
CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) ->
|
||||
if err?
|
||||
logger.err {projectId, email, sendingUserId}, "error creating project invite"
|
||||
return next(err)
|
||||
logger.log {projectId, email, sendingUserId}, "invite created"
|
||||
EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true})
|
||||
return res.json {invite: invite}
|
||||
|
||||
revokeInvite: (req, res, next) ->
|
||||
projectId = req.params.Project_id
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
_ = require("underscore")
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = _.template """
|
||||
<table class="row" style="border-collapse: collapse; border-spacing: 0; display: table; padding: 0; position: relative; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;">
|
||||
<th class="small-12 large-12 columns first last" style="Margin: 0 auto; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0 auto; padding: 0; padding-bottom: 16px; padding-left: 16px; padding-right: 16px; text-align: left; width: 564px;"><table style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tr style="padding: 0; text-align: left; vertical-align: top;"><th style="Margin: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left;">
|
||||
<h3 class="avoid-auto-linking" style="Margin: 0; Margin-bottom: px; color: inherit; font-family: Baskerville, 'Baskerville Old Face', Georgia, serif; font-size: 24px; font-weight: normal; line-height: 1.3; margin: 0; margin-bottom: px; padding: 0; text-align: left; word-wrap: normal;">
|
||||
<%= title %>
|
||||
</h3>
|
||||
<table class="spacer" style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="20px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 20px; font-weight: normal; hyphens: auto; line-height: 20px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
<p style="Margin: 0; Margin-bottom: 10px; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; margin-bottom: 10px; padding: 0; text-align: left;">
|
||||
<%= greeting %>
|
||||
</p>
|
||||
<p class="avoid-auto-linking" style="Margin: 0; Margin-bottom: 10px; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; margin-bottom: 10px; padding: 0; text-align: left;">
|
||||
<%= message %>
|
||||
</p>
|
||||
<table class="spacer" style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="20px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 20px; font-weight: normal; hyphens: auto; line-height: 20px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
<center data-parsed="" style="min-width: 532px; width: 100%;">
|
||||
<table class="button float-center" style="Margin: 0 0 16px 0; border-collapse: collapse; border-spacing: 0; float: none; margin: 0 0 16px 0; padding: 0; text-align: center; vertical-align: top; width: auto;"><tr style="padding: 0; text-align: left; vertical-align: top;"><td style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"><table style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tr style="padding: 0; text-align: left; vertical-align: top;"><td style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; background: #a93529; border: 2px solid #a93529; border-collapse: collapse !important; color: #fefefe; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<a href="<%= ctaURL %>" style="Margin: 0; border: 0 solid #a93529; border-radius: 3px; color: #fefefe; display: inline-block; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; line-height: 1.3; margin: 0; padding: 8px 16px 8px 16px; text-align: left; text-decoration: none;">
|
||||
<%= ctaText %>
|
||||
</a>
|
||||
</td></tr></table></td></tr></table>
|
||||
</center>
|
||||
<% if (secondaryMessage) { %>
|
||||
<table class="spacer" style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="20px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 20px; font-weight: normal; hyphens: auto; line-height: 20px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
<p class="avoid-auto-linking" style="Margin: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left;">
|
||||
<%= secondaryMessage %>
|
||||
</p>
|
||||
<% } %>
|
||||
</th>
|
||||
<th class="expander" style="Margin: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; padding: 0 !important; text-align: left; visibility: hidden; width: 0;"></th></tr></table></th>
|
||||
</tr></tbody></table>
|
||||
<% if (gmailGoToAction) { %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "EmailMessage",
|
||||
"potentialAction": {
|
||||
"@type": "ViewAction",
|
||||
"target": "<%= gmailGoToAction.target %>",
|
||||
"url": "<%= gmailGoToAction.target %>",
|
||||
"name": "<%= gmailGoToAction.name %>"
|
||||
},
|
||||
"description": "<%= gmailGoToAction.description %>"
|
||||
}
|
||||
</script>
|
||||
<% } %>
|
||||
"""
|
||||
@@ -1,6 +1,12 @@
|
||||
_ = require('underscore')
|
||||
|
||||
PersonalEmailLayout = require("./Layouts/PersonalEmailLayout")
|
||||
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
|
||||
BaseWithHeaderEmailLayout = require("./Layouts/BaseWithHeaderEmailLayout")
|
||||
|
||||
SingleCTAEmailBody = require("./Bodies/SingleCTAEmailBody")
|
||||
|
||||
|
||||
settings = require("settings-sharelatex")
|
||||
|
||||
|
||||
@@ -61,7 +67,7 @@ ShareLaTeX Co-founder
|
||||
|
||||
templates.passwordResetRequested =
|
||||
subject: _.template "Password Reset - #{settings.appName}"
|
||||
layout: NotificationEmailLayout
|
||||
layout: BaseWithHeaderEmailLayout
|
||||
type:"notification"
|
||||
plainTextTemplate: _.template """
|
||||
Password Reset
|
||||
@@ -78,36 +84,21 @@ Thank you
|
||||
|
||||
#{settings.appName} - <%= siteUrl %>
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<h2>Password Reset</h2>
|
||||
<p>
|
||||
We got a request to reset your #{settings.appName} password.
|
||||
<p>
|
||||
<center>
|
||||
<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:24px;">
|
||||
<div style="padding-right:10px;padding-left:10px">
|
||||
<a href="<%= setNewPasswordUrl %>" style="text-decoration:none" target="_blank">
|
||||
<span style= "font-size:16px;font-family:Arial;font-weight:bold;color:#fff;white-space:nowrap;display:block; text-align:center">
|
||||
Reset password
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
If you ignore this message, your password won't be changed.
|
||||
<p>
|
||||
If you didn't request a password reset, let us know.
|
||||
|
||||
</p>
|
||||
<p>Thank you</p>
|
||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
compiledTemplate: (opts) ->
|
||||
SingleCTAEmailBody({
|
||||
title: "Password Reset"
|
||||
greeting: "Hi,"
|
||||
message: "We got a request to reset your #{settings.appName} password."
|
||||
secondaryMessage: "If you ignore this message, your password won't be changed.<br>If you didn't request a password reset, let us know."
|
||||
ctaText: "Reset password"
|
||||
ctaURL: opts.setNewPasswordUrl
|
||||
gmailGoToAction: null
|
||||
})
|
||||
|
||||
|
||||
templates.projectInvite =
|
||||
subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
|
||||
layout: NotificationEmailLayout
|
||||
layout: BaseWithHeaderEmailLayout
|
||||
type:"notification"
|
||||
plainTextTemplate: _.template """
|
||||
Hi, <%= owner.email %> wants to share '<%= project.name %>' with you.
|
||||
@@ -118,23 +109,25 @@ Thank you
|
||||
|
||||
#{settings.appName} - <%= siteUrl %>
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<p>Hi, <%= owner.email %> wants to share <a href="<%= inviteUrl %>">'<%= project.name %>'</a> with you</p>
|
||||
<center>
|
||||
<a style="text-decoration: none; width: 200px; background-color: #a93629; border: 1px solid #e24b3b; border-radius: 3px; padding: 15px; margin: 24px; display: block;" href="<%= inviteUrl %>" style="text-decoration:none" target="_blank">
|
||||
<span style= "font-size:16px;font-family:Helvetica,Arial;font-weight:400;color:#fff;white-space:nowrap;display:block; text-align:center">
|
||||
View Project
|
||||
</span>
|
||||
</a>
|
||||
</center>
|
||||
<p> Thank you</p>
|
||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
compiledTemplate: (opts) ->
|
||||
SingleCTAEmailBody({
|
||||
title: "#{ opts.project.name } – shared by #{ opts.owner.email }"
|
||||
greeting: "Hi,"
|
||||
message: "#{ opts.owner.email } wants to share “#{ opts.project.name }” with you."
|
||||
secondaryMessage: null
|
||||
ctaText: "View project"
|
||||
ctaURL: opts.inviteUrl
|
||||
gmailGoToAction:
|
||||
target: opts.inviteUrl
|
||||
name: "View project"
|
||||
description: "Join #{ opts.project.name } at ShareLaTeX"
|
||||
})
|
||||
|
||||
|
||||
|
||||
templates.completeJoinGroupAccount =
|
||||
subject: _.template "Verify Email to join <%= group_name %> group"
|
||||
layout: NotificationEmailLayout
|
||||
layout: BaseWithHeaderEmailLayout
|
||||
type:"notification"
|
||||
plainTextTemplate: _.template """
|
||||
Hi, please verify your email to join the <%= group_name %> and get your free premium account
|
||||
@@ -145,23 +138,16 @@ Thank You
|
||||
|
||||
#{settings.appName} - <%= siteUrl %>
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<p>Hi, please verify your email to join the <%= group_name %> and get your free premium account</p>
|
||||
<center>
|
||||
<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:24px;">
|
||||
<div style="padding-right:10px;padding-left:10px">
|
||||
<a href="<%= completeJoinUrl %>" style="text-decoration:none" target="_blank">
|
||||
<span style= "font-size:16px;font-family:Helvetica,Arial;font-weight:400;color:#fff;white-space:nowrap;display:block; text-align:center">
|
||||
Verify now
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
<p> Thank you</p>
|
||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
|
||||
compiledTemplate: (opts) ->
|
||||
SingleCTAEmailBody({
|
||||
title: "Verify Email to join #{ opts.group_name } group"
|
||||
greeting: "Hi,"
|
||||
message: "please verify your email to join the #{ opts.group_name } group and get your free premium account."
|
||||
secondaryMessage: null
|
||||
ctaText: "Verify now"
|
||||
ctaURL: opts.completeJoinUrl
|
||||
gmailGoToAction: null
|
||||
})
|
||||
|
||||
module.exports =
|
||||
templates: templates
|
||||
@@ -177,4 +163,4 @@ module.exports =
|
||||
html: template.layout(opts)
|
||||
text: template?.plainTextTemplate?(opts)
|
||||
type:template.type
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
_ = require("underscore")
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = _.template """
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" style="Margin: 0; background: #f6f6f6 !important; margin: 0; min-height: 100%; padding: 0;">
|
||||
<head>
|
||||
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Project invite</title>
|
||||
<style>.avoid-auto-linking a,
|
||||
.avoid-auto-linking a[href] {
|
||||
color: #a93529 !important;
|
||||
text-decoration: none !important;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none; }
|
||||
.avoid-auto-linking a:visited,
|
||||
.avoid-auto-linking a[href]:visited {
|
||||
color: #a93529; }
|
||||
.avoid-auto-linking a:hover,
|
||||
.avoid-auto-linking a[href]:hover {
|
||||
color: #80281f; }
|
||||
.avoid-auto-linking a:active,
|
||||
.avoid-auto-linking a[href]:active {
|
||||
color: #80281f; }
|
||||
@media only screen {
|
||||
html {
|
||||
min-height: 100%;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
.small-float-center {
|
||||
margin: 0 auto !important;
|
||||
float: none !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.small-text-center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.small-text-left {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.small-text-right {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
.hide-for-large {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
font-size: inherit !important;
|
||||
line-height: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
table.body table.container .hide-for-large,
|
||||
table.body table.container .row.hide-for-large {
|
||||
display: table !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
table.body table.container .callout-inner.hide-for-large {
|
||||
display: table-cell !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
table.body table.container .show-for-large {
|
||||
display: none !important;
|
||||
width: 0;
|
||||
mso-hide: all;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 596px) {
|
||||
table.body img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
table.body center {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
table.body .container {
|
||||
width: 95% !important;
|
||||
}
|
||||
|
||||
table.body .columns,
|
||||
table.body .column {
|
||||
height: auto !important;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
||||
table.body .columns .column,
|
||||
table.body .columns .columns,
|
||||
table.body .column .column,
|
||||
table.body .column .columns {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
table.body .collapse .columns,
|
||||
table.body .collapse .column {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
td.small-1,
|
||||
th.small-1 {
|
||||
display: inline-block !important;
|
||||
width: 8.33333% !important;
|
||||
}
|
||||
|
||||
td.small-2,
|
||||
th.small-2 {
|
||||
display: inline-block !important;
|
||||
width: 16.66667% !important;
|
||||
}
|
||||
|
||||
td.small-3,
|
||||
th.small-3 {
|
||||
display: inline-block !important;
|
||||
width: 25% !important;
|
||||
}
|
||||
|
||||
td.small-4,
|
||||
th.small-4 {
|
||||
display: inline-block !important;
|
||||
width: 33.33333% !important;
|
||||
}
|
||||
|
||||
td.small-5,
|
||||
th.small-5 {
|
||||
display: inline-block !important;
|
||||
width: 41.66667% !important;
|
||||
}
|
||||
|
||||
td.small-6,
|
||||
th.small-6 {
|
||||
display: inline-block !important;
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
td.small-7,
|
||||
th.small-7 {
|
||||
display: inline-block !important;
|
||||
width: 58.33333% !important;
|
||||
}
|
||||
|
||||
td.small-8,
|
||||
th.small-8 {
|
||||
display: inline-block !important;
|
||||
width: 66.66667% !important;
|
||||
}
|
||||
|
||||
td.small-9,
|
||||
th.small-9 {
|
||||
display: inline-block !important;
|
||||
width: 75% !important;
|
||||
}
|
||||
|
||||
td.small-10,
|
||||
th.small-10 {
|
||||
display: inline-block !important;
|
||||
width: 83.33333% !important;
|
||||
}
|
||||
|
||||
td.small-11,
|
||||
th.small-11 {
|
||||
display: inline-block !important;
|
||||
width: 91.66667% !important;
|
||||
}
|
||||
|
||||
td.small-12,
|
||||
th.small-12 {
|
||||
display: inline-block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.columns td.small-12,
|
||||
.column td.small-12,
|
||||
.columns th.small-12,
|
||||
.column th.small-12 {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-1,
|
||||
table.body th.small-offset-1 {
|
||||
margin-left: 8.33333% !important;
|
||||
Margin-left: 8.33333% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-2,
|
||||
table.body th.small-offset-2 {
|
||||
margin-left: 16.66667% !important;
|
||||
Margin-left: 16.66667% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-3,
|
||||
table.body th.small-offset-3 {
|
||||
margin-left: 25% !important;
|
||||
Margin-left: 25% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-4,
|
||||
table.body th.small-offset-4 {
|
||||
margin-left: 33.33333% !important;
|
||||
Margin-left: 33.33333% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-5,
|
||||
table.body th.small-offset-5 {
|
||||
margin-left: 41.66667% !important;
|
||||
Margin-left: 41.66667% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-6,
|
||||
table.body th.small-offset-6 {
|
||||
margin-left: 50% !important;
|
||||
Margin-left: 50% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-7,
|
||||
table.body th.small-offset-7 {
|
||||
margin-left: 58.33333% !important;
|
||||
Margin-left: 58.33333% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-8,
|
||||
table.body th.small-offset-8 {
|
||||
margin-left: 66.66667% !important;
|
||||
Margin-left: 66.66667% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-9,
|
||||
table.body th.small-offset-9 {
|
||||
margin-left: 75% !important;
|
||||
Margin-left: 75% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-10,
|
||||
table.body th.small-offset-10 {
|
||||
margin-left: 83.33333% !important;
|
||||
Margin-left: 83.33333% !important;
|
||||
}
|
||||
|
||||
table.body td.small-offset-11,
|
||||
table.body th.small-offset-11 {
|
||||
margin-left: 91.66667% !important;
|
||||
Margin-left: 91.66667% !important;
|
||||
}
|
||||
|
||||
table.body table.columns td.expander,
|
||||
table.body table.columns th.expander {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
table.body .right-text-pad,
|
||||
table.body .text-pad-right {
|
||||
padding-left: 10px !important;
|
||||
}
|
||||
|
||||
table.body .left-text-pad,
|
||||
table.body .text-pad-left {
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
table.menu {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table.menu td,
|
||||
table.menu th {
|
||||
width: auto !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
table.menu.vertical td,
|
||||
table.menu.vertical th,
|
||||
table.menu.small-vertical td,
|
||||
table.menu.small-vertical th {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
table.menu[align="center"] {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
table.button.small-expand,
|
||||
table.button.small-expanded {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table.button.small-expand table,
|
||||
table.button.small-expanded table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.button.small-expand table a,
|
||||
table.button.small-expanded table a {
|
||||
text-align: center !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
table.button.small-expand center,
|
||||
table.button.small-expanded center {
|
||||
min-width: 0;
|
||||
}
|
||||
}</style>
|
||||
</head>
|
||||
<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0" bgcolor="#F6F6F6" style="-moz-box-sizing: border-box; -ms-text-size-adjust: 100%; -webkit-box-sizing: border-box; -webkit-text-size-adjust: 100%; Margin: 0; background: #f6f6f6 !important; box-sizing: border-box; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; min-height: 100%; min-width: 100%; padding: 0; text-align: left; width: 100% !important;">
|
||||
<!-- <span class="preheader"></span> -->
|
||||
<table class="body" border="0" cellspacing="0" cellpadding="0" width="100%" height="100%" style="Margin: 0; background: #f6f6f6 !important; border-collapse: collapse; border-spacing: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; height: 100%; line-height: 1.3; margin: 0; min-height: 100%; padding: 0; text-align: left; vertical-align: top; width: 100%;">
|
||||
<tr style="padding: 0; text-align: left; vertical-align: top;">
|
||||
<td class="body-cell" align="center" valign="top" bgcolor="#F6F6F6" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; background: #f6f6f6 !important; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; padding-bottom: 20px; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<center data-parsed="" style="min-width: 580px; width: 100%;">
|
||||
|
||||
<table align="center" class="wrapper header float-center" style="Margin: 0 auto; background: #fefefe; border-bottom: solid 1px #cfcfcf; border-collapse: collapse; border-spacing: 0; float: none; margin: 0 auto; padding: 0; text-align: center; vertical-align: top; width: 100%;"><tr style="padding: 0; text-align: left; vertical-align: top;"><td class="wrapper-inner" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 20px; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<table align="center" class="container" style="Margin: 0 auto; background: transparent; border-collapse: collapse; border-spacing: 0; margin: 0 auto; padding: 0; text-align: inherit; vertical-align: top; width: 580px;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<table class="row collapse" style="border-collapse: collapse; border-spacing: 0; display: table; padding: 0; position: relative; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;">
|
||||
<th class="small-12 large-12 columns first last" style="Margin: 0 auto; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0 auto; padding: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; text-align: left; width: 588px;"><table style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tr style="padding: 0; text-align: left; vertical-align: top;"><th style="Margin: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left;">
|
||||
<h1 class="sl-logotype" style="Margin: 0; Margin-bottom: 0; color: #333333; font-family: Baskerville, 'Baskerville Old Face', Georgia, serif; font-size: 26px; font-weight: normal; line-height: 1.3; margin: 0; margin-bottom: 0; padding: 0; text-align: left; word-wrap: normal;">
|
||||
<span>S</span><span class="sl-logotype-small" style="font-size: 80%;">HARE</span><span>L</span><span class="sl-logotype-small" style="font-size: 80%;">A</span><span>T</span><span class="sl-logotype-small" style="font-size: 80%;">E</span><span>X</span>
|
||||
</h1>
|
||||
</th>
|
||||
<th class="expander" style="Margin: 0; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; padding: 0 !important; text-align: left; visibility: hidden; width: 0;"></th></tr></table></th>
|
||||
</tr></tbody></table>
|
||||
</td></tr></tbody></table>
|
||||
</td></tr></table>
|
||||
<table class="spacer float-center" style="Margin: 0 auto; border-collapse: collapse; border-spacing: 0; float: none; margin: 0 auto; padding: 0; text-align: center; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="20px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 20px; font-weight: normal; hyphens: auto; line-height: 20px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
<table align="center" class="container main float-center" style="Margin: 0 auto; Margin-top: 10px; background: #fefefe; border-collapse: collapse; border-spacing: 0; float: none; margin: 0 auto; margin-top: 10px; padding: 0; text-align: center; vertical-align: top; width: 580px;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<table class="spacer" style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="20px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 20px; font-weight: normal; hyphens: auto; line-height: 20px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
|
||||
<%= body %>
|
||||
|
||||
<table class="wrapper secondary" align="center" style="background: #f6f6f6; border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tr style="padding: 0; text-align: left; vertical-align: top;"><td class="wrapper-inner" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; hyphens: auto; line-height: 1.3; margin: 0; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;">
|
||||
<table class="spacer" style="border-collapse: collapse; border-spacing: 0; padding: 0; text-align: left; vertical-align: top; width: 100%;"><tbody><tr style="padding: 0; text-align: left; vertical-align: top;"><td height="10px" style="-moz-hyphens: auto; -webkit-hyphens: auto; Margin: 0; border-collapse: collapse !important; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 10px; font-weight: normal; hyphens: auto; line-height: 10px; margin: 0; mso-line-height-rule: exactly; padding: 0; text-align: left; vertical-align: top; word-wrap: break-word;"> </td></tr></tbody></table>
|
||||
<p style="Margin: 0; Margin-bottom: 10px; color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-weight: normal; line-height: 1.3; margin: 0; margin-bottom: 10px; padding: 0; text-align: left;"><small style="color: #7a7a7a; font-size: 80%;">
|
||||
#{ settings.appName} • <a href="#{ settings.siteUrl }" style="Margin: 0; color: #a93529; font-family: Helvetica, Arial, sans-serif; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left; text-decoration: none;">#{ settings.siteUrl }</a>
|
||||
</small></p>
|
||||
</td></tr></table>
|
||||
</td></tr></tbody></table>
|
||||
|
||||
</center>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- prevent Gmail on iOS font size manipulation -->
|
||||
<div style="display:none; white-space:nowrap; font:15px courier; line-height:0;"> </div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
"""
|
||||
@@ -197,7 +197,7 @@ module.exports = ProjectController =
|
||||
user_id = null
|
||||
|
||||
project_id = req.params.Project_id
|
||||
logger.log project_id:project_id, "loading editor"
|
||||
logger.log project_id:project_id, anonymous:anonymous, user_id:user_id, "loading editor"
|
||||
|
||||
async.parallel {
|
||||
project: (cb)->
|
||||
|
||||
@@ -244,6 +244,8 @@ module.exports = (app, webRouter, apiRouter)->
|
||||
for key, value of Settings.nav
|
||||
res.locals.nav[key] = _.clone(Settings.nav[key])
|
||||
res.locals.templates = Settings.templateLinks
|
||||
if res.locals.nav.header
|
||||
console.error {}, "The `nav.header` setting is no longer supported, use `nav.header_extras` instead"
|
||||
next()
|
||||
|
||||
webRouter.use (req, res, next) ->
|
||||
|
||||
@@ -11,4 +11,4 @@ block content
|
||||
| Sorry, ShareLaTeX is briefly down for maintenance.
|
||||
| We should be back within minutes, but if not, or you have
|
||||
| an urgent request, please contact us at
|
||||
| support@sharelatex.com
|
||||
| #{settings.adminEmail}
|
||||
|
||||
@@ -24,7 +24,10 @@ nav.navbar.navbar-default
|
||||
li
|
||||
a(href="/admin/user") Manage Users
|
||||
|
||||
each item in nav.header
|
||||
|
||||
// loop over header_extras
|
||||
each item in nav.header_extras
|
||||
|
||||
if ((item.only_when_logged_in && getSessionUser()) || (item.only_when_logged_out && (!getSessionUser())) || (!item.only_when_logged_out && !item.only_when_logged_in))
|
||||
if item.dropdown
|
||||
li.dropdown(class=item.class, dropdown)
|
||||
@@ -35,9 +38,6 @@ nav.navbar.navbar-default
|
||||
each child in item.dropdown
|
||||
if child.divider
|
||||
li.divider
|
||||
else if child.user_email
|
||||
li
|
||||
div.subdued #{getUserEmail()}
|
||||
else
|
||||
li
|
||||
if child.url
|
||||
@@ -50,7 +50,35 @@ nav.navbar.navbar-default
|
||||
a(href=item.url, class=item.class) !{translate(item.text)}
|
||||
else
|
||||
| !{translate(item.text)}
|
||||
|
||||
|
||||
|
||||
|
||||
// logged out
|
||||
if !getSessionUser()
|
||||
// register link
|
||||
if !externalAuthenticationSystemUsed()
|
||||
li
|
||||
a(href="/register") #{translate('register')}
|
||||
|
||||
// login link
|
||||
li
|
||||
a(href="/login") #{translate('log_in')}
|
||||
|
||||
// projects link and account menu
|
||||
if getSessionUser()
|
||||
li
|
||||
a(href="/project") #{translate('Projects')}
|
||||
li.dropdown(dropdown)
|
||||
a.dropbodw-toggle(href, dropdown-toggle)
|
||||
| #{translate('Account')}
|
||||
b.caret
|
||||
ul.dropdown-menu
|
||||
li
|
||||
div.subdued #{getUserEmail()}
|
||||
li.divider
|
||||
li
|
||||
a(href="/user/settings") #{translate('Account Settings')}
|
||||
if nav.showSubscriptionLink
|
||||
li
|
||||
a(href="/user/subscription") #{translate('subscription')}
|
||||
li.divider
|
||||
li
|
||||
a(href="/logout") #{translate('log_out')}
|
||||
|
||||
@@ -137,10 +137,15 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
|
||||
p.small(ng-show="startedFreeTrial")
|
||||
| #{translate("refresh_page_after_starting_free_trial")}.
|
||||
|
||||
.modal-footer
|
||||
.modal-footer.modal-footer-share
|
||||
.modal-footer-left
|
||||
i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
|
||||
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
||||
span.text-danger.error(ng-show="state.error")
|
||||
span(ng-switch="state.errorReason")
|
||||
span(ng-switch-when="cannot_invite_non_user")
|
||||
| #{translate("cannot_invite_non_user")}
|
||||
span(ng-switch-default)
|
||||
| #{translate("generic_something_went_wrong")}
|
||||
button.btn.btn-default(
|
||||
ng-click="done()"
|
||||
) #{translate("close")}
|
||||
|
||||
@@ -19,12 +19,46 @@ block content
|
||||
}
|
||||
};
|
||||
|
||||
.content.content-alt(ng-controller="ProjectPageController")
|
||||
.content.content-alt.project-list-page(ng-controller="ProjectPageController")
|
||||
.container
|
||||
|
||||
//- div(ng-controller="AnnouncementsController", ng-cloak)
|
||||
//- .alert.alert-success(ng-show="dataRecived")
|
||||
//- a(href, ng-click="openLink()") {{title}} and {{totalAnnouncements}} others
|
||||
.announcements(
|
||||
ng-controller="AnnouncementsController"
|
||||
ng-class="{ 'announcements-open': ui.isOpen }"
|
||||
ng-cloak
|
||||
)
|
||||
.announcements-backdrop(
|
||||
ng-if="ui.isOpen"
|
||||
ng-click="toggleAnnouncementsUI();"
|
||||
)
|
||||
a.announcements-btn(
|
||||
href
|
||||
ng-if="announcements.length"
|
||||
ng-click="toggleAnnouncementsUI();"
|
||||
ng-class="{ 'announcements-btn-open': ui.isOpen, 'announcements-btn-has-new': ui.newItems }"
|
||||
)
|
||||
span.announcements-badge(ng-if="ui.newItems") {{ ui.newItems }}
|
||||
.announcements-body(
|
||||
ng-if="ui.isOpen"
|
||||
)
|
||||
.announcements-scroller
|
||||
.announcement(
|
||||
ng-repeat="announcement in announcements | filter:(ui.newItems ? { read: false } : '') track by announcement.id"
|
||||
)
|
||||
h2.announcement-header {{ announcement.title }}
|
||||
p.announcement-description(ng-bind-html="announcement.excerpt")
|
||||
.announcement-meta
|
||||
p.announcement-date {{ announcement.date | date:"longDate" }}
|
||||
a.announcement-link(
|
||||
ng-href="{{ announcement.url }}"
|
||||
target="_blank"
|
||||
) Read more
|
||||
div.text-center(
|
||||
ng-if="ui.newItems > 0 && ui.newItems < announcements.length"
|
||||
)
|
||||
a.btn.btn-default.btn-sm(
|
||||
href
|
||||
ng-click="showAll();"
|
||||
) Show all
|
||||
|
||||
.row(ng-cloak)
|
||||
span(ng-if="projects.length > 0")
|
||||
|
||||
@@ -276,6 +276,10 @@ module.exports = settings =
|
||||
# Cookie max age (in milliseconds). Set to false for a browser session.
|
||||
cookieSessionLength: 5 * 24 * 60 * 60 * 1000 # 5 days
|
||||
|
||||
# When true, only allow invites to be sent to email addresses that
|
||||
# already have user accounts
|
||||
restrictInvitesToExistingAccounts: false
|
||||
|
||||
# Should we allow access to any page without logging in? This includes
|
||||
# public projects, /learn, /templates, about pages, etc.
|
||||
allowPublicAccess: if process.env["SHARELATEX_ALLOW_PUBLIC_ACCESS"] == 'true' then true else false
|
||||
@@ -331,35 +335,11 @@ module.exports = settings =
|
||||
url: "https://github.com/sharelatex/sharelatex"
|
||||
}]
|
||||
|
||||
header: [{
|
||||
text: "Register"
|
||||
url: "/register"
|
||||
only_when_logged_out: true
|
||||
}, {
|
||||
text: "Log In"
|
||||
url: "/login"
|
||||
only_when_logged_out: true
|
||||
}, {
|
||||
text: "Projects"
|
||||
url: "/project"
|
||||
only_when_logged_in: true
|
||||
}, {
|
||||
text: "Account"
|
||||
only_when_logged_in: true
|
||||
dropdown: [{
|
||||
user_email: true
|
||||
},{
|
||||
divider: true
|
||||
}, {
|
||||
text: "Account Settings"
|
||||
url: "/user/settings"
|
||||
}, {
|
||||
divider: true
|
||||
}, {
|
||||
text: "Log out"
|
||||
url: "/logout"
|
||||
}]
|
||||
}]
|
||||
showSubscriptionLink: false
|
||||
|
||||
header_extras: []
|
||||
# Example:
|
||||
# header_extras: [{text: "Some Page", url: "http://example.com/some/page", class: "subdued"}]
|
||||
|
||||
customisation: {}
|
||||
|
||||
|
||||
@@ -269,6 +269,10 @@ define [
|
||||
catch
|
||||
mode = "ace/mode/plain_text"
|
||||
|
||||
# Give beta users the next release of the syntax checker
|
||||
if mode is "ace/mode/latex" and window.user?.betaProgram
|
||||
mode = "ace/mode/latex_beta"
|
||||
|
||||
# create our new session
|
||||
session = new EditSession(lines, mode)
|
||||
|
||||
|
||||
+18
-10
@@ -8,6 +8,7 @@ define [
|
||||
}
|
||||
$scope.state = {
|
||||
error: null
|
||||
errorReason: null
|
||||
inflight: false
|
||||
startedFreeTrial: false
|
||||
invites: []
|
||||
@@ -69,7 +70,8 @@ define [
|
||||
|
||||
members = $scope.inputs.contacts
|
||||
$scope.inputs.contacts = []
|
||||
$scope.state.error = null
|
||||
$scope.state.error = false
|
||||
$scope.state.errorReason = null
|
||||
$scope.state.inflight = true
|
||||
|
||||
if !$scope.project.invites?
|
||||
@@ -101,17 +103,22 @@ define [
|
||||
|
||||
request
|
||||
.success (data) ->
|
||||
if data.invite
|
||||
invite = data.invite
|
||||
$scope.project.invites.push invite
|
||||
if data.error
|
||||
$scope.state.error = true
|
||||
$scope.state.errorReason = "#{data.error}"
|
||||
$scope.state.inflight = false
|
||||
else
|
||||
if data.users?
|
||||
users = data.users
|
||||
else if data.user?
|
||||
users = [data.user]
|
||||
if data.invite
|
||||
invite = data.invite
|
||||
$scope.project.invites.push invite
|
||||
else
|
||||
users = []
|
||||
$scope.project.members.push users...
|
||||
if data.users?
|
||||
users = data.users
|
||||
else if data.user?
|
||||
users = [data.user]
|
||||
else
|
||||
users = []
|
||||
$scope.project.members.push users...
|
||||
|
||||
setTimeout () ->
|
||||
# Give $scope a chance to update $scope.canAddCollaborators
|
||||
@@ -121,6 +128,7 @@ define [
|
||||
.error () ->
|
||||
$scope.state.inflight = false
|
||||
$scope.state.error = true
|
||||
$scope.state.errorReason = null
|
||||
|
||||
$timeout addMembers, 50 # Give email list a chance to update
|
||||
|
||||
|
||||
@@ -33,4 +33,18 @@ define [
|
||||
"filters/formatDate"
|
||||
"__MAIN_CLIENTSIDE_INCLUDES__"
|
||||
], () ->
|
||||
angular.bootstrap(document.body, ["SharelatexApp"])
|
||||
angular.module('SharelatexApp').config(
|
||||
($locationProvider) ->
|
||||
try
|
||||
$locationProvider.html5Mode({
|
||||
enabled: false,
|
||||
requireBase: false,
|
||||
rewriteLinks: false
|
||||
})
|
||||
catch e
|
||||
console.error "Error while trying to fix '#' links: ", e
|
||||
)
|
||||
angular.bootstrap(
|
||||
document.body,
|
||||
["SharelatexApp"]
|
||||
)
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "AnnouncementsController", ($scope, $http, event_tracking, $window) ->
|
||||
App.controller "AnnouncementsController", ($scope, $http, event_tracking, $window, _) ->
|
||||
$scope.announcements = []
|
||||
$scope.ui =
|
||||
isOpen: false
|
||||
newItems: 0
|
||||
|
||||
refreshAnnouncements = ->
|
||||
$http.get("/announcements").success (announcements) ->
|
||||
$scope.announcements = announcements
|
||||
$scope.ui.newItems = _.filter(announcements, (announcement) -> !announcement.read).length
|
||||
|
||||
markAnnouncementsAsRead = ->
|
||||
event_tracking.sendMB "announcement-alert-dismissed", { blogPostId: $scope.announcements[0].id }
|
||||
|
||||
$scope.dataRecived = false
|
||||
announcement = null
|
||||
$http.get("/announcements").success (announcements) ->
|
||||
if announcements?[0]?
|
||||
announcement = announcements[0]
|
||||
$scope.title = announcement.title
|
||||
$scope.totalAnnouncements = announcements.length
|
||||
$scope.dataRecived = true
|
||||
refreshAnnouncements()
|
||||
|
||||
dismissannouncement = ->
|
||||
event_tracking.sendMB "announcement-alert-dismissed", {blogPostId:announcement.id}
|
||||
$scope.toggleAnnouncementsUI = ->
|
||||
$scope.ui.isOpen = !$scope.ui.isOpen
|
||||
|
||||
if !$scope.ui.isOpen and $scope.ui.newItems
|
||||
$scope.ui.newItems = 0
|
||||
markAnnouncementsAsRead()
|
||||
|
||||
$scope.showAll = ->
|
||||
$scope.ui.newItems = 0
|
||||
|
||||
$scope.openLink = ->
|
||||
dismissannouncement()
|
||||
$window.location.href = announcement.url
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
ace.define("ace/mode/latex_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("../lib/oop");
|
||||
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
|
||||
|
||||
var LatexHighlightRules = function() {
|
||||
|
||||
this.$rules = {
|
||||
"start" : [{
|
||||
token : "comment",
|
||||
regex : "%.*$"
|
||||
}, {
|
||||
token : ["keyword", "lparen", "variable.parameter", "rparen", "lparen", "storage.type", "rparen"],
|
||||
regex : "(\\\\(?:documentclass|usepackage|input))(?:(\\[)([^\\]]*)(\\]))?({)([^}]*)(})"
|
||||
}, {
|
||||
token : ["keyword","lparen", "variable.parameter", "rparen"],
|
||||
regex : "(\\\\(?:label|v?ref|cite(?:[^{]*)))(?:({)([^}]*)(}))?"
|
||||
}, {
|
||||
token : ["storage.type", "lparen", "variable.parameter", "rparen"],
|
||||
regex : "(\\\\(?:begin|end))({)(\\w*)(})"
|
||||
}, {
|
||||
token : "storage.type",
|
||||
regex : "\\\\[a-zA-Z]+"
|
||||
}, {
|
||||
token : "lparen",
|
||||
regex : "[[({]"
|
||||
}, {
|
||||
token : "rparen",
|
||||
regex : "[\\])}]"
|
||||
}, {
|
||||
token : "constant.character.escape",
|
||||
regex : "\\\\[^a-zA-Z]?"
|
||||
}, {
|
||||
token : "string",
|
||||
regex : "\\${1,2}",
|
||||
next : "equation"
|
||||
}],
|
||||
"equation" : [{
|
||||
token : "comment",
|
||||
regex : "%.*$"
|
||||
}, {
|
||||
token : "string",
|
||||
regex : "\\${1,2}",
|
||||
next : "start"
|
||||
}, {
|
||||
token : "constant.character.escape",
|
||||
regex : "\\\\(?:[^a-zA-Z]|[a-zA-Z]+)"
|
||||
}, {
|
||||
token : "error",
|
||||
regex : "^\\s*$",
|
||||
next : "start"
|
||||
}, {
|
||||
defaultToken : "string"
|
||||
}]
|
||||
|
||||
};
|
||||
};
|
||||
oop.inherits(LatexHighlightRules, TextHighlightRules);
|
||||
|
||||
exports.LatexHighlightRules = LatexHighlightRules;
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/folding/latex",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range","ace/token_iterator"], function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("../../lib/oop");
|
||||
var BaseFoldMode = require("./fold_mode").FoldMode;
|
||||
var Range = require("../../range").Range;
|
||||
var TokenIterator = require("../../token_iterator").TokenIterator;
|
||||
|
||||
var FoldMode = exports.FoldMode = function() {};
|
||||
|
||||
oop.inherits(FoldMode, BaseFoldMode);
|
||||
|
||||
(function() {
|
||||
|
||||
this.foldingStartMarker = /^\s*\\(begin)|(section|subsection|paragraph)\b|{\s*$/;
|
||||
this.foldingStopMarker = /^\s*\\(end)\b|^\s*}/;
|
||||
|
||||
this.getFoldWidgetRange = function(session, foldStyle, row) {
|
||||
var line = session.doc.getLine(row);
|
||||
var match = this.foldingStartMarker.exec(line);
|
||||
if (match) {
|
||||
if (match[1])
|
||||
return this.latexBlock(session, row, match[0].length - 1);
|
||||
if (match[2])
|
||||
return this.latexSection(session, row, match[0].length - 1);
|
||||
|
||||
return this.openingBracketBlock(session, "{", row, match.index);
|
||||
}
|
||||
|
||||
var match = this.foldingStopMarker.exec(line);
|
||||
if (match) {
|
||||
if (match[1])
|
||||
return this.latexBlock(session, row, match[0].length - 1);
|
||||
|
||||
return this.closingBracketBlock(session, "}", row, match.index + match[0].length);
|
||||
}
|
||||
};
|
||||
|
||||
this.latexBlock = function(session, row, column) {
|
||||
var keywords = {
|
||||
"\\begin": 1,
|
||||
"\\end": -1
|
||||
};
|
||||
|
||||
var stream = new TokenIterator(session, row, column);
|
||||
var token = stream.getCurrentToken();
|
||||
if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape"))
|
||||
return;
|
||||
|
||||
var val = token.value;
|
||||
var dir = keywords[val];
|
||||
|
||||
var getType = function() {
|
||||
var token = stream.stepForward();
|
||||
var type = token.type == "lparen" ?stream.stepForward().value : "";
|
||||
if (dir === -1) {
|
||||
stream.stepBackward();
|
||||
if (type)
|
||||
stream.stepBackward();
|
||||
}
|
||||
return type;
|
||||
};
|
||||
var stack = [getType()];
|
||||
var startColumn = dir === -1 ? stream.getCurrentTokenColumn() : session.getLine(row).length;
|
||||
var startRow = row;
|
||||
|
||||
stream.step = dir === -1 ? stream.stepBackward : stream.stepForward;
|
||||
while(token = stream.step()) {
|
||||
if (!token || !(token.type == "storage.type" || token.type == "constant.character.escape"))
|
||||
continue;
|
||||
var level = keywords[token.value];
|
||||
if (!level)
|
||||
continue;
|
||||
var type = getType();
|
||||
if (level === dir)
|
||||
stack.unshift(type);
|
||||
else if (stack.shift() !== type || !stack.length)
|
||||
break;
|
||||
}
|
||||
|
||||
if (stack.length)
|
||||
return;
|
||||
|
||||
var row = stream.getCurrentTokenRow();
|
||||
if (dir === -1)
|
||||
return new Range(row, session.getLine(row).length, startRow, startColumn);
|
||||
stream.stepBackward();
|
||||
return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn());
|
||||
};
|
||||
|
||||
this.latexSection = function(session, row, column) {
|
||||
var keywords = ["\\subsection", "\\section", "\\begin", "\\end", "\\paragraph"];
|
||||
|
||||
var stream = new TokenIterator(session, row, column);
|
||||
var token = stream.getCurrentToken();
|
||||
if (!token || token.type != "storage.type")
|
||||
return;
|
||||
|
||||
var startLevel = keywords.indexOf(token.value);
|
||||
var stackDepth = 0
|
||||
var endRow = row;
|
||||
|
||||
while(token = stream.stepForward()) {
|
||||
if (token.type !== "storage.type")
|
||||
continue;
|
||||
var level = keywords.indexOf(token.value);
|
||||
|
||||
if (level >= 2) {
|
||||
if (!stackDepth)
|
||||
endRow = stream.getCurrentTokenRow() - 1;
|
||||
stackDepth += level == 2 ? 1 : - 1;
|
||||
if (stackDepth < 0)
|
||||
break
|
||||
} else if (level >= startLevel)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!stackDepth)
|
||||
endRow = stream.getCurrentTokenRow() - 1;
|
||||
|
||||
while (endRow > row && !/\S/.test(session.getLine(endRow)))
|
||||
endRow--;
|
||||
|
||||
return new Range(
|
||||
row, session.getLine(row).length,
|
||||
endRow, session.getLine(endRow).length
|
||||
);
|
||||
};
|
||||
|
||||
}).call(FoldMode.prototype);
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/latex_beta",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/latex_highlight_rules","ace/mode/folding/latex","ace/range","ace/worker/worker_client"], function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var oop = require("../lib/oop");
|
||||
var TextMode = require("./text").Mode;
|
||||
var LatexHighlightRules = require("./latex_highlight_rules").LatexHighlightRules;
|
||||
var LatexFoldMode = require("./folding/latex").FoldMode;
|
||||
var Range = require("../range").Range;
|
||||
var WorkerClient = require("ace/worker/worker_client").WorkerClient;
|
||||
|
||||
var createLatexWorker = function (session) {
|
||||
var doc = session.getDocument();
|
||||
var selection = session.getSelection();
|
||||
var cursorAnchor = selection.lead;
|
||||
|
||||
var savedRange = {};
|
||||
var suppressions = [];
|
||||
var hints = [];
|
||||
var changeHandler = null;
|
||||
var docChangePending = false;
|
||||
var firstPass = true;
|
||||
|
||||
var worker = new WorkerClient(["ace"], "ace/mode/latex_beta_worker", "LatexWorker");
|
||||
worker.attachToDocument(doc);
|
||||
var docChangeHandler = doc.on("change", function () {
|
||||
docChangePending = true;
|
||||
if(changeHandler) {
|
||||
clearTimeout(changeHandler);
|
||||
changeHandler = null;
|
||||
}
|
||||
});
|
||||
|
||||
var cursorHandler = selection.on("changeCursor", function () {
|
||||
if (docChangePending) { return; } ;
|
||||
changeHandler = setTimeout(function () {
|
||||
updateMarkers({cursorMoveOnly:true});
|
||||
suppressions = [];
|
||||
changeHandler = null;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var updateMarkers = function (options) {
|
||||
if (!options) { options = {};};
|
||||
var cursorMoveOnly = options.cursorMoveOnly;
|
||||
var annotations = [];
|
||||
var newRange = {};
|
||||
var cursor = selection.getCursor();
|
||||
var maxRow = session.getLength() - 1;
|
||||
var maxCol = (maxRow > 0) ? session.getLine(maxRow).length : 0;
|
||||
var cursorAtEndOfDocument = (cursor.row == maxRow) && (cursor.column === maxCol);
|
||||
|
||||
suppressions = [];
|
||||
|
||||
for (var i = 0, len = hints.length; i<len; i++) {
|
||||
var hint = hints[i];
|
||||
|
||||
var suppressedChanges = 0;
|
||||
var hintRange = new Range(hint.start_row, hint.start_col, hint.end_row, hint.end_col);
|
||||
|
||||
var cursorInRange = hintRange.insideEnd(cursor.row, cursor.column);
|
||||
var cursorAtStart = hintRange.isStart(cursor.row, cursor.column - 1); // cursor after start not before
|
||||
var cursorAtEnd = hintRange.isEnd(cursor.row, cursor.column);
|
||||
if (hint.suppressIfEditing && (cursorAtStart || cursorAtEnd)) {
|
||||
suppressions.push(hintRange);
|
||||
if (!hint.suppressed) { suppressedChanges++; };
|
||||
hint.suppressed = true;
|
||||
continue;
|
||||
}
|
||||
var isCascadeError = false;
|
||||
for (var j = 0, suplen = suppressions.length; j < suplen; j++) {
|
||||
var badRange = suppressions[j];
|
||||
if (badRange.intersects(hintRange)) {
|
||||
isCascadeError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isCascadeError) {
|
||||
if (!hint.suppressed) { suppressedChanges++; };
|
||||
hint.suppressed = true;
|
||||
continue;
|
||||
};
|
||||
|
||||
if (hint.suppressed) { suppressedChanges++; };
|
||||
hint.suppressed = false;
|
||||
|
||||
annotations.push(hint);
|
||||
if (hint.type === "info") {
|
||||
continue;
|
||||
};
|
||||
var key = hintRange.toString() + (cursorInRange ? "+cursor" : "");
|
||||
newRange[key] = {hint: hint, cursorInRange: cursorInRange, range: hintRange};
|
||||
}
|
||||
for (key in newRange) {
|
||||
if (!savedRange[key]) { // doesn't exist in already displayed errors
|
||||
var new_range = newRange[key].range;
|
||||
cursorInRange = newRange[key].cursorInRange;
|
||||
hint = newRange[key].hint;
|
||||
var errorAtStart = (hint.row === hint.start_row && hint.column === hint.start_col);
|
||||
var movableStart = (cursorInRange && !errorAtStart) && !cursorAtEndOfDocument;
|
||||
var movableEnd = (cursorInRange && errorAtStart) && !cursorAtEndOfDocument;
|
||||
var a = movableStart ? cursorAnchor : doc.createAnchor(new_range.start);
|
||||
var b = movableEnd ? cursorAnchor : doc.createAnchor(new_range.end);
|
||||
var range = new Range();
|
||||
range.start = a;
|
||||
range.end = b;
|
||||
var cssClass = "ace_error-marker";
|
||||
if (hint.type === "warning") { cssClass = "ace_highlight-marker"; };
|
||||
range.id = session.addMarker(range, cssClass, "text");
|
||||
savedRange[key] = range;
|
||||
}
|
||||
}
|
||||
for (key in savedRange) {
|
||||
if (!newRange[key]) { // no longer present in list of errors to display
|
||||
range = savedRange[key];
|
||||
if (range.start !== cursorAnchor) { range.start.detach(); }
|
||||
if (range.end !== cursorAnchor) { range.end.detach(); }
|
||||
session.removeMarker(range.id);
|
||||
delete savedRange[key];
|
||||
}
|
||||
}
|
||||
if (!cursorMoveOnly || suppressedChanges) {
|
||||
if (firstPass) {
|
||||
if (annotations.length > 0) {
|
||||
var originalAnnotations = session.getAnnotations();
|
||||
session.setAnnotations(originalAnnotations.concat(annotations));
|
||||
};
|
||||
firstPass = false;
|
||||
} else {
|
||||
session.setAnnotations(annotations);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
worker.on("lint", function(results) {
|
||||
if(docChangePending) { docChangePending = false; };
|
||||
hints = results.data;
|
||||
if (hints.length > 100) {
|
||||
hints = hints.slice(0, 100); // limit to 100 errors
|
||||
};
|
||||
updateMarkers();
|
||||
});
|
||||
worker.on("terminate", function() {
|
||||
if(changeHandler) {
|
||||
clearTimeout(changeHandler);
|
||||
changeHandler = null;
|
||||
}
|
||||
doc.off("change", docChangeHandler);
|
||||
selection.off("changeCursor", cursorHandler);
|
||||
for (var key in savedRange) {
|
||||
var range = savedRange[key];
|
||||
if (range.start !== cursorAnchor) { range.start.detach(); }
|
||||
if (range.end !== cursorAnchor) { range.end.detach(); }
|
||||
session.removeMarker(range.id);
|
||||
}
|
||||
savedRange = {};
|
||||
hints = [];
|
||||
suppressions = [];
|
||||
session.clearAnnotations();
|
||||
});
|
||||
|
||||
return worker;
|
||||
};
|
||||
|
||||
var Mode = function() {
|
||||
this.HighlightRules = LatexHighlightRules;
|
||||
this.foldingRules = new LatexFoldMode();
|
||||
this.createWorker = createLatexWorker;
|
||||
};
|
||||
oop.inherits(Mode, TextMode);
|
||||
|
||||
(function() {
|
||||
this.type = "text";
|
||||
|
||||
this.lineCommentStart = "%";
|
||||
|
||||
this.$id = "ace/mode/latex_beta";
|
||||
}).call(Mode.prototype);
|
||||
|
||||
exports.Mode = Mode;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
ace.define("ace/snippets/latex_beta",["require","exports","module"], function(require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
exports.snippetText =undefined;
|
||||
exports.scope = "latex";
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,4 +47,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-footer-share {
|
||||
.modal-footer-left {
|
||||
max-width: 70%;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
@announcements-shadow: 0 2px 20px rgba(0, 0, 0, 0.5);
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: .7;
|
||||
}
|
||||
100% {
|
||||
opacity: .9;
|
||||
}
|
||||
}
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.project-header {
|
||||
.btn-group > .btn {
|
||||
padding-left: @line-height-base / 2;
|
||||
@@ -293,3 +316,146 @@ ul.project-list {
|
||||
margin-left:-100px;
|
||||
}
|
||||
}
|
||||
|
||||
.announcements {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
&-open {
|
||||
top: -100%;
|
||||
height: auto;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.announcements-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.35);
|
||||
opacity: 0;
|
||||
animation: fade-in 0.35s forwards;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.announcements-btn {
|
||||
position: absolute;
|
||||
bottom: -50px;
|
||||
right: 3%;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: url(/img/lion-128.png) no-repeat center/80% transparent;
|
||||
border-radius: 50%;
|
||||
box-shadow: none;
|
||||
z-index: 1;
|
||||
pointer-events: all;
|
||||
transition: bottom 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55),
|
||||
background 0.25s ease,
|
||||
box-shadow 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
bottom: -45px;
|
||||
}
|
||||
|
||||
&-open, &-open:hover,
|
||||
&-has-new, &-has-new:hover {
|
||||
background-color: #FFF;
|
||||
box-shadow: @announcements-shadow;
|
||||
bottom: 30px;
|
||||
}
|
||||
}
|
||||
.announcements-badge {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
font-size: 11px;
|
||||
height: 1.8em;
|
||||
min-width: 1.8em;
|
||||
border-radius: 0.9em;
|
||||
line-height: 1.8;
|
||||
padding: 0 2px;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
font-weight: bold;
|
||||
color: #FFF;
|
||||
background-color: @red;
|
||||
vertical-align: baseline;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
animation: pulse 1s alternate infinite;
|
||||
}
|
||||
|
||||
.announcements-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
position: absolute;
|
||||
right: 3%;
|
||||
margin-right: 95px;
|
||||
bottom: 30px;
|
||||
width: 700px;
|
||||
max-height: 52%;
|
||||
min-height: 100px;
|
||||
background: #FFF;
|
||||
z-index: 1;
|
||||
box-shadow: @announcements-shadow;
|
||||
border-radius: @border-radius-base;
|
||||
animation: fade-in 0.35s forwards;
|
||||
|
||||
&::after {
|
||||
content: "\25b8";
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
bottom: 17px;
|
||||
width: 30px;
|
||||
color: #FFF;
|
||||
text-shadow: @announcements-shadow;
|
||||
font-size: 2em;
|
||||
overflow: hidden;
|
||||
text-indent: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
.announcements-scroller {
|
||||
padding: @line-height-computed;
|
||||
flex-grow: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.announcement {
|
||||
margin-bottom: @line-height-computed * 1.5;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.announcement-header {
|
||||
.page-header;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.announcement-description {
|
||||
margin: (@line-height-computed / 4) 0 (@line-height-computed / 2);
|
||||
}
|
||||
|
||||
.announcement-meta {
|
||||
.clearfix;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.announcement-date {
|
||||
float: left;
|
||||
color: @gray;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.announcement-link {
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -42,10 +42,13 @@ describe 'AnnouncementsHandler', ->
|
||||
@BlogHandler.getLatestAnnouncements.callsArgWith(0, null, @stubbedAnnouncements)
|
||||
|
||||
|
||||
it "should return all announcements if there are no getLastOccurance", (done)->
|
||||
it "should mark all announcements as read is false", (done)->
|
||||
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, [])
|
||||
@handler.getUnreadAnnouncements @user_id, (err, announcements)=>
|
||||
announcements.length.should.equal 4
|
||||
announcements[0].read.should.equal false
|
||||
announcements[1].read.should.equal false
|
||||
announcements[2].read.should.equal false
|
||||
announcements[3].read.should.equal false
|
||||
done()
|
||||
|
||||
it "should should be sorted again to ensure correct order", (done)->
|
||||
@@ -57,16 +60,30 @@ describe 'AnnouncementsHandler', ->
|
||||
announcements[0].should.equal @stubbedAnnouncements[0]
|
||||
done()
|
||||
|
||||
it "should return ones older than the last blog id", (done)->
|
||||
it "should return older ones marked as read as well", (done)->
|
||||
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2014/04/12/title-date-irrelivant"}})
|
||||
@handler.getUnreadAnnouncements @user_id, (err, announcements)=>
|
||||
announcements.length.should.equal 2
|
||||
announcements[0].id.should.equal @stubbedAnnouncements[0].id
|
||||
announcements[0].read.should.equal false
|
||||
|
||||
announcements[1].id.should.equal @stubbedAnnouncements[1].id
|
||||
announcements[1].read.should.equal false
|
||||
|
||||
announcements[2].id.should.equal @stubbedAnnouncements[3].id
|
||||
announcements[2].read.should.equal true
|
||||
|
||||
announcements[3].id.should.equal @stubbedAnnouncements[2].id
|
||||
announcements[3].read.should.equal true
|
||||
|
||||
done()
|
||||
|
||||
it "should return none when the latest id is the first element", (done)->
|
||||
it "should return all of them marked as read", (done)->
|
||||
@AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2016/11/01/introducting-latex-code-checker"}})
|
||||
@handler.getUnreadAnnouncements @user_id, (err, announcements)=>
|
||||
announcements.length.should.equal 0
|
||||
announcements[0].read.should.equal true
|
||||
announcements[1].read.should.equal true
|
||||
announcements[2].read.should.equal true
|
||||
announcements[3].read.should.equal true
|
||||
done()
|
||||
|
||||
|
||||
|
||||
@@ -387,6 +387,10 @@ describe "AuthenticationController", ->
|
||||
beforeEach ->
|
||||
@req.headers = {}
|
||||
@AuthenticationController.httpAuth = sinon.stub()
|
||||
@_setRedirect = sinon.spy(@AuthenticationController, '_setRedirectInSession')
|
||||
|
||||
afterEach ->
|
||||
@_setRedirect.restore()
|
||||
|
||||
describe "with white listed url", ->
|
||||
beforeEach ->
|
||||
@@ -431,6 +435,9 @@ describe "AuthenticationController", ->
|
||||
@req.session = {}
|
||||
@AuthenticationController.requireGlobalLogin @req, @res, @next
|
||||
|
||||
it 'should have called setRedirectInSession', ->
|
||||
@_setRedirect.callCount.should.equal 1
|
||||
|
||||
it "should redirect to the /login page", ->
|
||||
@res.redirectedTo.should.equal "/login"
|
||||
|
||||
|
||||
+158
@@ -27,6 +27,7 @@ describe "CollaboratorsInviteController", ->
|
||||
"../Notifications/NotificationsBuilder": @NotificationsBuilder = {}
|
||||
"../Analytics/AnalyticsManager": @AnalyticsManger
|
||||
'../Authentication/AuthenticationController': @AuthenticationController
|
||||
'settings-sharelatex': @settings = {}
|
||||
@res = new MockResponse()
|
||||
@req = new MockRequest()
|
||||
|
||||
@@ -103,9 +104,15 @@ describe "CollaboratorsInviteController", ->
|
||||
describe 'when all goes well', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, null, true)
|
||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should produce json response', ->
|
||||
@res.json.callCount.should.equal 1
|
||||
({invite: @invite}).should.deep.equal(@res.json.firstCall.args[0])
|
||||
@@ -114,6 +121,10 @@ describe "CollaboratorsInviteController", ->
|
||||
@LimitationsManager.canAddXCollaborators.callCount.should.equal 1
|
||||
@LimitationsManager.canAddXCollaborators.calledWith(@project_id).should.equal true
|
||||
|
||||
it 'should have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 1
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
|
||||
|
||||
it 'should have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1
|
||||
@CollaboratorsInviteHandler.inviteToProject.calledWith(@project_id,@current_user,@targetEmail,@privileges).should.equal true
|
||||
@@ -125,37 +136,63 @@ describe "CollaboratorsInviteController", ->
|
||||
describe 'when the user is not allowed to add more collaborators', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, null, true)
|
||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, false)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should produce json response without an invite', ->
|
||||
@res.json.callCount.should.equal 1
|
||||
({invite: null}).should.deep.equal(@res.json.firstCall.args[0])
|
||||
|
||||
it 'should not have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 0
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal false
|
||||
|
||||
it 'should not have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
|
||||
|
||||
describe 'when canAddXCollaborators produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, null, true)
|
||||
@err = new Error('woops')
|
||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, @err)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@next.callCount.should.equal 1
|
||||
@next.calledWith(@err).should.equal true
|
||||
|
||||
it 'should not have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 0
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal false
|
||||
|
||||
it 'should not have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
|
||||
|
||||
describe 'when inviteToProject produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, null, true)
|
||||
@err = new Error('woops')
|
||||
@CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, @err)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@next.callCount.should.equal 1
|
||||
@next.calledWith(@err).should.equal true
|
||||
@@ -164,10 +201,60 @@ describe "CollaboratorsInviteController", ->
|
||||
@LimitationsManager.canAddXCollaborators.callCount.should.equal 1
|
||||
@LimitationsManager.canAddXCollaborators.calledWith(@project_id).should.equal true
|
||||
|
||||
it 'should have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 1
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
|
||||
|
||||
it 'should have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1
|
||||
@CollaboratorsInviteHandler.inviteToProject.calledWith(@project_id,@current_user,@targetEmail,@privileges).should.equal true
|
||||
|
||||
describe 'when _checkShouldInviteEmail disallows the invite', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, null, false)
|
||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should produce json response with no invite, and an error property', ->
|
||||
@res.json.callCount.should.equal 1
|
||||
({invite: null, error: 'cannot_invite_non_user'}).should.deep.equal(@res.json.firstCall.args[0])
|
||||
|
||||
it 'should have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 1
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
|
||||
|
||||
it 'should not have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
|
||||
|
||||
describe 'when _checkShouldInviteEmail produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@_checkShouldInviteEmail = sinon.stub(
|
||||
@CollaboratorsInviteController, '_checkShouldInviteEmail'
|
||||
).callsArgWith(1, new Error('woops'))
|
||||
@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true)
|
||||
@CollaboratorsInviteController.inviteToProject @req, @res, @next
|
||||
|
||||
afterEach ->
|
||||
@_checkShouldInviteEmail.restore()
|
||||
|
||||
it 'should call next with an error', ->
|
||||
@next.callCount.should.equal 1
|
||||
@next.calledWith(@err).should.equal true
|
||||
|
||||
it 'should have called _checkShouldInviteEmail', ->
|
||||
@_checkShouldInviteEmail.callCount.should.equal 1
|
||||
@_checkShouldInviteEmail.calledWith(@targetEmail).should.equal true
|
||||
|
||||
it 'should not have called inviteToProject', ->
|
||||
@CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0
|
||||
|
||||
describe "viewInvite", ->
|
||||
|
||||
beforeEach ->
|
||||
@@ -579,3 +666,74 @@ describe "CollaboratorsInviteController", ->
|
||||
|
||||
it 'should have called acceptInvite', ->
|
||||
@CollaboratorsInviteHandler.acceptInvite.callCount.should.equal 1
|
||||
|
||||
describe '_checkShouldInviteEmail', ->
|
||||
|
||||
beforeEach ->
|
||||
@email = 'user@example.com'
|
||||
@call = (callback) =>
|
||||
@CollaboratorsInviteController._checkShouldInviteEmail @email, callback
|
||||
|
||||
describe 'when we should be restricting to existing accounts', ->
|
||||
|
||||
beforeEach ->
|
||||
@settings.restrictInvitesToExistingAccounts = true
|
||||
|
||||
describe 'when user account is present', ->
|
||||
|
||||
beforeEach ->
|
||||
@user = {_id: ObjectId().toString()}
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
|
||||
it 'should callback with `true`', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
expect(err).to.equal null
|
||||
expect(shouldAllow).to.equal true
|
||||
done()
|
||||
|
||||
describe 'when user account is absent', ->
|
||||
|
||||
beforeEach ->
|
||||
@user = null
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
|
||||
it 'should callback with `false`', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
expect(err).to.equal null
|
||||
expect(shouldAllow).to.equal false
|
||||
done()
|
||||
|
||||
it 'should have called getUser', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
@UserGetter.getUser.callCount.should.equal 1
|
||||
@UserGetter.getUser.calledWith({email: @email}, {_id: 1}).should.equal true
|
||||
done()
|
||||
|
||||
describe 'when getUser produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@user = null
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, new Error('woops'))
|
||||
|
||||
it 'should callback with an error', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
expect(err).to.not.equal null
|
||||
expect(err).to.be.instanceof Error
|
||||
expect(shouldAllow).to.equal undefined
|
||||
done()
|
||||
|
||||
describe 'when we should not be restricting', ->
|
||||
|
||||
beforeEach ->
|
||||
@settings.restrictInvitesToExistingAccounts = false
|
||||
|
||||
it 'should callback with `true`', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
expect(err).to.equal null
|
||||
expect(shouldAllow).to.equal true
|
||||
done()
|
||||
|
||||
it 'should not have called getUser', (done) ->
|
||||
@call (err, shouldAllow) =>
|
||||
@UserGetter.getUser.callCount.should.equal 0
|
||||
done()
|
||||
|
||||
Reference in New Issue
Block a user