loft 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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:
|