hyde_admin 0.0.13 → 0.0.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7dc5c1f3c3e06393c4c25ce2a7463e035c156e071a5263c9207b8a05df3d5b9b
4
- data.tar.gz: bf460efdb34250deebffd318269cf82dd9cc61a1bd92812131dd9d8033f1e302
3
+ metadata.gz: 1543e23f13b0754c2c354bed6a97ecf2ba5a6e2a06bd0dc7a7700e8d0118cc03
4
+ data.tar.gz: 21a4fb84e2fda24664dce4d9b160e9188fa03178e14d523ff1786d2131cf7739
5
5
  SHA512:
6
- metadata.gz: ec1cb2e4a56c723f3930999c68976a460dc0ed70e12417b5a9ee51c319f48246d2ab6de6801ccee9f3682aa17a0ec44ad8c2614d62d2806f70c3853afcbce19f
7
- data.tar.gz: 7ad9661636366333ca352891047b189c851876c4dc4166c6cf7fe8319c5bf3fbbbb0b651ee413b8dd2aaab24c288933db0e09386d21180b7209f84030940fe9e
6
+ metadata.gz: 339665fac60c5c1c56b8846495a90b23b76ebbd9c959e094bff37340096db7b768f165bdfede986bc1f5cc49340e86be8c0547cf326a022d92514f4497d61825
7
+ data.tar.gz: d8136ec6badb9af96ad5471f755ea44bbd9b22180e28950f14dab34b15f26a13b2b354708d1a4ed610c276573397a2786c9bef389f866c23763038cd85a04d61
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
- *.gem
1
+ *.gem
2
+ Gemfile.lock
3
+ .idea
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ # 0.0.14
2
+
3
+ Security:
4
+ - Use YAML.safe_load instead of YAML.load
5
+ - Prevent path traversal on file operations
6
+ - Escape shell arguments in rebuild and deploy commands
7
+ - Escape HTML output in post/draft templates to prevent XSS
8
+
9
+ Features:
10
+ - Add preview & SFTP settings
11
+ - Add search, improved filenames for posts, UI improvements, autosave/recovery
12
+ - Add flash notice messages after rebuild, deploy and configuration save
13
+ - Add posts/drafts/pages counters to dashboard
14
+
15
+ Improvements:
16
+ - Remove jQuery dependency, use vanilla JS and fetch API
17
+ - Update Bootstrap from 5.1.1 to 5.3.8
18
+ - Update Font Awesome from 5.15.4 to 6.7.2
19
+ - Relax gem dependency version constraints
20
+
21
+ Bugfixes:
22
+ - Fix deploy/rebuild JS handler not working
23
+ - Fix image upload duplicate rename producing wrong filenames
24
+ - Frontmatter parser now supports keys with dashes and underscores
25
+ - Handle directory deletion safely in file browser
26
+ - Remove typo in french translation for previous_images
27
+
28
+ # 0.0.13
29
+
30
+ Add code highlighting
31
+
1
32
  # 0.0.12
2
33
 
3
34
  Add liquid code highlighter in wysiwyg editor
data/CLAUDE.md ADDED
@@ -0,0 +1,56 @@
1
+ # Hyde Admin - CLAUDE.md
2
+
3
+ ## Project Overview
4
+ Hyde Admin is a Ruby gem providing a web-based administration interface for Jekyll static sites. It allows managing pages, posts, drafts, files, images, and site configuration through a browser UI.
5
+
6
+ ## Tech Stack
7
+ - **Language**: Ruby
8
+ - **Web framework**: Roda (lightweight Ruby web framework)
9
+ - **Server**: Puma (via Rack)
10
+ - **Templates**: ERB (in `bin/admin_views/`)
11
+ - **Frontend**: Bootstrap 5.3, jQuery 3.6, Font Awesome 5.15, CodeMirror (code editor), FSLightbox
12
+ - **Assets**: Served as static files (no asset pipeline), CDN for Bootstrap/jQuery/Font Awesome
13
+ - **Distribution**: Ruby gem (`hyde_admin.gemspec`)
14
+
15
+ ## Project Structure
16
+ ```
17
+ bin/
18
+ hyde_admin.ru # Main Roda application (routes, plugins, logic)
19
+ hyde_admin # CLI executable
20
+ hyde_admin_config # Config installer script
21
+ admin_views/ # ERB templates
22
+ admin_layout.html.erb # Main layout (CDN links, sidebar, modals)
23
+ partials/ # Reusable template fragments
24
+ hyde_assets/ # Custom CSS (hyde_admin.css) and JS (hyde_admin.js)
25
+ lib/ # CodeMirror editor library
26
+ mode/ # CodeMirror language modes
27
+ fslightbox/ # Lightbox library
28
+ i18n/ # Locale files (YAML)
29
+ img/ # Images (logo)
30
+ lib/
31
+ hyde_admin/version.rb # Gem version constant
32
+ ```
33
+
34
+ ## Key Commands
35
+ ```bash
36
+ # Build the gem
37
+ gem build hyde_admin.gemspec
38
+
39
+ # Install locally
40
+ gem install hyde_admin-*.gem
41
+
42
+ # Run the admin server (from a Jekyll site directory)
43
+ hyde_admin
44
+
45
+ # Install/update config file
46
+ hyde_admin_config
47
+ ```
48
+
49
+ ## Development Notes
50
+ - All frontend dependencies (Bootstrap, jQuery, Font Awesome) are loaded via CDN in `admin_layout.html.erb`
51
+ - CodeMirror and FSLightbox are bundled locally in `bin/`
52
+ - The app uses `roda-i18n` for internationalization, locale files are in `bin/i18n/`
53
+ - Image upload uses `image_processing` + `mini_magick` for resizing
54
+ - Deployment uses rsync (configurable via `hyde_admin.yml`)
55
+ - No test suite currently exists
56
+ - Version is defined in `lib/hyde_admin/version.rb`
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem "rspec", "~> 3.0"
7
+ gem "rack-test", "~> 2.0"
8
+ end
@@ -8,9 +8,9 @@
8
8
  <meta name="author" content="Sylvain Claudel (https://blog.rivsc.ovh)">
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
10
 
11
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
12
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
13
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
11
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
12
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
13
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
14
14
 
15
15
  <script src="/lib/codemirror.js"></script>
16
16
  <link rel="stylesheet" href="/lib/codemirror.css">
@@ -21,8 +21,6 @@
21
21
  <script src="/mode/yaml/yaml.js"></script>
22
22
  <script src="/mode/xml/xml.js"></script>
23
23
  <script src="/mode/markdown/markdown.js"></script>
24
- <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
25
-
26
24
  <link rel="stylesheet" href="/hyde_assets/hyde_admin.css">
27
25
  <script src="/hyde_assets/hyde_admin.js"></script>
28
26
  </head>
@@ -32,64 +30,78 @@
32
30
  <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
33
31
  <div class="position-sticky pt-3">
34
32
  <a href="/"><img src="/img/logo.png" alt="Hyde Admin" title="Logo hyde admin" class="sidebarMenu-logo" /></a>
33
+ <form action="/search" method="get" class="px-3 mb-3">
34
+ <div class="input-group input-group-sm">
35
+ <input type="text" name="q" class="form-control" placeholder="<%= CGI.escapeHTML t.global_search_placeholder %>">
36
+ <button class="btn btn-outline-secondary" type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
37
+ </div>
38
+ </form>
35
39
  <ul class="nav flex-column">
36
40
  <li class="nav-item">
37
41
  </li>
38
42
  <li class="nav-item">
39
43
  <a class="nav-link active" aria-current="page" href="/dashboard">
40
44
  <span data-feather="home" class="fas fa-tachometer-alt"></span>
41
- <%= EscapeUtils.escape_html t.dashboard.capitalize %>
45
+ <%= CGI.escapeHTML t.dashboard.capitalize %>
42
46
  </a>
43
47
  </li>
44
48
  <li class="nav-item">
45
49
  <a class="nav-link" href="/pages/index">
46
50
  <span data-feather="file" class="fas fa-file"></span>
47
- <%= EscapeUtils.escape_html t.pages.capitalize %>
51
+ <%= CGI.escapeHTML t.pages.capitalize %>
48
52
  </a>
49
53
  </li>
50
54
  <li class="nav-item">
51
55
  <a class="nav-link" href="/drafts/index">
52
56
  <span data-feather="shopping-cart" class="fas fa-file"></span>
53
- <%= EscapeUtils.escape_html t.drafts.capitalize %>
57
+ <%= CGI.escapeHTML t.drafts.capitalize %>
54
58
  </a>
55
59
  </li>
56
60
  <li class="nav-item">
57
61
  <a class="nav-link" href="/posts/index">
58
62
  <span data-feather="users" class="fas fa-file"></span>
59
- <%= EscapeUtils.escape_html t.posts.capitalize %>
63
+ <%= CGI.escapeHTML t.posts.capitalize %>
60
64
  </a>
61
65
  </li>
62
66
  <li class="nav-item">
63
67
  <a class="nav-link" href="/files/index">
64
68
  <span data-feather="users" class="fas fa-copy"></span>
65
- <%= EscapeUtils.escape_html t.files.capitalize %>
69
+ <%= CGI.escapeHTML t.files.capitalize %>
66
70
  </a>
67
71
  </li>
68
72
  <li></li>
69
73
  <li class="nav-item mb-4 mt-4">
70
74
  <a class="nav-link" href="/configuration">
71
75
  <span data-feather="layers" class="fas fa-tools"></span>
72
- <%= EscapeUtils.escape_html t.configuration.capitalize %>
76
+ <%= CGI.escapeHTML t.configuration.capitalize %>
73
77
  </a>
74
78
  </li>
75
79
  <li></li>
76
80
  <li class="nav-item">
77
81
  <a class="nav-link active" aria-current="page" href="/rebuild" id="btn-rebuild">
78
82
  <span data-feather="home" class="fas fa-hammer"></span>
79
- <%= EscapeUtils.escape_html t.rebuild.capitalize %>
83
+ <%= CGI.escapeHTML t.rebuild.capitalize %>
80
84
  </a>
81
85
  </li>
82
86
  <li class="nav-item">
83
87
  <a class="nav-link active" aria-current="page" target="_blank" href="/<%= @hyde_parameters['site_index'] %>">
84
88
  <span data-feather="home" class="fas fa-eye"></span>
85
- <%= EscapeUtils.escape_html t.overview.capitalize %>
89
+ <%= CGI.escapeHTML t.overview.capitalize %>
86
90
  </a>
87
91
  </li>
88
92
  <% if !@hyde_parameters['deploy_dest_address'].to_s.empty? %>
89
93
  <li class="nav-item">
90
94
  <a class="nav-link active" aria-current="page" href="/deploy" id="btn-deploy">
91
95
  <span data-feather="home" class="fas fa-cloud-upload-alt"></span>
92
- <%= EscapeUtils.escape_html t.deploy.capitalize %>
96
+ <%= CGI.escapeHTML t.deploy.capitalize %>
97
+ </a>
98
+ </li>
99
+ <% end %>
100
+ <% if !@hyde_parameters['sftp_host'].to_s.empty? %>
101
+ <li class="nav-item">
102
+ <a class="nav-link active" aria-current="page" href="/deploy_sftp" id="btn-deploy-sftp">
103
+ <span data-feather="home" class="fas fa-cloud-upload-alt"></span>
104
+ <%= CGI.escapeHTML t.deploy_sftp.capitalize %>
93
105
  </a>
94
106
  </li>
95
107
  <% end %>
@@ -97,7 +109,13 @@
97
109
  <p class="text-center text-muted"><br><br><em>Hyde Admin V.<%= HydeAdmin::VERSION %></em>&nbsp;&nbsp;&nbsp;<a href="https://github.com/rivsc/hyde_admin" class="link-secondary"><i class="fab fa-github"></i></a></p>
98
110
  </div>
99
111
  </nav>
100
- <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
112
+ <main class="col-md-9 ms-sm-auto col-lg-10 p-4">
113
+ <% if @notice && !@notice.empty? %>
114
+ <div class="alert alert-success alert-dismissible fade show mt-2" role="alert">
115
+ <%= CGI.escapeHTML(@notice) %>
116
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
117
+ </div>
118
+ <% end %>
101
119
  <%= yield %>
102
120
  </main>
103
121
  </div>
@@ -110,14 +128,14 @@
110
128
  <div class="modal modal-image" tabindex="-1" role="dialog">
111
129
  <div class="modal-dialog" role="document">
112
130
  <div class="modal-content">
113
- <div class="modal-header">
131
+ <div class="modal-header d-flex" style="gap:8px;">
114
132
  <h5 class="modal-title"><%= t.images.capitalize %></h5>
115
133
  <!--
116
134
  <button type="button" class="btn btn-default close" data-dismiss="modal" aria-label="Close">
117
135
  <span aria-hidden="true">&times;</span>
118
136
  </button>
119
137
  -->
120
- <button type="button" class="btn btn-outline-secondary btn-sm load-image">
138
+ <button type="button" class="btn btn-outline-secondary btn-sm load-image ml-4">
121
139
  <%= t.load_images.capitalize %>
122
140
  </button>
123
141
  </div>
@@ -127,18 +145,18 @@
127
145
  <form action="" class="form-inline">
128
146
  <div class="form-group mb-2">
129
147
  <label>
130
- <input class="form-check-input" type="radio" name="sort_date" value="asc"> <%= EscapeUtils.escape_html t.newer.capitalize %>
148
+ <input class="form-check-input" type="radio" name="sort_date" value="asc"> <%= CGI.escapeHTML t.newer.capitalize %>
131
149
  </label>
132
150
  &nbsp;&nbsp;&nbsp;
133
151
  <label>
134
- <input class="form-check-input" type="radio" name="sort_date" value="desc" checked> <%= EscapeUtils.escape_html t.older.capitalize %>
152
+ <input class="form-check-input" type="radio" name="sort_date" value="desc" checked> <%= CGI.escapeHTML t.older.capitalize %>
135
153
  </label>
136
154
  </div>
137
155
  <div class="form-group mb-2">
138
- <label for="inputFilename" class="sr-only"><%= EscapeUtils.escape_html t.filename.capitalize %></label>
139
- <input type="text" class="form-control" name="filename" id="inputFilename" placeholder="<%= EscapeUtils.escape_html t.filename_placeholder.capitalize %>">
156
+ <label for="inputFilename" class="sr-only"><%= CGI.escapeHTML t.filename.capitalize %></label>
157
+ <input type="text" class="form-control" name="filename" id="inputFilename" placeholder="<%= CGI.escapeHTML t.filename_placeholder.capitalize %>">
140
158
  </div>
141
- <button type="submit" class="btn btn-primary image-selector-search-submit d-block mb-2"><%= EscapeUtils.escape_html t.search.capitalize %></button>
159
+ <button type="submit" class="btn btn-primary image-selector-search-submit d-block mb-2"><%= CGI.escapeHTML t.search.capitalize %></button>
142
160
  </form>
143
161
  </div>
144
162
  <div class="image-selector-content">
@@ -146,10 +164,10 @@
146
164
  <%= ERB.new(File.read(path)).result(binding) %>
147
165
  </div>
148
166
  <div class="image-selector-page text-center">
149
- <a href="#" title="<%= EscapeUtils.escape_html t.previous_images %>" class="btn btn-secondary image-selector-page-prev px-4">
167
+ <a href="#" title="<%= CGI.escapeHTML t.previous_images %>" class="btn btn-secondary image-selector-page-prev px-4">
150
168
  <i class="fas fa-chevron-left"></i>
151
169
  </a>
152
- <a href="#" title="<%= EscapeUtils.escape_html t.next_images %>" class="btn btn-secondary image-selector-page-next px-4">
170
+ <a href="#" title="<%= CGI.escapeHTML t.next_images %>" class="btn btn-secondary image-selector-page-next px-4">
153
171
  <i class="fas fa-chevron-right"></i>
154
172
  </a>
155
173
  </div>
@@ -173,42 +191,49 @@
173
191
  <% end %>
174
192
 
175
193
  function search(offset_page){
176
- let sort_date = $('.image-selector-search input[name=sort_date]:checked').val();
177
- let filename = $('.image-selector-search input[name=filename]').val();
178
- let page = $('.image-selector-search').attr('data-page');
179
- let new_page = parseInt(page) + offset_page;
194
+ var container = document.querySelector('.image-selector-search');
195
+ var checkedRadio = container.querySelector('input[name=sort_date]:checked');
196
+ var sort_date = checkedRadio ? checkedRadio.value : 'desc';
197
+ var filename = container.querySelector('input[name=filename]').value;
198
+ var page = parseInt(container.getAttribute('data-page')) + offset_page;
180
199
 
181
- if(new_page < 0){
182
- new_page = 0;
183
- }
200
+ if(page < 0){ page = 0; }
184
201
 
185
- $.post( "/ajax/images", { sort_date: sort_date, filename: filename, page: new_page })
186
- .done(function( data ) {
187
- $('.image-selector-content').html(data);
202
+ var body = new URLSearchParams({ sort_date: sort_date, filename: filename, page: page });
203
+ fetch("/ajax/images", { method: 'POST', body: body })
204
+ .then(function(r){ return r.text(); })
205
+ .then(function(data){
206
+ document.querySelector('.image-selector-content').innerHTML = data;
188
207
  });
189
208
 
190
- $('.image-selector-search').attr('data-page', new_page);
209
+ container.setAttribute('data-page', page);
191
210
  return false;
192
211
  }
193
212
 
194
- $(document).on('click', '.image-selector-search-submit', function(){
195
- search(0);
196
- return false;
197
- });
198
- $(document).on('click', '.image-selector-page-prev', function(){
199
- search(-1);
200
- return false;
201
- });
202
- $(document).on('click', '.image-selector-page-next', function(){
203
- search(1);
204
- return false;
205
- });
206
- $(document).on('click', '.load-image', function(){
207
- var windowObjectReference = window.open("/upload_image_form", "update_image", "dialog=yes,menubar=no,location=no,resizable=no,scrollbars=yes,status=yes,outerWidth=800,innerHeight=300,width=800,height=300");
208
- return false;
213
+ document.addEventListener('click', function(e){
214
+ if(e.target.closest('.image-selector-search-submit')){
215
+ e.preventDefault();
216
+ search(0);
217
+ return;
218
+ }
219
+ if(e.target.closest('.image-selector-page-prev')){
220
+ e.preventDefault();
221
+ search(-1);
222
+ return;
223
+ }
224
+ if(e.target.closest('.image-selector-page-next')){
225
+ e.preventDefault();
226
+ search(1);
227
+ return;
228
+ }
229
+ if(e.target.closest('.load-image')){
230
+ e.preventDefault();
231
+ window.open("/upload_image_form", "update_image", "dialog=yes,menubar=no,location=no,resizable=no,scrollbars=yes,status=yes,outerWidth=800,innerHeight=300,width=800,height=300");
232
+ return;
233
+ }
209
234
  });
210
235
  function reload_image(){
211
- $('.load-image').click();
236
+ document.querySelector('.load-image').click();
212
237
  }
213
238
  </script>
214
239
  <script src="/fslightbox/fslightbox.js"></script>
@@ -1,13 +1,13 @@
1
- <h2><%= EscapeUtils.escape_html t.configuration.capitalize %></h2>
1
+ <h2><%= CGI.escapeHTML t.configuration.capitalize %></h2>
2
2
 
3
3
  <form action="/configuration" method="post">
4
4
  <% @hyde_parameters.each_pair do |setting, value| %>
5
5
  <div class="mb-3">
6
- <label for="i-<%= setting %>" class="form-label"><%= (EscapeUtils.escape_html t.send(setting).capitalize rescue setting) %></label>
6
+ <label for="i-<%= setting %>" class="form-label"><%= (CGI.escapeHTML t.send(setting).capitalize rescue setting) %></label>
7
7
  <input type="text" class="form-control" value="<%= value %>" name="<%= setting %>" id="i-<%= setting %>">
8
- <div id="i-<%= setting %>-help" class="form-text"><%= (EscapeUtils.escape_html t.send("help_#{setting}").capitalize rescue '') %></div>
8
+ <div id="i-<%= setting %>-help" class="form-text"><%= (CGI.escapeHTML t.send("help_#{setting}").capitalize rescue '') %></div>
9
9
  </div>
10
10
  <% end %>
11
- <button type="submit" class="btn btn-primary"><%= EscapeUtils.escape_html t.submit.capitalize %></button>
11
+ <button type="submit" class="btn btn-primary"><%= CGI.escapeHTML t.submit.capitalize %></button>
12
12
  </form>
13
13
 
@@ -1 +1,37 @@
1
- <h2><%= EscapeUtils.escape_html t.dashboard.capitalize %></h2>
1
+ <h2><%= CGI.escapeHTML t.dashboard.capitalize %></h2>
2
+
3
+ <div class="row mt-4">
4
+ <% posts_count = Dir[File.join(Dir.pwd, '_posts', '*')].size %>
5
+ <% drafts_count = Dir[File.join(Dir.pwd, '_drafts', '*')].size %>
6
+ <% pages_count = Dir[File.join(Dir.pwd, '_pages', '*')].size %>
7
+
8
+ <div class="col-md-4 mb-3">
9
+ <div class="card text-center">
10
+ <div class="card-body">
11
+ <h5 class="card-title"><i class="fas fa-file"></i> <%= CGI.escapeHTML t.posts.capitalize %></h5>
12
+ <p class="display-6"><%= posts_count %></p>
13
+ <a href="/posts/index" class="btn btn-outline-primary btn-sm"><%= CGI.escapeHTML t.view.capitalize %></a>
14
+ </div>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="col-md-4 mb-3">
19
+ <div class="card text-center">
20
+ <div class="card-body">
21
+ <h5 class="card-title"><i class="fas fa-file"></i> <%= CGI.escapeHTML t.drafts.capitalize %></h5>
22
+ <p class="display-6"><%= drafts_count %></p>
23
+ <a href="/drafts/index" class="btn btn-outline-primary btn-sm"><%= CGI.escapeHTML t.view.capitalize %></a>
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="col-md-4 mb-3">
29
+ <div class="card text-center">
30
+ <div class="card-body">
31
+ <h5 class="card-title"><i class="fas fa-file"></i> <%= CGI.escapeHTML t.pages.capitalize %></h5>
32
+ <p class="display-6"><%= pages_count %></p>
33
+ <a href="/pages/index" class="btn btn-outline-primary btn-sm"><%= CGI.escapeHTML t.view.capitalize %></a>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
@@ -1,29 +1,32 @@
1
1
  <div class="codemirror-toolbar btn-toolbar" role="toolbar">
2
2
  <div class="btn-group mr-2" role="group" aria-label="Undo/redo">
3
- <button type="button" class="btn btn-light"><i class="fas fa-undo-alt" title="<%= EscapeUtils.escape_html t.editor_undo %>"></i></button>
4
- <button type="button" class="btn btn-light"><i class="fas fa-redo-alt" title="<%= EscapeUtils.escape_html t.editor_redo %>"></i></button>
3
+ <button type="button" class="btn btn-light"><i class="fas fa-undo-alt" title="<%= CGI.escapeHTML t.editor_undo %>"></i></button>
4
+ <button type="button" class="btn btn-light"><i class="fas fa-redo-alt" title="<%= CGI.escapeHTML t.editor_redo %>"></i></button>
5
5
  </div>
6
6
  <div class="btn-group mr-2" role="group" aria-label="Structural tags">
7
- <button type="button" class="btn btn-light"><i class="fas fa-file-image" title="<%= EscapeUtils.escape_html t.editor_file %>"></i></button>
8
- <button type="button" class="btn btn-light"><i class="fas fa-list cmt-replace" title="<%= EscapeUtils.escape_html t.editor_list %>"></i></button>
9
- <button type="button" class="btn btn-light"><i class="fas fa-list-ol cmt-replace" title="<%= EscapeUtils.escape_html t.editor_list_ol %>"></i></button>
10
- <button type="button" class="btn btn-light"><i class="fas fa-link cmt-replace" title="<%= EscapeUtils.escape_html t.editor_link %>"></i></button>
11
- <button type="button" class="btn btn-light"><i class="fas fa-quote-left cmt-replace" title="<%= EscapeUtils.escape_html t.editor_quote %>"></i></button>
7
+ <button type="button" class="btn btn-light"><i class="fas fa-file-image" title="<%= CGI.escapeHTML t.editor_file %>"></i></button>
8
+ <button type="button" class="btn btn-light"><i class="fas fa-list cmt-replace" title="<%= CGI.escapeHTML t.editor_list %>"></i></button>
9
+ <button type="button" class="btn btn-light"><i class="fas fa-list-ol cmt-replace" title="<%= CGI.escapeHTML t.editor_list_ol %>"></i></button>
10
+ <button type="button" class="btn btn-light"><i class="fas fa-link cmt-replace" title="<%= CGI.escapeHTML t.editor_link %>"></i></button>
11
+ <button type="button" class="btn btn-light"><i class="fas fa-quote-left cmt-replace" title="<%= CGI.escapeHTML t.editor_quote %>"></i></button>
12
12
  </div>
13
13
  <div class="btn-group mr-2" role="group" aria-label="Style tags">
14
- <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-1" title="<%= EscapeUtils.escape_html t.editor_title_h1 %>">1</i></button>
15
- <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-2" title="<%= EscapeUtils.escape_html t.editor_title_h2 %>">2</i></button>
16
- <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-3" title="<%= EscapeUtils.escape_html t.editor_title_h3 %>">3</i></button>
17
- <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-4" title="<%= EscapeUtils.escape_html t.editor_title_h4 %>">4</i></button>
18
- <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-5" title="<%= EscapeUtils.escape_html t.editor_title_h5 %>">5</i></button>
19
- <button type="button" class="btn btn-light"><i class="fas fa-underline cmt-replace" title="<%= EscapeUtils.escape_html t.editor_underline %>"></i></button>
20
- <button type="button" class="btn btn-light"><i class="fas fa-bold cmt-replace" title="<%= EscapeUtils.escape_html t.editor_bold %>"></i></button>
21
- <button type="button" class="btn btn-light"><i class="fas fa-italic cmt-replace" title="<%= EscapeUtils.escape_html t.editor_italic %>"></i></button>
22
- <button type="button" class="btn btn-light"><i class="fas fa-strikethrough cmt-replace" title="<%= EscapeUtils.escape_html t.editor_strikethrough %>"></i></button>
14
+ <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-1" title="<%= CGI.escapeHTML t.editor_title_h1 %>">1</i></button>
15
+ <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-2" title="<%= CGI.escapeHTML t.editor_title_h2 %>">2</i></button>
16
+ <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-3" title="<%= CGI.escapeHTML t.editor_title_h3 %>">3</i></button>
17
+ <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-4" title="<%= CGI.escapeHTML t.editor_title_h4 %>">4</i></button>
18
+ <button type="button" class="btn btn-light"><i class="fas fa-heading cmt-heading-5" title="<%= CGI.escapeHTML t.editor_title_h5 %>">5</i></button>
19
+ <button type="button" class="btn btn-light"><i class="fas fa-underline cmt-replace" title="<%= CGI.escapeHTML t.editor_underline %>"></i></button>
20
+ <button type="button" class="btn btn-light"><i class="fas fa-bold cmt-replace" title="<%= CGI.escapeHTML t.editor_bold %>"></i></button>
21
+ <button type="button" class="btn btn-light"><i class="fas fa-italic cmt-replace" title="<%= CGI.escapeHTML t.editor_italic %>"></i></button>
22
+ <button type="button" class="btn btn-light"><i class="fas fa-strikethrough cmt-replace" title="<%= CGI.escapeHTML t.editor_strikethrough %>"></i></button>
23
23
  </div>
24
24
  <div class="btn-group mr-2" role="group" aria-label="Liquid tags">
25
- <button type="button" class="btn btn-light"><i class="fas fa-code cmt-replace" title="<%= EscapeUtils.escape_html t.editor_liquid_code %>"></i></button>
26
- <button type="button" class="btn btn-light"><i class="fas fa-terminal cmt-replace" title="<%= EscapeUtils.escape_html t.editor_pre_code %>"></i></button>
25
+ <button type="button" class="btn btn-light"><i class="fas fa-code cmt-replace" title="<%= CGI.escapeHTML t.editor_liquid_code %>"></i></button>
26
+ <button type="button" class="btn btn-light"><i class="fas fa-terminal cmt-replace" title="<%= CGI.escapeHTML t.editor_pre_code %>"></i></button>
27
+ </div>
28
+ <div class="btn-group mr-2" role="group" aria-label="Preview">
29
+ <button type="button" class="btn btn-light" id="btn-preview"><i class="fas fa-eye" title="<%= CGI.escapeHTML t.editor_preview %>"></i> <small><%= CGI.escapeHTML t.editor_preview %></small></button>
27
30
  </div>
28
31
  </div>
29
32
  <% if @hyde_parameters['text_editor_maximized'].to_s == 'true' %>