loft 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +34 -21
- data/app/assets/images/loft/library@3x.png +0 -0
- data/app/assets/javascripts/chr/form/input-loft-image.coffee +87 -0
- data/app/assets/javascripts/chr/loft/asset-item.coffee +27 -8
- data/app/assets/javascripts/chr/loft/group-actions.coffee +56 -15
- data/app/assets/javascripts/chr/loft/module.coffee +76 -12
- data/app/assets/javascripts/chr/loft/type-item.coffee +37 -0
- data/app/assets/javascripts/chr/redactor/loft.coffee +67 -0
- data/app/assets/javascripts/loft.coffee +6 -0
- data/app/assets/stylesheets/_loft.scss +208 -71
- data/app/assets/stylesheets/chr/form/_input-loft-image.scss +11 -0
- data/lib/{uploaders → concerns}/asset_file_uploader.rb +7 -3
- data/lib/loft.rb +1 -1
- data/lib/loft/version.rb +1 -1
- data/lib/mongoid/loft_asset.rb +4 -4
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb9743cdf74ccbaeba6c950df7dd300cbf01366e
|
4
|
+
data.tar.gz: 4c0eab2a4a70f0b97040990532c2d109abd41fb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e89aaeae7d1f9a4a8de33b35558b5302ac667254bb2c529764c5f8a9cb51e952e010ed678618e0199a78c8d27820f693d689381379fad26828e9e19a1ec060f7
|
7
|
+
data.tar.gz: 8d9d873ea22d68690893052c4bb81c69beaf01179819d3b308625ae1ca3395e1977af1f15537f6e50307d4b99d3331a8ea13ec199f68fbac4e0af4b56abc076f
|
data/.gitignore
ADDED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Loft
|
2
2
|
|
3
|
-
|
3
|
+
*Media assets manager for [Character CMS](https://github.com/slate-studio/chr).*
|
4
|
+
|
4
5
|
|
5
6
|
### Installation
|
6
7
|
|
@@ -10,46 +11,58 @@ Add to ```Gemfile```:
|
|
10
11
|
|
11
12
|
Setup a new model for assets ```asset.rb```:
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
```ruby
|
15
|
+
class Asset
|
16
|
+
include Mongoid::Document
|
17
|
+
include Mongoid::Timestamps
|
18
|
+
include Mongoid::SerializableId
|
19
|
+
include Mongoid::LoftAsset
|
20
|
+
end
|
21
|
+
```
|
19
22
|
|
20
23
|
Add controller for asset model to make it accesible via CMS, e.g. ```app/controllers/admin/assets_controller.rb```:
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
```ruby
|
26
|
+
class Admin::AssetsController < Admin::BaseController
|
27
|
+
mongosteen
|
28
|
+
has_scope :by_type
|
29
|
+
json_config({ methods: [ :item_thumbnail, :created_ago ] })
|
30
|
+
end
|
31
|
+
```
|
27
32
|
|
28
33
|
Add admin assets controller to ```routes.rb```:
|
29
34
|
|
30
|
-
|
35
|
+
```ruby
|
36
|
+
resources :assets
|
37
|
+
```
|
31
38
|
|
32
39
|
Add to ```admin.scss```:
|
33
40
|
|
34
|
-
|
41
|
+
```scss
|
42
|
+
@import "loft";
|
43
|
+
```
|
35
44
|
|
36
45
|
Add to ```admin.coffee``` character configuration object:
|
37
46
|
|
38
|
-
|
47
|
+
```coffee
|
48
|
+
assets: new Loft('Library', 'asset', '/admin/assets')
|
49
|
+
```
|
39
50
|
|
40
51
|
|
41
52
|
## Loft family
|
42
53
|
|
54
|
+
- [Character](https://github.com/slate-studio/chr): Powerful javascript CMS for apps
|
43
55
|
- [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add restful actions for mongoid models
|
44
|
-
- [Character](https://github.com/slate-studio/chr): A simple and lightweight javascript library for building data management web apps
|
45
56
|
- [Inverter](https://github.com/slate-studio/inverter): An easy way to connect Rails templates content to CMS
|
46
57
|
|
47
|
-
## Credits
|
48
58
|
|
49
|
-
|
59
|
+
## License
|
50
60
|
|
51
|
-
|
61
|
+
Copyright © 2015 [Slate Studio, LLC](http://slatestudio.com). Loft is free software, and may be redistributed under the terms specified in the [license](LICENSE.md).
|
52
62
|
|
53
|
-
## License
|
54
63
|
|
55
|
-
|
64
|
+
## About Slate Studio
|
65
|
+
|
66
|
+
[![Slate Studio](https://slate-git-images.s3-us-west-1.amazonaws.com/slate.png)](http://slatestudio.com)
|
67
|
+
|
68
|
+
Loft is maintained and funded by [Slate Studio, LLC](http://slatestudio.com). Tweet your questions or suggestions to [@slatestudio](https://twitter.com/slatestudio) and while you’re at it follow us too.
|
Binary file
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# INPUT LOFT IMAGE
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
class @InputLoftImage extends InputString
|
13
|
+
_addInput: ->
|
14
|
+
@config.placeholder ?= 'Image url'
|
15
|
+
|
16
|
+
@$input =$ "<input type='string' name='#{ @name }' value='#{ @_valueSafe() }' id='#{ @name }' />"
|
17
|
+
@$el.append @$input
|
18
|
+
@$input.on 'change', (e) =>
|
19
|
+
@updateValue($(e.target).val())
|
20
|
+
|
21
|
+
@_add_image()
|
22
|
+
@_add_choose_button()
|
23
|
+
@_add_remove_button()
|
24
|
+
@_update_input_class()
|
25
|
+
|
26
|
+
|
27
|
+
_add_image: ->
|
28
|
+
@$image =$ "<a href='' target='_blank' class='image'><img src='' /></a>"
|
29
|
+
@$el.append @$image
|
30
|
+
@_update_image()
|
31
|
+
|
32
|
+
|
33
|
+
_add_choose_button: ->
|
34
|
+
@$chooseBtn =$ "<a href='#' class='choose'></a><br/>"
|
35
|
+
@$el.append @$chooseBtn
|
36
|
+
|
37
|
+
@_update_choose_button_title()
|
38
|
+
|
39
|
+
@$chooseBtn.on 'click', (e) =>
|
40
|
+
e.preventDefault()
|
41
|
+
chr.modules.assets.showModal 'images', false, (objects) =>
|
42
|
+
asset = objects[0]
|
43
|
+
@updateValue(asset.file.url)
|
44
|
+
|
45
|
+
|
46
|
+
_add_remove_button: ->
|
47
|
+
@$removeBtn =$ "<a href='#' class='remove'>Remove</a>"
|
48
|
+
@$el.append @$removeBtn
|
49
|
+
|
50
|
+
@$removeBtn.on 'click', (e) =>
|
51
|
+
e.preventDefault()
|
52
|
+
if confirm('Are you sure?')
|
53
|
+
@updateValue('')
|
54
|
+
|
55
|
+
|
56
|
+
_update_image: ->
|
57
|
+
url = @value
|
58
|
+
@$image.attr('href', @value).children().attr('src', @value)
|
59
|
+
if @value == '' then @$image.hide() else @$image.show()
|
60
|
+
|
61
|
+
|
62
|
+
_update_choose_button_title: ->
|
63
|
+
title = if @value == '' then 'Choose or upload' else 'Choose other or upload'
|
64
|
+
@$chooseBtn.html(title)
|
65
|
+
|
66
|
+
|
67
|
+
_update_input_class: ->
|
68
|
+
if @value == '' then @$el.removeClass('has-value') else @$el.addClass('has-value')
|
69
|
+
|
70
|
+
|
71
|
+
#
|
72
|
+
# PUBLIC
|
73
|
+
#
|
74
|
+
|
75
|
+
updateValue: (@value) ->
|
76
|
+
@$input.val(@value)
|
77
|
+
|
78
|
+
@_update_image()
|
79
|
+
@_update_choose_button_title()
|
80
|
+
@_update_input_class()
|
81
|
+
|
82
|
+
|
83
|
+
_chrFormInputs['loft-image'] = InputLoftImage
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
@@ -1,10 +1,17 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# Loft Asset Item
|
11
|
+
# -----------------------------------------------------------------------------
|
5
12
|
class @LoftAssetItem extends Item
|
6
13
|
constructor: (@module, @path, @object, @config) ->
|
7
|
-
@$el =$ "<div class='item asset' data-id='#{ @object._id }' data-title=''></div>"
|
14
|
+
@$el =$ "<div class='item asset asset-#{ @object.type }' data-id='#{ @object._id }' data-title=''></div>"
|
8
15
|
@render()
|
9
16
|
|
10
17
|
|
@@ -18,14 +25,26 @@ class @LoftAssetItem extends Item
|
|
18
25
|
@$link =$ "<a class='asset-icon' href='#{ @object.file.url }' target='_blank'></a>"
|
19
26
|
@$el.prepend(@$link)
|
20
27
|
|
28
|
+
# thumbnail for images
|
29
|
+
if @object.type == 'image' && @object.grid_item_thumbnail != ''
|
30
|
+
@$thumbnailSmall =$ "<img class='asset-thumbnail-small' src='#{ @object.item_thumbnail.small }' />"
|
31
|
+
@$thumbnailMedium =$ "<img class='asset-thumbnail-medium' src='#{ @object.item_thumbnail.medium }' />"
|
32
|
+
@$link.append @$thumbnailSmall
|
33
|
+
@$link.append @$thumbnailMedium
|
34
|
+
|
21
35
|
# checkbox for item selection
|
22
|
-
@$checkbox
|
36
|
+
@$checkbox =$ "<div class='asset-checkbox'></div>"
|
37
|
+
@$checkboxInput =$ "<input type='checkbox' />"
|
38
|
+
@$checkbox.append(@$checkboxInput)
|
23
39
|
@$el.prepend(@$checkbox)
|
24
40
|
|
41
|
+
|
25
42
|
# input for assets name
|
26
43
|
name = @$el.attr('data-title')
|
27
|
-
@$
|
28
|
-
@$
|
44
|
+
@$name =$ "<div class='asset-name'></div>"
|
45
|
+
@$nameInput =$ "<input type='text' value='#{ name }' />"
|
46
|
+
@$name.append @$nameInput
|
47
|
+
@$title.before @$name
|
29
48
|
@_bind_name_input()
|
30
49
|
|
31
50
|
# handler for asset name change on title click
|
@@ -1,5 +1,16 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# Loft Group Actions
|
11
|
+
# -----------------------------------------------------------------------------
|
1
12
|
class @LoftGroupActions
|
2
|
-
constructor: (@list, @
|
13
|
+
constructor: (@list, @loft) ->
|
3
14
|
@_render()
|
4
15
|
@_bind_checkboxes()
|
5
16
|
|
@@ -8,53 +19,83 @@ class @LoftGroupActions
|
|
8
19
|
@$el =$ "<div class='assets-group-actions' style='display:none;'></div>"
|
9
20
|
@list.$header.append @$el
|
10
21
|
|
11
|
-
|
12
|
-
@$
|
13
|
-
@$
|
22
|
+
# accept selected
|
23
|
+
@$acceptBtn =$ "<a href='#' class='accept'>Accept</a>"
|
24
|
+
@$acceptBtn.on 'click', (e) => e.preventDefault(); @_accept_selected_items()
|
25
|
+
@$el.append @$acceptBtn
|
14
26
|
|
27
|
+
# delete button
|
15
28
|
@$deleteBtn =$ "<a href='#' class='delete'>Delete Selected</a>"
|
16
29
|
@$deleteBtn.on 'click', (e) => e.preventDefault(); @_delete_selected_list_items()
|
17
30
|
@$el.append @$deleteBtn
|
18
31
|
|
32
|
+
# unselect button
|
33
|
+
@$unselectBtn =$ "<a href='#' class='unselect'>Unselect</a>"
|
34
|
+
@$unselectBtn.on 'click', (e) => e.preventDefault(); @_unselect_list_items()
|
35
|
+
@$el.append @$unselectBtn
|
36
|
+
|
19
37
|
|
20
38
|
_bind_checkboxes: ->
|
21
|
-
@list.$el.on 'click', '.asset .asset-checkbox', (e) =>
|
39
|
+
@list.$el.on 'click', '.asset .asset-checkbox input', (e) =>
|
40
|
+
# when multiple selection disabled select only one asset a time
|
41
|
+
if ! @loft.selectMultipleAssets
|
42
|
+
@_select_single_item($(e.target))
|
43
|
+
|
22
44
|
selectedItems = @_selected_list_items()
|
23
45
|
if selectedItems.length > 0
|
24
46
|
@_show()
|
25
47
|
else
|
26
|
-
@
|
48
|
+
@hide()
|
49
|
+
|
50
|
+
|
51
|
+
_select_single_item: ($checkbox) ->
|
52
|
+
if $checkbox.prop('checked')
|
53
|
+
@list.$el.find('.asset .asset-checkbox input:checked').prop('checked' , false)
|
54
|
+
$checkbox.prop('checked', true)
|
27
55
|
|
28
56
|
|
29
57
|
_selected_list_items: ->
|
30
|
-
@list.$el.find
|
58
|
+
$.map @list.$el.find('.asset .asset-checkbox input:checked'), (checkbox) -> $(checkbox).parent().parent()
|
31
59
|
|
32
60
|
|
33
61
|
_unselect_list_items: ->
|
34
|
-
@list.$el.find('.asset .asset-checkbox').prop('checked', false)
|
35
|
-
@
|
62
|
+
@list.$el.find('.asset .asset-checkbox input').prop('checked', false)
|
63
|
+
@hide()
|
36
64
|
|
37
65
|
|
38
66
|
_delete_selected_list_items: ->
|
39
67
|
if confirm("Are you sure?")
|
40
|
-
selectedItems
|
41
|
-
filesToRemoveCounter = selectedItems.length
|
68
|
+
$selectedItems = @_selected_list_items()
|
69
|
+
filesToRemoveCounter = $selectedItems.length
|
42
70
|
|
43
71
|
# we have on scroll pagination so after some items are removed,
|
44
72
|
# next page request might skip items that replaced removed ones
|
45
|
-
for item in selectedItems
|
46
|
-
objectId = $
|
73
|
+
for $item in $selectedItems
|
74
|
+
objectId = $item.attr('data-id')
|
47
75
|
@list.config.arrayStore.remove objectId,
|
48
76
|
onSuccess: => # success notification
|
49
77
|
onError: => # error notification
|
50
|
-
@
|
78
|
+
@hide()
|
79
|
+
|
80
|
+
|
81
|
+
_accept_selected_items: ->
|
82
|
+
$selectedItems = @_selected_list_items()
|
83
|
+
|
84
|
+
objects = []
|
85
|
+
for $item in $selectedItems
|
86
|
+
objectId = $item.attr('data-id')
|
87
|
+
object = @list.config.arrayStore.get(objectId)
|
88
|
+
objects.push object
|
89
|
+
|
90
|
+
@loft.onAcceptCallback(objects)
|
91
|
+
@loft.closeModal()
|
51
92
|
|
52
93
|
|
53
94
|
_show: ->
|
54
95
|
@$el.show()
|
55
96
|
|
56
97
|
|
57
|
-
|
98
|
+
hide: ->
|
58
99
|
@$el.hide()
|
59
100
|
|
60
101
|
|
@@ -1,3 +1,20 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# Loft
|
11
|
+
#
|
12
|
+
# public methods:
|
13
|
+
# new Loft(title, @resource, @resourcePath)
|
14
|
+
# showModal(assetType, @selectMultipleAssets, @onAcceptCallback)
|
15
|
+
# closeModal()
|
16
|
+
#
|
17
|
+
# -----------------------------------------------------------------------------
|
1
18
|
class @Loft
|
2
19
|
constructor: (title, @resource, @resourcePath) ->
|
3
20
|
@module = {}
|
@@ -6,8 +23,9 @@ class @Loft
|
|
6
23
|
@_uploadsCounter = 0
|
7
24
|
|
8
25
|
moduleConfig =
|
9
|
-
title:
|
26
|
+
title: title
|
10
27
|
showNestedListsAside: true
|
28
|
+
itemClass: LoftTypeItem
|
11
29
|
items:
|
12
30
|
assets_all: @_nested_list_config 'All'
|
13
31
|
assets_images: @_nested_list_config 'Images', 'image'
|
@@ -27,12 +45,28 @@ class @Loft
|
|
27
45
|
@module = module
|
28
46
|
@store = @module.nestedLists.assets_all.config.arrayStore
|
29
47
|
|
48
|
+
# API method
|
49
|
+
@module.showModal = (assetType, selectMultipleAssets, callback) =>
|
50
|
+
@showModal(assetType, selectMultipleAssets, callback)
|
51
|
+
@selectMultipleAssets = true
|
52
|
+
|
53
|
+
# modal close button
|
54
|
+
@module.rootList.$modalCloseBtn =$ "<a href='#' class='modal-close'>Cancel</a>"
|
55
|
+
@module.rootList.$header.prepend @module.rootList.$modalCloseBtn
|
56
|
+
@module.rootList.$modalCloseBtn.on 'click', (e) => e.preventDefault() ; @closeModal()
|
57
|
+
|
58
|
+
# enable grid mode as default on desktop/tablet
|
59
|
+
if ! _isMobile()
|
60
|
+
@module.$el.addClass('grid-mode')
|
61
|
+
|
30
62
|
|
31
63
|
_nested_list_config: (moduleName, assetType) ->
|
32
64
|
arrayStoreConfig =
|
33
|
-
resource:
|
34
|
-
path:
|
35
|
-
searchable:
|
65
|
+
resource: @resource
|
66
|
+
path: @resourcePath
|
67
|
+
searchable: true
|
68
|
+
sortBy: 'created_at'
|
69
|
+
sortReverse: true
|
36
70
|
|
37
71
|
if assetType
|
38
72
|
$.extend(arrayStoreConfig, { urlParams: { by_type: assetType } })
|
@@ -44,15 +78,12 @@ class @Loft
|
|
44
78
|
itemClass: LoftAssetItem
|
45
79
|
arrayStore: new MongosteenArrayStore(arrayStoreConfig)
|
46
80
|
onListInit: (list) => @_inititialize_list(list)
|
81
|
+
onListShow: (list) => @_clear_assets_selection()
|
47
82
|
|
48
83
|
return config
|
49
84
|
|
50
85
|
|
51
86
|
_inititialize_list: (list) ->
|
52
|
-
# uploading spinner
|
53
|
-
list.$loading =$ "<div class='loader'></div>"
|
54
|
-
list.$el.append list.$loading
|
55
|
-
|
56
87
|
# file input button for uploading new files
|
57
88
|
list.$uploadInput =$ "<input class='asset-upload' type='file' multiple='multiple' />"
|
58
89
|
list.$search.before list.$uploadInput
|
@@ -64,7 +95,12 @@ class @Loft
|
|
64
95
|
@_upload(file, list) for file in files
|
65
96
|
|
66
97
|
# group actions toolbar
|
67
|
-
list
|
98
|
+
list.groupActions = new LoftGroupActions(list, this)
|
99
|
+
|
100
|
+
# grid/list checkbox
|
101
|
+
list.$switchMode =$ "<a class='assets-switch-mode' href='#'></a>"
|
102
|
+
list.$backBtn.after list.$switchMode
|
103
|
+
list.$switchMode.on 'click', (e) => e.preventDefault() ; @module.$el.toggleClass('grid-mode')
|
68
104
|
|
69
105
|
|
70
106
|
_upload: (file, list) ->
|
@@ -74,8 +110,9 @@ class @Loft
|
|
74
110
|
@_start_file_upload()
|
75
111
|
@store.push obj,
|
76
112
|
onSuccess: (object) => @_finish_file_upload(list)
|
77
|
-
onError: (errors) =>
|
78
|
-
|
113
|
+
onError: (errors) =>
|
114
|
+
@_finish_file_upload(list)
|
115
|
+
chr.showError('Can\'t upload file.')
|
79
116
|
|
80
117
|
|
81
118
|
_start_file_upload: ->
|
@@ -89,11 +126,38 @@ class @Loft
|
|
89
126
|
@module.$el.removeClass('assets-uploading')
|
90
127
|
|
91
128
|
# update data in list if it's not assets_all,
|
92
|
-
# in
|
129
|
+
# in assets_all new objects are added automatically
|
93
130
|
visibleList = @module.visibleNestedListShownWithParent()
|
94
131
|
if visibleList.name != 'assets_all'
|
95
132
|
visibleList.updateItems()
|
96
133
|
|
97
134
|
|
135
|
+
_clear_assets_selection: ->
|
136
|
+
for name, list of @module.nestedLists
|
137
|
+
list.groupActions.hide()
|
138
|
+
list.$items.find('.asset-checkbox').prop('checked', false)
|
139
|
+
|
140
|
+
|
141
|
+
closeModal: ->
|
142
|
+
@selectMultipleAssets = true
|
143
|
+
@_clear_assets_selection()
|
144
|
+
@module.$el.removeClass('module-modal')
|
145
|
+
@module.hide()
|
146
|
+
|
147
|
+
|
148
|
+
# chr.modules.assets.showModal()
|
149
|
+
showModal: (assetType='all', @selectMultipleAssets=false, @onAcceptCallback=$.noop) ->
|
150
|
+
# modal mode
|
151
|
+
@module.$el.addClass('module-modal')
|
152
|
+
# show nested list
|
153
|
+
@module.showNestedList("assets_#{ assetType }")
|
154
|
+
# select active item
|
155
|
+
@module.rootList.$items.children().removeClass('active')
|
156
|
+
@module.rootList.$items.children("[href='#/assets/assets_#{ assetType }']").addClass('active')
|
157
|
+
# show module
|
158
|
+
@module.show()
|
159
|
+
|
160
|
+
|
161
|
+
|
98
162
|
|
99
163
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# Loft Asset Item
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
class @LoftTypeItem extends Item
|
13
|
+
onClick: (e) ->
|
14
|
+
if @.$el.hasClass('active') then e.preventDefault() ; return
|
15
|
+
|
16
|
+
if ! @module.$el.hasClass 'module-modal'
|
17
|
+
window._skipHashchange = true
|
18
|
+
|
19
|
+
location.hash = $(e.currentTarget).attr('href')
|
20
|
+
crumbs = location.href.split('/')
|
21
|
+
|
22
|
+
@module.showNestedList(_last(crumbs), true)
|
23
|
+
|
24
|
+
else
|
25
|
+
e.preventDefault()
|
26
|
+
|
27
|
+
$item = $(e.currentTarget)
|
28
|
+
|
29
|
+
$item.parent().children('.active').removeClass('active')
|
30
|
+
$item.addClass('active')
|
31
|
+
|
32
|
+
listName = $item.attr('href').split('/')[2]
|
33
|
+
@module.showNestedList(listName, true)
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
# Author: Alexander Kravets <alex@slatestudio.com>,
|
3
|
+
# Slate Studio (http://www.slatestudio.com)
|
4
|
+
#
|
5
|
+
# Coding Guide:
|
6
|
+
# https://github.com/thoughtbot/guides/tree/master/style/coffeescript
|
7
|
+
# -----------------------------------------------------------------------------
|
8
|
+
|
9
|
+
# -----------------------------------------------------------------------------
|
10
|
+
# Redactor.js Loft Plugin
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
|
13
|
+
if ! RedactorPlugins then @RedactorPlugins = {}
|
14
|
+
|
15
|
+
RedactorPlugins.loft = ->
|
16
|
+
methods =
|
17
|
+
init: ->
|
18
|
+
imageButton = @button.add('image', 'Insert Image')
|
19
|
+
@button.addCallback(imageButton, @loft.showImagesModal)
|
20
|
+
|
21
|
+
fileButton = @button.add('file', 'Insert File')
|
22
|
+
@button.addCallback(fileButton, @loft.showAllModal)
|
23
|
+
|
24
|
+
|
25
|
+
showImagesModal: ->
|
26
|
+
chr.modules.assets.showModal 'images', true, (objects) => @loft.insertImages(objects)
|
27
|
+
|
28
|
+
|
29
|
+
# allow multiple assets when no text is selected
|
30
|
+
showAllModal: ->
|
31
|
+
multipleAssets = this.selection.getText() == ''
|
32
|
+
chr.modules.assets.showModal 'all', multipleAssets, (objects) => @loft.insertFiles(objects)
|
33
|
+
|
34
|
+
|
35
|
+
# if text is selected replace text with <a>{{ text }}</a>
|
36
|
+
# otherwise add link(s) split by <br/> tag
|
37
|
+
insertFiles: (objects) ->
|
38
|
+
if objects.length > 0
|
39
|
+
selectedText = this.selection.getText()
|
40
|
+
if selectedText != ''
|
41
|
+
asset = objects[0]
|
42
|
+
html = "<a href='#{ asset.file.url }' target='_blank'>#{ selectedText }</a>"
|
43
|
+
else
|
44
|
+
links = []
|
45
|
+
for asset in objects
|
46
|
+
links.push "<a href='#{ asset.file.url }' target='_blank'>#{ asset.name }</a>"
|
47
|
+
html = links.join('<br>')
|
48
|
+
|
49
|
+
this.insert.html(html, false)
|
50
|
+
|
51
|
+
|
52
|
+
insertImages: (objects) ->
|
53
|
+
if objects.length > 0
|
54
|
+
images = []
|
55
|
+
for asset in objects
|
56
|
+
images.push "<img src='#{ asset.file.url }' alt='#{ asset.name }' />"
|
57
|
+
|
58
|
+
html = images.join('<br>')
|
59
|
+
|
60
|
+
this.insert.html(html, false)
|
61
|
+
|
62
|
+
|
63
|
+
return methods
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
@@ -2,106 +2,243 @@
|
|
2
2
|
// Styles for Loft Character CMS plugin, this should be included in
|
3
3
|
// admin.scss (default)
|
4
4
|
//
|
5
|
-
// - mobile version
|
6
|
-
//
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
@import "chr/form/input-loft-image";
|
7
|
+
|
8
|
+
// MIXINS
|
9
|
+
|
10
|
+
@mixin iconBase($bgSize, $width, $height) {
|
11
|
+
background-image: image-url('loft/library@3x.png'); background-size: $bgSize;
|
12
|
+
display: block; width: $width; height: $height;
|
13
|
+
}
|
14
|
+
|
15
|
+
@mixin iconLabel($title) {
|
16
|
+
&:before {
|
17
|
+
display: block; margin-top: 2.6em;
|
18
|
+
content: $title; color: $contrastColor;
|
19
|
+
font-size: 1.5em; font-weight: 300; letter-spacing: 8px;
|
20
|
+
text-align: center; text-transform: uppercase;
|
19
21
|
}
|
20
22
|
}
|
21
23
|
|
22
24
|
|
23
|
-
//
|
25
|
+
// ASSET TYPE ICONS
|
26
|
+
|
27
|
+
.module.assets .list:first-child .items {
|
28
|
+
.item {
|
29
|
+
.item-title { margin-left: 2.05em; }
|
30
|
+
&:before { @include absolutePosition(inherit inherit inherit 1em); content: ''; display: block; }
|
31
|
+
}
|
32
|
+
|
33
|
+
.item {
|
34
|
+
&:before { @include iconBase(32px 112px, 16px, 16px); }
|
35
|
+
&.active:before { background-position: 16px 0px; }
|
36
|
+
}
|
37
|
+
.item:nth-child(2) {
|
38
|
+
&:before { background-position: 0px -16px; }
|
39
|
+
&.active { &:before { background-position: 16px -16px; } }
|
40
|
+
}
|
41
|
+
.item:nth-child(3) {
|
42
|
+
&:before { background-position: 0px -32px; }
|
43
|
+
&.active { &:before { background-position: 16px -32px; } }
|
44
|
+
}
|
45
|
+
.item:nth-child(4) {
|
46
|
+
&:before { background-position: 0px -48px; }
|
47
|
+
&.active { &:before { background-position: 16px -48px; } }
|
48
|
+
}
|
49
|
+
.item:nth-child(5) {
|
50
|
+
&:before { background-position: 0px -64px; }
|
51
|
+
&.active { &:before { background-position: 16px -64px; } }
|
52
|
+
}
|
53
|
+
.item:nth-child(6) {
|
54
|
+
&:before { background-position: 0px -80px; }
|
55
|
+
&.active { &:before { background-position: 16px -80px; } }
|
56
|
+
}
|
57
|
+
.item:nth-child(7) {
|
58
|
+
&:before { background-position: 0px -96px; }
|
59
|
+
&.active { &:before { background-position: 16px -96px; } }
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
// LIST MODE
|
65
|
+
|
66
|
+
.module.assets .list:not(:first-child) .items .item.asset {
|
67
|
+
padding-left: 6.25em; // 100px;
|
68
|
+
|
69
|
+
// checkbox
|
70
|
+
.asset-checkbox {
|
71
|
+
@include absolutePosition(1.3em inherit inherit 1em); z-index: 1;
|
72
|
+
input { @include absolutePosition(0px inherit inherit 0px); }
|
73
|
+
}
|
74
|
+
|
75
|
+
// type icon
|
76
|
+
.asset-icon {
|
77
|
+
@include iconBase(64px 224px, 32px, 32px);
|
78
|
+
@include absolutePosition(12px inherit inherit 3.05em);
|
79
|
+
}
|
80
|
+
|
81
|
+
// image thumbnail
|
82
|
+
&.asset-image .asset-icon {
|
83
|
+
@include absolutePosition(8px inherit inherit 2.8em);
|
84
|
+
overflow: hidden; width: 40px; height: 40px; border-radius: 20px;
|
85
|
+
img { height: 100%; }
|
86
|
+
}
|
87
|
+
|
88
|
+
&.asset-image .asset-icon { background-position: 0px -32px; }
|
89
|
+
&.asset-text .asset-icon { background-position: 0px -64px; }
|
90
|
+
&.asset-archive .asset-icon { background-position: 0px -96px; }
|
91
|
+
&.asset-audio .asset-icon { background-position: 0px -128px; }
|
92
|
+
&.asset-video .asset-icon { background-position: 0px -160px; }
|
93
|
+
&.asset-other .asset-icon { background-position: 0px -192px; }
|
94
|
+
|
95
|
+
.asset-thumnail-small { display: block; }
|
96
|
+
.asset-thumnail-medium { display: none; }
|
97
|
+
|
98
|
+
// name
|
99
|
+
.item-title { cursor: pointer; }
|
100
|
+
.asset-name {
|
101
|
+
display: none; z-index: 1;
|
102
|
+
@include absolutePosition(7px .5em inherit 94px);
|
103
|
+
input { width: 100%; padding: .1em .3em .3em .3em; @include noFocus();
|
104
|
+
border: 1px solid $stableColor; box-shadow: 0 0 10px $lightColor; border-radius: 3px; }
|
105
|
+
}
|
106
|
+
&.edit-name .asset-name { display: block; }
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
// LIST-GRID MODE SWITCHER
|
111
|
+
|
112
|
+
.assets-switch-mode { display: none; }
|
113
|
+
|
114
|
+
|
115
|
+
// GROUP ACTIONS
|
116
|
+
|
24
117
|
.assets-group-actions {
|
25
118
|
@include absolutePosition(0 0 0 0); background: $white;
|
26
|
-
.
|
119
|
+
.accept { @include headerButton($positiveColor); float: right; margin-right: 1em; }
|
27
120
|
.delete { @include headerButton($assertiveColor); float: right; margin-right: 1em; }
|
121
|
+
.unselect { @include headerButton($stableColor); float: left; margin-left: 1em; }
|
28
122
|
}
|
29
123
|
|
124
|
+
.assets-group-actions .accept { display: none; }
|
125
|
+
.module-modal .assets-group-actions .accept { display: inline; }
|
126
|
+
.module-modal .assets-group-actions .delete { display: none; }
|
30
127
|
|
31
|
-
// Icons for Parrent List
|
32
|
-
.list.assets {
|
33
|
-
.items .item {
|
34
|
-
.item-title { margin-left: 2.05em; }
|
35
|
-
&:before {
|
36
|
-
content: '';
|
37
|
-
position: absolute;
|
38
|
-
display: block; width: 16px; height: 16px;
|
39
|
-
margin-right: 1em;
|
40
|
-
background-color: $lightColor;
|
41
|
-
}
|
42
|
-
}
|
43
|
-
}
|
44
128
|
|
129
|
+
// UPLOAD BUTTON
|
45
130
|
|
46
|
-
// Upload Button
|
47
131
|
.asset-upload {
|
48
|
-
@include absolutePosition(0
|
49
|
-
@extend .icon-plus;
|
50
|
-
cursor: pointer;
|
51
|
-
right: -40px;
|
52
|
-
padding-left: 80px;
|
53
|
-
&:focus { outline: none; }
|
132
|
+
@include absolutePosition(0 -40px inherit inherit);
|
133
|
+
@extend .icon-plus; @include noFocus;
|
134
|
+
cursor: pointer; padding-left: 80px;
|
54
135
|
}
|
55
136
|
|
56
|
-
.list header {
|
57
|
-
|
58
|
-
}
|
137
|
+
.list header .asset-upload + .search { @include absolutePosition(0 40px inherit inherit); }
|
138
|
+
.list.list-search header .asset-upload + .search { @include absolutePosition(0 0 inherit 0); }
|
139
|
+
.module.assets.assets-uploading .list:not(:first-child) header .spinner { display: inline-block; }
|
59
140
|
|
60
|
-
.list.list-search header {
|
61
|
-
.asset-upload + .search { @include absolutePosition(0 0 inherit 0); }
|
62
|
-
}
|
63
141
|
|
142
|
+
// MODAL MODE
|
64
143
|
|
65
|
-
|
66
|
-
.
|
144
|
+
.module.assets {
|
145
|
+
.modal-close {
|
146
|
+
display: none;
|
147
|
+
@include headerButton();
|
148
|
+
@include absolutePosition(null null null 1em );
|
149
|
+
}
|
150
|
+
&.module-modal {
|
151
|
+
@include absolutePosition(.5em .5em 0em .5em); z-index: 1100;
|
67
152
|
|
153
|
+
&:after { display: none; }
|
154
|
+
|
155
|
+
// dark background
|
156
|
+
&:before {
|
157
|
+
@include position(fixed, 0px 0px 0px 0px); z-index: 0;
|
158
|
+
content: ''; display: block; background: rgba(0,0,0,.25);
|
159
|
+
}
|
68
160
|
|
69
|
-
|
70
|
-
.
|
71
|
-
.item-title {
|
72
|
-
cursor: pointer;
|
73
|
-
position: relative;
|
74
|
-
&:hover { color: $positiveColor; text-decoration: underline; }
|
161
|
+
.modal-close { display: inline; }
|
162
|
+
.list:first-child .back { display: none }
|
75
163
|
}
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
164
|
+
}
|
165
|
+
|
166
|
+
|
167
|
+
/* TABLET LAYOUT */
|
168
|
+
|
169
|
+
@media #{ $tablet } {
|
170
|
+
.module.assets .list:not(:first-child) .items { font-size: 1em; }
|
171
|
+
|
172
|
+
// ITEM
|
173
|
+
.module.assets .list:not(:first-child) .items .item.asset {
|
174
|
+
padding-top: 19px; padding-bottom: 19px;
|
175
|
+
.item-title {
|
176
|
+
display: inline; cursor: pointer;
|
177
|
+
&:hover { color: $positiveColor; text-decoration: underline; }
|
89
178
|
}
|
179
|
+
.item-subtitle { @include absolutePosition(1.55em 1em inherit inherit); }
|
180
|
+
.asset-name { top: 16px; }
|
90
181
|
}
|
91
|
-
|
92
|
-
|
93
|
-
|
182
|
+
|
183
|
+
// MODAL MODE
|
184
|
+
.module.assets.module-modal { @include absolutePosition(1.5em 1.5em 0em 1.5em) }
|
185
|
+
|
186
|
+
|
187
|
+
// GRID MODE
|
188
|
+
.assets-switch-mode {
|
189
|
+
@include absolutePosition(inherit inherit inherit 1em);
|
190
|
+
display: inline;
|
191
|
+
font-size: .9em;
|
192
|
+
&:before { content: 'Grid'; color: $lightColor; margin-right: .25em; }
|
193
|
+
&:after { content: 'List'; color: $stableColor; }
|
194
|
+
}
|
195
|
+
.module.assets.grid-mode .assets-switch-mode {
|
196
|
+
&:before { color: $stableColor; }
|
197
|
+
&:after { color: $lightColor; }
|
94
198
|
}
|
95
|
-
.asset-icon {
|
96
|
-
display: inline-block; width: 16px; height: 16px;
|
97
|
-
margin-right: 1em;
|
98
|
-
background-color: $lightColor; }
|
99
199
|
|
100
|
-
|
101
|
-
.
|
200
|
+
.module.assets.grid-mode {
|
201
|
+
.list:not(:first-child) .items {
|
202
|
+
padding: .25em .25em 5em .75em;
|
203
|
+
.item.asset { margin: 0 .75em 1.5em; float: left; display: inline-block; }
|
204
|
+
|
205
|
+
.item.asset {
|
206
|
+
padding-left: 1em; width: 222px; height: 222px;
|
207
|
+
@include noBorder(); border: 1px solid $contrastColor; border-radius: 4px;
|
208
|
+
|
209
|
+
// checkbox: position + decoration
|
210
|
+
.asset-checkbox {
|
211
|
+
top: 10px; left: 10px; width: 24px; height: 24px; background: $white;
|
212
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.05),1px 0 0 rgba(0,0,0,0.05); border-bottom-right-radius: 4px;
|
213
|
+
}
|
214
|
+
|
215
|
+
// thumbnail
|
216
|
+
.asset-icon {
|
217
|
+
@include absolutePosition(10px inherit inherit 10px);
|
218
|
+
width: 200px; height: 150px; background: $white; border-radius: 0;
|
219
|
+
&:after { @include absolutePosition(0px 0px 0px 0px); box-shadow: 0 0 1px rgba(0,0,0,0.2) inset; content: ''; display: block; }
|
220
|
+
}
|
221
|
+
|
222
|
+
.asset-thumbnail-small { display: none; }
|
223
|
+
.asset-thumbnail-medium { display: block; }
|
224
|
+
|
225
|
+
// name & subtitle
|
226
|
+
.item-title { display: block; margin-top: 9.45em; }
|
227
|
+
.item-subtitle { display: block; position: initial; margin-top: .3em; }
|
228
|
+
&.edit-name .asset-name { top: inherit; bottom: 27px; left: 10px; right: 10px; }
|
229
|
+
}
|
230
|
+
|
231
|
+
// asset type labels
|
232
|
+
.asset-text .asset-icon { @include iconLabel('Text'); }
|
233
|
+
.asset-archive .asset-icon { @include iconLabel('Archive'); }
|
234
|
+
.asset-audio .asset-icon { @include iconLabel('Audio'); }
|
235
|
+
.asset-video .asset-icon { @include iconLabel('Video'); }
|
236
|
+
.asset-other .asset-icon { @include iconLabel('File'); }
|
237
|
+
}
|
102
238
|
}
|
103
239
|
}
|
104
240
|
|
105
241
|
|
106
242
|
|
107
243
|
|
244
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
.input-loft-image {
|
2
|
+
&.has-value { min-height: 11em; }
|
3
|
+
|
4
|
+
input { margin-bottom: .25em; }
|
5
|
+
.image img { max-height: 6em; margin: .25em .75em .25em 0; float: left; }
|
6
|
+
.choose, .remove { color: $positiveColor; display: inline-block; }
|
7
|
+
|
8
|
+
&.has-value .choose { margin-top: .25em; }
|
9
|
+
&.has-value .remove { display: inline-block; }
|
10
|
+
.remove { display: none; font-size: .8em; margin-top: .5em; }
|
11
|
+
}
|
@@ -6,11 +6,15 @@ module Loft
|
|
6
6
|
include CarrierWave::MiniMagick
|
7
7
|
|
8
8
|
def store_dir
|
9
|
-
"
|
9
|
+
"loft/#{ model._number }"
|
10
10
|
end
|
11
11
|
|
12
|
-
version :
|
13
|
-
process :resize_to_fill => [
|
12
|
+
version :_200x150_2x, if: :is_image? do
|
13
|
+
process :resize_to_fill => [400, 300]
|
14
|
+
end
|
15
|
+
|
16
|
+
version :_40x40_2x, if: :is_image? do
|
17
|
+
process :resize_to_fill => [80, 80]
|
14
18
|
end
|
15
19
|
|
16
20
|
def is_image? new_file
|
data/lib/loft.rb
CHANGED
data/lib/loft/version.rb
CHANGED
data/lib/mongoid/loft_asset.rb
CHANGED
@@ -26,7 +26,7 @@ module Mongoid
|
|
26
26
|
|
27
27
|
# increment value, used by uploader
|
28
28
|
field :_number, type: Integer
|
29
|
-
increments :
|
29
|
+
increments :_number
|
30
30
|
|
31
31
|
# uploaders
|
32
32
|
mount_uploader :file, AssetFileUploader
|
@@ -54,11 +54,11 @@ module Mongoid
|
|
54
54
|
end
|
55
55
|
|
56
56
|
|
57
|
-
def
|
57
|
+
def item_thumbnail
|
58
58
|
if is_image? and file?
|
59
|
-
file.
|
59
|
+
{ medium: file._200x150_2x.url, small: file._40x40_2x.url }
|
60
60
|
else
|
61
|
-
|
61
|
+
{}
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Kravets
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid_search
|
@@ -88,22 +88,28 @@ executables: []
|
|
88
88
|
extensions: []
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
|
+
- ".gitignore"
|
91
92
|
- CONTRIBUTING.md
|
92
93
|
- Gemfile
|
93
94
|
- LICENSE.md
|
94
95
|
- README.md
|
95
96
|
- Rakefile
|
97
|
+
- app/assets/images/loft/library@3x.png
|
98
|
+
- app/assets/javascripts/chr/form/input-loft-image.coffee
|
96
99
|
- app/assets/javascripts/chr/loft/asset-item.coffee
|
97
100
|
- app/assets/javascripts/chr/loft/group-actions.coffee
|
98
101
|
- app/assets/javascripts/chr/loft/module.coffee
|
102
|
+
- app/assets/javascripts/chr/loft/type-item.coffee
|
103
|
+
- app/assets/javascripts/chr/redactor/loft.coffee
|
99
104
|
- app/assets/javascripts/loft.coffee
|
100
105
|
- app/assets/stylesheets/_loft.scss
|
106
|
+
- app/assets/stylesheets/chr/form/_input-loft-image.scss
|
101
107
|
- app/uploaders/asset_file_uploader.rb
|
108
|
+
- lib/concerns/asset_file_uploader.rb
|
102
109
|
- lib/loft.rb
|
103
110
|
- lib/loft/engine.rb
|
104
111
|
- lib/loft/version.rb
|
105
112
|
- lib/mongoid/loft_asset.rb
|
106
|
-
- lib/uploaders/asset_file_uploader.rb
|
107
113
|
- loft.gemspec
|
108
114
|
homepage: http://slatestudio.com
|
109
115
|
licenses:
|