recycle_bin 1.1.0 → 1.2.0
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/CHANGELOG.md +57 -0
- data/README.md +228 -13
- data/app/controllers/recycle_bin/trash_controller.rb +424 -21
- data/app/helpers/recycle_bin/application_helper.rb +175 -0
- data/app/views/{layouts/recycle_bin/application.html.erb → recycle_bin/layouts/recycle_bin.html.erb} +850 -609
- data/app/views/recycle_bin/shared/_error_messages.html.erb +10 -0
- data/app/views/recycle_bin/shared/_flash_messages.html.erb +6 -0
- data/app/views/recycle_bin/trash/_action_history.html.erb +75 -0
- data/app/views/recycle_bin/trash/_associations.html.erb +50 -0
- data/app/views/recycle_bin/trash/_filters.html.erb +561 -0
- data/app/views/recycle_bin/trash/_item.html.erb +267 -0
- data/app/views/recycle_bin/trash/_pagination.html.erb +75 -0
- data/app/views/recycle_bin/trash/_stats.html.erb +50 -0
- data/app/views/recycle_bin/trash/dashboard.html.erb +618 -0
- data/app/views/recycle_bin/trash/index.html.erb +247 -278
- data/app/views/recycle_bin/trash/show.html.erb +60 -215
- data/config/routes.rb +9 -2
- data/docs/index.html +928 -0
- data/docs/logo.svg +71 -0
- data/lib/recycle_bin/version.rb +1 -1
- data/lib/recycle_bin.rb +111 -1
- metadata +18 -8
- data/app/views/layouts/recycle_bin/recycle_bin/application.html.erb +0 -266
- data/app/views/layouts/recycle_bin/recycle_bin/trash/index.html.erb +0 -133
- data/app/views/layouts/recycle_bin/recycle_bin/trash/show.html.erb +0 -175
- data/recycle_bin.gemspec +0 -47
data/docs/logo.svg
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
|
2
|
+
<defs>
|
|
3
|
+
<!-- Gradient for the main container -->
|
|
4
|
+
<linearGradient id="containerGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5
|
+
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
|
|
6
|
+
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
|
|
7
|
+
</linearGradient>
|
|
8
|
+
|
|
9
|
+
<!-- Gradient for the lid -->
|
|
10
|
+
<linearGradient id="lidGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
11
|
+
<stop offset="0%" style="stop-color:#8b9cf7;stop-opacity:1" />
|
|
12
|
+
<stop offset="100%" style="stop-color:#667eea;stop-opacity:1" />
|
|
13
|
+
</linearGradient>
|
|
14
|
+
|
|
15
|
+
<!-- Shadow filter -->
|
|
16
|
+
<filter id="dropShadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
17
|
+
<feDropShadow dx="3" dy="6" stdDeviation="4" flood-color="rgba(0,0,0,0.3)"/>
|
|
18
|
+
</filter>
|
|
19
|
+
|
|
20
|
+
<!-- Glow effect -->
|
|
21
|
+
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
22
|
+
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
|
23
|
+
<feMerge>
|
|
24
|
+
<feMergeNode in="coloredBlur"/>
|
|
25
|
+
<feMergeNode in="SourceGraphic"/>
|
|
26
|
+
</feMerge>
|
|
27
|
+
</filter>
|
|
28
|
+
</defs>
|
|
29
|
+
|
|
30
|
+
<!-- Background circle -->
|
|
31
|
+
<circle cx="100" cy="100" r="95" fill="url(#containerGradient)" filter="url(#dropShadow)" opacity="0.1"/>
|
|
32
|
+
|
|
33
|
+
<!-- Main trash bin body -->
|
|
34
|
+
<rect x="60" y="80" width="80" height="90" rx="8" ry="8" fill="url(#containerGradient)" filter="url(#dropShadow)"/>
|
|
35
|
+
|
|
36
|
+
<!-- Trash bin lid -->
|
|
37
|
+
<rect x="50" y="70" width="100" height="12" rx="6" ry="6" fill="url(#lidGradient)" filter="url(#dropShadow)"/>
|
|
38
|
+
|
|
39
|
+
<!-- Lid handle -->
|
|
40
|
+
<rect x="90" y="55" width="20" height="8" rx="4" ry="4" fill="url(#lidGradient)"/>
|
|
41
|
+
<rect x="95" y="63" width="10" height="12" fill="url(#lidGradient)"/>
|
|
42
|
+
|
|
43
|
+
<!-- Vertical lines on trash bin -->
|
|
44
|
+
<line x1="80" y1="90" x2="80" y2="155" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
|
|
45
|
+
<line x1="100" y1="90" x2="100" y2="155" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
|
|
46
|
+
<line x1="120" y1="90" x2="120" y2="155" stroke="rgba(255,255,255,0.3)" stroke-width="2" stroke-linecap="round"/>
|
|
47
|
+
|
|
48
|
+
<!-- Restore arrow (curved arrow indicating recovery) -->
|
|
49
|
+
<g transform="translate(130, 40)" filter="url(#glow)">
|
|
50
|
+
<path d="M 0 15 Q 10 5 20 15 Q 15 10 15 20" stroke="#28a745" stroke-width="3" fill="none" stroke-linecap="round"/>
|
|
51
|
+
<polygon points="12,18 18,18 15,24" fill="#28a745"/>
|
|
52
|
+
</g>
|
|
53
|
+
|
|
54
|
+
<!-- Floating particles (representing restored items) -->
|
|
55
|
+
<circle cx="45" cy="50" r="3" fill="#28a745" opacity="0.7">
|
|
56
|
+
<animate attributeName="cy" values="50;45;50" dur="2s" repeatCount="indefinite"/>
|
|
57
|
+
</circle>
|
|
58
|
+
<circle cx="155" cy="60" r="2" fill="#ffc107" opacity="0.8">
|
|
59
|
+
<animate attributeName="cy" values="60;55;60" dur="1.5s" repeatCount="indefinite"/>
|
|
60
|
+
<animate attributeName="opacity" values="0.8;0.4;0.8" dur="1.5s" repeatCount="indefinite"/>
|
|
61
|
+
</circle>
|
|
62
|
+
<circle cx="40" cy="120" r="2.5" fill="#17a2b8" opacity="0.6">
|
|
63
|
+
<animate attributeName="cy" values="120;115;120" dur="2.5s" repeatCount="indefinite"/>
|
|
64
|
+
</circle>
|
|
65
|
+
|
|
66
|
+
<!-- Subtle highlight on the bin -->
|
|
67
|
+
<rect x="62" y="82" width="15" height="60" rx="4" ry="4" fill="rgba(255,255,255,0.2)"/>
|
|
68
|
+
|
|
69
|
+
<!-- Brand text (small) -->
|
|
70
|
+
<text x="100" y="190" text-anchor="middle" font-family="Inter, sans-serif" font-size="14" font-weight="600" fill="url(#containerGradient)">RecycleBin</text>
|
|
71
|
+
</svg>
|
data/lib/recycle_bin/version.rb
CHANGED
data/lib/recycle_bin.rb
CHANGED
|
@@ -165,15 +165,52 @@ module RecycleBin
|
|
|
165
165
|
# Configuration class for RecycleBin
|
|
166
166
|
class Configuration
|
|
167
167
|
attr_accessor :enable_web_interface, :items_per_page, :ui_theme,
|
|
168
|
-
:auto_cleanup_after, :current_user_method, :max_items_per_page
|
|
168
|
+
:auto_cleanup_after, :current_user_method, :max_items_per_page,
|
|
169
|
+
:search_settings, :export_settings, :error_logging_service,
|
|
170
|
+
:enable_advanced_filters, :enable_search_highlighting,
|
|
171
|
+
:max_search_results, :enable_export,
|
|
172
|
+
:export_formats, :export_max_items, :enable_audit_logging
|
|
169
173
|
|
|
170
174
|
def initialize
|
|
175
|
+
# Basic settings
|
|
171
176
|
@enable_web_interface = true
|
|
172
177
|
@items_per_page = 25
|
|
173
178
|
@max_items_per_page = 1000
|
|
174
179
|
@ui_theme = 'default'
|
|
175
180
|
@auto_cleanup_after = nil
|
|
176
181
|
@current_user_method = :current_user
|
|
182
|
+
|
|
183
|
+
# Search settings
|
|
184
|
+
@search_settings = {
|
|
185
|
+
enable_full_text_search: true,
|
|
186
|
+
searchable_fields: %w[title name email content body description],
|
|
187
|
+
max_search_length: 100,
|
|
188
|
+
search_timeout: 30.seconds,
|
|
189
|
+
enable_fuzzy_search: false,
|
|
190
|
+
min_search_length: 2
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Export settings
|
|
194
|
+
@export_settings = {
|
|
195
|
+
enable_csv_export: true,
|
|
196
|
+
enable_json_export: true,
|
|
197
|
+
max_export_items: 10_000,
|
|
198
|
+
export_timeout: 5.minutes,
|
|
199
|
+
include_metadata: true,
|
|
200
|
+
compress_large_exports: true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Advanced features
|
|
204
|
+
@enable_advanced_filters = true
|
|
205
|
+
@enable_search_highlighting = true
|
|
206
|
+
@max_search_results = 10_000
|
|
207
|
+
@enable_export = true
|
|
208
|
+
@export_formats = %w[csv json]
|
|
209
|
+
@export_max_items = 10_000
|
|
210
|
+
@enable_audit_logging = false
|
|
211
|
+
|
|
212
|
+
# Error logging service (can be set to external service like Sentry)
|
|
213
|
+
@error_logging_service = nil
|
|
177
214
|
end
|
|
178
215
|
|
|
179
216
|
def authorize_with(&block)
|
|
@@ -183,5 +220,78 @@ module RecycleBin
|
|
|
183
220
|
def authorization_method
|
|
184
221
|
@authorization_method || proc { true }
|
|
185
222
|
end
|
|
223
|
+
|
|
224
|
+
# Search configuration methods
|
|
225
|
+
def search_config
|
|
226
|
+
@search_settings
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def enable_full_text_search=(value)
|
|
230
|
+
@search_settings[:enable_full_text_search] = value
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def searchable_fields=(fields)
|
|
234
|
+
@search_settings[:searchable_fields] = Array(fields)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def max_search_length=(length)
|
|
238
|
+
@search_settings[:max_search_length] = length
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def search_timeout=(timeout)
|
|
242
|
+
@search_settings[:search_timeout] = timeout
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Export configuration methods
|
|
246
|
+
def export_config
|
|
247
|
+
@export_settings
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def enable_csv_export=(value)
|
|
251
|
+
@export_settings[:enable_csv_export] = value
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def enable_json_export=(value)
|
|
255
|
+
@export_settings[:enable_json_export] = value
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def max_export_items=(count)
|
|
259
|
+
@export_settings[:max_export_items] = count
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def export_timeout=(timeout)
|
|
263
|
+
@export_settings[:export_timeout] = timeout
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Validation methods
|
|
267
|
+
def valid?
|
|
268
|
+
validate_configuration
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def validate_configuration
|
|
272
|
+
errors = []
|
|
273
|
+
|
|
274
|
+
# Validate search settings
|
|
275
|
+
errors << 'min_search_length cannot be greater than max_search_length' if @search_settings[:min_search_length] > @search_settings[:max_search_length]
|
|
276
|
+
|
|
277
|
+
errors << 'search_timeout must be positive' if @search_settings[:search_timeout] <= 0
|
|
278
|
+
|
|
279
|
+
# Validate export settings
|
|
280
|
+
errors << 'max_export_items must be positive' if @export_settings[:max_export_items] <= 0
|
|
281
|
+
|
|
282
|
+
errors << 'export_timeout must be positive' if @export_settings[:export_timeout] <= 0
|
|
283
|
+
|
|
284
|
+
# Validate basic settings
|
|
285
|
+
errors << 'items_per_page must be positive' if @items_per_page <= 0
|
|
286
|
+
|
|
287
|
+
errors << 'max_items_per_page cannot be less than items_per_page' if @max_items_per_page < @items_per_page
|
|
288
|
+
|
|
289
|
+
errors.empty?
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def errors
|
|
293
|
+
validate_configuration
|
|
294
|
+
[]
|
|
295
|
+
end
|
|
186
296
|
end
|
|
187
297
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: recycle_bin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rishi Somani
|
|
8
8
|
- Shobhit Jain
|
|
9
9
|
- Raghav Agrawal
|
|
10
|
+
autorequire:
|
|
10
11
|
bindir: bin
|
|
11
12
|
cert_chain: []
|
|
12
|
-
date: 2025-
|
|
13
|
+
date: 2025-09-08 00:00:00.000000000 Z
|
|
13
14
|
dependencies:
|
|
14
15
|
- !ruby/object:Gem::Dependency
|
|
15
16
|
name: rails
|
|
@@ -92,13 +93,21 @@ files:
|
|
|
92
93
|
- app/controllers/recycle_bin/application_controller.rb
|
|
93
94
|
- app/controllers/recycle_bin/trash_controller.rb
|
|
94
95
|
- app/helpers/recycle_bin/application_helper.rb
|
|
95
|
-
- app/views/layouts/recycle_bin
|
|
96
|
-
- app/views/
|
|
97
|
-
- app/views/
|
|
98
|
-
- app/views/
|
|
96
|
+
- app/views/recycle_bin/layouts/recycle_bin.html.erb
|
|
97
|
+
- app/views/recycle_bin/shared/_error_messages.html.erb
|
|
98
|
+
- app/views/recycle_bin/shared/_flash_messages.html.erb
|
|
99
|
+
- app/views/recycle_bin/trash/_action_history.html.erb
|
|
100
|
+
- app/views/recycle_bin/trash/_associations.html.erb
|
|
101
|
+
- app/views/recycle_bin/trash/_filters.html.erb
|
|
102
|
+
- app/views/recycle_bin/trash/_item.html.erb
|
|
103
|
+
- app/views/recycle_bin/trash/_pagination.html.erb
|
|
104
|
+
- app/views/recycle_bin/trash/_stats.html.erb
|
|
105
|
+
- app/views/recycle_bin/trash/dashboard.html.erb
|
|
99
106
|
- app/views/recycle_bin/trash/index.html.erb
|
|
100
107
|
- app/views/recycle_bin/trash/show.html.erb
|
|
101
108
|
- config/routes.rb
|
|
109
|
+
- docs/index.html
|
|
110
|
+
- docs/logo.svg
|
|
102
111
|
- lib/generators/recycle_bin/add_deleted_at/add_deleted_at_generator.rb
|
|
103
112
|
- lib/generators/recycle_bin/add_deleted_at/templates/add_deleted_at_migration.rb.erb
|
|
104
113
|
- lib/generators/recycle_bin/install/install_generator.rb
|
|
@@ -108,7 +117,6 @@ files:
|
|
|
108
117
|
- lib/recycle_bin/install/templates/recycle_bin.rb
|
|
109
118
|
- lib/recycle_bin/soft_deletable.rb
|
|
110
119
|
- lib/recycle_bin/version.rb
|
|
111
|
-
- recycle_bin.gemspec
|
|
112
120
|
- sig/recycle_bin.rbs
|
|
113
121
|
homepage: https://github.com/R95-del/recycle_bin
|
|
114
122
|
licenses:
|
|
@@ -121,6 +129,7 @@ metadata:
|
|
|
121
129
|
bug_tracker_uri: https://github.com/R95-del/recycle_bin/issues
|
|
122
130
|
documentation_uri: https://github.com/R95-del/recycle_bin/blob/main/README.md
|
|
123
131
|
wiki_uri: https://github.com/R95-del/recycle_bin/wiki
|
|
132
|
+
post_install_message:
|
|
124
133
|
rdoc_options: []
|
|
125
134
|
require_paths:
|
|
126
135
|
- lib
|
|
@@ -135,7 +144,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
135
144
|
- !ruby/object:Gem::Version
|
|
136
145
|
version: '0'
|
|
137
146
|
requirements: []
|
|
138
|
-
rubygems_version: 3.
|
|
147
|
+
rubygems_version: 3.5.18
|
|
148
|
+
signing_key:
|
|
139
149
|
specification_version: 4
|
|
140
150
|
summary: Soft delete and trash management for Ruby on Rails applications
|
|
141
151
|
test_files: []
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
<!DOCTYPE html>
|
|
3
|
-
<html>
|
|
4
|
-
<head>
|
|
5
|
-
<title>Recycle Bin</title>
|
|
6
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
|
-
<%= csrf_meta_tags %>
|
|
8
|
-
<%= csp_meta_tag %>
|
|
9
|
-
|
|
10
|
-
<style>
|
|
11
|
-
/* Simple, clean CSS for V1 MVP */
|
|
12
|
-
* {
|
|
13
|
-
box-sizing: border-box;
|
|
14
|
-
margin: 0;
|
|
15
|
-
padding: 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
body {
|
|
19
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
20
|
-
line-height: 1.6;
|
|
21
|
-
color: #333;
|
|
22
|
-
background-color: #f8f9fa;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.container {
|
|
26
|
-
max-width: 1200px;
|
|
27
|
-
margin: 0 auto;
|
|
28
|
-
padding: 0 20px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* Header */
|
|
32
|
-
.header {
|
|
33
|
-
background: #fff;
|
|
34
|
-
border-bottom: 1px solid #dee2e6;
|
|
35
|
-
padding: 1rem 0;
|
|
36
|
-
margin-bottom: 2rem;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.header h1 {
|
|
40
|
-
color: #495057;
|
|
41
|
-
font-size: 1.5rem;
|
|
42
|
-
display: flex;
|
|
43
|
-
align-items: center;
|
|
44
|
-
gap: 0.5rem;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.header .trash-icon {
|
|
48
|
-
font-size: 1.8rem;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/* Navigation */
|
|
52
|
-
.nav {
|
|
53
|
-
margin-top: 1rem;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.nav a {
|
|
57
|
-
color: #6c757d;
|
|
58
|
-
text-decoration: none;
|
|
59
|
-
margin-right: 1.5rem;
|
|
60
|
-
padding: 0.5rem 0;
|
|
61
|
-
border-bottom: 2px solid transparent;
|
|
62
|
-
transition: all 0.2s;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.nav a:hover,
|
|
66
|
-
.nav a.active {
|
|
67
|
-
color: #495057;
|
|
68
|
-
border-bottom-color: #007bff;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* Cards */
|
|
72
|
-
.card {
|
|
73
|
-
background: #fff;
|
|
74
|
-
border-radius: 8px;
|
|
75
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
76
|
-
padding: 1.5rem;
|
|
77
|
-
margin-bottom: 1rem;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* Buttons */
|
|
81
|
-
.btn {
|
|
82
|
-
display: inline-block;
|
|
83
|
-
padding: 0.5rem 1rem;
|
|
84
|
-
border: none;
|
|
85
|
-
border-radius: 4px;
|
|
86
|
-
text-decoration: none;
|
|
87
|
-
cursor: pointer;
|
|
88
|
-
font-size: 0.875rem;
|
|
89
|
-
transition: all 0.2s;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.btn-primary {
|
|
93
|
-
background: #007bff;
|
|
94
|
-
color: white;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.btn-primary:hover {
|
|
98
|
-
background: #0056b3;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.btn-success {
|
|
102
|
-
background: #28a745;
|
|
103
|
-
color: white;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.btn-success:hover {
|
|
107
|
-
background: #1e7e34;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.btn-danger {
|
|
111
|
-
background: #dc3545;
|
|
112
|
-
color: white;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.btn-danger:hover {
|
|
116
|
-
background: #c82333;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.btn-sm {
|
|
120
|
-
padding: 0.25rem 0.75rem;
|
|
121
|
-
font-size: 0.8rem;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/* Tables */
|
|
125
|
-
.table {
|
|
126
|
-
width: 100%;
|
|
127
|
-
border-collapse: collapse;
|
|
128
|
-
background: #fff;
|
|
129
|
-
border-radius: 8px;
|
|
130
|
-
overflow: hidden;
|
|
131
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.table th,
|
|
135
|
-
.table td {
|
|
136
|
-
padding: 1rem;
|
|
137
|
-
text-align: left;
|
|
138
|
-
border-bottom: 1px solid #dee2e6;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.table th {
|
|
142
|
-
background: #f8f9fa;
|
|
143
|
-
font-weight: 600;
|
|
144
|
-
color: #495057;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.table tr:hover {
|
|
148
|
-
background: #f8f9fa;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/* Stats */
|
|
152
|
-
.stats {
|
|
153
|
-
display: grid;
|
|
154
|
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
155
|
-
gap: 1rem;
|
|
156
|
-
margin-bottom: 2rem;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.stat-card {
|
|
160
|
-
background: #fff;
|
|
161
|
-
padding: 1.5rem;
|
|
162
|
-
border-radius: 8px;
|
|
163
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
164
|
-
text-align: center;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.stat-number {
|
|
168
|
-
font-size: 2rem;
|
|
169
|
-
font-weight: bold;
|
|
170
|
-
color: #007bff;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.stat-label {
|
|
174
|
-
color: #6c757d;
|
|
175
|
-
font-size: 0.875rem;
|
|
176
|
-
margin-top: 0.5rem;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* Empty state */
|
|
180
|
-
.empty-state {
|
|
181
|
-
text-align: center;
|
|
182
|
-
padding: 4rem 2rem;
|
|
183
|
-
color: #6c757d;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.empty-state-icon {
|
|
187
|
-
font-size: 4rem;
|
|
188
|
-
margin-bottom: 1rem;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* Responsive */
|
|
192
|
-
@media (max-width: 768px) {
|
|
193
|
-
.container {
|
|
194
|
-
padding: 0 15px;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.table {
|
|
198
|
-
font-size: 0.875rem;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.table th,
|
|
202
|
-
.table td {
|
|
203
|
-
padding: 0.75rem 0.5rem;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.btn {
|
|
207
|
-
font-size: 0.8rem;
|
|
208
|
-
padding: 0.4rem 0.8rem;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* Alerts */
|
|
213
|
-
.alert {
|
|
214
|
-
padding: 0.75rem 1rem;
|
|
215
|
-
margin-bottom: 1rem;
|
|
216
|
-
border-radius: 4px;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.alert-success {
|
|
220
|
-
background: #d4edda;
|
|
221
|
-
color: #155724;
|
|
222
|
-
border: 1px solid #c3e6cb;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.alert-danger {
|
|
226
|
-
background: #f8d7da;
|
|
227
|
-
color: #721c24;
|
|
228
|
-
border: 1px solid #f5c6cb;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.alert-info {
|
|
232
|
-
background: #d1ecf1;
|
|
233
|
-
color: #0c5460;
|
|
234
|
-
border: 1px solid #bee5eb;
|
|
235
|
-
}
|
|
236
|
-
</style>
|
|
237
|
-
</head>
|
|
238
|
-
|
|
239
|
-
<body>
|
|
240
|
-
<div class="header">
|
|
241
|
-
<div class="container">
|
|
242
|
-
<h1>
|
|
243
|
-
<span class="trash-icon">🗑️</span>
|
|
244
|
-
Recycle Bin
|
|
245
|
-
</h1>
|
|
246
|
-
<nav class="nav">
|
|
247
|
-
<%= link_to "All Items", recycle_bin.trash_index_path,
|
|
248
|
-
class: ("active" if current_page?(recycle_bin.trash_index_path)) %>
|
|
249
|
-
<%= link_to "Statistics", "#", class: "disabled" %>
|
|
250
|
-
</nav>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<div class="container">
|
|
255
|
-
<% if notice %>
|
|
256
|
-
<div class="alert alert-success"><%= notice %></div>
|
|
257
|
-
<% end %>
|
|
258
|
-
|
|
259
|
-
<% if alert %>
|
|
260
|
-
<div class="alert alert-danger"><%= alert %></div>
|
|
261
|
-
<% end %>
|
|
262
|
-
|
|
263
|
-
<%= yield %>
|
|
264
|
-
</div>
|
|
265
|
-
</body>
|
|
266
|
-
</html>
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
<% content_for :title, "Recycle Bin" %>
|
|
2
|
-
|
|
3
|
-
<!-- Statistics Cards -->
|
|
4
|
-
<div class="stats">
|
|
5
|
-
<div class="stat-card">
|
|
6
|
-
<div class="stat-number"><%= @deleted_items.count %></div>
|
|
7
|
-
<div class="stat-label">Items in Trash</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="stat-card">
|
|
11
|
-
<div class="stat-number"><%= @model_types.count %></div>
|
|
12
|
-
<div class="stat-label">Different Types</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<div class="stat-card">
|
|
16
|
-
<div class="stat-number">
|
|
17
|
-
<%= @deleted_items.where('deleted_at > ?', 1.day.ago).count %>
|
|
18
|
-
</div>
|
|
19
|
-
<div class="stat-label">Deleted Today</div>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<!-- Main Content -->
|
|
24
|
-
<div class="card">
|
|
25
|
-
<% if @deleted_items.any? %>
|
|
26
|
-
<h2 style="margin-bottom: 1.5rem; color: #495057;">
|
|
27
|
-
Deleted Items
|
|
28
|
-
<span style="font-size: 0.8em; color: #6c757d;">
|
|
29
|
-
(<%= @deleted_items.count %> items)
|
|
30
|
-
</span>
|
|
31
|
-
</h2>
|
|
32
|
-
|
|
33
|
-
<!-- Model Type Filter -->
|
|
34
|
-
<div style="margin-bottom: 1.5rem;">
|
|
35
|
-
<strong>Filter by type:</strong>
|
|
36
|
-
<%= link_to "All", recycle_bin.trash_index_path,
|
|
37
|
-
class: "btn btn-sm #{'btn-primary' if params[:type].blank?}" %>
|
|
38
|
-
<% @model_types.each do |model_type| %>
|
|
39
|
-
<%= link_to model_type, recycle_bin.trash_index_path(type: model_type),
|
|
40
|
-
class: "btn btn-sm #{'btn-primary' if params[:type] == model_type}" %>
|
|
41
|
-
<% end %>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<!-- Items Table -->
|
|
45
|
-
<table class="table">
|
|
46
|
-
<thead>
|
|
47
|
-
<tr>
|
|
48
|
-
<th>Type</th>
|
|
49
|
-
<th>Title</th>
|
|
50
|
-
<th>Deleted At</th>
|
|
51
|
-
<th>Actions</th>
|
|
52
|
-
</tr>
|
|
53
|
-
</thead>
|
|
54
|
-
<tbody>
|
|
55
|
-
<% @deleted_items.each do |item| %>
|
|
56
|
-
<tr>
|
|
57
|
-
<td>
|
|
58
|
-
<strong><%= item.class.name %></strong>
|
|
59
|
-
</td>
|
|
60
|
-
<td>
|
|
61
|
-
<%= link_to truncate(item.recyclable_title, length: 50),
|
|
62
|
-
recycle_bin.trash_path(item),
|
|
63
|
-
style: "text-decoration: none; color: #007bff;" %>
|
|
64
|
-
</td>
|
|
65
|
-
<td>
|
|
66
|
-
<span style="color: #6c757d;">
|
|
67
|
-
<%= time_ago_in_words(item.deleted_at) %> ago
|
|
68
|
-
</span>
|
|
69
|
-
<br>
|
|
70
|
-
<small style="color: #adb5bd;">
|
|
71
|
-
<%= item.deleted_at.strftime('%B %d, %Y at %I:%M %p') %>
|
|
72
|
-
</small>
|
|
73
|
-
</td>
|
|
74
|
-
<td>
|
|
75
|
-
<%= link_to "View", recycle_bin.trash_path(item),
|
|
76
|
-
class: "btn btn-sm btn-primary" %>
|
|
77
|
-
<%= link_to "Restore", recycle_bin.restore_trash_path(item),
|
|
78
|
-
method: :patch,
|
|
79
|
-
class: "btn btn-sm btn-success",
|
|
80
|
-
data: {
|
|
81
|
-
confirm: "Are you sure you want to restore this #{item.class.name.downcase}?"
|
|
82
|
-
} %>
|
|
83
|
-
<%= link_to "Delete Forever", recycle_bin.trash_path(item),
|
|
84
|
-
method: :delete,
|
|
85
|
-
class: "btn btn-sm btn-danger",
|
|
86
|
-
data: {
|
|
87
|
-
confirm: "This will permanently delete this #{item.class.name.downcase}. This action cannot be undone!"
|
|
88
|
-
} %>
|
|
89
|
-
</td>
|
|
90
|
-
</tr>
|
|
91
|
-
<% end %>
|
|
92
|
-
</tbody>
|
|
93
|
-
</table>
|
|
94
|
-
|
|
95
|
-
<!-- Bulk Actions -->
|
|
96
|
-
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #dee2e6;">
|
|
97
|
-
<p style="color: #6c757d; font-size: 0.875rem;">
|
|
98
|
-
<strong>Bulk Actions:</strong>
|
|
99
|
-
Select items above to restore or delete multiple items at once.
|
|
100
|
-
<em>(Coming in v1.1)</em>
|
|
101
|
-
</p>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<% else %>
|
|
105
|
-
<!-- Empty State -->
|
|
106
|
-
<div class="empty-state">
|
|
107
|
-
<div class="empty-state-icon">🎉</div>
|
|
108
|
-
<h3>Your trash is empty!</h3>
|
|
109
|
-
<p>When you delete items from your application, they'll appear here so you can restore them if needed.</p>
|
|
110
|
-
|
|
111
|
-
<div style="margin-top: 2rem;">
|
|
112
|
-
<h4 style="color: #495057;">How to use RecycleBin:</h4>
|
|
113
|
-
<div style="text-align: left; max-width: 500px; margin: 1rem auto;">
|
|
114
|
-
<p><strong>1.</strong> Add <code>include RecycleBin::SoftDeletable</code> to your models</p>
|
|
115
|
-
<p><strong>2.</strong> Add <code>deleted_at:datetime</code> column to your tables</p>
|
|
116
|
-
<p><strong>3.</strong> Call <code>.destroy</code> on records - they'll come here instead of being deleted</p>
|
|
117
|
-
<p><strong>4.</strong> Use <code>.restore</code> to bring them back</p>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
<% end %>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<!-- Help Section -->
|
|
125
|
-
<div class="card" style="margin-top: 2rem; background: #f8f9fa;">
|
|
126
|
-
<h4 style="color: #495057; margin-bottom: 1rem;">💡 Pro Tips</h4>
|
|
127
|
-
<ul style="color: #6c757d; line-height: 1.8;">
|
|
128
|
-
<li><strong>Restore:</strong> Click "Restore" to bring items back to your application</li>
|
|
129
|
-
<li><strong>Permanent Delete:</strong> "Delete Forever" removes items completely (cannot be undone)</li>
|
|
130
|
-
<li><strong>Auto-cleanup:</strong> Items older than 30 days are automatically cleaned up</li>
|
|
131
|
-
<li><strong>Search:</strong> Use your browser's find (Ctrl/Cmd+F) to search this page</li>
|
|
132
|
-
</ul>
|
|
133
|
-
</div>
|