Compare commits
4 commits
feature/to
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
98081097b4 | ||
|
f753392d49 | ||
|
38a209a970 | ||
|
bd7bf20810 |
2
Gemfile
|
@ -38,7 +38,7 @@ gem 'uglifier'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'brakeman', require: false
|
gem 'brakeman', require: false
|
||||||
gem 'factory_girl_rails'
|
gem 'factory_bot_rails'
|
||||||
gem 'json-schema'
|
gem 'json-schema'
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
gem 'shoulda-matchers'
|
gem 'shoulda-matchers'
|
||||||
|
|
|
@ -105,10 +105,10 @@ GEM
|
||||||
actionmailer (>= 4.0, < 6)
|
actionmailer (>= 4.0, < 6)
|
||||||
activesupport (>= 4.0, < 6)
|
activesupport (>= 4.0, < 6)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
factory_girl (4.8.0)
|
factory_bot (4.8.2)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
factory_girl_rails (4.8.0)
|
factory_bot_rails (4.8.2)
|
||||||
factory_girl (~> 4.8.0)
|
factory_bot (~> 4.8.2)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
faker (1.8.4)
|
faker (1.8.4)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
|
@ -311,7 +311,7 @@ DEPENDENCIES
|
||||||
doorkeeper
|
doorkeeper
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
exception_notification
|
exception_notification
|
||||||
factory_girl_rails
|
factory_bot_rails
|
||||||
faker
|
faker
|
||||||
httparty
|
httparty
|
||||||
jquery-rails
|
jquery-rails
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="document" viewBox="0 0 8 8">
|
|
||||||
<path d="M0 0v8h7v-4h-4v-4h-3zm4 0v3h3l-3-3zm-3 2h1v1h-1v-1zm0 2h1v1h-1v-1zm0 2h4v1h-4v-1z" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 218 B |
|
@ -1,3 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="file" viewBox="0 0 8 8">
|
|
||||||
<path d="M0 0v8h7v-4h-4v-4h-3zm4 0v3h3l-3-3z" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 168 B |
|
@ -1,3 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="image" viewBox="0 0 8 8">
|
|
||||||
<path d="M0 0v8h8v-8h-8zm1 1h6v3l-1-1-1 1 2 2v1h-1l-4-4-1 1v-3z" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 188 B |
|
@ -1,4 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="musical-note" viewBox="0 0 8 8">
|
|
||||||
<path d="M8 0c-5 0-6 1-6 1v4.093999999999999c-.154-.054-.327-.094-.5-.094-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5v-3.969c.732-.226 1.99-.438 4-.5v2.063c-.154-.054-.327-.094-.5-.094-.828 0-1.5.672-1.5 1.5s.672 1.5 1.5 1.5 1.5-.672 1.5-1.5v-5.5z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 394 B |
|
@ -1,4 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="8" height="8" data-icon="question-mark" data-container-transform="translate(2)" viewBox="0 0 8 8">
|
|
||||||
<path d="M4.469 0c-.854 0-1.48.256-1.875.656s-.54.901-.594 1.281l1 .125c.036-.26.125-.497.313-.688.188-.19.491-.375 1.156-.375.664 0 1.019.163 1.219.344.199.181.281.405.281.656 0 .833-.313 1.063-.813 1.5-.5.438-1.188 1.083-1.188 2.25v.25h1v-.25c0-.833.344-1.063.844-1.5.5-.438 1.156-1.083 1.156-2.25 0-.479-.168-1.02-.594-1.406-.426-.387-1.071-.594-1.906-.594zm-.5 7v1h1v-1h-1z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.8 KiB |
|
@ -13,13 +13,6 @@ Metamaps.ServerData['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.
|
||||||
Metamaps.ServerData['exploremaps_sprite.png'] = '<%= asset_path 'exploremaps_sprite.png' %>'
|
Metamaps.ServerData['exploremaps_sprite.png'] = '<%= asset_path 'exploremaps_sprite.png' %>'
|
||||||
Metamaps.ServerData['map_control_sprite.png'] = '<%= asset_path 'map_control_sprite.png' %>'
|
Metamaps.ServerData['map_control_sprite.png'] = '<%= asset_path 'map_control_sprite.png' %>'
|
||||||
Metamaps.ServerData['user_sprite.png'] = '<%= asset_path 'user_sprite.png' %>'
|
Metamaps.ServerData['user_sprite.png'] = '<%= asset_path 'user_sprite.png' %>'
|
||||||
Metamaps.ServerData.attachmentFileTypeIcons = {
|
|
||||||
pdf: '<%= asset_path('attachmentFileTypeIcons//open-iconic-document.svg') %>',
|
|
||||||
text: '<%= asset_path('attachmentFileTypeIcons//open-iconic-file.svg') %>',
|
|
||||||
image: '<%= asset_path('attachmentFileTypeIcons//open-iconic-image.svg') %>',
|
|
||||||
audio: '<%= asset_path('attachmentFileTypeIcons//open-iconic-musical-note.svg') %>',
|
|
||||||
unknown: '<%= asset_path('attachmentFileTypeIcons//open-iconic-question-mark.svg') %>'
|
|
||||||
}
|
|
||||||
Metamaps.ServerData.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
Metamaps.ServerData.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||||
Metamaps.ServerData.REALTIME_SERVER = '<%= ENV['REALTIME_SERVER'] %>'
|
Metamaps.ServerData.REALTIME_SERVER = '<%= ENV['REALTIME_SERVER'] %>'
|
||||||
Metamaps.ServerData.RAILS_ENV = '<%= ENV['RAILS_ENV'] %>'
|
Metamaps.ServerData.RAILS_ENV = '<%= ENV['RAILS_ENV'] %>'
|
||||||
|
|
|
@ -197,7 +197,6 @@ $mid-gray-opacity: rgba(66, 66, 66, 0.6);
|
||||||
|
|
||||||
.CardOnGraph .links {
|
.CardOnGraph .links {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #e0e0e0;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
.linkItem {
|
.linkItem {
|
||||||
|
@ -626,6 +625,156 @@ background-color: #E0E0E0;
|
||||||
z-index:100;
|
z-index:100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#embedlyLinkLoader {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CardOnGraph .link-adder {
|
||||||
|
width:100%;
|
||||||
|
height:47px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-adder a {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
margin-left: 40px;
|
||||||
|
padding-top:9px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addlink, #addupload {
|
||||||
|
display: inline-block;
|
||||||
|
width: 102px;
|
||||||
|
height: 12px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 18px 0 18px 48px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9e9e9e;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addlink:hover, #addupload:hover {
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachmentIcon {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
#linkIcon {
|
||||||
|
background-image: url(<%= asset_data_uri('link_sprite.png') %>);
|
||||||
|
}
|
||||||
|
#uploadIcon {
|
||||||
|
background-image: url(<%= asset_data_uri('upload_sprite.png') %>);
|
||||||
|
}
|
||||||
|
#addlink:hover #linkIcon, #addupload:hover #uploadIcon {
|
||||||
|
background-position: 0 -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addLink {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#addLinkInput {
|
||||||
|
height: 32px;
|
||||||
|
width: 268px;
|
||||||
|
padding: 8px 16px 8px 16px;
|
||||||
|
position: relative;
|
||||||
|
border: none;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addLinkInput input{
|
||||||
|
padding: 9px 27px 9px 31px;
|
||||||
|
height: 12px;
|
||||||
|
width: 210px;
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
background: white;
|
||||||
|
color: black;
|
||||||
|
font-family: 'din-regular', helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
#addLinkIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 20px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
background-image: url(<%= asset_data_uri('link_sprite.png') %>);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addLinkReset {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 15px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
float:none;
|
||||||
|
background-image: url(<%= asset_data_uri('remove.png') %>);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embeds.nonEmbedlyLink {
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#embedlyLink {
|
||||||
|
border-left: 8px solid #CCC;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px;
|
||||||
|
padding-left: 12px;
|
||||||
|
-moz-box-shadow: 1px 1px 5px 0 #ccc;
|
||||||
|
-webkit-box-shadow: 1px 1px 5px 0 #ccc;
|
||||||
|
box-shadow: 1px 1px 5px 0 #ccc;
|
||||||
|
-moz-border-radius-topright: 5px;
|
||||||
|
-webkit-border-top-right-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
-moz-border-radius-bottomright: 8px;
|
||||||
|
-webkit-border-bottom-right-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkActions {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CardOnGraph .embeds {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#linkremove {
|
||||||
|
background-image: url(<%= asset_data_uri 'remove.png' %>);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.cardSettings {
|
.cardSettings {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
|
|
|
@ -1,254 +0,0 @@
|
||||||
$attachment_button_size: 32px;
|
|
||||||
|
|
||||||
.attachments {
|
|
||||||
border-top: 1px solid #bdbdbd;
|
|
||||||
position: relative;
|
|
||||||
min-height: 3em;
|
|
||||||
|
|
||||||
.file {
|
|
||||||
margin-top: 0.75em;
|
|
||||||
max-width: 85%;
|
|
||||||
|
|
||||||
.filetype-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
padding: 0.5em;
|
|
||||||
padding-top: 0;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-audio-start,
|
|
||||||
.upload-file-dropzone,
|
|
||||||
.upload-photo-dropzone,
|
|
||||||
.CardOnGraph .attachment-type-chooser > div {
|
|
||||||
text-align: center;
|
|
||||||
color: #cccccc;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.photo-upload > div {
|
|
||||||
background-image: url(<%= asset_path('upload_icons/CameraIcons.png') %>);
|
|
||||||
}
|
|
||||||
&.link-upload > div {
|
|
||||||
background-image: url(<%= asset_path('upload_icons/LinkIcons.png') %>);
|
|
||||||
}
|
|
||||||
&.audio-upload > div {
|
|
||||||
background-image: url(<%= asset_path('upload_icons/MicIcons.png') %>);
|
|
||||||
}
|
|
||||||
&.file-upload > div {
|
|
||||||
background-image: url(<%= asset_path('upload_icons/CloudIcons.png') %>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-upload,
|
|
||||||
.link-upload,
|
|
||||||
.audio-upload,
|
|
||||||
.file-upload {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: $attachment_button_size $attachment_button_size;
|
|
||||||
background-position: 0 center;
|
|
||||||
width: $attachment_button_size;
|
|
||||||
height: $attachment_button_size;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
width: $attachment_button_size;
|
|
||||||
height: $attachment_button_size;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: $attachment_button_size;
|
|
||||||
background-size: $attachment_button_size;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-position: 0 $attachment_button_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-audio-start,
|
|
||||||
.upload-file-dropzone,
|
|
||||||
.upload-photo-dropzone {
|
|
||||||
padding-top: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-audio-recording {
|
|
||||||
font-size: small;
|
|
||||||
color: #aaa;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
$recording_button_size: 48px;
|
|
||||||
.upload-audio-stop {
|
|
||||||
background-image: url(<%= asset_path('recording-button.png') %>);
|
|
||||||
background-size: $recording_button_size;
|
|
||||||
width: $recording_button_size;
|
|
||||||
height: $recording_button_size;
|
|
||||||
margin: 0 auto;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CardOnGraph .attachment-type-chooser {
|
|
||||||
padding-top: .75em;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
display: inline-block;
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#embedlyLinkLoader {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CardOnGraph .link-chooser {
|
|
||||||
width:100%;
|
|
||||||
height:47px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-chooser a {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: block;
|
|
||||||
margin-left: 40px;
|
|
||||||
padding-top:9px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addlink, #addupload {
|
|
||||||
display: inline-block;
|
|
||||||
width: 102px;
|
|
||||||
height: 12px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 18px 0 18px 48px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9e9e9e;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addlink:hover, #addupload:hover {
|
|
||||||
color: #616161;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentIcon {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 0;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
left: 12px;
|
|
||||||
}
|
|
||||||
#linkIcon {
|
|
||||||
background-image: url(<%= asset_data_uri('link_sprite.png') %>);
|
|
||||||
}
|
|
||||||
#uploadIcon {
|
|
||||||
background-image: url(<%= asset_data_uri('upload_sprite.png') %>);
|
|
||||||
}
|
|
||||||
#addlink:hover #linkIcon, #addupload:hover #uploadIcon {
|
|
||||||
background-position: 0 -24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addLink {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addLinkInput {
|
|
||||||
height: 32px;
|
|
||||||
width: 268px;
|
|
||||||
padding: 8px 16px 8px 16px;
|
|
||||||
position: relative;
|
|
||||||
border: none;
|
|
||||||
line-height: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addLinkInput input{
|
|
||||||
padding: 9px 27px 9px 31px;
|
|
||||||
height: 12px;
|
|
||||||
width: 210px;
|
|
||||||
margin: 0 0 0 0;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 12px;
|
|
||||||
background: white;
|
|
||||||
color: black;
|
|
||||||
font-family: 'din-regular', helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
#addLinkIcon {
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
left: 20px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 0;
|
|
||||||
background-image: url(<%= asset_data_uri('link_sprite.png') %>);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment-cancel {
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
right: 15px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
cursor: pointer;
|
|
||||||
float:none;
|
|
||||||
background-image: url(<%= asset_data_uri('remove.png') %>);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embeds.nonEmbedlyLink {
|
|
||||||
padding-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#embedlyLink {
|
|
||||||
border-left: 8px solid #CCC;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 8px;
|
|
||||||
padding-left: 12px;
|
|
||||||
-moz-box-shadow: 1px 1px 5px 0 #ccc;
|
|
||||||
-webkit-box-shadow: 1px 1px 5px 0 #ccc;
|
|
||||||
box-shadow: 1px 1px 5px 0 #ccc;
|
|
||||||
-moz-border-radius-topright: 5px;
|
|
||||||
-webkit-border-top-right-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
-moz-border-radius-bottomright: 8px;
|
|
||||||
-webkit-border-bottom-right-radius: 8px;
|
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkActions {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CardOnGraph .embeds {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#linkremove {
|
|
||||||
background-image: url(<%= asset_data_uri 'remove.png' %>);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
right: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
module V2
|
|
||||||
class AttachmentsController < RestfulController
|
|
||||||
def searchable_columns
|
|
||||||
[:file]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,37 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AttachmentsController < ApplicationController
|
|
||||||
before_action :set_attachment, only: [:destroy]
|
|
||||||
after_action :verify_authorized
|
|
||||||
|
|
||||||
def create
|
|
||||||
@attachment = Attachment.new(attachment_params)
|
|
||||||
authorize @attachment
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
if @attachment.save
|
|
||||||
format.json { render json: @attachment, status: :created }
|
|
||||||
else
|
|
||||||
format.json { render json: @attachment.errors, status: :unprocessable_entity }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@attachment.destroy
|
|
||||||
respond_to do |format|
|
|
||||||
format.json { head :no_content }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_attachment
|
|
||||||
@attachment = Attachment.find(params[:id])
|
|
||||||
authorize @attachment
|
|
||||||
end
|
|
||||||
|
|
||||||
def attachment_params
|
|
||||||
params.require(:attachment).permit(:id, :file, :attachable_id, :attachable_type)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -42,7 +42,7 @@ class UsersController < ApplicationController
|
||||||
correct_pass = @user.valid_password?(params[:current_password])
|
correct_pass = @user.valid_password?(params[:current_password])
|
||||||
|
|
||||||
if correct_pass && @user.update_attributes(user_params)
|
if correct_pass && @user.update_attributes(user_params)
|
||||||
update_follow_settings(@user, params[:settings]) if is_tester(@user)
|
update_follow_settings(@user, params[:settings])
|
||||||
@user.image = nil if params[:remove_image] == '1'
|
@user.image = nil if params[:remove_image] == '1'
|
||||||
@user.save
|
@user.save
|
||||||
sign_in(@user, bypass: true)
|
sign_in(@user, bypass: true)
|
||||||
|
|
|
@ -15,9 +15,7 @@ class Attachment < ApplicationRecord
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
validates :attachable, presence: true
|
|
||||||
validates_attachment_content_type :file, content_type: Attachable.allowed_types
|
validates_attachment_content_type :file, content_type: Attachable.allowed_types
|
||||||
validates_attachment_size :file, :in => 0.megabytes..5.megabytes
|
|
||||||
|
|
||||||
def image?
|
def image?
|
||||||
Attachable.image_types.include?(file.instance.file_content_type)
|
Attachable.image_types.include?(file.instance.file_content_type)
|
||||||
|
|
|
@ -33,8 +33,7 @@ module Attachable
|
||||||
end
|
end
|
||||||
|
|
||||||
def audio_types
|
def audio_types
|
||||||
# .ogg files might be labelled as video
|
['audio/ogg', 'audio/mp3']
|
||||||
['audio/ogg', 'video/ogg', 'audio/mpeg', 'audio/wav', 'video/webm']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def text_types
|
def text_types
|
||||||
|
|
|
@ -69,23 +69,10 @@ class Topic < ApplicationRecord
|
||||||
Pundit.policy_scope(user, maps).map(&:id)
|
Pundit.policy_scope(user, maps).map(&:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def attachments_json
|
|
||||||
attachments.map do |a|
|
|
||||||
{
|
|
||||||
id: a.id,
|
|
||||||
file_name: a.file_file_name,
|
|
||||||
content_type: a.file_content_type,
|
|
||||||
file_size: a.file_file_size,
|
|
||||||
url: a.file.url
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(options = {})
|
def as_json(options = {})
|
||||||
super(methods: %i[user_name user_image collaborator_ids])
|
super(methods: %i[user_name user_image collaborator_ids])
|
||||||
.merge(inmaps: inmaps(options[:user]), inmapsLinks: inmaps_links(options[:user]),
|
.merge(inmaps: inmaps(options[:user]), inmapsLinks: inmaps_links(options[:user]),
|
||||||
map_count: map_count(options[:user]), synapse_count: synapse_count(options[:user]),
|
map_count: map_count(options[:user]), synapse_count: synapse_count(options[:user]))
|
||||||
attachments: attachments_json)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_rdf
|
def as_rdf
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class AttachmentPolicy < ApplicationPolicy
|
|
||||||
class Scope < Scope
|
|
||||||
def resolve
|
|
||||||
scope.where(attachable: TopicPolicy::Scope.new(user, Topic).resolve)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def index?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def create?
|
|
||||||
Pundit.policy(user, record.attachable).update?
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy?
|
|
||||||
Pundit.policy(user, record.attachable).update?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,14 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Api
|
|
||||||
module V2
|
|
||||||
class AttachmentSerializer < ApplicationSerializer
|
|
||||||
attributes :id,
|
|
||||||
:file,
|
|
||||||
:attachable_type,
|
|
||||||
:attachable_id,
|
|
||||||
:created_at,
|
|
||||||
:updated_at
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -14,8 +14,7 @@ module Api
|
||||||
def self.embeddable
|
def self.embeddable
|
||||||
{
|
{
|
||||||
user: {},
|
user: {},
|
||||||
metacode: {},
|
metacode: {}
|
||||||
attachments: {}
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ Metamaps::Application.routes.draw do
|
||||||
root to: 'main#home', via: :get
|
root to: 'main#home', via: :get
|
||||||
get 'request', to: 'main#requestinvite', as: :request
|
get 'request', to: 'main#requestinvite', as: :request
|
||||||
|
|
||||||
resources :attachments, only: %i[create destroy], shallow: true
|
|
||||||
|
|
||||||
namespace :explore do
|
namespace :explore do
|
||||||
get 'active'
|
get 'active'
|
||||||
get 'featured'
|
get 'featured'
|
||||||
|
@ -123,7 +121,6 @@ Metamaps::Application.routes.draw do
|
||||||
|
|
||||||
namespace :api, path: '/api', default: { format: :json } do
|
namespace :api, path: '/api', default: { format: :json } do
|
||||||
namespace :v2, path: '/v2' do
|
namespace :v2, path: '/v2' do
|
||||||
resources :attachments, only: %i[index show]
|
|
||||||
resources :metacodes, only: %i[index show]
|
resources :metacodes, only: %i[index show]
|
||||||
resources :mappings, only: %i[index create show update destroy]
|
resources :mappings, only: %i[index create show update destroy]
|
||||||
resources :maps, only: %i[index create show update destroy] do
|
resources :maps, only: %i[index create show update destroy] do
|
||||||
|
|
|
@ -26,13 +26,13 @@ At the time of writing, there are four directories in the spec folder. One,
|
||||||
`support`, is for helper functions. `rails_helper.rb` and `spec_helper.rb` are
|
`support`, is for helper functions. `rails_helper.rb` and `spec_helper.rb` are
|
||||||
also for helper functions.
|
also for helper functions.
|
||||||
|
|
||||||
`factories` is for a gem called [factory-girl][factory-girl]. This gem lets you
|
`factories` is for a gem called [factory-bot][factory_bot]. This gem lets you
|
||||||
use the `create` and `build` functions to quickly create the simplest possible
|
use the `create` and `build` functions to quickly create the simplest possible
|
||||||
valid version of a given model. For instance:
|
valid version of a given model. For instance:
|
||||||
|
|
||||||
let(:map1) { create :map }
|
let(:map1) { create :map }
|
||||||
let(:ronald) { create :user, name: "Ronald" }
|
let(:alex) { create :user, name: "Alex" }
|
||||||
let(:map2) { create :map, user: ronald }
|
let(:map2) { create :map, user: alex }
|
||||||
|
|
||||||
As you can see, you can also customize the factories. You can read the full
|
As you can see, you can also customize the factories. You can read the full
|
||||||
documentation at the link above or check the existing specs to see how it works.
|
documentation at the link above or check the existing specs to see how it works.
|
||||||
|
@ -53,5 +53,5 @@ the added code works. This will help in a few ways:
|
||||||
|
|
||||||
Happy testing!
|
Happy testing!
|
||||||
|
|
||||||
[factory-girl]: https://github.com/thoughtbot/factory_girl
|
[factory-bot]: https://github.com/thoughtbot/factory_bot
|
||||||
[rspec-docs]: http://rspec.info
|
[rspec-docs]: http://rspec.info
|
||||||
|
|
|
@ -22,7 +22,6 @@ traits:
|
||||||
searchable: !include traits/searchable.raml
|
searchable: !include traits/searchable.raml
|
||||||
|
|
||||||
schemas:
|
schemas:
|
||||||
attachment: !include schemas/_attachment.json
|
|
||||||
map: !include schemas/_map.json
|
map: !include schemas/_map.json
|
||||||
mapping: !include schemas/_mapping.json
|
mapping: !include schemas/_mapping.json
|
||||||
metacode: !include schemas/_metacode.json
|
metacode: !include schemas/_metacode.json
|
||||||
|
@ -36,7 +35,6 @@ schemas:
|
||||||
# item: !include resourceTypes/item.raml
|
# item: !include resourceTypes/item.raml
|
||||||
# collection: !include resourceTypes/collection.raml
|
# collection: !include resourceTypes/collection.raml
|
||||||
|
|
||||||
/attachments: !include apis/attachments.raml
|
|
||||||
/maps: !include apis/maps.raml
|
/maps: !include apis/maps.raml
|
||||||
/mappings: !include apis/mappings.raml
|
/mappings: !include apis/mappings.raml
|
||||||
/metacodes: !include apis/metacodes.raml
|
/metacodes: !include apis/metacodes.raml
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
get:
|
|
||||||
is: [ searchable: { searchFields: "file" }, orderable, pageable ]
|
|
||||||
securedBy: [ null, token, oauth_2_0 ]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
body:
|
|
||||||
application/json:
|
|
||||||
example: !include ../examples/attachments.json
|
|
||||||
/{id}:
|
|
||||||
get:
|
|
||||||
securedBy: [ null, token, oauth_2_0 ]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
body:
|
|
||||||
application/json:
|
|
||||||
example: !include ../examples/attachment.json
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": 1,
|
|
||||||
"file": "https://example.org/file.png",
|
|
||||||
"attachable_type": "Topic",
|
|
||||||
"attachable_id": 187,
|
|
||||||
"created_at": "2017-03-01T05:48:09.533Z",
|
|
||||||
"updated_at": "2017-03-01T05:48:09.533Z"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"file": "https://example.org/file.png",
|
|
||||||
"attachable_type": "Topic",
|
|
||||||
"attachable_id": 187,
|
|
||||||
"created_at": "2017-03-01T05:48:09.533Z",
|
|
||||||
"updated_at": "2017-03-01T05:48:09.533Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"file": "https://example.org/file.docx",
|
|
||||||
"attachable_type": "Message",
|
|
||||||
"attachable_id": 1043,
|
|
||||||
"created_at": "2017-03-01T05:50:19.533Z",
|
|
||||||
"updated_at": "2017-03-01T05:50:19.533Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"page": {
|
|
||||||
"current_page": 1,
|
|
||||||
"next_page": 2,
|
|
||||||
"prev_page": 0,
|
|
||||||
"total_pages": 156,
|
|
||||||
"total_count": 312,
|
|
||||||
"per": 2
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Attachment",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"$ref": "_id.json"
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"format": "uri",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"attachable_type": {
|
|
||||||
"pattern": "(Topic|Message)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"attachable_id": {
|
|
||||||
"$ref": "_id.json"
|
|
||||||
},
|
|
||||||
"created_at": {
|
|
||||||
"$ref": "_datetimestamp.json"
|
|
||||||
},
|
|
||||||
"updated_at": {
|
|
||||||
"$ref": "_datetimestamp.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"file",
|
|
||||||
"attachable_type",
|
|
||||||
"attachable_id",
|
|
||||||
"created_at",
|
|
||||||
"updated_at"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Attachment Envelope",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "_attachment.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"data"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Attachments",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "_attachment.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"page": {
|
|
||||||
"$ref": "_page.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"data",
|
|
||||||
"page"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -37,14 +37,12 @@ const ReactApp = {
|
||||||
mobileTitle: '',
|
mobileTitle: '',
|
||||||
mobileTitleWidth: 0,
|
mobileTitleWidth: 0,
|
||||||
metacodeSets: [],
|
metacodeSets: [],
|
||||||
attachmentFileTypeIcons: {},
|
|
||||||
init: function(serverData, openLightbox) {
|
init: function(serverData, openLightbox) {
|
||||||
const self = ReactApp
|
const self = ReactApp
|
||||||
self.serverData = serverData
|
self.serverData = serverData
|
||||||
self.mobileTitle = serverData.mobileTitle
|
self.mobileTitle = serverData.mobileTitle
|
||||||
self.openLightbox = openLightbox
|
self.openLightbox = openLightbox
|
||||||
self.metacodeSets = serverData.metacodeSets
|
self.metacodeSets = serverData.metacodeSets
|
||||||
self.attachmentFileTypeIcons = serverData.attachmentFileTypeIcons
|
|
||||||
routes = makeRoutes(serverData.ActiveMapper)
|
routes = makeRoutes(serverData.ActiveMapper)
|
||||||
self.resize()
|
self.resize()
|
||||||
window && window.addEventListener('resize', self.resize)
|
window && window.addEventListener('resize', self.resize)
|
||||||
|
@ -156,13 +154,10 @@ const ReactApp = {
|
||||||
getTopicCardProps: function() {
|
getTopicCardProps: function() {
|
||||||
const self = ReactApp
|
const self = ReactApp
|
||||||
return {
|
return {
|
||||||
metacodeSets: self.metacodeSets,
|
|
||||||
onTopicFollow: Topic.onTopicFollow,
|
|
||||||
openTopic: TopicCard.openTopic,
|
openTopic: TopicCard.openTopic,
|
||||||
|
metacodeSets: self.metacodeSets,
|
||||||
updateTopic: (topic, obj) => topic.save(obj),
|
updateTopic: (topic, obj) => topic.save(obj),
|
||||||
uploadAttachment: TopicCard.uploadAttachment,
|
onTopicFollow: Topic.onTopicFollow
|
||||||
removeAttachment: TopicCard.removeAttachment,
|
|
||||||
fileTypeIcons: self.attachmentFileTypeIcons
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getContextMenuProps: function() {
|
getContextMenuProps: function() {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
import { ReactApp } from '../GlobalUI'
|
import { ReactApp } from '../GlobalUI'
|
||||||
|
|
||||||
const TopicCard = {
|
const TopicCard = {
|
||||||
|
@ -11,56 +9,6 @@ const TopicCard = {
|
||||||
hideCard: function() {
|
hideCard: function() {
|
||||||
TopicCard.openTopic = null
|
TopicCard.openTopic = null
|
||||||
ReactApp.render()
|
ReactApp.render()
|
||||||
},
|
|
||||||
uploadAttachment: (topic, file) => {
|
|
||||||
const data = new window.FormData()
|
|
||||||
data.append('attachment[file]', file)
|
|
||||||
data.append('attachment[attachable_type]', 'Topic')
|
|
||||||
data.append('attachment[attachable_id]', topic.id)
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
$.ajax({
|
|
||||||
url: '/attachments',
|
|
||||||
type: 'POST',
|
|
||||||
data,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
success: (data) => {
|
|
||||||
console.log('file upolad success', data)
|
|
||||||
topic.fetch({ success: () => {
|
|
||||||
ReactApp.render()
|
|
||||||
resolve(true)
|
|
||||||
}})
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
console.error(error)
|
|
||||||
window.alert('File upload failed')
|
|
||||||
topic.fetch({ success: () => {
|
|
||||||
ReactApp.render()
|
|
||||||
resolve(false)
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removeAttachment: (topic) => {
|
|
||||||
const attachments = topic.get('attachments')
|
|
||||||
if (!attachments || attachments.length < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: `/attachments/${attachments[0].id}`,
|
|
||||||
type: 'DELETE',
|
|
||||||
success: () => {
|
|
||||||
console.log('delete success, syncing topic')
|
|
||||||
topic.fetch({ success: () => ReactApp.render() })
|
|
||||||
},
|
|
||||||
error: error => {
|
|
||||||
console.error(error)
|
|
||||||
window.alert('Failed to remove attachment')
|
|
||||||
topic.fetch({ success: () => ReactApp.render() })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,101 +1,20 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import EmbedlyLinkChooser from './EmbedlyLinkChooser'
|
import EmbedlyLink from './EmbedlyLink'
|
||||||
import EmbedlyCard from './EmbedlyCard'
|
|
||||||
import FileUploader from './FileUploader'
|
|
||||||
import PhotoUploader from './PhotoUploader'
|
|
||||||
import AudioUploader from './AudioUploader'
|
|
||||||
import FileAttachment from './FileAttachment'
|
|
||||||
|
|
||||||
class Attachments extends Component {
|
class Attachments extends Component {
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = this.defaultState
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultState = {
|
|
||||||
addingPhoto: false,
|
|
||||||
addingLink: false,
|
|
||||||
addingAudio: false,
|
|
||||||
addingFile: false
|
|
||||||
}
|
|
||||||
|
|
||||||
clearState = () => {
|
|
||||||
this.setState(this.defaultState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// onClick handler for the 4 buttons, which triggers showing the proper uploader
|
|
||||||
choose = key => () => {
|
|
||||||
this.setState(Object.assign({}, this.defaultState, { [key]: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const { topic, authorizedToEdit, updateTopic } = this.props
|
const { topic, authorizedToEdit, updateTopic } = this.props
|
||||||
const link = topic.get('link')
|
const link = topic.get('link')
|
||||||
const attachments = topic.get('attachments')
|
|
||||||
const file = attachments && attachments.length ? attachments[0] : null
|
|
||||||
|
|
||||||
let childComponent
|
|
||||||
if (link) {
|
|
||||||
childComponent = (
|
|
||||||
<EmbedlyCard link={link}
|
|
||||||
authorizedToEdit={authorizedToEdit}
|
|
||||||
removeLink={this.clearState}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (file) {
|
|
||||||
childComponent = (
|
|
||||||
<FileAttachment file={file}
|
|
||||||
authorizedToEdit={authorizedToEdit}
|
|
||||||
removeAttachment={this.props.removeAttachment}
|
|
||||||
fileTypeIcons={this.props.fileTypeIcons}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (!authorizedToEdit) {
|
|
||||||
childComponent = null
|
|
||||||
} else if (this.state.addingPhoto) {
|
|
||||||
childComponent = (
|
|
||||||
<PhotoUploader updateTopic={updateTopic}
|
|
||||||
uploadAttachment={this.props.uploadAttachment}
|
|
||||||
cancel={this.clearState}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (this.state.addingLink) {
|
|
||||||
childComponent = (
|
|
||||||
<EmbedlyLinkChooser updateTopic={updateTopic}
|
|
||||||
cancel={this.clearState}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (this.state.addingAudio) {
|
|
||||||
childComponent = (
|
|
||||||
<AudioUploader updateTopic={updateTopic}
|
|
||||||
uploadAttachment={this.props.uploadAttachment}
|
|
||||||
cancel={this.clearState}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (this.state.addingFile) {
|
|
||||||
childComponent = (
|
|
||||||
<FileUploader updateTopic={updateTopic}
|
|
||||||
uploadAttachment={this.props.uploadAttachment}
|
|
||||||
cancel={this.clearState}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
childComponent = (
|
|
||||||
<div className="attachment-type-chooser">
|
|
||||||
<div className="photo-upload"><div onClick={this.choose('addingPhoto')}>Photo</div></div>
|
|
||||||
<div className="link-upload"><div onClick={this.choose('addingLink')}>Link</div></div>
|
|
||||||
<div className="audio-upload"><div onClick={this.choose('addingAudio')}>Audio</div></div>
|
|
||||||
<div className="file-upload"><div onClick={this.choose('addingFile')}>Upload</div></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="attachments">
|
<div className="attachments">
|
||||||
{childComponent}
|
<EmbedlyLink topicId={topic.id}
|
||||||
|
link={link}
|
||||||
|
authorizedToEdit={authorizedToEdit}
|
||||||
|
updateTopic={updateTopic}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -104,10 +23,7 @@ class Attachments extends Component {
|
||||||
Attachments.propTypes = {
|
Attachments.propTypes = {
|
||||||
topic: PropTypes.object, // Backbone object
|
topic: PropTypes.object, // Backbone object
|
||||||
authorizedToEdit: PropTypes.bool,
|
authorizedToEdit: PropTypes.bool,
|
||||||
updateTopic: PropTypes.func,
|
updateTopic: PropTypes.func
|
||||||
uploadAttachment: PropTypes.func,
|
|
||||||
removeAttachment: PropTypes.func,
|
|
||||||
fileTypeIcons: PropTypes.objectOf(PropTypes.string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Attachments
|
export default Attachments
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
|
|
||||||
import Recorder from 'react-recorder'
|
|
||||||
|
|
||||||
class AudioUploader extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
command: 'none'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeLimit30sTimeoutId = null
|
|
||||||
|
|
||||||
enforce30sTimeLimit = cmd => {
|
|
||||||
window.clearTimeout(this.timeLimit30sTimeoutId)
|
|
||||||
if (cmd === 'start') {
|
|
||||||
this.timeLimit30sTimeoutId = window.setTimeout(() => {
|
|
||||||
this.command('stop')()
|
|
||||||
}, 30000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command = cmd => () => {
|
|
||||||
this.enforce30sTimeLimit(cmd)
|
|
||||||
this.setState({ command: cmd })
|
|
||||||
}
|
|
||||||
|
|
||||||
onStop = blob => {
|
|
||||||
const now = new Date()
|
|
||||||
const date = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}-${now.getHours()}:${now.getMinutes()}`
|
|
||||||
const filename = `metamaps-recorded-audio-${date}.wav`
|
|
||||||
const file = new window.File([blob], filename, { lastModifiedDate: now })
|
|
||||||
|
|
||||||
this.props.uploadAttachment(file).then(success => {
|
|
||||||
if (!success) {
|
|
||||||
this.command('none')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRecordingError = () => {
|
|
||||||
window.alert(`Audio recording failed. Some possible reasons include:
|
|
||||||
not using an HTTPS connection,
|
|
||||||
missing microphone,
|
|
||||||
you haven't allowed your browser access to your microphone,
|
|
||||||
or you need to reload the page.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="audio-uploader">
|
|
||||||
<Recorder command={this.state.command}
|
|
||||||
onStop={this.onStop}
|
|
||||||
onError={this.handleRecordingError}
|
|
||||||
/>
|
|
||||||
{this.state.command === 'start' && (
|
|
||||||
<div className="upload-audio-recording">
|
|
||||||
<div className="stop upload-audio-stop" onClick={this.command('stop')} />
|
|
||||||
<div className="upload-audio-recording-text"> Recording...</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.state.command === 'none' && (
|
|
||||||
<div className="start upload-audio-start" onClick={this.command('start')}>
|
|
||||||
Click to record <br />
|
|
||||||
(max 30 seconds)
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="attachment-cancel" onClick={this.props.cancel} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioUploader.propTypes = {
|
|
||||||
uploadAttachment: PropTypes.func,
|
|
||||||
cancel: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AudioUploader
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global embedly */
|
/* global $, embedly */
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
@ -38,33 +38,29 @@ class EmbedlyCard extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
|
const { link } = this.props
|
||||||
const { embedlyLinkLoaded, embedlyLinkStarted, embedlyLinkError } = this.state
|
const { embedlyLinkLoaded, embedlyLinkStarted, embedlyLinkError } = this.state
|
||||||
|
|
||||||
const notReady = embedlyLinkStarted && !embedlyLinkLoaded && !embedlyLinkError
|
const notReady = embedlyLinkStarted && !embedlyLinkLoaded && !embedlyLinkError
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="embeds">
|
<div>
|
||||||
<a style={{ display: notReady ? 'none' : 'block' }}
|
<a style={{ display: notReady ? 'none' : 'block' }}
|
||||||
href={this.props.link}
|
href={link}
|
||||||
id="embedlyLink"
|
id="embedlyLink"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
data-card-description="0"
|
data-card-description="0"
|
||||||
>
|
>
|
||||||
{this.props.link}
|
{link}
|
||||||
</a>
|
</a>
|
||||||
{notReady && <div id="embedlyLinkLoader">loading...</div>}
|
{notReady && <div id="embedlyLinkLoader">loading...</div>}
|
||||||
{this.props.authorizedToEdit && (
|
|
||||||
<div id="linkremove" onClick={this.props.removeLink} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbedlyCard.propTypes = {
|
EmbedlyCard.propTypes = {
|
||||||
link: PropTypes.string,
|
link: PropTypes.string
|
||||||
authorizedToEdit: PropTypes.bool,
|
|
||||||
removeLink: PropTypes.func
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EmbedlyCard
|
export default EmbedlyCard
|
|
@ -1,7 +1,10 @@
|
||||||
|
/* global embedly */
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
class EmbedlyLinkChooser extends Component {
|
import Card from './Card'
|
||||||
|
|
||||||
|
class EmbedlyLink extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
@ -10,9 +13,12 @@ class EmbedlyLinkChooser extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeLink = () => {
|
||||||
|
this.props.updateTopic({ link: null })
|
||||||
|
}
|
||||||
|
|
||||||
resetLink = () => {
|
resetLink = () => {
|
||||||
this.setState({ linkEdit: '' })
|
this.setState({ linkEdit: '' })
|
||||||
this.props.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkChangeHandler = e => {
|
onLinkChangeHandler = e => {
|
||||||
|
@ -29,11 +35,17 @@ class EmbedlyLinkChooser extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
|
const { link, authorizedToEdit, topicId } = this.props
|
||||||
const { linkEdit } = this.state
|
const { linkEdit } = this.state
|
||||||
|
const hasAttachment = !!link
|
||||||
|
|
||||||
|
if (!hasAttachment && !authorizedToEdit) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="link-chooser">
|
<div className={hasAttachment ? 'embeds' : 'link-adder'}>
|
||||||
<div className="addLink">
|
<div className="addLink"
|
||||||
|
style={{ display: hasAttachment ? 'none' : 'block' }}
|
||||||
|
>
|
||||||
<div id="addLinkIcon"></div>
|
<div id="addLinkIcon"></div>
|
||||||
<div id="addLinkInput">
|
<div id="addLinkInput">
|
||||||
<input ref={input => (this.linkInput = input)}
|
<input ref={input => (this.linkInput = input)}
|
||||||
|
@ -41,17 +53,26 @@ class EmbedlyLinkChooser extends Component {
|
||||||
value={linkEdit}
|
value={linkEdit}
|
||||||
onChange={this.onLinkChangeHandler}
|
onChange={this.onLinkChangeHandler}
|
||||||
onKeyUp={this.onLinkKeyUpHandler}></input>
|
onKeyUp={this.onLinkKeyUpHandler}></input>
|
||||||
<div className="attachment-cancel" onClick={this.resetLink}></div>
|
{linkEdit && <div id="addLinkReset" onClick={this.resetLink}></div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{link && <Card key={topicId} link={link} />}
|
||||||
|
{authorizedToEdit && (
|
||||||
|
<div id="linkremove"
|
||||||
|
style={{ display: hasAttachment ? 'block' : 'none' }}
|
||||||
|
onClick={this.removeLink}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbedlyLinkChooser.propTypes = {
|
EmbedlyLink.propTypes = {
|
||||||
updateTopic: PropTypes.func,
|
topicId: PropTypes.number,
|
||||||
cancel: PropTypes.func
|
link: PropTypes.string,
|
||||||
|
authorizedToEdit: PropTypes.bool,
|
||||||
|
updateTopic: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EmbedlyLinkChooser
|
export default EmbedlyLink
|
|
@ -1,57 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
|
|
||||||
class FileAttachment extends Component {
|
|
||||||
getFileType = contentType => {
|
|
||||||
if (contentType === 'text/plain') {
|
|
||||||
return 'text'
|
|
||||||
} else if (contentType === 'application/pdf') {
|
|
||||||
return 'pdf'
|
|
||||||
} else if (contentType.match(/^image\//)) {
|
|
||||||
return 'image'
|
|
||||||
} else if (contentType.match(/^audio\//) ||
|
|
||||||
contentType === 'video/ogg' ||
|
|
||||||
contentType === 'video/webm') {
|
|
||||||
return 'audio'
|
|
||||||
} else {
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getFileIcon = file => {
|
|
||||||
const type = this.getFileType(file.content_type)
|
|
||||||
|
|
||||||
if (this.props.fileTypeIcons[type]) {
|
|
||||||
return this.props.fileTypeIcons[type]
|
|
||||||
} else {
|
|
||||||
return this.props.fileTypeIcons.unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { file } = this.props
|
|
||||||
return (
|
|
||||||
<div className={`file ${this.getFileType(file.content_type)}-file-type`}
|
|
||||||
style={{ clear: 'both' }}
|
|
||||||
>
|
|
||||||
<a href={file.url} target="_blank">
|
|
||||||
<img src={this.getFileIcon(file)} className="filetype-icon" />
|
|
||||||
{file.file_name}
|
|
||||||
</a>
|
|
||||||
<div className="attachment-cancel" onClick={this.props.removeAttachment} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileAttachment.propTypes = {
|
|
||||||
file: PropTypes.shape({
|
|
||||||
content_type: PropTypes.string,
|
|
||||||
file_name: PropTypes.string,
|
|
||||||
url: PropTypes.string
|
|
||||||
}),
|
|
||||||
authorizedToEdit: PropTypes.bool,
|
|
||||||
removeAttachment: PropTypes.func,
|
|
||||||
fileTypeIcons: PropTypes.objectOf(PropTypes.string)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FileAttachment
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
import Dropzone from 'react-dropzone'
|
|
||||||
|
|
||||||
class FileUploader extends Component {
|
|
||||||
handleFileUpload = (acceptedFiles, rejectedFiles) => {
|
|
||||||
if (acceptedFiles.length >= 1) {
|
|
||||||
this.props.uploadAttachment(acceptedFiles[0])
|
|
||||||
} else {
|
|
||||||
window.alert('File upload failed, please try again.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="upload-file">
|
|
||||||
<Dropzone className="upload-file-dropzone"
|
|
||||||
onDrop={this.handleFileUpload}
|
|
||||||
>
|
|
||||||
Drag file here <br />
|
|
||||||
(maximum 5mb)
|
|
||||||
</Dropzone>
|
|
||||||
<div className="attachment-cancel" onClick={this.props.cancel} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileUploader.propTypes = {
|
|
||||||
updateTopic: PropTypes.func,
|
|
||||||
uploadAttachment: PropTypes.func,
|
|
||||||
cancel: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FileUploader
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
import Dropzone from 'react-dropzone'
|
|
||||||
|
|
||||||
class PhotoUploader extends Component {
|
|
||||||
handleFileUpload = (acceptedFiles, rejectedFiles) => {
|
|
||||||
if (acceptedFiles.length >= 1) {
|
|
||||||
this.props.uploadAttachment(acceptedFiles[0])
|
|
||||||
} else {
|
|
||||||
window.alert('File upload failed, please try again.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="upload-photo">
|
|
||||||
<Dropzone className="upload-photo-dropzone"
|
|
||||||
onDrop={this.handleFileUpload}
|
|
||||||
>
|
|
||||||
Drag photo here <br />
|
|
||||||
or click to upload
|
|
||||||
</Dropzone>
|
|
||||||
<div className="attachment-cancel" onClick={this.props.cancel} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PhotoUploader.propTypes = {
|
|
||||||
updateTopic: PropTypes.func,
|
|
||||||
uploadAttachment: PropTypes.func,
|
|
||||||
cancel: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PhotoUploader
|
|
|
@ -10,17 +10,12 @@ import Info from './Info'
|
||||||
|
|
||||||
class ReactTopicCard extends Component {
|
class ReactTopicCard extends Component {
|
||||||
render = () => {
|
render = () => {
|
||||||
const {
|
const { currentUser, onTopicFollow, updateTopic } = this.props
|
||||||
currentUser, onTopicFollow, updateTopic, uploadAttachment,
|
|
||||||
removeAttachment
|
|
||||||
} = this.props
|
|
||||||
const topic = this.props.openTopic
|
const topic = this.props.openTopic
|
||||||
|
|
||||||
if (!topic) return null
|
if (!topic) return null
|
||||||
|
|
||||||
const wrappedUpdateTopic = obj => updateTopic(topic, obj)
|
const wrappedUpdateTopic = obj => updateTopic(topic, obj)
|
||||||
const wrappedUploadAttachment = file => uploadAttachment(topic, file)
|
|
||||||
const wrappedRemoveAttachment = () => removeAttachment(topic)
|
|
||||||
|
|
||||||
const authorizedToEdit = topic.authorizeToEdit(currentUser)
|
const authorizedToEdit = topic.authorizeToEdit(currentUser)
|
||||||
const hasAttachment = topic.get('link') && topic.get('link') !== ''
|
const hasAttachment = topic.get('link') && topic.get('link') !== ''
|
||||||
|
@ -53,13 +48,9 @@ class ReactTopicCard extends Component {
|
||||||
authorizedToEdit={authorizedToEdit}
|
authorizedToEdit={authorizedToEdit}
|
||||||
onChange={wrappedUpdateTopic}
|
onChange={wrappedUpdateTopic}
|
||||||
/>
|
/>
|
||||||
<Attachments key={topic.id}
|
<Attachments topic={topic}
|
||||||
topic={topic}
|
|
||||||
authorizedToEdit={authorizedToEdit}
|
authorizedToEdit={authorizedToEdit}
|
||||||
updateTopic={wrappedUpdateTopic}
|
updateTopic={wrappedUpdateTopic}
|
||||||
uploadAttachment={wrappedUploadAttachment}
|
|
||||||
removeAttachment={wrappedRemoveAttachment}
|
|
||||||
fileTypeIcons={this.props.fileTypeIcons}
|
|
||||||
/>
|
/>
|
||||||
<Info topic={topic} />
|
<Info topic={topic} />
|
||||||
<div className='clearfloat' />
|
<div className='clearfloat' />
|
||||||
|
@ -84,9 +75,7 @@ ReactTopicCard.propTypes = {
|
||||||
name: PropTypes.string
|
name: PropTypes.string
|
||||||
}))
|
}))
|
||||||
})),
|
})),
|
||||||
redrawCanvas: PropTypes.func,
|
redrawCanvas: PropTypes.func
|
||||||
uploadAttachment: PropTypes.func,
|
|
||||||
fileTypeIcons: PropTypes.objectOf(PropTypes.string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReactTopicCard
|
export default ReactTopicCard
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
"react-draggable": "3.0.3",
|
"react-draggable": "3.0.3",
|
||||||
"react-dropzone": "4.1.2",
|
"react-dropzone": "4.1.2",
|
||||||
"react-onclickoutside": "6.5.0",
|
"react-onclickoutside": "6.5.0",
|
||||||
"react-recorder": "1.0.0",
|
|
||||||
"react-router": "3.0.5",
|
"react-router": "3.0.5",
|
||||||
"redux": "3.7.2",
|
"redux": "3.7.2",
|
||||||
"riek": "1.1.0",
|
"riek": "1.1.0",
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'topics API', type: :request do
|
|
||||||
let(:user) { create(:user, admin: true) }
|
|
||||||
let(:token) { create(:token, user: user).token }
|
|
||||||
let(:attachment) { create(:attachment) }
|
|
||||||
|
|
||||||
it 'GET /api/v2/attachments' do
|
|
||||||
create_list(:attachment, 5)
|
|
||||||
get '/api/v2/attachments', params: { access_token: token }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
expect(response).to match_json_schema(:attachments)
|
|
||||||
expect(JSON.parse(response.body)['data'].count).to eq 5
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'GET /api/v2/attachments/:id' do
|
|
||||||
get "/api/v2/attachments/#{attachment.id}"
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
expect(response).to match_json_schema(:attachment)
|
|
||||||
expect(JSON.parse(response.body)['data']['id']).to eq attachment.id
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'RAML example' do
|
|
||||||
let(:resource) { get_json_example(:attachment) }
|
|
||||||
let(:collection) { get_json_example(:attachments) }
|
|
||||||
|
|
||||||
it 'resource matches schema' do
|
|
||||||
expect(resource).to match_json_schema(:attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'collection matches schema' do
|
|
||||||
expect(collection).to match_json_schema(:attachments)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :access_request do
|
factory :access_request do
|
||||||
map
|
map
|
||||||
user
|
user
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
FactoryGirl.define do
|
|
||||||
factory :attachment do
|
|
||||||
association :attachable, factory: :topic
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :mapping do
|
factory :mapping do
|
||||||
xloc 0
|
xloc 0
|
||||||
yloc 0
|
yloc 0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :map do
|
factory :map do
|
||||||
sequence(:name) { |n| "Cool Map ##{n}" }
|
sequence(:name) { |n| "Cool Map ##{n}" }
|
||||||
permission :commons
|
permission :commons
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :message do
|
factory :message do
|
||||||
association :resource, factory: :map
|
association :resource, factory: :map
|
||||||
user
|
user
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :metacode do
|
factory :metacode do
|
||||||
sequence(:name) { |n| "Cool Metacode ##{n}" }
|
sequence(:name) { |n| "Cool Metacode ##{n}" }
|
||||||
manual_icon 'https://images.com/image.png'
|
manual_icon 'https://images.com/image.png'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :star do
|
factory :star do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :synapse do
|
factory :synapse do
|
||||||
sequence(:desc) { |n| "Cool synapse ##{n}" }
|
sequence(:desc) { |n| "Cool synapse ##{n}" }
|
||||||
category :'from-to'
|
category :'from-to'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :token do
|
factory :token do
|
||||||
user
|
user
|
||||||
description ''
|
description ''
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :topic do
|
factory :topic do
|
||||||
user
|
user
|
||||||
association :updated_by, factory: :user
|
association :updated_by, factory: :user
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :user_map do
|
factory :user_map do
|
||||||
map
|
map
|
||||||
user
|
user
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
# have actual codes, you'll need to specify one simple_user and then you
|
# have actual codes, you'll need to specify one simple_user and then you
|
||||||
# can specify other :code_user users based on the pre-existing user's code.
|
# can specify other :code_user users based on the pre-existing user's code.
|
||||||
|
|
||||||
FactoryGirl.define do
|
FactoryBot.define do
|
||||||
factory :code_user, class: User do
|
factory :code_user, class: User do
|
||||||
sequence(:name) { |n| "Cool User ##{n}" }
|
sequence(:name) { |n| "Cool User ##{n}" }
|
||||||
sequence(:email) { |n| "cooluser#{n}@cooldomain.com" }
|
sequence(:email) { |n| "cooluser#{n}@cooldomain.com" }
|
||||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe MapPolicy, type: :policy do
|
||||||
context 'private' do
|
context 'private' do
|
||||||
let(:map) { create(:map, permission: :private) }
|
let(:map) { create(:map, permission: :private) }
|
||||||
permissions :show?, :create?, :update?, :destroy? do
|
permissions :show?, :create?, :update?, :destroy? do
|
||||||
it 'denies access' do
|
it 'permits access' do
|
||||||
expect(subject).to_not permit(nil, map)
|
expect(subject).to_not permit(nil, map)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# lets you type create(:user) instead of FactoryGirl.create(:user)
|
# lets you type create(:user) instead of FactoryBot.create(:user)
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.include FactoryGirl::Syntax::Methods
|
config.include FactoryBot::Syntax::Methods
|
||||||
end
|
end
|
||||||
|
|