Merge pull request #932 from metamaps/feature/mailboxer
mailboxer notification centre
This commit is contained in:
commit
9ab1c9c647
87 changed files with 1206 additions and 237 deletions
|
@ -20,6 +20,8 @@ engines:
|
||||||
enabled: true
|
enabled: true
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
exclude_fingerprints:
|
||||||
|
- 74f18007b920e8d81148d2f6a2756534
|
||||||
ratings:
|
ratings:
|
||||||
paths:
|
paths:
|
||||||
- 'Gemfile.lock'
|
- 'Gemfile.lock'
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -17,6 +17,7 @@ gem 'exception_notification'
|
||||||
gem 'httparty'
|
gem 'httparty'
|
||||||
gem 'json'
|
gem 'json'
|
||||||
gem 'kaminari'
|
gem 'kaminari'
|
||||||
|
gem 'mailboxer'
|
||||||
gem 'paperclip'
|
gem 'paperclip'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
|
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -65,6 +65,12 @@ GEM
|
||||||
brakeman (3.4.0)
|
brakeman (3.4.0)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
byebug (9.0.5)
|
byebug (9.0.5)
|
||||||
|
carrierwave (0.11.2)
|
||||||
|
activemodel (>= 3.2.0)
|
||||||
|
activesupport (>= 3.2.0)
|
||||||
|
json (>= 1.7)
|
||||||
|
mime-types (>= 1.16)
|
||||||
|
mimemagic (>= 0.3.0)
|
||||||
climate_control (0.0.3)
|
climate_control (0.0.3)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
|
@ -125,6 +131,9 @@ GEM
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.4)
|
mail (2.6.4)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
|
mailboxer (0.14.0)
|
||||||
|
carrierwave (>= 0.5.8)
|
||||||
|
rails (>= 4.2.0)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
|
@ -284,6 +293,7 @@ DEPENDENCIES
|
||||||
json
|
json
|
||||||
json-schema
|
json-schema
|
||||||
kaminari
|
kaminari
|
||||||
|
mailboxer
|
||||||
paperclip
|
paperclip
|
||||||
pg
|
pg
|
||||||
pry-byebug
|
pry-byebug
|
||||||
|
|
BIN
app/assets/images/.DS_Store
vendored
BIN
app/assets/images/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
app/assets/images/user_sprite.png
Executable file → Normal file
BIN
app/assets/images/user_sprite.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -826,7 +826,7 @@ label {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
pointer-events:none;
|
pointer-events:none;
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-image: url(<%= asset_data_uri('user_sprite.png') %>);
|
background-image: url(<%= asset_path('user_sprite.png') %>);
|
||||||
}
|
}
|
||||||
.accountSettings .accountIcon {
|
.accountSettings .accountIcon {
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
|
@ -3076,3 +3076,7 @@ script.data-gratipay-username {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
.centerContent {
|
.centerContent {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 92px auto 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px 0 60px 20px;
|
width: auto;
|
||||||
width: 760px;
|
max-width: 800px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
|
box-shadow: 0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
-webkit-border-radius: 3px;
|
box-sizing: border-box;
|
||||||
-moz-border-radius: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #dcdcdc;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
font-family: 'din-regular', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centerContent .page-header {
|
.centerContent .page-header {
|
||||||
|
@ -129,3 +126,9 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centerContent.withPadding {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.animations {
|
/*.animations {
|
||||||
|
@ -210,7 +212,13 @@
|
||||||
}
|
}
|
||||||
.addMap {
|
.addMap {
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
margin-right:10px;
|
}
|
||||||
|
.notificationsIcon {
|
||||||
|
background-position: -128px 0;
|
||||||
|
margin-right: 10px; // make it look more natural next to the account menu icon
|
||||||
|
}
|
||||||
|
.notificationsIcon:hover {
|
||||||
|
background-position: -128px -32px;
|
||||||
}
|
}
|
||||||
.importDialog:hover {
|
.importDialog:hover {
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
|
@ -223,7 +231,6 @@
|
||||||
}
|
}
|
||||||
.addMap:hover {
|
.addMap:hover {
|
||||||
background-position: -96px -32px;
|
background-position: -96px -32px;
|
||||||
margin-right:10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -471,7 +478,7 @@
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .notificationsIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .importDialog:hover .tooltipsUnder, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips, .importDialog:hover .tooltipsUnder, .starMap:hover .tooltipsAbove, .openMetacodeSwitcher:hover .tooltipsAbove, .pinCarousel:not(.isPinned):hover .tooltipsAbove.helpPin, .pinCarousel.isPinned:hover .tooltipsAbove.helpUnpin {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -535,6 +542,9 @@
|
||||||
.sidebarFilterIcon .tooltipsUnder {
|
.sidebarFilterIcon .tooltipsUnder {
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon .tooltipsUnder {
|
||||||
|
left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebarForkIcon .tooltipsUnder {
|
.sidebarForkIcon .tooltipsUnder {
|
||||||
margin-left: -34px;
|
margin-left: -34px;
|
||||||
|
@ -612,7 +622,12 @@
|
||||||
border-bottom: 5px solid transparent;
|
border-bottom: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.importDialog div:after, .sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
.addMap div:after,
|
||||||
|
.importDialog div:after,
|
||||||
|
.sidebarForkIcon div:after,
|
||||||
|
.sidebarFilterIcon div:after,
|
||||||
|
.notificationsIcon div:after,
|
||||||
|
.sidebarAccountIcon .tooltipsUnder:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 40%;
|
right: 40%;
|
||||||
|
@ -623,9 +638,15 @@
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon .unread-notifications-dot:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
.sidebarFilterIcon div:after {
|
.sidebarFilterIcon div:after {
|
||||||
right: 37% !important;
|
right: 37% !important;
|
||||||
}
|
}
|
||||||
|
.notificationsIcon div:after {
|
||||||
|
right: 46% !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after, .openMetacodeSwitcher div:after, .pinCarousel div:after {
|
.mapInfoIcon div:after, .openCheatsheet div:after, .starMap div:after, .openMetacodeSwitcher div:after, .pinCarousel div:after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -758,7 +779,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsCenter .authedApps .exploreMapsIcon {
|
.exploreMapsCenter .authedApps .exploreMapsIcon {
|
||||||
background-image: url(<%= asset_data_uri('user_sprite.png') %>);
|
background-image: url(<%= asset_path('user_sprite.png') %>);
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
||||||
|
@ -781,6 +802,10 @@
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
|
.exploreMapsCenter .notificationsLink .exploreMapsIcon {
|
||||||
|
background-image: url(<%= asset_path 'topright_sprite.png' %>);
|
||||||
|
background-position: -128px 0;
|
||||||
|
}
|
||||||
.authedApps:hover .exploreMapsIcon, .authedApps.active .exploreMapsIcon {
|
.authedApps:hover .exploreMapsIcon, .authedApps.active .exploreMapsIcon {
|
||||||
background-position-x: -32px;
|
background-position-x: -32px;
|
||||||
}
|
}
|
||||||
|
@ -799,6 +824,9 @@
|
||||||
.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
|
.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
|
||||||
background-position: -128px -32px;
|
background-position: -128px -32px;
|
||||||
}
|
}
|
||||||
|
.notificationsLink:hover .exploreMapsIcon, .notificationsLink.active .exploreMapsIcon {
|
||||||
|
background-position-y: -32px;
|
||||||
|
}
|
||||||
|
|
||||||
.mapsWrapper {
|
.mapsWrapper {
|
||||||
/*overflow-y: auto; */
|
/*overflow-y: auto; */
|
||||||
|
|
|
@ -2,12 +2,19 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width : 720px) and (min-width : 504px) {
|
@media only screen and (max-width : 752px) and (min-width : 504px) {
|
||||||
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
||||||
width: 160px !important;
|
width: 160px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* when this switches to two lines */
|
||||||
|
@media only screen and (max-width : 728px) {
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-read-unread a {
|
||||||
|
margin-top: -20px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width : 390px) {
|
@media only screen and (max-width : 390px) {
|
||||||
.map .mapCard .mobileMetadata {
|
.map .mapCard .mobileMetadata {
|
||||||
width: 190px;
|
width: 190px;
|
||||||
|
@ -18,6 +25,14 @@
|
||||||
width: 390px;
|
width: 390px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* 800 is the max-width for centerContent */
|
||||||
|
@media only screen and (max-width : 800px) {
|
||||||
|
.centerContent.withPadding {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
||||||
@media only screen and (max-width : 504px) {
|
@media only screen and (max-width : 504px) {
|
||||||
|
@ -25,6 +40,17 @@
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notificationsPage .page-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-read-unread {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.controller-notifications .notificationsPage .notification .notification-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#mobile_header {
|
#mobile_header {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +83,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#yield {
|
#yield {
|
||||||
height: 100%;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new_session, .new_user, .edit_user, .login, .forgotPassword {
|
.new_session, .new_user, .edit_user, .login, .forgotPassword {
|
||||||
|
@ -66,7 +92,7 @@
|
||||||
left: auto;
|
left: auto;
|
||||||
width: 78%;
|
width: 78%;
|
||||||
padding: 16px 10%;
|
padding: 16px 10%;
|
||||||
margin: 50px auto 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centerGreyForm input[type="text"], .centerGreyForm input[type="email"], .centerGreyForm input[type="password"] {
|
.centerGreyForm input[type="text"], .centerGreyForm input[type="email"], .centerGreyForm input[type="password"] {
|
||||||
|
@ -213,8 +239,17 @@
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mobile_header #menu_icon .unread-notifications-dot {
|
||||||
|
top: 5px;
|
||||||
|
left: 29px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border: 3px solid #eee;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
#mobile_menu {
|
#mobile_menu {
|
||||||
display: none;
|
display: none;
|
||||||
background: #EEE;
|
background: #EEE;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
|
@ -222,11 +257,21 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
||||||
}
|
|
||||||
|
|
||||||
#mobile_menu li {
|
li {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
|
&.notifications {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.unread-notifications-dot {
|
||||||
|
top: 50%;
|
||||||
|
left: 0px;
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
138
app/assets/stylesheets/notifications.scss.erb
Normal file
138
app/assets/stylesheets/notifications.scss.erb
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
$unread_notifications_dot_size: 8px;
|
||||||
|
.unread-notifications-dot {
|
||||||
|
width: $unread_notifications_dot_size;
|
||||||
|
height: $unread_notifications_dot_size;
|
||||||
|
background-color: #e22;
|
||||||
|
border-radius: $unread_notifications_dot_size / 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upperRightUI {
|
||||||
|
.notificationsIcon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller-notifications {
|
||||||
|
ul.notifications {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationPage,
|
||||||
|
.notificationsPage {
|
||||||
|
font-family: 'din-regular', Sans-Serif;
|
||||||
|
|
||||||
|
& a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .notification-title {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsPage {
|
||||||
|
header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyInbox {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #F6F6F6;
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
float: left;
|
||||||
|
width: 85%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actor {
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
margin-left: 50px;
|
||||||
|
|
||||||
|
.in-bold {
|
||||||
|
font-family: 'din-medium', Sans-Serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
background: #4fb5c0;
|
||||||
|
color: #FFF;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
color: #607d8b;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 13px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display: none;
|
||||||
|
float: left;
|
||||||
|
width: 15%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unread {
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.notificationPage .notification-body {
|
||||||
|
p, div {
|
||||||
|
margin: 1em auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ class AccessController < ApplicationController
|
||||||
:deny_access, :deny_access_post, :request_access]
|
:deny_access, :deny_access_post, :request_access]
|
||||||
after_action :verify_authorized
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
|
||||||
# GET maps/:id/request_access
|
# GET maps/:id/request_access
|
||||||
def request_access
|
def request_access
|
||||||
@map = nil
|
@map = nil
|
||||||
|
@ -20,13 +19,10 @@ class AccessController < ApplicationController
|
||||||
# POST maps/:id/access_request
|
# POST maps/:id/access_request
|
||||||
def access_request
|
def access_request
|
||||||
request = AccessRequest.create(user: current_user, map: @map)
|
request = AccessRequest.create(user: current_user, map: @map)
|
||||||
# what about push notification to map owner?
|
NotificationService.access_request(request)
|
||||||
MapMailer.access_request_email(request, @map).deliver_later
|
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json { head :ok }
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -36,22 +32,21 @@ class AccessController < ApplicationController
|
||||||
|
|
||||||
@map.add_new_collaborators(user_ids).each do |user_id|
|
@map.add_new_collaborators(user_ids).each do |user_id|
|
||||||
# add_new_collaborators returns array of added users,
|
# add_new_collaborators returns array of added users,
|
||||||
# who we then send an email to
|
# who we then send a notification to
|
||||||
MapMailer.invite_to_edit_email(@map, current_user, User.find(user_id)).deliver_later
|
user = User.find(user_id)
|
||||||
|
NotificationService.invite_to_edit(@map, current_user, user)
|
||||||
end
|
end
|
||||||
@map.remove_old_collaborators(user_ids)
|
@map.remove_old_collaborators(user_ids)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json { head :ok }
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET maps/:id/approve_access/:request_id
|
# GET maps/:id/approve_access/:request_id
|
||||||
def approve_access
|
def approve_access
|
||||||
request = AccessRequest.find(params[:request_id])
|
request = AccessRequest.find(params[:request_id])
|
||||||
request.approve()
|
request.approve # also marks mailboxer notification as read
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to map_path(@map), notice: 'Request was approved' }
|
format.html { redirect_to map_path(@map), notice: 'Request was approved' }
|
||||||
end
|
end
|
||||||
|
@ -60,7 +55,7 @@ class AccessController < ApplicationController
|
||||||
# GET maps/:id/deny_access/:request_id
|
# GET maps/:id/deny_access/:request_id
|
||||||
def deny_access
|
def deny_access
|
||||||
request = AccessRequest.find(params[:request_id])
|
request = AccessRequest.find(params[:request_id])
|
||||||
request.deny()
|
request.deny # also marks mailboxer notification as read
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to map_path(@map), notice: 'Request was turned down' }
|
format.html { redirect_to map_path(@map), notice: 'Request was turned down' }
|
||||||
end
|
end
|
||||||
|
@ -69,7 +64,7 @@ class AccessController < ApplicationController
|
||||||
# POST maps/:id/approve_access/:request_id
|
# POST maps/:id/approve_access/:request_id
|
||||||
def approve_access_post
|
def approve_access_post
|
||||||
request = AccessRequest.find(params[:request_id])
|
request = AccessRequest.find(params[:request_id])
|
||||||
request.approve()
|
request.approve
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
head :ok
|
head :ok
|
||||||
|
@ -80,7 +75,7 @@ class AccessController < ApplicationController
|
||||||
# POST maps/:id/deny_access/:request_id
|
# POST maps/:id/deny_access/:request_id
|
||||||
def deny_access_post
|
def deny_access_post
|
||||||
request = AccessRequest.find(params[:request_id])
|
request = AccessRequest.find(params[:request_id])
|
||||||
request.deny()
|
request.deny
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
head :ok
|
head :ok
|
||||||
|
@ -94,5 +89,4 @@ class AccessController < ApplicationController
|
||||||
@map = Map.find(params[:id])
|
@map = Map.find(params[:id])
|
||||||
authorize @map
|
authorize @map
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :admin?
|
helper_method :admin?
|
||||||
|
|
||||||
def handle_unauthorized
|
def handle_unauthorized
|
||||||
if authenticated? and params[:controller] == 'maps' and params[:action] == 'show'
|
if authenticated? && (params[:controller] == 'maps') && (params[:action] == 'show')
|
||||||
redirect_to request_access_map_path(params[:id])
|
redirect_to request_access_map_path(params[:id])
|
||||||
elsif authenticated?
|
elsif authenticated?
|
||||||
redirect_to root_path, notice: "You don't have permission to see that page."
|
redirect_to root_path, notice: "You don't have permission to see that page."
|
||||||
|
@ -41,13 +41,13 @@ class ApplicationController < ActionController::Base
|
||||||
def require_no_user
|
def require_no_user
|
||||||
return true unless authenticated?
|
return true unless authenticated?
|
||||||
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_user
|
def require_user
|
||||||
return true if authenticated?
|
return true if authenticated?
|
||||||
redirect_to sign_in_path, notice: 'You must be logged in.'
|
redirect_to sign_in_path, notice: 'You must be logged in.'
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_admin
|
def require_admin
|
||||||
|
|
|
@ -8,6 +8,7 @@ class MapsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
|
UserMap.where(map: @map, user: current_user).map(&:mark_invite_notifications_as_read)
|
||||||
@allmappers = @map.contributors
|
@allmappers = @map.contributors
|
||||||
@allcollaborators = @map.editors
|
@allcollaborators = @map.editors
|
||||||
@alltopics = policy_scope(@map.topics)
|
@alltopics = policy_scope(@map.topics)
|
||||||
|
|
97
app/controllers/notifications_controller.rb
Normal file
97
app/controllers/notifications_controller.rb
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class NotificationsController < ApplicationController
|
||||||
|
before_action :set_receipts, only: [:index, :show, :mark_read, :mark_unread]
|
||||||
|
before_action :set_notification, only: [:show, :mark_read, :mark_unread]
|
||||||
|
before_action :set_receipt, only: [:show, :mark_read, :mark_unread]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@notifications = current_user.mailbox.notifications.page(params[:page]).per(25)
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.json do
|
||||||
|
render json: @notifications.map do |notification|
|
||||||
|
receipt = @receipts.find_by(notification_id: notification.id)
|
||||||
|
notification.as_json.merge(is_read: receipt.is_read)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@receipt.update(is_read: true)
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.json do
|
||||||
|
render json: @notification.as_json.merge(
|
||||||
|
is_read: @receipt.is_read
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_read
|
||||||
|
@receipt.update(is_read: true)
|
||||||
|
respond_to do |format|
|
||||||
|
format.js
|
||||||
|
format.json do
|
||||||
|
render json: @notification.as_json.merge(
|
||||||
|
is_read: @receipt.is_read
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_unread
|
||||||
|
@receipt.update(is_read: false)
|
||||||
|
respond_to do |format|
|
||||||
|
format.js
|
||||||
|
format.json do
|
||||||
|
render json: @notification.as_json.merge(
|
||||||
|
is_read: @receipt.is_read
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe
|
||||||
|
unsubscribe_redirect_if_logged_out!
|
||||||
|
check_if_already_unsubscribed!
|
||||||
|
return if performed? # if one of these checks already redirected, we're done
|
||||||
|
|
||||||
|
if current_user.update(emails_allowed: false)
|
||||||
|
redirect_to edit_user_path(current_user),
|
||||||
|
notice: 'You will no longer receive emails from Metamaps.'
|
||||||
|
else
|
||||||
|
flash[:alert] = 'Sorry, something went wrong. You have not been unsubscribed from emails.'
|
||||||
|
redirect_to edit_user_path(current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unsubscribe_redirect_if_logged_out!
|
||||||
|
return if current_user.present?
|
||||||
|
|
||||||
|
flash[:notice] = 'Continue to unsubscribe from emails by logging in.'
|
||||||
|
redirect_to "#{sign_in_path}?redirect_to=#{unsubscribe_notifications_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_if_already_unsubscribed!
|
||||||
|
return if current_user.emails_allowed
|
||||||
|
|
||||||
|
redirect_to edit_user_path(current_user), notice: 'You were already unsubscribed from emails.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_receipts
|
||||||
|
@receipts = current_user.mailboxer_notification_receipts
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_notification
|
||||||
|
@notification = current_user.mailbox.notifications.find_by(id: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_receipt
|
||||||
|
@receipt = @receipts.find_by(notification_id: params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,14 +14,14 @@ class TopicsController < ApplicationController
|
||||||
@topics = policy_scope(Topic).where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
@topics = policy_scope(Topic).where('LOWER("name") like ?', term.downcase + '%').order('"name"')
|
||||||
@mapTopics = @topics.select { |t| t&.metacode&.name == 'Metamap' }
|
@mapTopics = @topics.select { |t| t&.metacode&.name == 'Metamap' }
|
||||||
# prioritize topics which point to maps, over maps
|
# prioritize topics which point to maps, over maps
|
||||||
@exclude = @mapTopics.length > 0 ? @mapTopics.map(&:name) : ['']
|
@exclude = @mapTopics.length.positive? ? @mapTopics.map(&:name) : ['']
|
||||||
@maps = policy_scope(Map).where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', @exclude).order('"name"')
|
@maps = policy_scope(Map).where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', @exclude).order('"name"')
|
||||||
else
|
else
|
||||||
@topics = []
|
@topics = []
|
||||||
@maps = []
|
@maps = []
|
||||||
end
|
end
|
||||||
@all= @topics.to_a.concat(@maps.to_a).sort { |a, b| a.name <=> b.name }
|
@all = @topics.to_a.concat(@maps.to_a).sort_by(&:name)
|
||||||
|
|
||||||
render json: autocomplete_array_json(@all).to_json
|
render json: autocomplete_array_json(@all).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,10 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def store_location
|
def store_location
|
||||||
if params[:redirect_to]
|
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
||||||
store_location_for(User, params[:redirect_to])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def configure_sign_up_params
|
def configure_sign_up_params
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
class Users::SessionsController < Devise::SessionsController
|
# frozen_string_literal: true
|
||||||
protected
|
module Users
|
||||||
|
class SessionsController < Devise::SessionsController
|
||||||
|
after_action :store_location, only: [:new]
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
protected
|
||||||
stored = stored_location_for(User)
|
|
||||||
return stored if stored
|
|
||||||
|
|
||||||
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
def after_sign_in_path_for(resource)
|
||||||
super
|
stored = stored_location_for(User)
|
||||||
else
|
return stored if stored
|
||||||
request.referer || root_path
|
|
||||||
|
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
||||||
|
super
|
||||||
|
else
|
||||||
|
request.referer || root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def store_location
|
||||||
|
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,13 +13,12 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
# GET /users/:id/edit
|
# GET /users/:id/edit
|
||||||
def edit
|
def edit
|
||||||
@user = current_user
|
@user = User.find(current_user.id)
|
||||||
respond_with(@user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# PUT /users/:id
|
# PUT /users/:id
|
||||||
def update
|
def update
|
||||||
@user = current_user
|
@user = User.find(current_user.id)
|
||||||
|
|
||||||
if user_params[:password] == '' && user_params[:password_confirmation] == ''
|
if user_params[:password] == '' && user_params[:password_confirmation] == ''
|
||||||
# not trying to change the password
|
# not trying to change the password
|
||||||
|
@ -96,6 +95,8 @@ class UsersController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(:name, :email, :image, :password, :password_confirmation)
|
params.require(:user).permit(
|
||||||
|
:name, :email, :image, :password, :password_confirmation, :emails_allowed
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,4 +37,11 @@ module ApplicationHelper
|
||||||
def invite_link
|
def invite_link
|
||||||
"#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
"#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_unread_notification_count
|
||||||
|
return 0 if current_user.nil?
|
||||||
|
@uunc ||= current_user.mailboxer_notification_receipts.reduce(0) do |total, receipt|
|
||||||
|
receipt.is_read ? total : total + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ module TopicsHelper
|
||||||
type: is_map ? metamapMetacode.name : t.metacode.name,
|
type: is_map ? metamapMetacode.name : t.metacode.name,
|
||||||
typeImageURL: is_map ? metamapMetacode.icon : t.metacode.icon,
|
typeImageURL: is_map ? metamapMetacode.icon : t.metacode.icon,
|
||||||
mapCount: is_map ? 0 : t.maps.count,
|
mapCount: is_map ? 0 : t.maps.count,
|
||||||
synapseCount: is_map ? 0 : t.synapses.count,
|
synapseCount: is_map ? 0 : t.synapses.count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,4 +2,23 @@
|
||||||
class ApplicationMailer < ActionMailer::Base
|
class ApplicationMailer < ActionMailer::Base
|
||||||
default from: 'team@metamaps.cc'
|
default from: 'team@metamaps.cc'
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
|
|
||||||
|
def deliver
|
||||||
|
raise NotImplementedError('Please use Mailboxer to send your emails.')
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def mail_for_notification(notification)
|
||||||
|
if notification.notification_code == MAILBOXER_CODE_ACCESS_REQUEST
|
||||||
|
request = notification.notified_object
|
||||||
|
MapMailer.access_request_email(request)
|
||||||
|
elsif notification.notification_code == MAILBOXER_CODE_ACCESS_APPROVED
|
||||||
|
request = notification.notified_object
|
||||||
|
MapMailer.access_approved_email(request)
|
||||||
|
elsif notification.notification_code == MAILBOXER_CODE_INVITE_TO_EDIT
|
||||||
|
user_map = notification.notified_object
|
||||||
|
MapMailer.invite_to_edit_email(user_map.map, user_map.map.user, user_map.user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,17 +2,21 @@
|
||||||
class MapMailer < ApplicationMailer
|
class MapMailer < ApplicationMailer
|
||||||
default from: 'team@metamaps.cc'
|
default from: 'team@metamaps.cc'
|
||||||
|
|
||||||
def access_request_email(request, map)
|
def access_request_email(request)
|
||||||
@request = request
|
@request = request
|
||||||
@map = map
|
@map = request.map
|
||||||
subject = @map.name + ' - request to edit'
|
mail(to: @map.user.email, subject: request.requested_text)
|
||||||
mail(to: @map.user.email, subject: subject)
|
end
|
||||||
|
|
||||||
|
def access_approved_email(request)
|
||||||
|
@request = request
|
||||||
|
@map = request.map
|
||||||
|
mail(to: request.user, subject: request.approved_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invite_to_edit_email(map, inviter, invitee)
|
def invite_to_edit_email(map, inviter, invitee)
|
||||||
@inviter = inviter
|
@inviter = inviter
|
||||||
@map = map
|
@map = map
|
||||||
subject = @map.name + ' - invitation to edit'
|
mail(to: invitee.email, subject: map.invited_text)
|
||||||
mail(to: invitee.email, subject: subject)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class AccessRequest < ApplicationRecord
|
class AccessRequest < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :map
|
belongs_to :map
|
||||||
|
@ -5,14 +6,31 @@ class AccessRequest < ApplicationRecord
|
||||||
def approve
|
def approve
|
||||||
self.approved = true
|
self.approved = true
|
||||||
self.answered = true
|
self.answered = true
|
||||||
self.save
|
save
|
||||||
UserMap.create(user: self.user, map: self.map)
|
|
||||||
MapMailer.invite_to_edit_email(self.map, self.map.user, self.user).deliver_later
|
Mailboxer::Notification.where(notified_object: self).find_each do |notification|
|
||||||
|
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
user_map = UserMap.create(user: user, map: map)
|
||||||
|
NotificationService.access_approved(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def deny
|
def deny
|
||||||
self.approved = false
|
self.approved = false
|
||||||
self.answered = true
|
self.answered = true
|
||||||
self.save
|
save
|
||||||
|
|
||||||
|
Mailboxer::Notification.where(notified_object: self).find_each do |notification|
|
||||||
|
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def requested_text
|
||||||
|
map.name + ' - request to edit'
|
||||||
|
end
|
||||||
|
|
||||||
|
def approved_text
|
||||||
|
map.name + ' - access approved'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,11 +18,11 @@ class Map < ApplicationRecord
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
# This method associates the attribute ":image" with a file attachment
|
||||||
has_attached_file :screenshot,
|
has_attached_file :screenshot,
|
||||||
styles: {
|
styles: {
|
||||||
thumb: ['220x220#', :png]
|
thumb: ['220x220#', :png]
|
||||||
#:full => ['940x630#', :png]
|
#:full => ['940x630#', :png]
|
||||||
},
|
},
|
||||||
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-square.png'
|
default_url: 'https://s3.amazonaws.com/metamaps-assets/site/missing-map-square.png'
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :arranged, inclusion: { in: [true, false] }
|
validates :arranged, inclusion: { in: [true, false] }
|
||||||
|
@ -123,4 +123,8 @@ class Map < ApplicationRecord
|
||||||
Topic.where(defer_to_map_id: id).update_all(permission: permission)
|
Topic.where(defer_to_map_id: id).update_all(permission: permission)
|
||||||
Synapse.where(defer_to_map_id: id).update_all(permission: permission)
|
Synapse.where(defer_to_map_id: id).update_all(permission: permission)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invited_text
|
||||||
|
name + ' - invited to edit'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
require 'open-uri'
|
require 'open-uri'
|
||||||
|
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
|
acts_as_messageable # mailboxer notifications
|
||||||
|
|
||||||
has_many :topics
|
has_many :topics
|
||||||
has_many :synapses
|
has_many :synapses
|
||||||
has_many :maps
|
has_many :maps
|
||||||
|
@ -108,4 +110,19 @@ class User < ApplicationRecord
|
||||||
def settings=(val)
|
def settings=(val)
|
||||||
self[:settings] = val
|
self[:settings] = val
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mailboxer hooks and helper functions
|
||||||
|
|
||||||
|
def mailboxer_email(_message)
|
||||||
|
return email if emails_allowed
|
||||||
|
# else return nil, which sends no email
|
||||||
|
end
|
||||||
|
|
||||||
|
def mailboxer_notifications
|
||||||
|
mailbox.notifications
|
||||||
|
end
|
||||||
|
|
||||||
|
def mailboxer_notification_receipts
|
||||||
|
mailbox.receipts.includes(:notification).where(mailbox_type: nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,4 +2,10 @@
|
||||||
class UserMap < ApplicationRecord
|
class UserMap < ApplicationRecord
|
||||||
belongs_to :map
|
belongs_to :map
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
def mark_invite_notifications_as_read
|
||||||
|
Mailboxer::Notification.where(notified_object: self).find_each do |notification|
|
||||||
|
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,9 +14,7 @@ Webhooks::Slack::Base = Struct.new(:webhook, :event) do
|
||||||
'something'
|
'something'
|
||||||
end
|
end
|
||||||
|
|
||||||
def channel
|
delegate :channel, to: :webhook
|
||||||
webhook.channel
|
|
||||||
end
|
|
||||||
|
|
||||||
def attachments
|
def attachments
|
||||||
[{
|
[{
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class ExplorePolicy < ApplicationPolicy
|
class ExplorePolicy < ApplicationPolicy
|
||||||
def active?
|
def active?
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class HackPolicy < ApplicationPolicy
|
class HackPolicy < ApplicationPolicy
|
||||||
def load_url_title?
|
def load_url_title?
|
||||||
true
|
true
|
||||||
|
|
|
@ -16,7 +16,7 @@ class MapPolicy < ApplicationPolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
def show?
|
def show?
|
||||||
record.permission.in?(['commons', 'public']) ||
|
record.permission.in?(%w(commons public)) ||
|
||||||
record.collaborators.include?(user) ||
|
record.collaborators.include?(user) ||
|
||||||
record.user == user
|
record.user == user
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TopicPolicy < ApplicationPolicy
|
||||||
if record.defer_to_map.present?
|
if record.defer_to_map.present?
|
||||||
map_policy.show?
|
map_policy.show?
|
||||||
else
|
else
|
||||||
record.permission.in?(['commons', 'public']) || record.user == user
|
record.permission.in?(%w(commons public)) || record.user == user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
38
app/services/notification_service.rb
Normal file
38
app/services/notification_service.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class NotificationService
|
||||||
|
def self.renderer
|
||||||
|
renderer ||= ApplicationController.renderer.new(
|
||||||
|
http_host: ENV['MAILER_DEFAULT_URL'],
|
||||||
|
https: Rails.env.production? ? true : false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.access_request(request)
|
||||||
|
body = renderer.render(template: 'map_mailer/access_request_email', locals: { map: request.map, request: request }, layout: false)
|
||||||
|
request.map.user.notify(request.requested_text, body, request, false, MAILBOXER_CODE_ACCESS_REQUEST, true, request.user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.access_approved(request)
|
||||||
|
body = renderer.render(template: 'map_mailer/access_approved_email', locals: { map: request.map }, layout: false)
|
||||||
|
receipt = request.user.notify(request.approved_text, body, request, false, MAILBOXER_CODE_ACCESS_APPROVED, true, request.map.user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.invite_to_edit(map, inviter, invited)
|
||||||
|
user_map = UserMap.find_by(user: invited, map: map)
|
||||||
|
body = renderer.render(template: 'map_mailer/invite_to_edit_email', locals: { map: map, inviter: inviter }, layout: false)
|
||||||
|
invited.notify(map.invited_text, body, user_map, false, MAILBOXER_CODE_INVITE_TO_EDIT, true, inviter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.text_for_notification(notification)
|
||||||
|
if notification.notification_code == MAILBOXER_CODE_ACCESS_REQUEST
|
||||||
|
map = notification.notified_object.map
|
||||||
|
'wants permission to map with you on <span class="in-bold">' + map.name + '</span> <div class="action">Offer a response</div>'
|
||||||
|
elsif notification.notification_code == MAILBOXER_CODE_ACCESS_APPROVED
|
||||||
|
map = notification.notified_object.map
|
||||||
|
'granted your request to edit map <span class="in-bold">' + map.name + '</span>'
|
||||||
|
elsif notification.notification_code == MAILBOXER_CODE_INVITE_TO_EDIT
|
||||||
|
map = notification.notified_object.map
|
||||||
|
'gave you edit access to map <span class="in-bold">' + map.name + '</span>'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,14 +18,14 @@
|
||||||
<%= link_to "Admin", metacodes_path %>
|
<%= link_to "Admin", metacodes_path %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li class="accountListItem accountInvite openLightbox" data-open="invite">
|
|
||||||
<div class="accountIcon"></div>
|
|
||||||
<span>Share Invite</span>
|
|
||||||
</li>
|
|
||||||
<li class="accountListItem accountApps">
|
<li class="accountListItem accountApps">
|
||||||
<div class="accountIcon"></div>
|
<div class="accountIcon"></div>
|
||||||
<%= link_to "Apps", oauth_authorized_applications_path %>
|
<%= link_to "Apps", oauth_authorized_applications_path %>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="accountListItem accountInvite openLightbox" data-open="invite">
|
||||||
|
<div class="accountIcon"></div>
|
||||||
|
<span>Share Invite</span>
|
||||||
|
</li>
|
||||||
<li class="accountListItem accountLogout">
|
<li class="accountListItem accountLogout">
|
||||||
<div class="accountIcon"></div>
|
<div class="accountIcon"></div>
|
||||||
<%= link_to "Sign Out", "/logout", id: "Logout" %>
|
<%= link_to "Sign Out", "/logout", id: "Logout" %>
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
<div id="header_content">
|
<div id="header_content">
|
||||||
<%= yield(:mobile_title) %>
|
<%= yield(:mobile_title) %>
|
||||||
</div>
|
</div>
|
||||||
<div id="menu_icon"></div>
|
<div id="menu_icon">
|
||||||
|
<% if user_unread_notification_count > 0 %>
|
||||||
|
<div class="unread-notifications-dot"></div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mobile_menu">
|
<div id="mobile_menu">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -49,6 +53,12 @@
|
||||||
<li>
|
<li>
|
||||||
<%= link_to "Account", edit_user_url(current_user) %>
|
<%= link_to "Account", edit_user_url(current_user) %>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="notifications">
|
||||||
|
<%= link_to "Notifications", notifications_path %>
|
||||||
|
<% if user_unread_notification_count > 0 %>
|
||||||
|
<div class="unread-notifications-dot"></div>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to "Sign Out", "/logout", id: "Logout" %>
|
<%= link_to "Sign Out", "/logout", id: "Logout" %>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="upperLeftUI">
|
<div class="upperLeftUI">
|
||||||
<!-- home button -->
|
<!-- home button -->
|
||||||
<div class="homeButton">
|
<div class="homeButton">
|
||||||
<a href="<%= root_url %>" <% if current_user && !appsPage %><%= 'data-router=true' %><% end %>>METAMAPS</a>
|
<a href="<%= root_url %>" <% if current_user && !noHardHomeLink %><%= 'data-router=true' %><% end %>>METAMAPS</a>
|
||||||
</div> <!-- end homeButton -->
|
</div> <!-- end homeButton -->
|
||||||
|
|
||||||
<!-- search box -->
|
<!-- search box -->
|
||||||
|
@ -71,6 +71,22 @@
|
||||||
</a><!-- end addMap -->
|
</a><!-- end addMap -->
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
Metamaps.ServerData.unreadNotificationsCount = <%= user_unread_notification_count %>
|
||||||
|
</script>
|
||||||
|
<% if current_user.present? %>
|
||||||
|
<span id="notification_icon">
|
||||||
|
<%= link_to notifications_path, class: "notificationsIcon upperRightEl upperRightIcon #{user_unread_notification_count > 0 ? 'unread' : 'read'}" do %>
|
||||||
|
<div class="tooltipsUnder">
|
||||||
|
Notifications
|
||||||
|
</div>
|
||||||
|
<% if user_unread_notification_count > 0 %>
|
||||||
|
<div class="unread-notifications-dot"></div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<!-- Account / Sign in -->
|
<!-- Account / Sign in -->
|
||||||
<% if !(controller_name == "sessions" && action_name == "new") %>
|
<% if !(controller_name == "sessions" && action_name == "new") %>
|
||||||
<div class="sidebarAccount upperRightEl">
|
<div class="sidebarAccount upperRightEl">
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
<div class="wrapper <%= classes %>" id="wrapper">
|
<div class="wrapper <%= classes %>" id="wrapper">
|
||||||
|
|
||||||
<%= render :partial => 'layouts/upperelements', :locals => { :appsPage => false } %>
|
<%= render :partial => 'layouts/upperelements', :locals => { :noHardHomeLink => controller_name == "notifications" ? true : false } %>
|
||||||
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
|
||||||
|
@ -64,9 +64,13 @@
|
||||||
<p id="toast" class="toast">
|
<p id="toast" class="toast">
|
||||||
<% if devise_error_messages? %>
|
<% if devise_error_messages? %>
|
||||||
<%= devise_error_messages! %>
|
<%= devise_error_messages! %>
|
||||||
<% elsif notice %>
|
<% end %>
|
||||||
|
<% if notice %>
|
||||||
<%= notice %>
|
<%= notice %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if alert %>
|
||||||
|
<%= alert %>
|
||||||
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<div id="loading"></div>
|
<div id="loading"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<div class="wrapper <%= classes %>" id="wrapper">
|
<div class="wrapper <%= classes %>" id="wrapper">
|
||||||
|
|
||||||
<%= render :partial => 'layouts/upperelements', :locals => {:appsPage => true } %>
|
<%= render :partial => 'layouts/upperelements', :locals => {:noHardHomeLink => true } %>
|
||||||
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
|
||||||
|
@ -38,6 +38,9 @@
|
||||||
<a href="<%= oauth_authorized_applications_path %>" class="authedApps exploreMapsButton <%= params[:controller] == 'doorkeeper/authorized_applications' ? 'active' : nil %>">
|
<a href="<%= oauth_authorized_applications_path %>" class="authedApps exploreMapsButton <%= params[:controller] == 'doorkeeper/authorized_applications' ? 'active' : nil %>">
|
||||||
<div class="exploreMapsIcon"></div>Authorized Apps
|
<div class="exploreMapsIcon"></div>Authorized Apps
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/" class="myMaps exploreMapsButton">
|
||||||
|
<div class="exploreMapsIcon"></div>Maps
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>You have a new message: <%= @subject %></h1>
|
||||||
|
<p>
|
||||||
|
You have received a new message:
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>
|
||||||
|
<%= raw @message.body %>
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<p>
|
||||||
|
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,10 @@
|
||||||
|
You have a new message: <%= @subject %>
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
You have received a new message:
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Visit <%= root_url %> and go to your inbox for more info.
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>You have a new reply: <%= @subject %></h1>
|
||||||
|
<p>
|
||||||
|
You have received a new reply:
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>
|
||||||
|
<%= raw @message.body %>
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<p>
|
||||||
|
Visit <%= link_to root_url, root_url %> and go to your inbox for more info.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,10 @@
|
||||||
|
You have a new reply: <%= @subject %>
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
You have received a new reply:
|
||||||
|
|
||||||
|
-----------------------------------------------
|
||||||
|
<%= @message.body.html_safe? ? @message.body : strip_tags(@message.body) %>
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Visit <%= root_url %> and go to your inbox for more info.
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div style="padding: 16px; background: white; text-align: left;">
|
||||||
|
<%= raw @notification.body %>
|
||||||
|
<p style="font-size: 12px;">Make sense with Metamaps</p>
|
||||||
|
<%= render partial: 'shared/mailer_unsubscribe_link' %>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<% mail = ApplicationMailer.mail_for_notification(@notification) %>
|
||||||
|
<% if mail %>
|
||||||
|
<%= mail.text_part&.body&.decoded %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
Make sense with Metamaps
|
||||||
|
|
||||||
|
<%= render partial: 'shared/mailer_unsubscribe_link' %>
|
8
app/views/map_mailer/access_approved_email.html.erb
Normal file
8
app/views/map_mailer/access_approved_email.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<% map = @map || map %>
|
||||||
|
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
||||||
|
<p><span style="font-weight: bold;"><%= map.user.name %></span> has responded to your access request and invited you to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
||||||
|
<p><%= link_to map.name, map_url(map), style: "font-size: 18px; text-decoration: none; color: #4fc059;" %></p>
|
||||||
|
<% if map.desc %>
|
||||||
|
<p style="font-size: 12px;"><%= map.desc %></p>
|
||||||
|
<% end %>
|
||||||
|
<%= link_to 'Go to Map', map_url(map), style: button_style %>
|
4
app/views/map_mailer/access_approved_email.text.erb
Normal file
4
app/views/map_mailer/access_approved_email.text.erb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<% map = @map || map %>
|
||||||
|
<%= map.user.name %> has responded to your access request and invited you to collaboratively edit the following map:
|
||||||
|
|
||||||
|
<%= map.name %> [<%= map_url(map) %>]
|
|
@ -1,23 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<% map = @map || map %>
|
||||||
<html>
|
<% request = @request || request %>
|
||||||
<head>
|
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
||||||
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
<p><span style="font-weight: bold;"><%= request.user.name %></span> is requesting access to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
||||||
</head>
|
<p><%= map.name %></p>
|
||||||
<body style="font-family: sans-serif; width: 100%; padding: 24px 16px 16px 16px; background-color: #f5f5f5; text-align: center;">
|
<p><%= link_to "Allow", approve_access_map_url(id: map.id, request_id: request.id), style: "font-size: 18px; text-decoration: none; color: #4fc059;" %>
|
||||||
|
<p><%= link_to "Decline", deny_access_map_url(id: map.id, request_id: request.id), style: "font-size: 18px; text-decoration: none; color: #DB5D5D;" %></p>
|
||||||
<div style="padding: 16px; background: white; text-align: left;">
|
<%= link_to 'Go to Map', map_url(map), style: button_style %>
|
||||||
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
|
||||||
|
|
||||||
<p><span style="font-weight: bold;"><%= @request.user.name %></span> is requesting access to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
|
||||||
|
|
||||||
<p><%= @map.name %></p>
|
|
||||||
|
|
||||||
<p><%= link_to "Allow", approve_access_map_url(id: @map.id, request_id: @request.id), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #4fc059;" %>
|
|
||||||
<p><%= link_to "Decline", deny_access_map_url(id: @map.id, request_id: @request.id), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #DB5D5D;" %></p>
|
|
||||||
|
|
||||||
<%= link_to 'Open in Metamaps', map_url(@map), target: "_blank", style: button_style %>
|
|
||||||
|
|
||||||
<p style="font-size: 12px;">Make sense with Metamaps</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<%= @request.user.name %> has requested to collaboratively edit the following map:
|
<% map = @map || map %>
|
||||||
|
<% request = @request || request %>
|
||||||
|
<%= request.user.name %> has requested to collaboratively edit the following map:
|
||||||
|
|
||||||
<%= @map.name %> [<%= map_url(@map) %>]
|
<%= map.name %> [<%= map_url(map) %>]
|
||||||
|
|
||||||
Allow [<%= approve_access_map_url(id: @map.id, request_id: @request.id) %>]
|
Allow [<%= approve_access_map_url(id: map.id, request_id: request.id) %>]
|
||||||
Decline [<%= deny_access_map_url(id: @map.id, request_id: @request.id) %>]
|
Decline [<%= deny_access_map_url(id: map.id, request_id: request.id) %>]
|
||||||
|
|
||||||
Make sense with Metamaps
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<% map = @map || map %>
|
||||||
<html>
|
<% inviter = @inviter || inviter %>
|
||||||
<head>
|
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
||||||
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
|
<p><span style="font-weight: bold;"><%= inviter.name %></span> has invited you to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
||||||
</head>
|
<p><%= link_to map.name, map_url(map), style: "font-size: 18px; text-decoration: none; color: #4fc059;" %></p>
|
||||||
<body style="font-family: sans-serif; width: 100%; padding: 24px 16px 16px 16px; background-color: #f5f5f5; text-align: center;">
|
<% if map.desc %>
|
||||||
|
<p style="font-size: 12px;"><%= map.desc %></p>
|
||||||
<div style="padding: 16px; background: white; text-align: left;">
|
<% end %>
|
||||||
<% button_style = "background-color:#4fc059;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;min-height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" %>
|
<%= link_to 'Go to Map', map_url(map), style: button_style %>
|
||||||
|
|
||||||
<p><span style="font-weight: bold;"><%= @inviter.name %></span> has invited you to <span style="font-weight: bold">collaboratively edit</span> the following map:</p>
|
|
||||||
<p><%= link_to @map.name, map_url(@map), target: "_blank", style: "font-size: 18px; text-decoration: none; color: #4fc059;" %></p>
|
|
||||||
<% if @map.desc %>
|
|
||||||
<p style="font-size: 12px;"><%= @map.desc %></p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to 'Open in Metamaps', map_url(@map), target: "_blank", style: button_style %>
|
|
||||||
|
|
||||||
<p style="font-size: 12px;">Make sense with Metamaps</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<%= @inviter.name %> has invited you to collaboratively edit the following map:
|
<% map = @map || map %>
|
||||||
|
<% inviter = @inviter || inviter %>
|
||||||
<%= @map.name %> [<%= map_url(@map) %>]
|
<%= inviter.name %> has invited you to collaboratively edit the following map:
|
||||||
|
|
||||||
Make sense with Metamaps
|
|
||||||
|
|
||||||
|
|
||||||
|
<%= map.name %> [<%= map_url(map) %>]
|
||||||
|
|
14
app/views/notifications/_header.html.erb
Normal file
14
app/views/notifications/_header.html.erb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<div id="exploreMapsHeader">
|
||||||
|
<div class="exploreMapsBar exploreElement">
|
||||||
|
<div class="exploreMapsMenu">
|
||||||
|
<div class="exploreMapsCenter">
|
||||||
|
<a href="<%= notifications_path %>" class="notificationsLink exploreMapsButton active">
|
||||||
|
<div class="exploreMapsIcon"></div>Notifications
|
||||||
|
</a>
|
||||||
|
<a href="/" class="exploreMapsButton myMaps">
|
||||||
|
<div class="exploreMapsIcon"></div>Maps
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
50
app/views/notifications/index.html.erb
Normal file
50
app/views/notifications/index.html.erb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<% content_for :title, 'Notifications | Metamaps' %>
|
||||||
|
<% content_for :mobile_title, 'Notifications' %>
|
||||||
|
|
||||||
|
<div id="yield">
|
||||||
|
<div class="centerContent notificationsPage">
|
||||||
|
<header class="page-header">
|
||||||
|
<h2 class="title">Notifications</h4>
|
||||||
|
</header>
|
||||||
|
<ul class="notifications">
|
||||||
|
<% @notifications.each do |notification| %>
|
||||||
|
<% receipt = @receipts.find_by(notification_id: notification.id) %>
|
||||||
|
<li class="notification <%= receipt.is_read? ? 'read' : 'unread' %>" id="notification-<%= notification.id %>">
|
||||||
|
<%= link_to notification_path(notification.id) do %>
|
||||||
|
<div class="notification-actor">
|
||||||
|
<%= image_tag notification.sender.image(:thirtytwo) %>
|
||||||
|
</div>
|
||||||
|
<div class="notification-body">
|
||||||
|
<div class="in-bold"><%= notification.sender.name %></div>
|
||||||
|
<%= raw NotificationService.text_for_notification(notification) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="notification-read-unread">
|
||||||
|
<% if receipt.is_read? %>
|
||||||
|
<%= link_to 'mark as unread', mark_unread_notification_path(notification.id), remote: true, method: :put %>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to 'mark as read', mark_read_notification_path(notification.id), remote: true, method: :put %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="notification-date">
|
||||||
|
<%= notification.created_at.strftime("%b %d") %>
|
||||||
|
</div>
|
||||||
|
<div class="clearfloat"></div>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% if @notifications.count == 0 %>
|
||||||
|
<div class="emptyInbox">
|
||||||
|
You have no notifications. More time for dancing.
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if @notifications.total_pages > 1 %>
|
||||||
|
<div class="centerContent withPadding pagination">
|
||||||
|
<%= paginate @notifications %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render partial: 'notifications/header' %>
|
7
app/views/notifications/mark_read.js.erb
Normal file
7
app/views/notifications/mark_read.js.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
$('#notification-<%= @notification.id %> .notification-read-unread > a')
|
||||||
|
.text('mark as unread')
|
||||||
|
.attr('href', '<%= mark_unread_notification_path(@notification.id) %>')
|
||||||
|
$('#notification-<%= @notification.id %>')
|
||||||
|
.removeClass('unread')
|
||||||
|
.addClass('read')
|
||||||
|
Metamaps.GlobalUI.NotificationIcon.render(Metamaps.GlobalUI.NotificationIcon.unreadNotificationsCount - 1)
|
7
app/views/notifications/mark_unread.js.erb
Normal file
7
app/views/notifications/mark_unread.js.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
$('#notification-<%= @notification.id %> .notification-read-unread > a')
|
||||||
|
.text('mark as read')
|
||||||
|
.attr('href', '<%= mark_read_notification_path(@notification.id) %>')
|
||||||
|
$('#notification-<%= @notification.id %>')
|
||||||
|
.removeClass('read')
|
||||||
|
.addClass('unread')
|
||||||
|
Metamaps.GlobalUI.NotificationIcon.render(Metamaps.GlobalUI.NotificationIcon.unreadNotificationsCount + 1)
|
16
app/views/notifications/show.html.erb
Normal file
16
app/views/notifications/show.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<% content_for :title, 'Notifications | Metamaps' %>
|
||||||
|
<% content_for :mobile_title, 'Notifications' %>
|
||||||
|
|
||||||
|
<div id="yield">
|
||||||
|
<div class="centerContent withPadding back">
|
||||||
|
<%= link_to 'Back to notifications', notifications_path %>
|
||||||
|
</div>
|
||||||
|
<div class="centerContent notificationPage">
|
||||||
|
<h2 class="notification-title"><%= @notification.subject %></h4>
|
||||||
|
<div class="notification-body">
|
||||||
|
<%= raw @notification.body %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render partial: 'notifications/header' %>
|
3
app/views/shared/_mailer_unsubscribe_link.html.erb
Normal file
3
app/views/shared/_mailer_unsubscribe_link.html.erb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="unsubscribe-link">
|
||||||
|
<%= link_to 'Click here to unsubscribe from all Metamaps emails', unsubscribe_notifications_url(protocol: Rails.env.production? ? :https : :http) %>
|
||||||
|
</div>
|
5
app/views/shared/_mailer_unsubscribe_link.text.erb
Normal file
5
app/views/shared/_mailer_unsubscribe_link.text.erb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
You can unsubscribe from all Metamaps emails by visiting the following link:
|
||||||
|
|
||||||
|
<%= unsubscribe_notifications_url(protocol: Rails.env.production? ? :https : :http) %>
|
|
@ -33,23 +33,35 @@
|
||||||
<div class="nameEdit"><%= @user.name %></div>
|
<div class="nameEdit"><%= @user.name %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="changeName">
|
<div class="changeName">
|
||||||
<%= form.label :name, "Name:", :class => "firstFieldText" %>
|
<%= form.label :name, "Name:", class: 'firstFieldText' %>
|
||||||
<%= form.text_field :name %>
|
<%= form.text_field :name %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= form.label :email, "Email:", class: 'firstFieldText' %>
|
||||||
|
<%= form.email_field :email %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= form.label :emails_allowed, class: 'firstFieldText' do %>
|
||||||
|
<%= form.check_box :emails_allowed, class: 'inline' %>
|
||||||
|
Send Metamaps notifications to my email.
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div><%= form.label :email, "Email:", :class => "firstFieldText" %>
|
|
||||||
<%= form.email_field :email %></div>
|
|
||||||
<div class="changePass" onclick="Metamaps.Account.showPass()">Change Password</div>
|
<div class="changePass" onclick="Metamaps.Account.showPass()">Change Password</div>
|
||||||
<div class="toHide">
|
<div class="toHide">
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :current_password, "Current Password:", :class => "firstFieldText" %>
|
<%= form.label :current_password, "Current Password:", :class => "firstFieldText" %>
|
||||||
<%= password_field_tag :current_password, params[:current_password] %>
|
<%= password_field_tag :current_password, params[:current_password] %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= form.label :password, "New Password:", :class => "firstFieldText" %>
|
||||||
|
<%= form.password_field :password, :autocomplete => :off%>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= form.label :password_confirmation, "Confirm New Password:", :class => "firstFieldText" %>
|
||||||
|
<%= form.password_field :password_confirmation, :autocomplete => :off%>
|
||||||
|
</div>
|
||||||
|
<div class="noChangePass" onclick="Metamaps.Account.hidePass()">Oops, don't change password</div>
|
||||||
</div>
|
</div>
|
||||||
<div><%= form.label :password, "New Password:", :class => "firstFieldText" %>
|
|
||||||
<%= form.password_field :password, :autocomplete => :off%></div>
|
|
||||||
<div><%= form.label :password_confirmation, "Confirm New Password:", :class => "firstFieldText" %>
|
|
||||||
<%= form.password_field :password_confirmation, :autocomplete => :off%></div>
|
|
||||||
<div class="noChangePass" onclick="Metamaps.Account.hidePass()">Oops, don't change password</div>
|
|
||||||
</div>
|
|
||||||
<div id="accountPageLoading"></div>
|
<div id="accountPageLoading"></div>
|
||||||
<%= form.submit "Update", class: "update", onclick: "Metamaps.Account.showLoading()" %>
|
<%= form.submit "Update", class: "update", onclick: "Metamaps.Account.showLoading()" %>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
|
|
|
@ -8,14 +8,15 @@ Bundler.require(*Rails.groups)
|
||||||
|
|
||||||
module Metamaps
|
module Metamaps
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
config.active_job.queue_adapter = :delayed_job
|
|
||||||
if ENV['ACTIVE_JOB_FRAMEWORK'] == 'sucker_punch'
|
|
||||||
config.active_job.queue_adapter = :sucker_punch
|
|
||||||
end
|
|
||||||
|
|
||||||
# Settings in config/environments/* take precedence over those specified here.
|
# Settings in config/environments/* take precedence over those specified here.
|
||||||
# Application configuration should go into files in config/initializers
|
# Application configuration should go into files in config/initializers
|
||||||
# -- all .rb files in that directory are automatically loaded.
|
# -- all .rb files in that directory are automatically loaded.
|
||||||
|
#
|
||||||
|
config.active_job.queue_adapter = if ENV['ACTIVE_JOB_FRAMEWORK'] == 'sucker_punch'
|
||||||
|
:sucker_punch
|
||||||
|
else
|
||||||
|
:delayed_job
|
||||||
|
end
|
||||||
|
|
||||||
# Custom directories with classes and modules you want to be autoloadable.
|
# Custom directories with classes and modules you want to be autoloadable.
|
||||||
config.autoload_paths << Rails.root.join('app', 'services')
|
config.autoload_paths << Rails.root.join('app', 'services')
|
||||||
|
|
24
config/brakeman.ignore
Normal file
24
config/brakeman.ignore
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"ignored_warnings": [
|
||||||
|
{
|
||||||
|
"warning_type": "Cross Site Scripting",
|
||||||
|
"warning_code": 2,
|
||||||
|
"fingerprint": "88694dca0bcc2226859746f9ed40cc682d6e5eaec1e73f2be557770a854ede0b",
|
||||||
|
"message": "Unescaped model attribute",
|
||||||
|
"file": "app/views/notifications/show.html.erb",
|
||||||
|
"line": 7,
|
||||||
|
"link": "http://brakemanscanner.org/docs/warning_types/cross_site_scripting",
|
||||||
|
"code": "current_user.mailbox.notifications.find_by(:id => params[:id]).body",
|
||||||
|
"render_path": [{"type":"controller","class":"NotificationsController","method":"show","line":24,"file":"app/controllers/notifications_controller.rb"}],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "notifications/show"
|
||||||
|
},
|
||||||
|
"user_input": "current_user.mailbox.notifications",
|
||||||
|
"confidence": "Weak",
|
||||||
|
"note": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": "2016-11-29 13:01:34 -0500",
|
||||||
|
"brakeman_version": "3.4.0"
|
||||||
|
}
|
|
@ -14,19 +14,11 @@ Rails.application.configure do
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = true
|
||||||
config.action_controller.perform_caching = false
|
config.action_controller.perform_caching = false
|
||||||
|
|
||||||
config.action_mailer.delivery_method = :smtp
|
config.action_mailer.delivery_method = :file
|
||||||
config.action_mailer.smtp_settings = {
|
config.action_mailer.file_settings = {
|
||||||
address: ENV['SMTP_SERVER'],
|
location: 'tmp/mails'
|
||||||
port: ENV['SMTP_PORT'],
|
|
||||||
user_name: ENV['SMTP_USERNAME'],
|
|
||||||
password: ENV['SMTP_PASSWORD'],
|
|
||||||
domain: ENV['SMTP_DOMAIN'],
|
|
||||||
authentication: 'plain',
|
|
||||||
enable_starttls_auto: true,
|
|
||||||
openssl_verify_mode: 'none'
|
|
||||||
}
|
}
|
||||||
config.action_mailer.default_url_options = { host: 'localhost:3000' }
|
config.action_mailer.default_url_options = { host: 'localhost:3000' }
|
||||||
# Don't care if the mailer can't send
|
|
||||||
config.action_mailer.raise_delivery_errors = true
|
config.action_mailer.raise_delivery_errors = true
|
||||||
|
|
||||||
# Print deprecation notices to the Rails logger
|
# Print deprecation notices to the Rails logger
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
|
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
# Settings specified here will take precedence over those in config/application.rb
|
# Settings specified here will take precedence over those in config/application.rb
|
||||||
|
|
||||||
config.log_level = :warn
|
# log to stdout
|
||||||
config.eager_load = true
|
logger = Logger.new(STDOUT)
|
||||||
|
|
||||||
# 12 factor: log to stdout
|
|
||||||
logger = ActiveSupport::Logger.new(STDOUT)
|
|
||||||
logger.formatter = config.log_formatter
|
logger.formatter = config.log_formatter
|
||||||
|
logger.level = :warn
|
||||||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||||
|
|
||||||
# Code is not reloaded between requests
|
# Code is not reloaded between requests
|
||||||
|
config.eager_load = true
|
||||||
config.cache_classes = true
|
config.cache_classes = true
|
||||||
|
|
||||||
# Full error reports are disabled and caching is turned on
|
# Full error reports are disabled and caching is turned on
|
||||||
|
|
|
@ -9,20 +9,20 @@ Doorkeeper.configure do
|
||||||
current_user
|
current_user
|
||||||
else
|
else
|
||||||
store_location_for(User, request.fullpath)
|
store_location_for(User, request.fullpath)
|
||||||
redirect_to(sign_in_url, notice: "Sign In to Connect")
|
redirect_to(sign_in_url, notice: 'Sign In to Connect')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If you want to restrict access to the web interface for adding oauth authorized applications,
|
# If you want to restrict access to the web interface for adding oauth authorized applications,
|
||||||
# you need to declare the block below.
|
# you need to declare the block below.
|
||||||
admin_authenticator do
|
admin_authenticator do
|
||||||
if current_user && current_user.admin
|
if current_user&.admin
|
||||||
current_user
|
current_user
|
||||||
elsif current_user && !current_user.admin
|
elsif current_user && !current_user.admin
|
||||||
redirect_to(root_url, notice: "Unauthorized")
|
redirect_to(root_url, notice: 'Unauthorized')
|
||||||
else
|
else
|
||||||
store_location_for(User, request.fullpath)
|
store_location_for(User, request.fullpath)
|
||||||
redirect_to(sign_in_url, notice: "Try signing in to do that")
|
redirect_to(sign_in_url, notice: 'Try signing in to do that')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
33
config/initializers/mailboxer.rb
Normal file
33
config/initializers/mailboxer.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# notification codes to differentiate different types of notifications
|
||||||
|
# e.g. a notification might have {
|
||||||
|
# notified_object_type: 'Map',
|
||||||
|
# notified_object_id: 1,
|
||||||
|
# notification_code: MAILBOXER_CODE_ACCESS_REQUEST
|
||||||
|
# },
|
||||||
|
# which would imply that this is an access request to Map.find(1)
|
||||||
|
MAILBOXER_CODE_ACCESS_REQUEST = 'ACCESS_REQUEST'
|
||||||
|
MAILBOXER_CODE_ACCESS_APPROVED = 'ACCESS_APPROVED'
|
||||||
|
MAILBOXER_CODE_INVITE_TO_EDIT = 'INVITE_TO_EDIT'
|
||||||
|
|
||||||
|
Mailboxer.setup do |config|
|
||||||
|
# Configures if your application uses or not email sending for Notifications and Messages
|
||||||
|
config.uses_emails = true
|
||||||
|
|
||||||
|
# Configures the default from for emails sent for Messages and Notifications
|
||||||
|
config.default_from = 'team@metamaps.cc'
|
||||||
|
|
||||||
|
# Configures the methods needed by mailboxer
|
||||||
|
config.email_method = :mailboxer_email
|
||||||
|
config.name_method = :name
|
||||||
|
|
||||||
|
# Configures if you use or not a search engine and which one you are using
|
||||||
|
# Supported engines: [:solr,:sphinx]
|
||||||
|
config.search_enabled = false
|
||||||
|
config.search_engine = :solr
|
||||||
|
|
||||||
|
# Configures maximum length of the message subject and body
|
||||||
|
config.subject_max_length = 255
|
||||||
|
config.body_max_length = 32_000
|
||||||
|
end
|
|
@ -1,3 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
class Rack::Attack
|
class Rack::Attack
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||||
|
|
||||||
|
@ -11,10 +12,8 @@ class Rack::Attack
|
||||||
# Throttle POST requests to /login by IP address
|
# Throttle POST requests to /login by IP address
|
||||||
#
|
#
|
||||||
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
||||||
throttle('logins/ip', :limit => 5, :period => 20.seconds) do |req|
|
throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
|
||||||
if req.path == '/login' && req.post?
|
req.ip if req.path == '/login' && req.post?
|
||||||
req.ip
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Throttle POST requests to /login by email param
|
# Throttle POST requests to /login by email param
|
||||||
|
@ -25,17 +24,17 @@ class Rack::Attack
|
||||||
# throttle logins for another user and force their login requests to be
|
# throttle logins for another user and force their login requests to be
|
||||||
# denied, but that's not very common and shouldn't happen to you. (Knock
|
# denied, but that's not very common and shouldn't happen to you. (Knock
|
||||||
# on wood!)
|
# on wood!)
|
||||||
throttle("logins/email", :limit => 5, :period => 20.seconds) do |req|
|
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
|
||||||
if req.path == '/login' && req.post?
|
if req.path == '/login' && req.post?
|
||||||
# return the email if present, nil otherwise
|
# return the email if present, nil otherwise
|
||||||
req.params['email'].presence
|
req.params['email'].presence
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
throttle('load_url_title/req/5mins/ip', :limit => 300, :period => 5.minutes) do |req|
|
throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
|
||||||
req.ip if req.path == 'hacks/load_url_title'
|
req.ip if req.path == 'hacks/load_url_title'
|
||||||
end
|
end
|
||||||
throttle('load_url_title/req/1s/ip', :limit => 5, :period => 1.second) do |req|
|
throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
|
||||||
# If the return value is truthy, the cache key for the return value
|
# If the return value is truthy, the cache key for the return value
|
||||||
# is incremented and compared with the limit. In this case:
|
# is incremented and compared with the limit. In this case:
|
||||||
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
# "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
|
||||||
|
@ -46,16 +45,16 @@ class Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
self.throttled_response = lambda do |env|
|
self.throttled_response = lambda do |env|
|
||||||
now = Time.now
|
now = Time.now
|
||||||
match_data = env['rack.attack.match_data']
|
match_data = env['rack.attack.match_data']
|
||||||
period = match_data[:period]
|
period = match_data[:period]
|
||||||
limit = match_data[:limit]
|
limit = match_data[:limit]
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'X-RateLimit-Limit' => limit.to_s,
|
'X-RateLimit-Limit' => limit.to_s,
|
||||||
'X-RateLimit-Remaining' => '0',
|
'X-RateLimit-Remaining' => '0',
|
||||||
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
|
||||||
}
|
}
|
||||||
|
|
||||||
[429, headers, ['']]
|
[429, headers, ['']]
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
# Sample localization file for English. Add more files in this directory for other locales.
|
|
||||||
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
|
|
||||||
|
|
||||||
en:
|
en:
|
||||||
activerecord:
|
mailboxer:
|
||||||
attributes:
|
notification_mailer:
|
||||||
user:
|
subject: "%{subject}"
|
||||||
joinedwithcode: "Access code"
|
|
||||||
|
|
|
@ -20,12 +20,25 @@ Metamaps::Application.routes.draw do
|
||||||
post 'events/:event', action: :events
|
post 'events/:event', action: :events
|
||||||
get :contains
|
get :contains
|
||||||
|
|
||||||
get :request_access, to: 'access#request_access'
|
get :request_access,
|
||||||
get 'approve_access/:request_id', to: 'access#approve_access', as: :approve_access
|
to: 'access#request_access'
|
||||||
get 'deny_access/:request_id', to: 'access#deny_access', as: :deny_access
|
get 'approve_access/:request_id',
|
||||||
post :access_request, to: 'access#access_request', default: { format: :json }
|
to: 'access#approve_access',
|
||||||
post 'approve_access/:request_id', to: 'access#approve_access_post', default: { format: :json }
|
as: :approve_access
|
||||||
post 'deny_access/:request_id', to: 'access#deny_access_post', default: { format: :json }
|
get 'deny_access/:request_id',
|
||||||
|
to: 'access#deny_access',
|
||||||
|
as: :deny_access
|
||||||
|
|
||||||
|
post :access_request,
|
||||||
|
to: 'access#access_request',
|
||||||
|
default: { format: :json }
|
||||||
|
post 'approve_access/:request_id',
|
||||||
|
to: 'access#approve_access_post',
|
||||||
|
default: { format: :json }
|
||||||
|
post 'deny_access/:request_id',
|
||||||
|
to: 'access#deny_access_post',
|
||||||
|
default: { format: :json }
|
||||||
|
|
||||||
post :access, to: 'access#access', default: { format: :json }
|
post :access, to: 'access#access', default: { format: :json }
|
||||||
|
|
||||||
post :star, to: 'stars#create', default: { format: :json }
|
post :star, to: 'stars#create', default: { format: :json }
|
||||||
|
@ -36,6 +49,15 @@ Metamaps::Application.routes.draw do
|
||||||
resources :mappings, except: [:index, :new, :edit]
|
resources :mappings, except: [:index, :new, :edit]
|
||||||
|
|
||||||
resources :messages, only: [:show, :create, :update, :destroy]
|
resources :messages, only: [:show, :create, :update, :destroy]
|
||||||
|
resources :notifications, only: [:index, :show] do
|
||||||
|
collection do
|
||||||
|
get :unsubscribe
|
||||||
|
end
|
||||||
|
member do
|
||||||
|
put :mark_read
|
||||||
|
put :mark_unread
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :metacode_sets, except: [:show]
|
resources :metacode_sets, except: [:show]
|
||||||
|
|
||||||
|
@ -109,3 +131,4 @@ Metamaps::Application.routes.draw do
|
||||||
get 'load_url_title'
|
get 'load_url_title'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Rubocop/Metrics/BlockLength
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
# This migration comes from mailboxer_engine (originally 20110511145103)
|
||||||
|
class CreateMailboxer < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
#Tables
|
||||||
|
#Conversations
|
||||||
|
create_table :mailboxer_conversations do |t|
|
||||||
|
t.column :subject, :string, :default => ""
|
||||||
|
t.column :created_at, :datetime, :null => false
|
||||||
|
t.column :updated_at, :datetime, :null => false
|
||||||
|
end
|
||||||
|
#Receipts
|
||||||
|
create_table :mailboxer_receipts do |t|
|
||||||
|
t.references :receiver, :polymorphic => true
|
||||||
|
t.column :notification_id, :integer, :null => false
|
||||||
|
t.column :is_read, :boolean, :default => false
|
||||||
|
t.column :trashed, :boolean, :default => false
|
||||||
|
t.column :deleted, :boolean, :default => false
|
||||||
|
t.column :mailbox_type, :string, :limit => 25
|
||||||
|
t.column :created_at, :datetime, :null => false
|
||||||
|
t.column :updated_at, :datetime, :null => false
|
||||||
|
end
|
||||||
|
#Notifications and Messages
|
||||||
|
create_table :mailboxer_notifications do |t|
|
||||||
|
t.column :type, :string
|
||||||
|
t.column :body, :text
|
||||||
|
t.column :subject, :string, :default => ""
|
||||||
|
t.references :sender, :polymorphic => true
|
||||||
|
t.column :conversation_id, :integer
|
||||||
|
t.column :draft, :boolean, :default => false
|
||||||
|
t.string :notification_code, :default => nil
|
||||||
|
t.references :notified_object, :polymorphic => true
|
||||||
|
t.column :attachment, :string
|
||||||
|
t.column :updated_at, :datetime, :null => false
|
||||||
|
t.column :created_at, :datetime, :null => false
|
||||||
|
t.boolean :global, default: false
|
||||||
|
t.datetime :expires
|
||||||
|
end
|
||||||
|
|
||||||
|
#Indexes
|
||||||
|
#Conversations
|
||||||
|
#Receipts
|
||||||
|
add_index "mailboxer_receipts","notification_id"
|
||||||
|
|
||||||
|
#Messages
|
||||||
|
add_index "mailboxer_notifications","conversation_id"
|
||||||
|
|
||||||
|
#Foreign keys
|
||||||
|
#Conversations
|
||||||
|
#Receipts
|
||||||
|
add_foreign_key "mailboxer_receipts", "mailboxer_notifications", :name => "receipts_on_notification_id", :column => "notification_id"
|
||||||
|
#Messages
|
||||||
|
add_foreign_key "mailboxer_notifications", "mailboxer_conversations", :name => "notifications_on_conversation_id", :column => "conversation_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
#Tables
|
||||||
|
remove_foreign_key "mailboxer_receipts", :name => "receipts_on_notification_id"
|
||||||
|
remove_foreign_key "mailboxer_notifications", :name => "notifications_on_conversation_id"
|
||||||
|
|
||||||
|
#Indexes
|
||||||
|
drop_table :mailboxer_receipts
|
||||||
|
drop_table :mailboxer_conversations
|
||||||
|
drop_table :mailboxer_notifications
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
# This migration comes from mailboxer_engine (originally 20131206080416)
|
||||||
|
class AddConversationOptout < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :mailboxer_conversation_opt_outs do |t|
|
||||||
|
t.references :unsubscriber, :polymorphic => true
|
||||||
|
t.references :conversation
|
||||||
|
end
|
||||||
|
add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", :name => "mb_opt_outs_on_conversations_id", :column => "conversation_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_foreign_key "mailboxer_conversation_opt_outs", :name => "mb_opt_outs_on_conversations_id"
|
||||||
|
drop_table :mailboxer_conversation_opt_outs
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
# This migration comes from mailboxer_engine (originally 20131206080417)
|
||||||
|
class AddMissingIndices < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
# We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63
|
||||||
|
# characters limitation.
|
||||||
|
add_index :mailboxer_conversation_opt_outs, [:unsubscriber_id, :unsubscriber_type],
|
||||||
|
name: 'index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type'
|
||||||
|
add_index :mailboxer_conversation_opt_outs, :conversation_id
|
||||||
|
|
||||||
|
add_index :mailboxer_notifications, :type
|
||||||
|
add_index :mailboxer_notifications, [:sender_id, :sender_type]
|
||||||
|
|
||||||
|
# We'll explicitly specify its name, as the auto-generated name is too long and exceeds 63
|
||||||
|
# characters limitation.
|
||||||
|
add_index :mailboxer_notifications, [:notified_object_id, :notified_object_type],
|
||||||
|
name: 'index_mailboxer_notifications_on_notified_object_id_and_type'
|
||||||
|
|
||||||
|
add_index :mailboxer_receipts, [:receiver_id, :receiver_type]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
# This migration comes from mailboxer_engine (originally 20151103080417)
|
||||||
|
class AddDeliveryTrackingInfoToMailboxerReceipts < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :mailboxer_receipts, :is_delivered, :boolean, default: false
|
||||||
|
add_column :mailboxer_receipts, :delivery_method, :string
|
||||||
|
add_column :mailboxer_receipts, :message_id, :string
|
||||||
|
end
|
||||||
|
end
|
5
db/migrate/20161125175229_add_emails_allowed_to_users.rb
Normal file
5
db/migrate/20161125175229_add_emails_allowed_to_users.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class AddEmailsAllowedToUsers < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
add_column :users, :emails_allowed, :boolean, default: true
|
||||||
|
end
|
||||||
|
end
|
63
db/schema.rb
63
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20161105160340) do
|
ActiveRecord::Schema.define(version: 20161125175229) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -63,6 +63,59 @@ ActiveRecord::Schema.define(version: 20161105160340) do
|
||||||
t.index ["metacode_set_id"], name: "index_in_metacode_sets_on_metacode_set_id", using: :btree
|
t.index ["metacode_set_id"], name: "index_in_metacode_sets_on_metacode_set_id", using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "mailboxer_conversation_opt_outs", force: :cascade do |t|
|
||||||
|
t.string "unsubscriber_type"
|
||||||
|
t.integer "unsubscriber_id"
|
||||||
|
t.integer "conversation_id"
|
||||||
|
t.index ["conversation_id"], name: "index_mailboxer_conversation_opt_outs_on_conversation_id", using: :btree
|
||||||
|
t.index ["unsubscriber_id", "unsubscriber_type"], name: "index_mailboxer_conversation_opt_outs_on_unsubscriber_id_type", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "mailboxer_conversations", force: :cascade do |t|
|
||||||
|
t.string "subject", default: ""
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "mailboxer_notifications", force: :cascade do |t|
|
||||||
|
t.string "type"
|
||||||
|
t.text "body"
|
||||||
|
t.string "subject", default: ""
|
||||||
|
t.string "sender_type"
|
||||||
|
t.integer "sender_id"
|
||||||
|
t.integer "conversation_id"
|
||||||
|
t.boolean "draft", default: false
|
||||||
|
t.string "notification_code"
|
||||||
|
t.string "notified_object_type"
|
||||||
|
t.integer "notified_object_id"
|
||||||
|
t.string "attachment"
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.boolean "global", default: false
|
||||||
|
t.datetime "expires"
|
||||||
|
t.index ["conversation_id"], name: "index_mailboxer_notifications_on_conversation_id", using: :btree
|
||||||
|
t.index ["notified_object_id", "notified_object_type"], name: "index_mailboxer_notifications_on_notified_object_id_and_type", using: :btree
|
||||||
|
t.index ["sender_id", "sender_type"], name: "index_mailboxer_notifications_on_sender_id_and_sender_type", using: :btree
|
||||||
|
t.index ["type"], name: "index_mailboxer_notifications_on_type", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "mailboxer_receipts", force: :cascade do |t|
|
||||||
|
t.string "receiver_type"
|
||||||
|
t.integer "receiver_id"
|
||||||
|
t.integer "notification_id", null: false
|
||||||
|
t.boolean "is_read", default: false
|
||||||
|
t.boolean "trashed", default: false
|
||||||
|
t.boolean "deleted", default: false
|
||||||
|
t.string "mailbox_type", limit: 25
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.boolean "is_delivered", default: false
|
||||||
|
t.string "delivery_method"
|
||||||
|
t.string "message_id"
|
||||||
|
t.index ["notification_id"], name: "index_mailboxer_receipts_on_notification_id", using: :btree
|
||||||
|
t.index ["receiver_id", "receiver_type"], name: "index_mailboxer_receipts_on_receiver_id_and_receiver_type", using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "mappings", force: :cascade do |t|
|
create_table "mappings", force: :cascade do |t|
|
||||||
t.text "category"
|
t.text "category"
|
||||||
t.integer "xloc"
|
t.integer "xloc"
|
||||||
|
@ -243,8 +296,8 @@ ActiveRecord::Schema.define(version: 20161105160340) do
|
||||||
t.string "password_salt", limit: 255
|
t.string "password_salt", limit: 255
|
||||||
t.string "persistence_token", limit: 255
|
t.string "persistence_token", limit: 255
|
||||||
t.string "perishable_token", limit: 255
|
t.string "perishable_token", limit: 255
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "code", limit: 8
|
t.string "code", limit: 8
|
||||||
t.string "joinedwithcode", limit: 8
|
t.string "joinedwithcode", limit: 8
|
||||||
t.text "settings"
|
t.text "settings"
|
||||||
|
@ -264,6 +317,7 @@ ActiveRecord::Schema.define(version: 20161105160340) do
|
||||||
t.integer "image_file_size"
|
t.integer "image_file_size"
|
||||||
t.datetime "image_updated_at"
|
t.datetime "image_updated_at"
|
||||||
t.integer "generation"
|
t.integer "generation"
|
||||||
|
t.boolean "emails_allowed", default: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -279,5 +333,8 @@ ActiveRecord::Schema.define(version: 20161105160340) do
|
||||||
|
|
||||||
add_foreign_key "access_requests", "maps"
|
add_foreign_key "access_requests", "maps"
|
||||||
add_foreign_key "access_requests", "users"
|
add_foreign_key "access_requests", "users"
|
||||||
|
add_foreign_key "mailboxer_conversation_opt_outs", "mailboxer_conversations", column: "conversation_id", name: "mb_opt_outs_on_conversations_id"
|
||||||
|
add_foreign_key "mailboxer_notifications", "mailboxer_conversations", column: "conversation_id", name: "notifications_on_conversation_id"
|
||||||
|
add_foreign_key "mailboxer_receipts", "mailboxer_notifications", column: "notification_id", name: "receipts_on_notification_id"
|
||||||
add_foreign_key "tokens", "users"
|
add_foreign_key "tokens", "users"
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,13 @@ Run these tests to be reasonably sure that your code changes haven't broken anyt
|
||||||
- Add a number of synapses to one of your maps. Reload to see if they are still there.
|
- Add a number of synapses to one of your maps. Reload to see if they are still there.
|
||||||
- Rearrange one of your maps and save the layout. Reload to see if the layout is preserved.
|
- Rearrange one of your maps and save the layout. Reload to see if the layout is preserved.
|
||||||
|
|
||||||
|
### Unsubscribing from Notifications
|
||||||
|
|
||||||
|
- Log out
|
||||||
|
- Visit /notifications/unsubscribe. It should redirect you to the login page.
|
||||||
|
- Log in.
|
||||||
|
- It should redirect you to the user edit page, and you should be unsubscribed.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
- Login as admin. Change metacode sets.
|
- Login as admin. Change metacode sets.
|
||||||
|
|
30
frontend/src/Metamaps/GlobalUI/NotificationIcon.js
Normal file
30
frontend/src/Metamaps/GlobalUI/NotificationIcon.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/* global $ */
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
|
import Active from '../Active'
|
||||||
|
import NotificationIconComponent from '../../components/NotificationIcon'
|
||||||
|
|
||||||
|
const NotificationIcon = {
|
||||||
|
unreadNotificationsCount: null,
|
||||||
|
|
||||||
|
init: function(serverData) {
|
||||||
|
const self = NotificationIcon
|
||||||
|
self.unreadNotificationsCount = serverData.unreadNotificationsCount
|
||||||
|
self.render()
|
||||||
|
},
|
||||||
|
render: function(newUnreadCount = null) {
|
||||||
|
if (newUnreadCount !== null) {
|
||||||
|
NotificationIcon.unreadNotificationsCount = newUnreadCount
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Active.Mapper !== null) {
|
||||||
|
ReactDOM.render(React.createElement(NotificationIconComponent, {
|
||||||
|
unreadNotificationsCount: NotificationIcon.unreadNotificationsCount
|
||||||
|
}), $('#notification_icon').get(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationIcon
|
|
@ -8,6 +8,7 @@ import Search from './Search'
|
||||||
import CreateMap from './CreateMap'
|
import CreateMap from './CreateMap'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
import ImportDialog from './ImportDialog'
|
import ImportDialog from './ImportDialog'
|
||||||
|
import NotificationIcon from './NotificationIcon'
|
||||||
|
|
||||||
const GlobalUI = {
|
const GlobalUI = {
|
||||||
notifyTimeout: null,
|
notifyTimeout: null,
|
||||||
|
@ -19,6 +20,7 @@ const GlobalUI = {
|
||||||
self.CreateMap.init(serverData)
|
self.CreateMap.init(serverData)
|
||||||
self.Account.init(serverData)
|
self.Account.init(serverData)
|
||||||
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
||||||
|
self.NotificationIcon.init(serverData)
|
||||||
|
|
||||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
||||||
|
|
||||||
|
@ -127,5 +129,5 @@ const GlobalUI = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Search, CreateMap, Account, ImportDialog }
|
export { Search, CreateMap, Account, ImportDialog, NotificationIcon }
|
||||||
export default GlobalUI
|
export default GlobalUI
|
||||||
|
|
|
@ -264,7 +264,7 @@ const InfoBox = {
|
||||||
var mapperIds = DataModel.Collaborators.models.map(function(mapper) { return mapper.id })
|
var mapperIds = DataModel.Collaborators.models.map(function(mapper) { return mapper.id })
|
||||||
$.post('/maps/' + Active.Map.id + '/access', { access: mapperIds })
|
$.post('/maps/' + Active.Map.id + '/access', { access: mapperIds })
|
||||||
var name = DataModel.Collaborators.get(newCollaboratorId).get('name')
|
var name = DataModel.Collaborators.get(newCollaboratorId).get('name')
|
||||||
GlobalUI.notifyUser(name + ' will be notified by email')
|
GlobalUI.notifyUser(name + ' will be notified')
|
||||||
self.updateNumbers()
|
self.updateNumbers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ import Create from './Create'
|
||||||
import Debug from './Debug'
|
import Debug from './Debug'
|
||||||
import Filter from './Filter'
|
import Filter from './Filter'
|
||||||
import GlobalUI, {
|
import GlobalUI, {
|
||||||
Search, CreateMap, ImportDialog, Account as GlobalUIAccount
|
Search, CreateMap, ImportDialog, Account as GlobalUIAccount,
|
||||||
|
NotificationIcon
|
||||||
} from './GlobalUI'
|
} from './GlobalUI'
|
||||||
import Import from './Import'
|
import Import from './Import'
|
||||||
import JIT from './JIT'
|
import JIT from './JIT'
|
||||||
|
@ -47,6 +48,7 @@ Metamaps.GlobalUI.Search = Search
|
||||||
Metamaps.GlobalUI.CreateMap = CreateMap
|
Metamaps.GlobalUI.CreateMap = CreateMap
|
||||||
Metamaps.GlobalUI.Account = GlobalUIAccount
|
Metamaps.GlobalUI.Account = GlobalUIAccount
|
||||||
Metamaps.GlobalUI.ImportDialog = ImportDialog
|
Metamaps.GlobalUI.ImportDialog = ImportDialog
|
||||||
|
Metamaps.GlobalUI.NotificationIcon = NotificationIcon
|
||||||
Metamaps.Import = Import
|
Metamaps.Import = Import
|
||||||
Metamaps.JIT = JIT
|
Metamaps.JIT = JIT
|
||||||
Metamaps.Listeners = Listeners
|
Metamaps.Listeners = Listeners
|
||||||
|
|
|
@ -36,6 +36,12 @@ class Header extends Component {
|
||||||
<div className="exploreMapsBar exploreElement">
|
<div className="exploreMapsBar exploreElement">
|
||||||
<div className="exploreMapsMenu">
|
<div className="exploreMapsMenu">
|
||||||
<div className="exploreMapsCenter">
|
<div className="exploreMapsCenter">
|
||||||
|
<MapLink show={explore}
|
||||||
|
href={signedIn ? '/' : '/explore/active'}
|
||||||
|
linkClass={activeClass('active')}
|
||||||
|
data-router="true"
|
||||||
|
text="All Maps"
|
||||||
|
/>
|
||||||
<MapLink show={signedIn && explore}
|
<MapLink show={signedIn && explore}
|
||||||
href="/explore/mine"
|
href="/explore/mine"
|
||||||
linkClass={activeClass('my')}
|
linkClass={activeClass('my')}
|
||||||
|
@ -54,12 +60,6 @@ class Header extends Component {
|
||||||
data-router="true"
|
data-router="true"
|
||||||
text="Starred By Me"
|
text="Starred By Me"
|
||||||
/>
|
/>
|
||||||
<MapLink show={explore}
|
|
||||||
href={signedIn ? '/' : '/explore/active'}
|
|
||||||
linkClass={activeClass('active')}
|
|
||||||
data-router="true"
|
|
||||||
text="All Maps"
|
|
||||||
/>
|
|
||||||
<MapLink show={!signedIn && explore}
|
<MapLink show={!signedIn && explore}
|
||||||
href="/explore/featured"
|
href="/explore/featured"
|
||||||
linkClass={activeClass('featured')}
|
linkClass={activeClass('featured')}
|
||||||
|
|
38
frontend/src/components/NotificationIcon.js
Normal file
38
frontend/src/components/NotificationIcon.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
|
||||||
|
class NotificationIcon extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
let linkClasses = 'notificationsIcon upperRightEl upperRightIcon '
|
||||||
|
|
||||||
|
if (this.props.unreadNotificationsCount > 0) {
|
||||||
|
linkClasses += 'unread'
|
||||||
|
} else {
|
||||||
|
linkClasses += 'read'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a className={linkClasses} href="/notifications">
|
||||||
|
<div className="tooltipsUnder">
|
||||||
|
Notifications
|
||||||
|
</div>
|
||||||
|
{this.props.unreadNotificationsCount === 0 ? null : (
|
||||||
|
<div className="unread-notifications-dot"></div>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationIcon.propTypes = {
|
||||||
|
unreadNotificationsCount: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationIcon
|
|
@ -18,7 +18,6 @@ RSpec.describe 'mappings API', type: :request do
|
||||||
it 'GET /api/v2/mappings/:id' do
|
it 'GET /api/v2/mappings/:id' do
|
||||||
get "/api/v2/mappings/#{mapping.id}", params: { access_token: token }
|
get "/api/v2/mappings/#{mapping.id}", params: { access_token: token }
|
||||||
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(response).to match_json_schema(:mapping)
|
expect(response).to match_json_schema(:mapping)
|
||||||
expect(JSON.parse(response.body)['data']['id']).to eq mapping.id
|
expect(JSON.parse(response.body)['data']['id']).to eq mapping.id
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#t frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
# t frozen_string_literal: true
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'maps API', type: :request do
|
RSpec.describe 'maps API', type: :request do
|
||||||
|
@ -35,7 +36,7 @@ RSpec.describe 'maps API', type: :request do
|
||||||
expect(response).to match_json_schema(:map)
|
expect(response).to match_json_schema(:map)
|
||||||
expect(JSON.parse(response.body)['data']['id']).to eq map.id
|
expect(JSON.parse(response.body)['data']['id']).to eq map.id
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'POST /api/v2/maps' do
|
it 'POST /api/v2/maps' do
|
||||||
post '/api/v2/maps', params: { map: map.attributes, access_token: token }
|
post '/api/v2/maps', params: { map: map.attributes, access_token: token }
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ RSpec.describe 'topics API', type: :request do
|
||||||
it 'GET /api/v2/topics/:id' do
|
it 'GET /api/v2/topics/:id' do
|
||||||
get "/api/v2/topics/#{topic.id}"
|
get "/api/v2/topics/#{topic.id}"
|
||||||
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(response).to match_json_schema(:topic)
|
expect(response).to match_json_schema(:topic)
|
||||||
expect(JSON.parse(response.body)['data']['id']).to eq topic.id
|
expect(JSON.parse(response.body)['data']['id']).to eq topic.id
|
||||||
|
|
|
@ -53,9 +53,9 @@ RSpec.describe SynapsesController, type: :controller do
|
||||||
expect(response.status).to eq 422
|
expect(response.status).to eq 422
|
||||||
end
|
end
|
||||||
it 'does not create a synapse' do
|
it 'does not create a synapse' do
|
||||||
expect {
|
expect do
|
||||||
post :create, format: :json, params: { synapse: invalid_attributes }
|
post :create, format: :json, params: { synapse: invalid_attributes }
|
||||||
}.to change {
|
end.to change {
|
||||||
Synapse.count
|
Synapse.count
|
||||||
}.by 0
|
}.by 0
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe MapMailer, type: :mailer do
|
RSpec.describe MapMailer, type: :mailer do
|
||||||
describe 'access_request_email' do
|
describe 'access_request_email' do
|
||||||
let(:request) { create(:access_request) }
|
|
||||||
let(:map) { create(:map) }
|
let(:map) { create(:map) }
|
||||||
let(:mail) { described_class.access_request_email(request, map) }
|
let(:request) { create(:access_request, map: map) }
|
||||||
|
let(:mail) { described_class.access_request_email(request) }
|
||||||
|
|
||||||
it { expect(mail.from).to eq ['team@metamaps.cc'] }
|
it { expect(mail.from).to eq ['team@metamaps.cc'] }
|
||||||
it { expect(mail.to).to eq [map.user.email] }
|
it { expect(mail.to).to eq [map.user.email] }
|
||||||
|
|
|
@ -7,6 +7,11 @@ class MapMailerPreview < ActionMailer::Preview
|
||||||
|
|
||||||
def access_request_email
|
def access_request_email
|
||||||
request = AccessRequest.first
|
request = AccessRequest.first
|
||||||
MapMailer.access_request_email(request, request.map)
|
MapMailer.access_request_email(request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_approved_email
|
||||||
|
request = AccessRequest.first
|
||||||
|
MapMailer.access_approved_email(request)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe AccessRequest, type: :model do
|
RSpec.describe AccessRequest, type: :model do
|
||||||
include ActiveJob::TestHelper # enqueued_jobs
|
|
||||||
|
|
||||||
let(:access_request) { create(:access_request) }
|
let(:access_request) { create(:access_request) }
|
||||||
|
|
||||||
describe 'approve' do
|
describe 'approve' do
|
||||||
|
@ -13,7 +12,7 @@ RSpec.describe AccessRequest, type: :model do
|
||||||
it { expect(access_request.approved).to be true }
|
it { expect(access_request.approved).to be true }
|
||||||
it { expect(access_request.answered).to be true }
|
it { expect(access_request.answered).to be true }
|
||||||
it { expect(UserMap.count).to eq 1 }
|
it { expect(UserMap.count).to eq 1 }
|
||||||
it { expect(enqueued_jobs.count).to eq 1 }
|
it { expect(Mailboxer::Notification.count).to eq 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'deny' do
|
describe 'deny' do
|
||||||
|
@ -24,6 +23,6 @@ RSpec.describe AccessRequest, type: :model do
|
||||||
it { expect(access_request.approved).to be false }
|
it { expect(access_request.approved).to be false }
|
||||||
it { expect(access_request.answered).to be true }
|
it { expect(access_request.answered).to be true }
|
||||||
it { expect(UserMap.count).to eq 0 }
|
it { expect(UserMap.count).to eq 0 }
|
||||||
it { expect(enqueued_jobs.count).to eq 0 }
|
it { expect(Mailboxer::Notification.count).to eq 0 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue