populate-me 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +655 -0
- data/Rakefile +14 -0
- data/example/config.ru +100 -0
- data/lib/populate_me.rb +2 -0
- data/lib/populate_me/admin.rb +157 -0
- data/lib/populate_me/admin/__assets__/css/asmselect.css +63 -0
- data/lib/populate_me/admin/__assets__/css/jquery-ui.min.css +6 -0
- data/lib/populate_me/admin/__assets__/css/main.css +244 -0
- data/lib/populate_me/admin/__assets__/img/help/children.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/create.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/delete.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/edit.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/form.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/list.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/login.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/logout.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/menu.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/overview.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/save.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/sort.png +0 -0
- data/lib/populate_me/admin/__assets__/img/help/sublist.png +0 -0
- data/lib/populate_me/admin/__assets__/js/asmselect.js +412 -0
- data/lib/populate_me/admin/__assets__/js/columnav.js +87 -0
- data/lib/populate_me/admin/__assets__/js/jquery-ui.min.js +7 -0
- data/lib/populate_me/admin/__assets__/js/main.js +388 -0
- data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
- data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
- data/lib/populate_me/admin/views/help.erb +94 -0
- data/lib/populate_me/admin/views/page.erb +189 -0
- data/lib/populate_me/api.rb +124 -0
- data/lib/populate_me/attachment.rb +186 -0
- data/lib/populate_me/document.rb +192 -0
- data/lib/populate_me/document_mixins/admin_adapter.rb +149 -0
- data/lib/populate_me/document_mixins/callbacks.rb +125 -0
- data/lib/populate_me/document_mixins/outcasting.rb +83 -0
- data/lib/populate_me/document_mixins/persistence.rb +95 -0
- data/lib/populate_me/document_mixins/schema.rb +198 -0
- data/lib/populate_me/document_mixins/typecasting.rb +70 -0
- data/lib/populate_me/document_mixins/validation.rb +44 -0
- data/lib/populate_me/file_system_attachment.rb +40 -0
- data/lib/populate_me/grid_fs_attachment.rb +103 -0
- data/lib/populate_me/mongo.rb +160 -0
- data/lib/populate_me/s3_attachment.rb +120 -0
- data/lib/populate_me/variation.rb +38 -0
- data/lib/populate_me/version.rb +4 -0
- data/populate-me.gemspec +34 -0
- data/test/helper.rb +37 -0
- data/test/test_admin.rb +183 -0
- data/test/test_api.rb +246 -0
- data/test/test_attachment.rb +167 -0
- data/test/test_document.rb +128 -0
- data/test/test_document_admin_adapter.rb +221 -0
- data/test/test_document_callbacks.rb +151 -0
- data/test/test_document_outcasting.rb +247 -0
- data/test/test_document_persistence.rb +83 -0
- data/test/test_document_schema.rb +280 -0
- data/test/test_document_typecasting.rb +128 -0
- data/test/test_grid_fs_attachment.rb +239 -0
- data/test/test_mongo.rb +324 -0
- data/test/test_s3_attachment.rb +281 -0
- data/test/test_variation.rb +91 -0
- data/test/test_version.rb +11 -0
- metadata +294 -0
@@ -0,0 +1,2 @@
|
|
1
|
+
"use strict";!function(a){var b,c,d=a();a.fn.sortable=function(e){var f=String(e);return e=a.extend({connectWith:!1,placeholder:null,dragImage:null},e),this.each(function(){if("reload"===f&&a(this).children(e.items).off("dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s"),/^enable|disable|destroy$/.test(f)){var g=a(this).children(a(this).data("items")).attr("draggable","enable"===f);return void("destroy"===f&&(a(this).off("sortupdate"),a(this).removeData("opts"),g.add(this).removeData("connectWith items").off("dragstart.h5s dragend.h5s selectstart.h5s dragover.h5s dragenter.h5s drop.h5s").off("sortupdate")))}var h=a(this).data("opts");"undefined"==typeof h?a(this).data("opts",e):e=h;var i,j,k,l,m=a(this).children(e.items),n=null===e.placeholder?a("<"+(/^ul|ol$/i.test(this.tagName)?"li":"div")+' class="sortable-placeholder"/>'):a(e.placeholder).addClass("sortable-placeholder");m.find(e.handle).mousedown(function(){i=!0}).mouseup(function(){i=!1}),a(this).data("items",e.items),d=d.add(n),e.connectWith&&a(e.connectWith).add(this).data("connectWith",e.connectWith),m.attr("role","option"),m.attr("aria-grabbed","false"),m.attr("draggable","true").on("dragstart.h5s",function(d){if(d.stopImmediatePropagation(),e.handle&&!i)return!1;i=!1;var f=d.originalEvent.dataTransfer;f.effectAllowed="move",f.setData("text",""),e.dragImage&&f.setDragImage&&f.setDragImage(e.dragImage,0,0),j=(b=a(this)).addClass("sortable-dragging").attr("aria-grabbed","true").index(),c=b.outerHeight(),k=a(this).parent()}).on("dragend.h5s",function(){b&&(b.removeClass("sortable-dragging").attr("aria-grabbed","false").show(),d.detach(),l=a(this).parent(),(j!==b.index()||k.get(0)!==l.get(0))&&b.parent().triggerHandler("sortupdate",{item:b,oldindex:j,startparent:k,endparent:l}),b=null,c=null)}).not("a[href], img").on("selectstart.h5s",function(){return e.handle&&!i?!0:(this.dragDrop&&this.dragDrop(),!1)}).end().add([this,n]).on("dragover.h5s dragenter.h5s drop.h5s",function(f){if(!m.is(b)&&e.connectWith!==a(b).parent().data("connectWith"))return!0;if("drop"===f.type)return f.stopPropagation(),d.filter(":visible").after(b),b.trigger("dragend.h5s"),!1;if(f.preventDefault(),f.originalEvent.dataTransfer.dropEffect="move",m.is(this)){var g=a(this).outerHeight();if(e.forcePlaceholderSize&&n.height(c),g>c){var h=g-c,i=a(this).offset().top;if(n.index()<a(this).index()&&f.originalEvent.pageY<i+h)return!1;if(n.index()>a(this).index()&&f.originalEvent.pageY>i+g-h)return!1}b.hide(),a(this)[n.index()<a(this).index()?"after":"before"](n),d.not(n).detach()}else d.is(this)||a(this).children(e.items).length||(d.detach(),a(this).append(n));return!1})})}}(jQuery);
|
2
|
+
//# sourceMappingURL=html.sortable.min.js.map
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<div class='text-page'>
|
2
|
+
|
3
|
+
<h1>Help</h1>
|
4
|
+
|
5
|
+
<h2>Overview</h2>
|
6
|
+
|
7
|
+
<p>The screen is divided in columns and expands on the right as you go further down the hierarchy, a little bit like a file explorer. </p>
|
8
|
+
|
9
|
+
<%= help_img 'Overview', 'overview.png' %>
|
10
|
+
|
11
|
+
<p>The very first column displays your login name (e.g. admin). At the bottom you have a "Logout" button so that you can securely finish your working session.</p>
|
12
|
+
|
13
|
+
<%= help_img 'Logout', 'logout.png' %>
|
14
|
+
|
15
|
+
<p>And then the second column is your starting point which represents as close as possible the sections of your website.</p>
|
16
|
+
|
17
|
+
<%= help_img 'Menu', 'menu.png' %>
|
18
|
+
|
19
|
+
<p>The item you've selected is always blue in order to show you the path you've been through, again like a file explorer.</p>
|
20
|
+
|
21
|
+
<%= help_img 'Subist', 'sublist.png' %>
|
22
|
+
|
23
|
+
<h2>List pages</h2>
|
24
|
+
|
25
|
+
<p>When you have reached a column like the last one on this image, it is a list page.</p>
|
26
|
+
|
27
|
+
<%= help_img 'List', 'list.png' %>
|
28
|
+
|
29
|
+
<p>This can be a list of blog articles, a list of images or a list of contact details. The presentation will always be similar. You will see a relevant title, a relevant image (if there is one), and buttons for actions.If you click the title of the item you'll be able to edit the information of this item.</p>
|
30
|
+
|
31
|
+
<%= help_img 'Edit', 'edit.png' %>
|
32
|
+
|
33
|
+
<p>The "delete" button allows you to delete the item.</p>
|
34
|
+
|
35
|
+
<%= help_img 'Delete', 'delete.png' %>
|
36
|
+
|
37
|
+
<p>While being irreversible and potentially dangerous, the Bureau will warn you and give you the opportunity to cancel before proceeding.</p>
|
38
|
+
|
39
|
+
<p>When you rollover the top of the item, your cursor will turn into a hand which is a clue that you can re-order the items by drag-and-drop.</p>
|
40
|
+
|
41
|
+
<%= help_img 'Sort', 'sort.png' %>
|
42
|
+
|
43
|
+
<p>Sometimes this hand does not appear, it means that the items cannot be sorted manually, generally because they are sorted automatically, alphabetically or by date for example.</p>
|
44
|
+
|
45
|
+
<p>When you want to create a new item, you can click the "plus" button.</p>
|
46
|
+
|
47
|
+
<%= help_img 'Create', 'create.png' %>
|
48
|
+
|
49
|
+
<p>Items will sometimes have other items attached to them. For example a blog article can have a list of images or an artist can have a list of social medias. These are called relationships.</p>
|
50
|
+
|
51
|
+
<p>When this is the case, you will have links for each relationship at the bottom of the items and when you click on them, the system will bring you to another list column which follows the same structure.</p>
|
52
|
+
|
53
|
+
<%= help_img 'Children', 'children.png' %>
|
54
|
+
|
55
|
+
<h2>Form</h2>
|
56
|
+
|
57
|
+
<p>Once you get to finally edit or create something, the page will be a form like the example below.</p>
|
58
|
+
|
59
|
+
<%= help_img 'Form', 'form.png' %>
|
60
|
+
|
61
|
+
<p>This page will probably be quite familiar to you. Field names within forms are meant to be as explicit as possible. For example they also include ideal image dimensions.</p>
|
62
|
+
|
63
|
+
<p>Press "Save"/"Update" when you are finished editing the item.</p>
|
64
|
+
|
65
|
+
<%= help_img 'Save', 'save.png' %>
|
66
|
+
|
67
|
+
<h2>Image specifications</h2>
|
68
|
+
|
69
|
+
<p>All images on the website should be exported for web in one of the following formats:</p>
|
70
|
+
|
71
|
+
<p>— Jpeg<br/>— PNG (if it requires transparancy)<br/>— SVG (for logos and anything vector-based)<br/>— Gif (not recommended unless a short animation is required)</p>
|
72
|
+
|
73
|
+
<p>You will not be able to upload images above a predifined maximum, usually 1MB. Typically most images saved for web can be compressed down to a file size between 150KB and 500KB.</p>
|
74
|
+
|
75
|
+
<h2>Text formating</h2>
|
76
|
+
|
77
|
+
<p>On most text fields, you can add links or make text bold or italic with a simple markup called <a href='https://en.wikipedia.org/wiki/Markdown' target='_blank'>markdown</a>. Here is an example of what you can do:</p>
|
78
|
+
|
79
|
+
<div class='frame'>
|
80
|
+
<p>Markdown is good for *making things look italic* or **making things look bold**. Your can also create links [like this which links to Google](https://www.google.com). Although if you write a URL or an email address, you don't have to do anything because it will be turned into a link automatically anyway.</p>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<p>Which will be rendered like this:</p>
|
84
|
+
|
85
|
+
<div class='frame'>
|
86
|
+
<p>Markdown is good for <em>making things look italic</em> or <strong>making things look bold</strong>. Your can also create links <a href="https://www.google.com" target='_blank'>like this which links to Google</a>. Although if you write a URL or an email address, you don't have to do anything because it will be turned into a link automatically anyway.</p>
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<p><em>*Surrounding with stars makes text italic*</em><br><strong>**Surrounding text with double stars makes it bold**</strong><br>Links look like: [<a href='javascript:void(0);'>text to link</a>](http://website.com)</p>
|
90
|
+
|
91
|
+
<p>It should be self explanatory but you can do much more with markdown. If you are interrested, we encourage you to read the <a href='https://daringfireball.net/projects/markdown/syntax' target='_blank'>full documentation</a>.</p>
|
92
|
+
|
93
|
+
</div>
|
94
|
+
|
@@ -0,0 +1,189 @@
|
|
1
|
+
<%# encoding: utf-8 %>
|
2
|
+
<!DOCTYPE HTML>
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta charset="utf-8" />
|
6
|
+
<title><%= settings.meta_title %></title>
|
7
|
+
<link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/jquery-ui.min.css" type="text/css" media='screen' />
|
8
|
+
<link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/asmselect.css" type="text/css" media='screen' />
|
9
|
+
<link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/main.css" type="text/css" media='screen' />
|
10
|
+
<% if settings.respond_to? :custom_css_url %>
|
11
|
+
<link rel="stylesheet" href="<%= settings.custom_css_url %>" type="text/css" media='screen' />
|
12
|
+
<% end %>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
|
16
|
+
<ol id='finder'><li class='column'>
|
17
|
+
<h1>Welcome<br><%= label_for_field user_name %></h1>
|
18
|
+
<% if settings.logout_path? %>
|
19
|
+
<p><a href="<%= request.script_name %><%= settings.logout_path %>">Logout</a></p>
|
20
|
+
<% end %>
|
21
|
+
</li></ol>
|
22
|
+
|
23
|
+
<!-- Templates -->
|
24
|
+
|
25
|
+
<script id="template-menu" type="x-tmpl-mustache">
|
26
|
+
<h1>{{page_title}}</h1>
|
27
|
+
<ol class='menu'>
|
28
|
+
{{#items}}
|
29
|
+
<li><a href="{{href}}" {{#new_page}}target='_blank'{{/new_page}}{{^new_page}}class='column-push'{{/new_page}} title='Open'>{{title}}</a></li>
|
30
|
+
{{/items}}
|
31
|
+
</ol>
|
32
|
+
</script>
|
33
|
+
|
34
|
+
<script id="template-list" type="x-tmpl-mustache">
|
35
|
+
<h1>
|
36
|
+
{{page_title}}
|
37
|
+
</h1>
|
38
|
+
<p class='new-document-btn-wrap'>
|
39
|
+
{{#is_polymorphic}}
|
40
|
+
<select class='polymorphic_type_values'>
|
41
|
+
{{#polymorphic_type_values}}
|
42
|
+
<option value='{{.}}'>{{.}}</option>
|
43
|
+
{{/polymorphic_type_values}}
|
44
|
+
</select>
|
45
|
+
|
46
|
+
{{/is_polymorphic}}
|
47
|
+
<a href="<%= request.script_name %>/form/{{dasherized_class_name}}{{#new_data}}?{{new_data}}{{/new_data}}" class='column-push new-document-btn' title='Create'>+</a>
|
48
|
+
</p>
|
49
|
+
<ol class='documents {{#grid_view}}grid{{/grid_view}}' data-sort-field='{{sort_field}}' data-sort-url='<%= request.script_name %>/api/{{dasherized_class_name}}'>
|
50
|
+
{{#items}}{{#custom_partial_or_default}}template_document{{/custom_partial_or_default}}{{/items}}
|
51
|
+
</ol>
|
52
|
+
</script>
|
53
|
+
|
54
|
+
<script id="template-document" type="x-tmpl-mustache">
|
55
|
+
<li class='admin-list-item' data-id='{{id}}'>
|
56
|
+
<header class='{{#sort_field}}handle{{/sort_field}}' title='{{#sort_field}}Drag and drop{{/sort_field}}'>
|
57
|
+
<button type='button' class='admin-delete' title='Delete' value='<%= request.script_name %>/api/{{admin_url}}'>×</button>
|
58
|
+
</header>
|
59
|
+
<a href="<%= request.script_name %>/form/{{admin_url}}" class='column-push' title='Edit'>
|
60
|
+
{{title}}
|
61
|
+
{{#image_url}}
|
62
|
+
<br />
|
63
|
+
<img src='{{image_url}}{{cache_buster}}' alt='{{title}}' width='300' />
|
64
|
+
{{/image_url}}
|
65
|
+
</a>
|
66
|
+
<ol class='local-menu'>
|
67
|
+
{{#local_menu}}
|
68
|
+
<li>→ <a href="{{href}}" {{#new_page}}target='_blank'{{/new_page}}{{^new_page}}class='column-push'{{/new_page}} title='Open'>{{title}}</a></li>
|
69
|
+
{{/local_menu}}
|
70
|
+
</ol>
|
71
|
+
</li>
|
72
|
+
</script>
|
73
|
+
|
74
|
+
<script id="template-form" type="x-tmpl-mustache">
|
75
|
+
<h1>{{page_title}}</h1>
|
76
|
+
{{#polymorphic_type}}
|
77
|
+
<p>({{polymorphic_type}})</p>
|
78
|
+
{{/polymorphic_type}}
|
79
|
+
<form action="<%= request.script_name %>/api/{{admin_url}}" method="POST" accept-charset="utf-8" class='admin-{{#is_new}}post{{/is_new}}{{^is_new}}put{{/is_new}}'>
|
80
|
+
{{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
|
81
|
+
{{^is_new}}
|
82
|
+
<input type="hidden" name="_method" value="PUT" />
|
83
|
+
{{/is_new}}
|
84
|
+
<input type="submit" value="{{#is_new}}Create{{/is_new}}{{^is_new}}Update{{/is_new}}" />
|
85
|
+
</form>
|
86
|
+
</script>
|
87
|
+
|
88
|
+
<script id="template-nested-form" type="x-tmpl-mustache">
|
89
|
+
<li>
|
90
|
+
<header>
|
91
|
+
<span class='handle handle-button' title='Drag and drop'>↕</span><button type='button' class='admin-delete-nested' title='Delete'>×</button>
|
92
|
+
</header>
|
93
|
+
{{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
|
94
|
+
</li>
|
95
|
+
</script>
|
96
|
+
|
97
|
+
<script id="template-form-fields" type="x-tmpl-mustache">
|
98
|
+
{{#fields}}
|
99
|
+
{{>template_form_field}}
|
100
|
+
{{/fields}}
|
101
|
+
</script>
|
102
|
+
|
103
|
+
<script id="template-form-field" type="x-tmpl-mustache">
|
104
|
+
{{#wrap}}
|
105
|
+
<div class='field' data-field-name='{{field_name}}'>
|
106
|
+
<label>{{label}}</label>
|
107
|
+
<br>
|
108
|
+
{{#adapted_field}}{{/adapted_field}}
|
109
|
+
</div>
|
110
|
+
{{/wrap}}
|
111
|
+
{{^wrap}}
|
112
|
+
{{#adapted_field}}{{/adapted_field}}
|
113
|
+
{{/wrap}}
|
114
|
+
</script>
|
115
|
+
|
116
|
+
<script id="template-string-field" type="x-tmpl-mustache">
|
117
|
+
<input name='{{input_name}}' value='{{input_value}}' {{#required}}required{{/required}}{{{build_input_attributes}}} />
|
118
|
+
</script>
|
119
|
+
|
120
|
+
<script id="template-text-field" type="x-tmpl-mustache">
|
121
|
+
<textarea name='{{input_name}}' {{#required}}required{{/required}}{{{build_input_attributes}}}>{{input_value}}</textarea>
|
122
|
+
</script>
|
123
|
+
|
124
|
+
<script id="template-boolean-field" type="x-tmpl-mustache">
|
125
|
+
<select name='{{input_name}}' {{{build_input_attributes}}}>
|
126
|
+
<option value='false' {{^input_value}}selected{{/input_value}}>No</option>
|
127
|
+
<option value='true' {{#input_value}}selected{{/input_value}}>Yes</option>
|
128
|
+
</select>
|
129
|
+
</script>
|
130
|
+
|
131
|
+
<script id="template-select-field" type="x-tmpl-mustache">
|
132
|
+
{{#multiple}}
|
133
|
+
<input type='hidden' name='{{input_name}}' value='nil' />
|
134
|
+
{{/multiple}}
|
135
|
+
<select name='{{input_name}}' {{#multiple}}multiple title='?'{{/multiple}}{{{build_input_attributes}}}>
|
136
|
+
{{#select_options}}
|
137
|
+
<option value='{{value}}' {{#selected}}selected{{/selected}} {{#preview_uri}}data-preview='{{preview_uri}}'{{/preview_uri}}>{{description}}</option>
|
138
|
+
{{/select_options}}
|
139
|
+
</select>
|
140
|
+
<div class='preview-container'></div>
|
141
|
+
</script>
|
142
|
+
|
143
|
+
<script id="template-attachment-field" type="x-tmpl-mustache">
|
144
|
+
{{#url}}
|
145
|
+
<img src='{{url}}{{cache_buster}}' alt='Preview' width='150' />
|
146
|
+
<button class='attachment-deleter'>x</button>
|
147
|
+
<br />
|
148
|
+
{{/url}}
|
149
|
+
<input type='file' name='{{input_name}}' {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
|
150
|
+
</script>
|
151
|
+
|
152
|
+
<script id="template-list-field" type="x-tmpl-mustache">
|
153
|
+
<fieldset class='field' data-field-name='{{field_name}}'>
|
154
|
+
<legend>{{label}}</legend>
|
155
|
+
{{#dasherized_class_name}}
|
156
|
+
<p>
|
157
|
+
<a href="<%= request.script_name %>/form/{{dasherized_class_name}}?input_name_prefix={{input_name}}[]&nested=true" class='new-nested-document-btn'>+</a>
|
158
|
+
</p>
|
159
|
+
{{/dasherized_class_name}}
|
160
|
+
<ol class='nested-documents'>
|
161
|
+
{{#items}}
|
162
|
+
{{>template_nested_form}}
|
163
|
+
{{/items}}
|
164
|
+
</ol>
|
165
|
+
</fieldset>
|
166
|
+
</script>
|
167
|
+
|
168
|
+
<% if settings.respond_to? :custom_templates_view %>
|
169
|
+
<%= erb settings.custom_templates_view, layout: false %>
|
170
|
+
<% end %>
|
171
|
+
|
172
|
+
<!-- JS -->
|
173
|
+
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript" charset="utf-8"></script>
|
174
|
+
<script src="<%= request.script_name %>/__assets__/js/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
|
175
|
+
<script src="<%= request.script_name %>/__assets__/js/mustache.js" type="text/javascript" charset="utf-8"></script>
|
176
|
+
<script src="<%= request.script_name %>/__assets__/js/columnav.js" type="text/javascript" charset="utf-8"></script>
|
177
|
+
<%# <script src="<%= request.script_name %1>/__assets__/js/sortable.js" type="text/javascript" charset="utf-8"></script> %>
|
178
|
+
<script src="<%= request.script_name %>/__assets__/js/asmselect.js" type="text/javascript" charset="utf-8"></script>
|
179
|
+
<script type="text/javascript">
|
180
|
+
window.admin_path = "<%= request.script_name %>";
|
181
|
+
window.index_path = "<%= settings.index_path %>";
|
182
|
+
</script>
|
183
|
+
<script src="<%= request.script_name %>/__assets__/js/main.js" type="text/javascript" charset="utf-8"></script>
|
184
|
+
<% if settings.respond_to? :custom_js_url %>
|
185
|
+
<script src="<%= settings.custom_js_url %>" type="text/javascript" charset="utf-8"></script>
|
186
|
+
<% end %>
|
187
|
+
</body>
|
188
|
+
</html>
|
189
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'web_utils'
|
3
|
+
require 'populate_me/version'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class PopulateMe::API < Sinatra::Base
|
7
|
+
|
8
|
+
use Rack::MethodOverride
|
9
|
+
|
10
|
+
set :show_exceptions, false
|
11
|
+
|
12
|
+
before do
|
13
|
+
content_type :json
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
redirect(params['_destination']) unless params['_destination'].nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/' do
|
21
|
+
status 200
|
22
|
+
{'success'=>true}.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/version' do
|
26
|
+
status 200
|
27
|
+
{'success'=>true, 'version'=>PopulateMe::VERSION}.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/:model' do
|
31
|
+
end
|
32
|
+
|
33
|
+
post '/:model' do
|
34
|
+
model_class = resolve_model_class params[:model]
|
35
|
+
model_instance = model_class.new.set_from_hash((params[:data]||{}), typecast: true)
|
36
|
+
if model_instance.valid?
|
37
|
+
model_instance.save
|
38
|
+
status 201
|
39
|
+
{
|
40
|
+
'success'=>true,'message'=>'Created Successfully',
|
41
|
+
'data'=>model_instance.to_h
|
42
|
+
}.to_json
|
43
|
+
else
|
44
|
+
status 400
|
45
|
+
{
|
46
|
+
'success'=>false,'message'=>'Invalid Document',
|
47
|
+
'data'=>model_instance.error_report
|
48
|
+
}.to_json
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
put '/:model' do
|
53
|
+
pass unless params[:action]=='sort'
|
54
|
+
model_class = resolve_model_class params[:model]
|
55
|
+
model_class.set_indexes(params[:field].to_sym,params[:ids])
|
56
|
+
{'success'=>true,'message'=>'Sorted Successfully'}.to_json
|
57
|
+
end
|
58
|
+
|
59
|
+
get '/:model/:id' do
|
60
|
+
model_class = resolve_model_class params[:model]
|
61
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
62
|
+
{'success'=>true,'data'=>model_instance.to_h}.to_json
|
63
|
+
end
|
64
|
+
|
65
|
+
put '/:model/:id' do
|
66
|
+
model_class = resolve_model_class params[:model]
|
67
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
68
|
+
model_instance.set_from_hash params[:data], typecast: true
|
69
|
+
if model_instance.valid?
|
70
|
+
model_instance.save
|
71
|
+
{
|
72
|
+
'success'=>true,'message'=>'Updated Successfully',
|
73
|
+
'data'=>model_instance.to_h
|
74
|
+
}.to_json
|
75
|
+
else
|
76
|
+
status 400
|
77
|
+
{
|
78
|
+
'success'=>false,'message'=>'Invalid Document',
|
79
|
+
'data'=>model_instance.error_report
|
80
|
+
}.to_json
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
delete '/:model/:id' do
|
85
|
+
model_class = resolve_model_class params[:model]
|
86
|
+
model_instance = resolve_model_instance model_class, params[:id]
|
87
|
+
model_instance.delete
|
88
|
+
{'success'=>true,'message'=>'Deleted Successfully','data'=>model_instance.to_h}.to_json
|
89
|
+
end
|
90
|
+
|
91
|
+
not_found do
|
92
|
+
response.headers['X-Cascade'] = 'pass'
|
93
|
+
{'success'=>false,'message'=>'Not Found'}.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
error do
|
97
|
+
puts
|
98
|
+
puts env['sinatra.error'].inspect
|
99
|
+
puts
|
100
|
+
{'success'=>false,'message'=>env['sinatra.error'].message}.to_json
|
101
|
+
end
|
102
|
+
|
103
|
+
module Helpers
|
104
|
+
|
105
|
+
include WebUtils
|
106
|
+
|
107
|
+
def resolve_model_class name
|
108
|
+
model_class = resolve_dasherized_class_name(name) rescue nil
|
109
|
+
halt(404) unless model_class.respond_to?(:admin_get)
|
110
|
+
model_class
|
111
|
+
end
|
112
|
+
|
113
|
+
def resolve_model_instance model_class, id
|
114
|
+
instance = model_class.admin_get id
|
115
|
+
halt(404) if instance.nil?
|
116
|
+
instance
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
helpers Helpers
|
122
|
+
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'web_utils'
|
2
|
+
require 'populate_me/variation'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module PopulateMe
|
7
|
+
|
8
|
+
class Attachment
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
attr_accessor :settings
|
13
|
+
|
14
|
+
# inheritable settings
|
15
|
+
def set name, value
|
16
|
+
self.settings[name] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def inherited sub
|
20
|
+
super
|
21
|
+
sub.settings = settings.dup
|
22
|
+
sub::Middleware.parent = sub
|
23
|
+
end
|
24
|
+
|
25
|
+
def middleware
|
26
|
+
Rack::Static
|
27
|
+
end
|
28
|
+
|
29
|
+
def middleware_options
|
30
|
+
[
|
31
|
+
{
|
32
|
+
urls: [settings.url_prefix],
|
33
|
+
root: settings.root
|
34
|
+
}
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :document, :field
|
41
|
+
|
42
|
+
def initialize doc, field
|
43
|
+
@document = doc
|
44
|
+
@field = field
|
45
|
+
end
|
46
|
+
|
47
|
+
def settings
|
48
|
+
self.class.settings
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_value
|
52
|
+
self.document.__send__(field)
|
53
|
+
end
|
54
|
+
|
55
|
+
def field_options
|
56
|
+
self.document.class.fields[self.field]
|
57
|
+
end
|
58
|
+
|
59
|
+
def variations
|
60
|
+
self.field_options[:variations] || []
|
61
|
+
end
|
62
|
+
|
63
|
+
def field_filename variation_name=:original
|
64
|
+
return nil if WebUtils.blank?(self.field_value)
|
65
|
+
return self.field_value if variation_name==:original
|
66
|
+
v = self.variations.find{|var|var.name==variation_name}
|
67
|
+
WebUtils.filename_variation(self.field_value, v.name, v.ext)
|
68
|
+
end
|
69
|
+
|
70
|
+
def attachee_prefix
|
71
|
+
WebUtils.dasherize_class_name self.document.class.name
|
72
|
+
end
|
73
|
+
|
74
|
+
def url variation_name=:original
|
75
|
+
return nil if WebUtils.blank?(self.field_filename(variation_name))
|
76
|
+
"#{settings.url_prefix.sub(/\/$/,'')}/#{self.attachee_prefix}/#{self.field_filename(variation_name)}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def location_root
|
80
|
+
File.join(
|
81
|
+
settings.root,
|
82
|
+
settings.url_prefix,
|
83
|
+
self.attachee_prefix
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def location variation_name=:original
|
88
|
+
self.location_for_filename self.field_filename(variation_name)
|
89
|
+
end
|
90
|
+
def location_for_filename filename
|
91
|
+
File.join self.location_root, filename
|
92
|
+
end
|
93
|
+
|
94
|
+
def ensure_local_path
|
95
|
+
self.location
|
96
|
+
end
|
97
|
+
|
98
|
+
def create form_hash
|
99
|
+
self.delete
|
100
|
+
future_field_value = perform_create form_hash
|
101
|
+
create_variations form_hash.merge({future_field_value: future_field_value})
|
102
|
+
future_field_value
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_variations hash
|
106
|
+
return if self.variations.nil?
|
107
|
+
tmppath = hash[:tempfile].path
|
108
|
+
path = self.location_for_filename hash[:future_field_value]
|
109
|
+
FileUtils.mkdir_p(File.dirname(path))
|
110
|
+
variations.each do |v|
|
111
|
+
self.delete v.name
|
112
|
+
v_path = WebUtils.filename_variation path, v.name, v.ext
|
113
|
+
v.job.call tmppath, v_path
|
114
|
+
self.perform_create hash.merge({variation: v, variation_path: v_path})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def perform_create hash
|
119
|
+
return File.basename(hash[:variation_path]) unless WebUtils.blank?(hash[:variation_path])
|
120
|
+
# Rack 1.6 deletes multipart files after request
|
121
|
+
# So we have to create a copy
|
122
|
+
FileUtils.mkdir_p self.location_root
|
123
|
+
tmppath = hash[:tempfile].path
|
124
|
+
unique_prefix = File.basename(tmppath, File.extname(tmppath))
|
125
|
+
unique_filename = "#{unique_prefix}-#{hash[:filename]}"
|
126
|
+
path = self.location_for_filename unique_filename
|
127
|
+
FileUtils.copy_entry(tmppath, path)
|
128
|
+
unique_filename
|
129
|
+
end
|
130
|
+
|
131
|
+
def deletable? variation_name=:original
|
132
|
+
!WebUtils.blank?(self.field_filename(variation_name)) and File.exist?(self.location(variation_name))
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete variation_name=:original
|
136
|
+
if self.deletable?(variation_name)
|
137
|
+
perform_delete variation_name
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete_all
|
142
|
+
to_delete = [:original] + variations.map(&:name)
|
143
|
+
to_delete.each do |v_name|
|
144
|
+
self.delete v_name
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def perform_delete variation_name=:original
|
149
|
+
FileUtils.rm self.location(variation_name)
|
150
|
+
end
|
151
|
+
|
152
|
+
self.settings = OpenStruct.new
|
153
|
+
set :root, File.join(Dir.tmpdir, 'populate-me')
|
154
|
+
set :url_prefix, '/attachment'
|
155
|
+
FileUtils.mkdir_p self.settings.root
|
156
|
+
|
157
|
+
class Middleware
|
158
|
+
|
159
|
+
# Used for proxing a rack middleware adapted to the attachment system.
|
160
|
+
# The options are then taken from the attachment class.
|
161
|
+
# It can be used in rackup file as:
|
162
|
+
#
|
163
|
+
# use AtachmentClass::Middleware
|
164
|
+
|
165
|
+
class << self
|
166
|
+
attr_accessor :parent
|
167
|
+
end
|
168
|
+
self.parent = PopulateMe::Attachment
|
169
|
+
def parent
|
170
|
+
self.class.parent
|
171
|
+
end
|
172
|
+
|
173
|
+
def initialize app
|
174
|
+
@proxied = parent.middleware.new(app,*parent.middleware_options)
|
175
|
+
end
|
176
|
+
|
177
|
+
def call env
|
178
|
+
@proxied.call env
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|